You are viewing a plain text version of this content. The canonical link for it is here.
Posted to issues@struts.apache.org by "Martin Gainty (JIRA)" <ji...@apache.org> on 2009/04/18 16:48:06 UTC

[jira] Commented: (WW-3091) Problems with sticky render parameters when creating urls in Oracle Portal

    [ https://issues.apache.org/struts/browse/WW-3091?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=46043#action_46043 ] 

Martin Gainty commented on WW-3091:
-----------------------------------

the problem as noted in the JIRA is the org.apache.struts2.portlet.dispatcher.JSR168Dispatcher specifically parseModeConfig

//the author constructs the action incorrectly here is an example
parseModeConfig(actionMap, cfg, PortletMode.VIEW, "viewNamespace","defaultViewAction");

void parseModeConfig(Map<PortletMode, ActionMapping> actionMap,
PortletConfig portletConfig,PortletMode portletMode, String nameSpaceParam,
String defaultActionParam)
{
        String namespace = portletConfig.getInitParameter(nameSpaceParam);
//where portlet-xml contains
//       <init-param>
//            <name>viewNamespace</name>
//          <value>/view</value>
//      </init-param>
//    namespace="view"
 
        if (!TextUtils.stringSet(namespace)) {
            namespace = "";
        }
//modeMap is a Hashmap
        modeMap.put(portletMode, namespace);

//get defaultViewAction
//   <init-param>
//          <name>defaultViewAction</name>
//        <value>index</value>
//  </init-param>
        String defaultAction = portletConfig.getInitParameter(defaultActionParam);
//defaultAction="index"
      
        String method = null;
        if (!TextUtils.stringSet(defaultAction)) {
            defaultAction = DEFAULT_ACTION_NAME;
        }
//defaultAction="default"

        if(defaultAction.indexOf('!') >= 0) {
            method = defaultAction.substring(defaultAction.indexOf('!') + 1);
            defaultAction = defaultAction.substring(0, defaultAction.indexOf('!'));
        }
//so in the case where defaultAction contains Action!Method
//method=Method
//defaultAction=Action

        StringBuffer fullPath = new StringBuffer();

//the portletNamespace should be in portlet.xml (so other portlets can be configured)
//cfg.getInitParameter("portletNamespace");
        if (TextUtils.stringSet(portletNamespace))
       {
            fullPath.append(portletNamespace);
        }
//fullPath has portletNamespace GOOD!

//incorrect
        if (TextUtils.stringSet(namespace)) {
//no intervening / ?
            fullPath.append(namespace).append("/");
//fullPath=portletNamespaceview/
        } else {
            fullPath.append("/");
        }

        fullPath.append(defaultAction);
//fullPath=portletNamespaceview/Action ??????

        ActionMapping mapping = new ActionMapping();

//at this point fullPath is incorrect and so it should not be stored in mapping
//    String getActionName(String actionPath) {
//        int idx = actionPath.lastIndexOf('/');
//        String action = actionPath;
//        if (idx >= 0) {
//            action = actionPath.substring(idx + 1);
//        }
//        return action;
//    }
//agreed..the method getActionName returns correct result of 'Action'
        mapping.setName(getActionName(fullPath.toString()));

//    String getNamespace(String actionPath) {
//        int idx = actionPath.lastIndexOf('/');
//        String namespace = "";
//        if (idx >= 0) {
//            namespace = actionPath.substring(0, idx);
//        }
//        return namespace;
//    }
//yes the method getNamespace returns the incorrect namespace portletNamespaceview
        mapping.setNamespace(getNamespace(fullPath.toString()));
        if(method != null) {
//correct as method = defaultAction.substring(defaultAction.indexOf('!') + 1);
            mapping.setMethod(method);
        }
//correct as one should associate PortletMode.VIEW portletMode with mapping
        actionMap.put(portletMode, mapping);
    }

Also the downcasts in createContextMap  method
the problem as noted in the JIRA is the org.apache.struts2.portlet.dispatcher.JSR168Dispatcher specifically parseModeConfig

//the author constructs the action incorrectly here is an example
parseModeConfig(actionMap, cfg, PortletMode.VIEW, "viewNamespace","defaultViewAction");

void parseModeConfig(Map<PortletMode, ActionMapping> actionMap,
PortletConfig portletConfig,PortletMode portletMode, String nameSpaceParam,
String defaultActionParam)
{
        String namespace = portletConfig.getInitParameter(nameSpaceParam);
//where portlet-xml contains
//       <init-param>
//            <name>viewNamespace</name>
//          <value>/view</value>
//      </init-param>
//    namespace="view"
 
        if (!TextUtils.stringSet(namespace)) {
            namespace = "";
        }
//modeMap is a Hashmap
        modeMap.put(portletMode, namespace);

//get defaultViewAction
//   <init-param>
//          <name>defaultViewAction</name>
//        <value>index</value>
//  </init-param>
        String defaultAction = portletConfig.getInitParameter(defaultActionParam);
//defaultAction="index"
      
        String method = null;
        if (!TextUtils.stringSet(defaultAction)) {
            defaultAction = DEFAULT_ACTION_NAME;
        }
//defaultAction="default"

        if(defaultAction.indexOf('!') >= 0) {
            method = defaultAction.substring(defaultAction.indexOf('!') + 1);
            defaultAction = defaultAction.substring(0, defaultAction.indexOf('!'));
        }
//so in the case where defaultAction contains Action!Method
//method=Method
//defaultAction=Action

        StringBuffer fullPath = new StringBuffer();

//the portletNamespace should be in portlet.xml (so other portlets can be configured)
//cfg.getInitParameter("portletNamespace");
        if (TextUtils.stringSet(portletNamespace))
       {
            fullPath.append(portletNamespace);
        }
//fullPath has portletNamespace GOOD!

//incorrect
        if (TextUtils.stringSet(namespace)) {
//no intervening / ?
            fullPath.append(namespace).append("/");
//fullPath=portletNamespaceview/
        } else {
            fullPath.append("/");
        }

        fullPath.append(defaultAction);
//fullPath=portletNamespaceview/Action ??????

        ActionMapping mapping = new ActionMapping();

//at this point fullPath is incorrect and so it should not be stored in mapping
//    String getActionName(String actionPath) {
//        int idx = actionPath.lastIndexOf('/');
//        String action = actionPath;
//        if (idx >= 0) {
//            action = actionPath.substring(idx + 1);
//        }
//        return action;
//    }
//agreed..the method getActionName returns correct result of 'Action'
        mapping.setName(getActionName(fullPath.toString()));

//    String getNamespace(String actionPath) {
//        int idx = actionPath.lastIndexOf('/');
//        String namespace = "";
//        if (idx >= 0) {
//            namespace = actionPath.substring(0, idx);
//        }
//        return namespace;
//    }
//yes the method getNamespace returns the incorrect namespace portletNamespaceview
        mapping.setNamespace(getNamespace(fullPath.toString()));
        if(method != null) {
//correct as method = defaultAction.substring(defaultAction.indexOf('!') + 1);
            mapping.setMethod(method);
        }
//correct as one should associate PortletMode.VIEW portletMode with mapping
        actionMap.put(portletMode, mapping);
    }

Also the downcasts in createContextMap  method assume interchangability:
as PortletSession assume PortletRequest and HttpServletRequest are interchangable the code:
as PortletSession assumes PortletResponse and HttpServletResponse are interchangable the code:
HttpServletResponse dummyResponse = new PortletServletResponse(response);
HttpServletRequest dummyRequest = new PortletServletRequest(request, getPortletContext());

display of the code at
http://kickjava.com/src/javax/portlet/PortletSession.java.htm specifically
 /**
272    * Binds an object to this session under the <code>PORTLET_SCOPE</code>, using the name specified.
273    * If an object of the same name in this scope is already bound to the session,
274    * that object is replaced.
275    *
276    * <p>After this method has been executed, and if the new object
277    * implements <code>HttpSessionBindingListener</code>,

/* IT IS THE ATTRIBUTES RESPONSIBILITY TO specifically implement
           HttpSessionBindingListener!!!**********/
       The HttpSessionBindingListener IS NOT implemented in any of the attributes
       when Master container attempts to communicate updates to those atrributes
       there is no communication
*****/

278 * the container calls
279 * <code>HttpSessionBindingListener.valueBound</code>. The container then
280 * notifies any <code>HttpSessionAttributeListeners</code> in the web
281    * application.
282    * <p>If an object was already bound to this session
283    * that implements <code>HttpSessionBindingListener</code>, its
284 * <code>HttpSessionBindingListener.valueUnbound</code> method is called.
285 *
286    * <p>If the value is <code>null</code>, this has the same effect as calling
287    * <code>removeAttribute()</code>.
288    *
289    *
290    * @param name the name to which the object is bound under
291    * the <code>PORTLET_SCOPE</code>;
292    * this cannot be <code>null</code>.
293    * @param value the object to be bound
294    *
295    * @exception java.lang.IllegalStateException if this method is called on a
296    * session which has been invalidated
297    * @exception java.lang.IllegalArgumentException
298    * if name is <code>null</code>.
299    */
301   public void setAttribute(java.lang.String JavaDoc name, java.lang.Object JavaDoc value);

In defence of authors implementing JSR-168 portlets is not a trivial undertaking and the language implying HttpSession and PortletSession are interchangeable described here
"The portlet session is based on the HttpSession. Therefore all HttpSession listeners do apply to the portlet session and attributes set in the portlet session are visible in the HttpSession and vice versa."
is not described well..

2 problems remain:
1)correct problematic appending of portletNamespace and actionNamespace with no intervening /
2)implement HttpSessionBindingListener binding for all (Portal)Session objects

