|
From: Rainer S. <Rai...@ab...> - 2004-10-14 07:26:24
Attachments:
SimplePortletFormController.java
|
Aloha! I've made some changes to SimplePortletFormController: In the original implementation the ActionRequest does nothing but forwarding to the RenderRequest, adding a RenderParameter as marker. The form action is executed in the RenderRequest handling. The disadvantage of this approach is that the form action is re-executed on each render request, i. e. every time the portlet window state is changed or another portlet at the same page is called. I've changed the code so that the action is called in the ActionRequest handler. The result of this execution (or the form ModelAndView in case of bind errors) is forwarded to the RenderRequest handler as request attribute. This way, when the portlet gets re-rendered, the form action is not re-executed. Since the request attribute is lost, the original form is displayed. If you prefer to keep the form execution result displaye in this case, it has to be stored in the portlet session. This can be achieved by setting the property 'storeResultInSession' of SimplePortletFormController to true. One problem (or is it a feature?) remains: if there are two instances of the portlet at the same page, both will get the form execution result in the render request and thus will display the same content. I've found no way to namespace encode the request property use for the result forwarding. If anyone has an idea... I'll append the changed code to this message. Feel free to use it or give any comments. Cheers Rainer |
|
From: William G. T. Jr. <wg...@ru...> - 2004-10-14 11:52:38
|
Changes sound good...not sure about the single RenderRequest two Portlet
"feature"...we'll have to figure that one out. To my dismay the spec
doesn't really talk to how multiple instances are supposed to
behave...for instance it is not defined whether Portlet prefs are per
Portlet or per instance. I've added this comment to the Porlet spec
wiki at Apache.
later.
Bill
Rainer Schmitz wrote:
> Aloha!
>
> I've made some changes to SimplePortletFormController:
>
> In the original implementation the ActionRequest does nothing but
> forwarding to the RenderRequest, adding a RenderParameter as marker. The
> form action is executed in the RenderRequest handling. The disadvantage
> of this approach is that the form action is re-executed on each render
> request, i. e. every time the portlet window state is changed or another
> portlet at the same page is called.
> I've changed the code so that the action is called in the ActionRequest
> handler. The result of this execution (or the form ModelAndView in case
> of bind errors) is forwarded to the RenderRequest handler as request
> attribute.
> This way, when the portlet gets re-rendered, the form action is not
> re-executed. Since the request attribute is lost, the original form is
> displayed. If you prefer to keep the form execution result displaye in
> this case, it has to be stored in the portlet session. This can be
> achieved by setting the property 'storeResultInSession' of
> SimplePortletFormController to true.
>
> One problem (or is it a feature?) remains: if there are two instances of
> the portlet at the same page, both will get the form execution result in
> the render request and thus will display the same content. I've found no
> way to namespace encode the request property use for the result
> forwarding. If anyone has an idea...
>
> I'll append the changed code to this message. Feel free to use it or
> give any comments.
>
> Cheers
> Rainer
>
>
> ------------------------------------------------------------------------
>
> package org.springframework.web.portlet;
>
> import java.util.Map;
>
> import javax.portlet.ActionRequest;
> import javax.portlet.ActionResponse;
> import javax.portlet.PortletRequest;
> import javax.portlet.RenderRequest;
> import javax.portlet.RenderResponse;
> import javax.servlet.ServletException;
>
> import org.springframework.validation.BindException;
> import org.springframework.validation.MessageCodesResolver;
> import org.springframework.validation.ValidationUtils;
> import org.springframework.validation.Validator;
> import org.springframework.web.bind.PortletRequestDataBinder;
> import org.springframework.web.portlet.support.PortletController;
> import org.springframework.web.servlet.ModelAndView;
>
> /**
> * <p>First cut of a FormController for Portlets.</p>
> *
> * <p>
> * <ul>
> * <li>TODO: Refactor this to have a sensible heirachy</li>
> * <li>TODO: Add session form support</li>
> * <li>TODO: Re-add hooks for easier customization in the same way the Web framwork does it.</li>
> * <ul>
> * </p>
> *
> * @author nl Nick Lothian
> * @author Rainer Schmitz
> *
> */
> public class SimplePortletFormController implements PortletController {
>
> public static final String CLEARRESULT = "clearResult";
> private final String MODEL_AND_VIEW = getClass().getName();
> private Class commandClass;
> private String commandName;
> private Validator[] validators;
> private boolean validateOnBinding;
>
> private boolean storeResultInSession = true;
>
> private String formView;
>
> private String successView;
>
> private MessageCodesResolver messageCodesResolver;
>
> /**
> * @return Returns the messageCodesResolver.
> */
> public MessageCodesResolver getMessageCodesResolver() {
> return messageCodesResolver;
> }
> /**
> * @param messageCodesResolver The messageCodesResolver to set.
> */
> public void setMessageCodesResolver(
> MessageCodesResolver messageCodesResolver) {
> this.messageCodesResolver = messageCodesResolver;
> }
> /**
> * Set the name of the command in the model.
> * The command object will be included in the model under this name.
> */
> public final void setCommandName(String commandName) {
> this.commandName = commandName;
> }
>
> /**
> * Return the name of the command in the model.
> */
> protected final String getCommandName() {
> return this.commandName;
> }
>
> /**
> * Set the command class for this controller.
> * An instance of this class gets populated and validated on each request.
> */
> public final void setCommandClass(Class commandClass) {
> this.commandClass = commandClass;
> }
>
> /**
> * Return the command class for this controller.
> */
> protected final Class getCommandClass() {
> return this.commandClass;
> }
>
> /**
> * Set the primary Validator for this controller.
> * The Validator must support the specified command class.
> */
> public final void setValidators(Validator[] validators) {
> this.validators = validators;
> }
>
> /**
> * Return the Validators for this controller.
> */
> protected final Validator[] getValidators() {
> return validators;
> }
>
> /**
> * Set the Validators for this controller.
> * The Validator must support the specified command class.
> */
> public final void setValidator(Validator validator) {
> this.validators = new Validator[] {validator};
> }
>
> /**
> * Return the primary Validator for this controller.
> */
> protected final Validator getValidator() {
> return (validators != null && validators.length > 0 ? validators[0] : null);
> }
>
> /**
> * Return if the form processing result is stored in the portlet session.
> *
> * The portlets render method is executed every time the page containing the
> * portlet is refreshed, e.g. when the portlet is resized. If the form
> * processing result is stored in the session it can be re-displayed after
> * the refresh.
> *
> * @return Returns true if the form processing result is stored.
> */
> public boolean isStoreResultInSession() {
> return storeResultInSession;
> }
>
> /**
> * Set if the form processing result should be stored in portlet session.
> *
> * The portlets render method is executed every time the page containing the
> * portlet is refreshed, e.g. when the portlet is resized. If the form
> * processing result is stored in the session it can be re-displayed after
> * the refresh.
> *
> * @param storeResultInSession
> * true if the form processing result is stored.
> */
> public void setStoreResultInSession(boolean storeResultInSession) {
> this.storeResultInSession = storeResultInSession;
> }
>
> /**
> * Set if the Validator should get applied when binding.
> */
> public final void setValidateOnBinding(boolean validateOnBinding) {
> this.validateOnBinding = validateOnBinding;
> }
>
> /**
> * Return if the Validator should get applied when binding.
> */
> protected final boolean isValidateOnBinding() {
> return validateOnBinding;
> }
>
> /**
> * Set the name of the view that should be used for form display.
> */
> public final void setFormView(String formView) {
> this.formView = formView;
> }
>
> /**
> * Return the name of the view that should be used for form display.
> */
> protected final String getFormView() {
> return this.formView;
> }
>
> /**
> * Set the name of the view that should be shown on successful submit.
> */
> public final void setSuccessView(String successView) {
> this.successView = successView;
> }
>
> /**
> * Return the name of the view that should be shown on successful submit.
> */
> protected final String getSuccessView() {
> return this.successView;
> }
>
> /**
> * Handle ActionRequest.
> *
> * Bind and validate command. If there are errors than redisplay form. Otherwise execute <code>onSubmit</code>.
> * The form execution result is stored in the portlet session if <code>isStoreResultInSession</code> is true.
> *
> * @see org.springframework.web.portlet.support.PortletController#handleRequest(javax.portlet.ActionRequest, javax.portlet.ActionResponse)
> */
> public void handleRequest(ActionRequest request, ActionResponse response) throws Exception {
> Object command = formBackingObject(request);
> PortletRequestDataBinder binder = bindAndValidate(request, command);
> ModelAndView result;
> if (binder.getErrors().hasErrors()) {
> result = showForm(request, getFormView(), binder.getErrors());
> } else {
> result = onSubmit(command, binder.getErrors());
> }
> if (isStoreResultInSession()) {
> request.getPortletSession().setAttribute(MODEL_AND_VIEW, result);
> }
> request.setAttribute(MODEL_AND_VIEW, result);
> }
>
> /**
> * Handle RenderRequest.
> *
> * If request contains a result from the preceeding ActionRequest it is displayed. Otherwise a result from a previous forum submit is
> * looked up in the portlet session.
> * If no result can be found, the form is displayed.
> * @see org.springframework.web.portlet.support.PortletController#handleRequest(javax.portlet.RenderRequest, javax.portlet.RenderResponse)
> */
> public ModelAndView handleRequest(RenderRequest request, RenderResponse response) throws Exception {
> if (request.getParameter(CLEARRESULT) != null) {
> request.getPortletSession().setAttribute(MODEL_AND_VIEW, null);
> }
> ModelAndView result = getFormProcessingResult(request);
> if (result == null) { //no result: first call or old result cleared -> show new form
> result = bindAndShowForm(request);
> }
> return result;
> }
>
> /**
> * Get result of form processing.
> *
> * First look in request for a 'fresh' result. If nothing found, look for 'old' result in session.
> *
> * @param request renderRequest
> * @return ModelAndView, if a result could be found.
> */
> private ModelAndView getFormProcessingResult(RenderRequest request) {
> ModelAndView result = (ModelAndView)request.getAttribute(MODEL_AND_VIEW);
> if (result == null) {
> result = (ModelAndView)request.getPortletSession().getAttribute(MODEL_AND_VIEW);
> }
> return result;
> }
>
> private ModelAndView bindAndShowForm(RenderRequest request) throws Exception {
> PortletRequestDataBinder binder = createBinder(request, formBackingObject(request));
> binder.bind(request);
> return showForm(request, getFormView(), binder.getErrors());
> }
>
> protected ModelAndView showForm(PortletRequest request, String viewName, BindException errors) {
> Map model = errors.getModel();
> return new ModelAndView(viewName, model);
> }
>
> protected ModelAndView onSubmit(Object command, BindException errors) throws Exception {
> ModelAndView mv = onSubmit(command);
> if (mv != null) {
> // simplest onSubmit version implemented in custom subclass
> // The Web version doesn't add the errors to the model - I'm not sure
> // how that works
> Map errorModel = errors.getModel();
> mv.getModel().putAll(errorModel);
>
> return mv;
> }
> else {
> // default behavior: render success view
> if (getSuccessView() == null) {
> throw new ServletException("successView isn't set");
> }
> return new ModelAndView(getSuccessView(), errors.getModel());
> }
> }
>
>
>
> public ModelAndView onSubmit(Object command) {
> return null;
> }
>
>
> protected boolean isFormChangeRequest(PortletRequest request) {
> return false;
> }
>
> protected boolean suppressValidation(PortletRequest request) {
> return false;
> }
>
> protected PortletRequestDataBinder createBinder(PortletRequest request, Object command) throws Exception {
> PortletRequestDataBinder binder = new PortletRequestDataBinder(command, getCommandName());
>
> if (this.messageCodesResolver != null) {
> binder.setMessageCodesResolver(this.messageCodesResolver);
> }
>
> initBinder(request, binder);
> return binder;
> }
>
> protected void initBinder(PortletRequest request, PortletRequestDataBinder binder) throws Exception {
> }
>
> protected final PortletRequestDataBinder bindAndValidate(PortletRequest request, Object command) throws Exception {
> PortletRequestDataBinder binder = createBinder(request, command);
> binder.bind(request);
> //onBind(request, command, binder.getErrors());
> if (this.validators != null && isValidateOnBinding() && !suppressValidation(request)) {
> for (int i = 0; i < this.validators.length; i++) {
> ValidationUtils.invokeValidator(this.validators[i], command,
> binder.getErrors());
> }
> }
> //onBindAndValidate(request, command, binder.getErrors());
> return binder;
> }
>
>
> protected Object formBackingObject(PortletRequest request) throws Exception {
> return createCommand();
> }
>
> protected final Object createCommand() throws InstantiationException, IllegalAccessException {
> if (this.commandClass == null) {
> throw new IllegalStateException("Cannot create command without commandClass being set - " +
> "either set commandClass or override formBackingObject");
> }
> return this.commandClass.newInstance();
> }
>
> protected void initApplicationContext() {
> if (this.validators != null) {
> for (int i = 0; i < this.validators.length; i++) {
> if (this.commandClass != null && !this.validators[i].supports(this.commandClass))
> throw new IllegalArgumentException("Validator [" + this.validators[i] +
> "] does not support command class [" +
> this.commandClass.getName() + "]");
> }
> }
> }
>
> }
|
|
From: J.Enrique R. <er...@di...> - 2004-10-14 11:56:03
|
Hi Rainer,
I think that it isn't a feature :) The specification says:
-----
A portlet can bind an object attribute into a |PortletSession| by name.
The |PortletSession| interface defines two scopes for storing objects:|
APPLICATION_SCOPE and ||PORTLET_SCOPE|
All objects stored in the session using the |APPLICATION_SCOPE| must be
available to all the portlets, servlets and JSPs that belongs to the
same portlet application and that handles a request identified as being
a part of the same session. Objects stored in the session using the
|PORTLET_SCOPE| must be available to the portlet during requests for the
same portlet window that the objects where stored from. Attributes
stored in the |PORTLET_SCOPE| are not protected from other web
components of the portlet application. They are just conveniently
namespaced.
-----
I understand that '|PORTLET_SCOPE| must be available to the portlet
during requests for the same portlet WINDOW ...' and '|PORTLET_SCOPE|
must be available to the portlet during requests for the same portlet
INSTANCE ...' are the same thing.
As you are using "request.getPortletSession().getAttribute("..")" and
the API says that this method returns the object with the specified name
for the |PORTLET_SCOPE, it could be there is a bug on your portlet
container.|
Regards.
> One problem (or is it a feature?) remains: if there are two instances
> of the portlet at the same page, both will get the form execution
> result in the render request and thus will display the same content.
> I've found no way to namespace encode the request property use for the
> result forwarding. If anyone has an idea...
>
> I'll append the changed code to this message. Feel free to use it or
> give any comments.
>
> Cheers
> Rainer
--
J.Enrique Ruiz
er...@di...
DISID, S.L.L.
|
|
From: Rainer S. <Rai...@ab...> - 2004-10-14 16:33:46
Attachments:
SimplePortletFormController.java
|
J.Enrique Ruiz wrote:
> Hi Rainer,
>
> I think that it isn't a feature :) The specification says:
[...]
> As you are using "request.getPortletSession().getAttribute("..")" and
> the API says that this method returns the object with the specified name
> for the |PORTLET_SCOPE, it could be there is a bug on your portlet
> container.|
No, everything works as expected for the result stored in session. It's
only the result propageted as request attribute in
request.setAttribute(MODEL_AND_VIEW, result);
which is picked up by the render action of every portlet window.
So everything would be fine if we always use the portlet session for
result propagation, but I don't want to enforce the usage of a session
just for result propagation.
Maybe a compromise would be to rely solely on the session if
'storeResultInSession' is true (since than a session is used anyway). In
this case the multiple window problem could be avoided. If you don't use
a session, the portlet window is refreshed to the initial form view
anyway, so there is not much point in creating multiple portlet windows
anyway.
The changed code is appended.
Rainer
|