10 Replies Latest reply on May 12, 2011 3:16 AM by arni

    Accessing portal content with bookmarkable URLs+Parameters

    antoine_h

      Hello,

       

      Some discussions are on this subject, but quite old and none with GateIn.

       

      See : http://community.jboss.org/message/425500#425500

       

      I really miss the PortalCommandFactory, and URLFactory (URLFactoryDelegate, UrlCodecService, etc...) and the way it was architectured and running in JBoss Portal.

      I can see the new architecture is very interesting, to be able to run many kind of "Application" in the portal.

      It also allows to run some PortalContainers, with each running several Portals.

       

      So, there is left to retrieve a way to build the URL, and by this way, to be able to manage the access to Pages and Portlets from outside of the Portlet providing it's ActionUrl or RenderUrl.

      "Outside" is for Bookmarks, or also for calling another page/portlet ActionUrl from a JSF application running in a Portlet in the portal, with the javax.portlet.faces.ViewLink=true of the JBoss Portlet Bridge, etc...

       

      For the "URLFactory" part, there is the :

      • URLBuilder, PortalURLBuilder that can be called with the GateIn Portal WebUI components : UIComponent, UIPage, UIPortlet, etc...
      • The PortalContainer, POMDataStorage, that can be called to get the GateIn Portal Config Model Objects (the "Pom") : Page, Application, etc... (the GateIn Portal WebUI components are only for the Pages and Portlet that are in the current active page)
      • The PortalDataMapper that provide good methods and samples on how to bridge from the GateIn Portal Config Model Objects to the GateIn Portal WebUI components.

      I managed with all this to make some builder for some Url that aims at any Page/Portlet in any Portal.

      It is not easy to find the Ids from the names, the UIPortlet "applicationId" from the Application Pom data (see UIPortlet#setState() method), etc... but it works.

       

      First question : Any tip to find the way the Url is parsed, and the process is dispatch in the Portal, then in the Page/Portlet processing ? Where to look for this ? any classes or component or service that are in charge of this ?

      I have not looked yet at this part, but I guess it is around the PortalRequestContext etc...

      Any clue is welcome...

       

      Second question : Is ther a way, yet built in, or quite ready to make, to have some PortletApplication "clear name or Id" in the Url Paramater, and that is not the database storageName of the Application ?

       

      As far as I can see, the Porlet Action or Render Url is built with this kind of parameter : "portal:componentId=5adf4224-35e9-444b-a65f-95156fb14a86"

      The portal:componentId is the storageName of the Pom Application in the Portal configuration (Page.xml, Navigation.xml, and the Portlet.xml in the War Application).

       

      This is annoying : the storageName is changing each time the Portal configuration is changed.

      To add a page, I have not found (yet) another way to clear the database, so the storageName and storageId are changed, so the Bookmark won't work anymore.

      When going from dev, test, to prod environments, I cannot see a way to modify the portal page structure without reloading the database of PortalConfig from the config files.

       

      Hence, the idea to make a custom delegate "CommandFactory" and "UrlFactory", to build and parse the Url with a custom way.

      The custom way would use some "Portlet/Application names/Ids" that would be stable over each config loading in the Portal configuration in the database.

       

      This is yet the case with the Page : the url uses the Page Navigation path, which is stable (unless to change the names and references etc...).

       

      I am quite sure this is possible and I have missed something.

      Any indication is very welcomed...

       

      JSF2 : With JSF2 coming, there is some JSF bookmarkable url provided.

      It would be worth to allow the full chain of Portal/Page/Application/Jsf content to be bookmarkable, even if the portal configuration have been reloaded in the database, from the config files.

       

      JIRA ? : main focus would be to provide Urls with the Application name/Id independant from the storageName (or Id).

      second focus : provide some more structured and module oriented way to code and decode the Url.

       

      If I did not missed these thing in the portal : make some jira ?

       

      Thanks for the answer or participation to the discussion.

      Antoine

      JBoss Portal and GateIn (JSR-286), JSF, Richfaces, J2EE, Drools, BRMS.

      http://www.sysemo.com

        • 1. Accessing portal content with bookmarkable URLs+Parameters
          arni

          Hi. Interesting post. Would you mind giving me an example on how to obtain an instance of the POMDataStorage/PortletContainer class? Need to find portlet on different page's componentId myself..

           

          Look at this post on how to set more stable componentId's : http://community.jboss.org/message/589755

           

          I'm trying to find a link between applicationId and storageId/componentId, as applicationId will be constant (thus no need to know componentid in advance..).

          • 2. Re: Accessing portal content with bookmarkable URLs+Parameters
            antoine_h

            Hi,

             

            Thanks.

             

            For the PomDataStorage and PortalContainer :

             

                /**
                 * @return
                 */
                public static final POMDataStorage findPOMDataStorageFromCurrentPortalContainer() {
            
                    final POMDataStorage pomDataStorageRet;
                    final PortalContainer portalContainer = PortalContainer.getInstance();
                    pomDataStorageRet = (POMDataStorage) portalContainer
                            .getComponentInstanceOfType(POMDataStorage.class);
            
                    return pomDataStorageRet;
            
                }
            
            
            
            

             

            Look at the PortalContainer.getInstance() code : it gives you the way it is retrieved.

             

            Another way is :

            final ExoContainer container = ExoContainerContext.getContainerByName("myPortalContainer");

             

            The PortalContainer, as a Container bean, contains many components instances.

            You shall use the getComponentInstances() method to get the list of them.

            There is some about 70 or 80 components instances, many are interesting to use, like OrganizationService, etc....

             

            ****************************************************

            Thanks for the post : http://community.jboss.org/message/589755

             

            But for what I understand, the UIComponent of type Tab are some Transient Application.

            It uses the TransientApplicationState (inheriting type of ApplicationState).

            There, the Id is the contentId, that is the "stable" value, as there is no storageId.

             

            For a portlet, that means also all the JSF application through the bridge, the PersistentApplicationState is used.

            There, there is a storageId, that will change each time the configuration is reloaded in the database.

             

            I am not sure about all that, as I have not investigated the Transient Application, but I don't feel it is the track to follow.

             

            ****************************************************

            I'm trying to find a link between applicationId and storageId/componentId, as applicationId will be constant

            That's what I do too.

             

            Here is the code.

            The "final List<ModelObject> applicationList" is obtained from the Page instance : Page.getChildren() (Container super class method).

             

            The main part is I guess youre talking of is:

            applicationIdItem = modelDataStorageToUse.getId(applicationStateItem);

             

            I found this way of getting the "applicationId" in the UIPortlet.setState() method.

            Seems quite good.

            I took only this code to avoid to instanciate the whole UIPortlet instance.

            This is good for performance (building an url shall not make a lot of processing to instanciate a full UIPortlet).

            And more : the UIPortlet is involved in many listener (see the class annotation), that may interfere with the portal running and disturb it, if trying to instanciate it for just building an url.

             

                /**
                 * Find the Application, in the list of Applications, that correspond to the
                 * Portlet with name portletApplicationId, and that is the
                 * uiPortletWithThisAppIdNumberInPage nth (énième) in the list.
                 * <p>
                 * The list of Applications is most of the time the list coming from a
                 * {@link Page} children.
                 * </p>
                 * <p>
                 * This code is inspired from the code of
                 * {@link UIPortlet#setState(org.exoplatform.portal.webui.application.PortletState)}
                 * : See
                 * <code>String applicationId = dataStorage.getId(state.getApplicationState());</code>
                 * , that is the way to get the {@link UIPortlet#getApplicationId()} from
                 * the Application Model Object.
                 * </p>
                 * <p>
                 * Note : if the applicationItem is not a PortletApplication (other type of
                 * content, such as Gadget or other type of content), the
                 * uiPortletApplicationId won't be found anyway.
                 * </p>
                 *
                 * @param <S>
                 * @param applicationList
                 *            The List of {@link Application}. It is a list of ModelObject,
                 *            for convenient call with a {@link Container#getChildren()}
                 *            list.
                 * @param uiPortletApplicationId
                 *            The Application Id of the Portlet. That is usually :
                 *            [webapp/portlet-name], such as
                 *            "pexPalApp95-PexSys-10-Lab-Gen/PexPtlt9530PexLab0110PortletUrlsLab00MainPortlet"
                 *            .
                 * @param uiPortletWithThisAppIdNumberInPage
                 *            The number of this portlet with this ApplicationId, in the
                 *            list of application. Start from 0, ie. : "0" is for the first
                 *            one, "1" for the possible second one, etc... This is for the
                 *            use case of several same portlet in the same page.
                 * @param modelDataStorageToUse
                 * @param sMsgErrorInfoFromCaller
                 * @return
                 */
                @SuppressWarnings("unchecked")
                public static final <S> Application<S> findApplicationFromListByPortletApplicationId(
                        final List<ModelObject> applicationList,
                        final String uiPortletApplicationId,
                        final int uiPortletWithThisAppIdNumberInPage,
                        final ModelDataStorage modelDataStorageToUse,
                        final String sMsgErrorInfoFromCaller) {
                    Application<S> applicationRet = null;
            
                    logger.info("findApplicationFromListByPortletApplicationId() : START.");
            
                    if (applicationList == null || applicationList.size() == 0
                            || uiPortletApplicationId == null
                            || "".equals(uiPortletApplicationId)
                            || modelDataStorageToUse == null) {
                        final MessageError msgError = MessageErrorFactory
                                .msgErrorCreateNewErrorForTechnical(
                                        ProcessErrorCodeConst.PR_ERRCODE_INSTANCE_NULL_VALUE,
                                        "Cannot find the Application, for this PortletApplicationId. The list of Application is null or empty, or the uiPortletApplicationId is null or empty, or the modelDataStorageToUse is null.",
                                        "uiPortletApplicationId="
                                                + uiPortletApplicationId
                                                + "applicationList="
                                                + PortalGateInConfigModelObjectsHelperToStringGen
                                                        .toStringOnlyValues(
                                                                applicationList, null)
                                                + "\n modelDataStorageToUse=" + modelDataStorageToUse
                                                + "\n sMsgErrorInfoFromCaller="
                                                + sMsgErrorInfoFromCaller);
                        throw new GlobAppExIllegalValueException(new ProcessErrorImpl(
                                msgError));
                    }
            
                    Application<S> applicationItem;
                    ApplicationState<S> applicationStateItem;
                    String applicationIdItem;
                    int iNumberFound = 0;
                    for (final ModelObject modelObjectItem : applicationList) {
                        if (modelObjectItem == null) {
                            continue;
                        }
                        if (!(modelObjectItem instanceof Application)) {
                            // If the item is not an Application, just continue with the
                            // others.
                            continue;
                        }
            
                        applicationItem = (Application<S>) modelObjectItem;
                        applicationStateItem = applicationItem.getState();
                        applicationIdItem = null; // reset the value.
                        try {
                            applicationIdItem = modelDataStorageToUse
                                    .getId(applicationStateItem);
                        } catch (final Exception e) {
                            ToolsLoggingProdUsualHelper
                                    .logAWarnStrangeUsual(
                                            "An error occured when trying to get the applicationId from the storage.",
                                            "uiPortletApplicationId="
                                                    + uiPortletApplicationId
                                                    + "applicationList="
                                                    + "\n applicationItem="
                                                    + PortalGateInConfigModelObjectsHelperToStringGen
                                                            .toString(applicationItem, null)
                                                    + "\n sMsgErrorInfoFromCaller="
                                                    + sMsgErrorInfoFromCaller, e, null,
                                            logger, 25);
                        }
                        // Note : if the applicationItem is not a PortletApplication (other
                        // type of content, such as Gadget or other type of content), the
                        // uiPortletApplicationId won't be found anyway.
                        if (uiPortletApplicationId.equals(applicationIdItem)) {
                            if (iNumberFound == uiPortletWithThisAppIdNumberInPage) {
                                applicationRet = applicationItem;
                                break;
                            } else {
                                iNumberFound++;
                            }
                        }
                    }
            
                    logger.info("findApplicationFromListByPortletApplicationId() : START. applicationRet="
                            + PortalGateInConfigModelObjectsHelperToStringGen.toString(
                                    applicationRet, null));
            
                    return applicationRet;
                }
            
            

             

             

            but stil, ... with that, you can build the Url in a stable way, that's ok.

            but this url will use the storageId of the found PortletApplication... then, it won't work if the server is relaunched with reloading of the configuration of the portal.

             

            I hope this discussion will allow to go further... on the side of the Url decoding in the portal engine.

             

            Antoine

            JBoss Portal and GateIn (JSR-286), JSF, Richfaces, J2EE, Drools, BRMS.

            http://www.sysemo.com/Sysemo-expertise-portails-jboss-portal.php

            • 3. Accessing portal content with bookmarkable URLs+Parameters
              arni

              Thank you very much for the detailed answer, and for sharing your code. I have one more question for you though. Am I right in assuming that you have to know the pageid to obtain the Page instance? I was hoping to traverse the "portal tree" looking for the application/portlet without any information other than applicationId..

              • 4. Accessing portal content with bookmarkable URLs+Parameters
                antoine_h

                Hi,

                 

                you're welcome.

                 

                Yes you can traverse all the pages and find "one" application/portlet.

                 

                But,... you have to take into account that a Portlet/Application can be in several page, and moreover, it can also be several times in the same page.

                 

                With JBoss Portal before version 3, this could be easily seen with the "PortletInstance" and "Window" objects.

                Now, with GateIn, the concept of integration of content and applications is brought much further, but still, there can be several instances of the portlet (or whatever kind of application) in the page.

                 

                Hence, looking for the proper Portlet/Application, without giving the "Uri" of the page, and the number of the same portlet/Application among it's "brothers in the page" might end with missing the objective.

                 

                As an example : in a portal that shows a lot of content from a CMS, most of the time, you have one Portlet/Application that can show a content, given it's Uri in the CMS.

                You place several of these Portlet/Application in "several window" of the page, to build the full presentation of all the textes and images you want to show.

                These Portlet/Application in the same page have only their parameters (preferences or set by portlet action/render parameters) that are different.

                Same kind of example : same portlet application of weather, with 2 instances : weather where you live, and where you want to travel...

                 

                Usually, when building an Url to trigger a Portlet Action, you know in what page and which one in the page you focus on.

                So it is better to build a "UrlFactory" that takes this parameters, and processes accordingly.

                This UrlFactory can be reused for other use case.

                 

                May be you can build the code where "if the page id is null", then, it looks in the portal for the unique (or first one) Portlet/Application.

                 

                For performance, it is far better to retrieve only the Page, then look among it's few children.

                 

                Antoine

                JBoss Portal and GateIn (JSR-286), JSF, Richfaces, J2EE, Drools, BRMS.

                http://www.sysemo.com

                • 5. Accessing portal content with bookmarkable URLs+Parameters
                  antoine_h

                  About the code to retrieve the Page :

                  POMDataStorage.getPage(PageKey key)

                  And PageKey has a constructor to give it the full string such as "portal::myPortal::myPage".

                   

                  so it is quite easy.

                   

                  The page part (PageKey and PageNode uri for the url to build) are stable when reloading the database with the portal configuration.

                   

                  The trouble remains on the Portlet/Application Id that is used in the url, and will change at each reload of the configuration...

                  • 6. Accessing portal content with bookmarkable URLs+Parameters
                    arni

                    You are right offcourse. I have managed to get this working now. Certainly a lot of work is done here just for sending a parameter into a portlet, so i'll agree it's not an optimal solution.

                     

                    Btw i tested setting componentid in pages.xml like it was mentioned in the page i linked to earlier, and it didnt work like expected. So until someone can verify that it can be done i'll assume it can't..

                     

                    Thanks

                    • 7. Accessing portal content with bookmarkable URLs+Parameters
                      arni

                      About your last point. My found applicationId is just PortletApplication/PortletId. These are certainly names I can control so can't see the problem here..

                      • 8. Accessing portal content with bookmarkable URLs+Parameters
                        antoine_h

                        Hello,

                         

                        You are right offcourse. I have managed to get this working now. Certainly a lot of work is done here just for sending a parameter into a portlet, so i'll agree it's not an optimal solution.

                        Yes, quite a lot of work : that's why the UrlFactory in the former portal was nice. Easy to override and make some delegates etc..

                        I built my custom code so it can be integrated this way, if the evolution of GateIn Url manipulation goes this way.

                        Btw i tested setting componentid in pages.xml like it was mentioned in the page i linked to earlier, and it didnt work like expected. So until someone can verify that it can be done i'll assume it can't.

                        AFAIK, this componentId is for UIComponents, such as Tabs in a Page. They do not seem to be for Portlet Application in a page.

                        I am not sure as I do not use the UIComponents...

                         

                        If someone may explains some more about this componentId in page.xml ?

                         

                        Antoine

                        JBoss Portal and GateIn (JSR-286), JSF, Richfaces, J2EE.

                        http://www.sysemo.com/Sysemo-logiciel.php

                        • 9. Accessing portal content with bookmarkable URLs+Parameters
                          antoine_h

                          Hello,

                           

                          About your last point. My found applicationId is just PortletApplication/PortletId. These are certainly names I can control so can't see the problem here..

                          I don't see the point of this too.

                          Did you ended with an url that uses the PortletApplication/PortletId ?

                           

                          For the PortletAction Url I saw, the portlet is indicated with the storageId, and unfortnately not with a PortletApplication/PortletId value in it.

                           

                          Antoine

                          JBoss Portal and GateIn (JSR-286), JSF, Richfaces, J2EE.

                          http://www.sysemo.com

                          • 10. Accessing portal content with bookmarkable URLs+Parameters
                            arni

                            Hi - I was just saying that I can now find the componentId knowing PortletApplication/PortleId. Even if componentId change, PA/PI will not.  In many Usecases, this is good help. It doesn't help you with bookmarkable URI's though.

                             

                            And componentId in pages.xml sets id on surrounding container/UIComponent, like you said.