MCG 18 April 15:40 GMT

> Problems with sticky render parameters when creating urls in Oracle Portal
> --------------------------------------------------------------------------
>
>                 Key: WW-3091
>                 URL: https://issues.apache.org/struts/browse/WW-3091
>             Project: Struts 2
>          Issue Type: Bug
>          Components: Plugin - Portlet
>    Affects Versions: 2.0.14
>         Environment: Oracle Portal
>            Reporter: Martin Gainty
>             Fix For: Future
>
>
> > Hello all -
> >
> > We've had some trouble using Struts 2 portlets (2.0.14) with Oracle
> > Portal, and I wondered if anyone else had used this combination
> > successfully.
> >
> > The problems occur with portlets that have a form and submit buttons, and
> > the issue seems to be that Struts uses render parameters to pass
> > information (like action name) from the event phase to the render phase.
> > Oracle Portal keeps these values in the generated URL for the form action,
> > and so they persist for longer than Struts is expecting them to.
> >
> > An aside - this is also an issue with straight JSR 168 portlets in the
> > Oracle portal, but we can work around this by using session attributes to
> > pass data from event to render, rather than render params. However, as
> > Struts uses render params internally to figure out which action to invoke,
> > there's no similar workaround for Struts. I suspect the problem is in the
> > Oracle portal - the JSR 168 spec seems to indicate that render params are
> > for passing data from event phase to render phase, but I wanted to see if
> > anyone was aware of the issue and if there is a workaround. This all works
> > perfectly when running in Pluto.
> >
> > Here's an illustration of the problem - we are using a "redirectAction" to
> > get from event to render.
> >
> > I have a simple Struts action class "StrutsTest" which has these methods:
> >
> > public String execute(); // implements event phase processing
> > public String doView(); // implements render phase processing
> >
> > These methods do nothing except logging.
> >
> > My form has a single text input field called "pie" and a single submit.
> > Here's the relevant part of the struts config:
> >
> > <action name="init">
> > <result name="success">/WEB-INF/view/index.jsp</result>
> > </action>
> >
> > <action name="StrutsTestAction" class="struts2hello.StrutsTest">
> > <result name="input">/WEB-INF/view/index.jsp</result>
> > <result name="success" type="redirectAction" >
> > <param name="actionName">StrutsTestRender</param>
> > <param name="portletMode">view</param>
> > <param name="pie">${pie}</param>
> > </result>
> > </action>
> >
> > <action name="StrutsTestRender" class="struts2hello.StrutsTest"
> > method="doView">
> > <result name="success">/WEB-INF/view/index.jsp</result>
> > </action>
> >
> > Here's what happens, step by step:
> >
> > *** 1. First display. "init" action is invoked, which simply forwards to
> > the jsp. The form action url has this in it:
> > struts.portlet.action%3D%252Fview%252FStrutsTestAction%26struts.portlet.mode%3Dview
> >
> > *** 2. I enter "apple" in the pie field and submit. The event phase mthod
> > (execute) receives the value correctly and form is redisplayed .
> >
> > Logging shows this:
> >
> > 2009-04-16 09:32:31,581 INFO struts2hello.StrutsTest.execute - Begin
> > execute
> > 2009-04-16 09:32:31,583 INFO struts2hello.StrutsTest.execute - End execute
> > 2009-04-16 09:32:31,765 INFO struts2hello.StrutsTest.doView - Begin doView
> > A. Pie = apple
> > 2009-04-16 09:32:31,767 INFO struts2hello.StrutsTest.doView - doView B.
> > Phase: RENDER
> > 2009-04-16 09:32:31,768 INFO struts2hello.StrutsTest.doView - End doView
> >
> > This is all correct - the event phase action is invoked, and then the
> > render phase action (doView method) is invoked and the phase (from
> > PortletActionContext) is RENDER.
> >
> > But, on redisplay, the form action URL now has this embedded:
> >
> > pie%3Dapple%26struts.portlet.action%3D%252Fview%252FStrutsTestRender%26struts.portlet.mode%3Dview%26struts.portlet.eventAction%3Dtrue
> >
> > AND
> >
> > struts.portlet.action%3D%252Fview%252FStrutsTestAction%26struts.portlet.mode%3Dview
> >
> > I believe this is where things start to go wrong - Oracle has put the
> > render parameters (action name of "StrutsTestRender" and pie of "apple")
> > into the generated action url, and subsequent submits will see these
> > parameters.
> >
> > *** 3. Entering "cherry" into the pie field and submitting again shows
> > this in the log:
> >
> > 2009-04-16 09:33:40,502 INFO struts2hello.StrutsTest.doView - Begin doView
> > A. Pie = apple, cherry
> > 2009-04-16 09:33:40,505 INFO struts2hello.StrutsTest.doView - doView B.
> > Phase: EVENT
> > 2009-04-16 09:33:40,508 INFO struts2hello.StrutsTest.doView - End doView
> >
> > So we've now gone directly into the render method, but we're in the event
> > phase. Presumably because of struts.portlet.action value of
> > "StrutsTestRender" in the form's action URL.
> >
> > Also we now have both "apple" and "cherry" in the "pie" parameter values:
> > "apple" from being embedded in the URL, "cherry" from being entered in the
> > form.
> > 
> the problem seems to be with JSR168DispatcherServlets ability to construct of /portletNamespace/modeNamespace/actionName
> the  cause seems to come from session management inability to handle the attributes of PortletSession specifically t 	PORTLET_SCOPE
>           This constant defines the scope of the session attribute to be private to the portlet and its included resources.
> As PortletSession is not being represented properly  as implementations seem to be created by upcasted HttpSessions as evidenced here
> //WARNING DOWNCAST here
>     	HttpServletResponse dummyServletResponse =new PortletServletResponse(portlet_response);
> //WARNING DOWNCAST here
>     	HttpServletRequest  dummyServletRequest  =new PortletServletRequest(portlet_request, getPortletContext());
> PortletSession is tied to PortletServletRequest not HttpServletRequest any attempts to access or use cause InvalidState Exception thrown with the side effect that required elements for PortletSession such as /portletNamespace/modeNamespace are invalid or not avialable
> and are thus not properly serialised so the container can read the contents
> The proposed solution would be to fix JSR168DispatcherServlet to use PortletSession
> specifically allow PortletSession to be cognisant of which phase to use  (EVENT_PHASE or RENDER_PHASE)
> http://portals.apache.org/pluto/portlet-1.0-apidocs/javax/portlet/PortletSession.html
> PortletRequest
> http://portals.apache.org/pluto/portlet-1.0-apidocs/javax/portlet/PortletRequest.html
> which has the ability to create PortletSession
> and finally PortletResponse
> http://portals.apache.org/pluto/portlet-1.0-apidocs/javax/portlet/PortletResponse.html
> MCG 17 April 2009 12:49 PM GMT

-- 
This message is automatically generated by JIRA.
-
You can reply to this email to add a comment to the issue online.