+

Search Tips   |   Advanced Search

Example 2: Filter class
Create a content URL generation filter class

This sample filter generates the friendly URL.

The filter performs several steps to create the friendly URL:

  1. The filter determines the target portal page from one of the following sources:

    • The configuration of the web content viewer.

    • Any web content pages that have a content association to the content for which the URL is generated.

    • A target page specified by the UrlCmpnt tag.

  2. If a target page is identified, the filter verifies the page is a web content page with a content association. The filter then validates the content for which the URL is generated is a child of the site area that is mapped to the page. If the content to render is not a child of the site area associated with the page, the filter writes a new URL.

  3. The filter then writes the friendly URL by combining the following information:

    • The friendly URL name of the target page.

    • The path to the content, relative to the site area associated with the target page.

    /******************************************************************
    * Copyright IBM Corp. 2011         
    ******************************************************************/
    package com.ibm.workplace.wcm.api.samples;
    import java.io.*;
    import java.net.*;
    import java.util.*;
    import java.util.logging.*;
    import java.util.regex.*;
    import javax.portlet.*;
    import com.ibm.portal.*;
    import com.ibm.portal.content.*;
    import com.ibm.portal.identification.*;
    import com.ibm.portal.mappingurl.*;
    import com.ibm.portal.resolver.friendly.*;
    import com.ibm.portal.resolver.friendly.accessors.url.*;
    import com.ibm.portal.resolver.friendly.helper.*;
    import com.ibm.portal.resolver.friendly.service.*;
    import com.ibm.portal.serialize.*;
    import com.ibm.portal.services.contentmapping.*;
    import com.ibm.portal.services.contentmapping.exceptions.*;
    import com.ibm.portal.state.*;
    import com.ibm.portal.state.accessors.selection.*;
    import com.ibm.portal.state.exceptions.*;
    import com.ibm.workplace.wcm.api.*;
    import com.ibm.workplace.wcm.api.exceptions.*;
    import com.ibm.workplace.wcm.api.extensions.url.*;
    import com.ibm.workplace.wcm.api.extensions.url.PortletContextSharingConfig.PublishConfig;
     /**
     * Content URL generation filter that tries to generate stateless friendly URLs 
     * for web content pages.
     * 
      * The filter that writes the friendly URL does the following steps to generate 
     * the friendly URL 
     * 
      * <ol>
     * <li>It determines the target portal page from one of the following sources The 
     * Web Content Viewer configuration Web content pages that have a content 
     * mapping for the content the URL is generated for A target page specification 
     * from the WCM [UrlCmpnt] tag 
     * </li>
     * <li>If a page could be determined it checks if the page is a web content page 
     * i.e. if the page has a content mapping assign. It then validates the 
     * content the URL is generated for is a children of the site area mapped to the page. 
     * In case the content is not a children of the site area mapped to the * page new URL is written by this filter.
     * </li>
     * <li>Finally the friendly URL is written that is build from the friendly URL 
     * name of the target page appended with the content path relative to the site 
     * area mapped to the target page.</li>
     * </ol>
     * <p>
     * <b>Note:</b> To use the following sample filter all pages a content URL is 
     * generated for need to be web content pages with a friendly name assigned and 
     * a default content mapping that points to a parent of the content.</p>
     */
    
    public class FriendlyUrlGenerationFilter implements ContentUrlGenerationFilter 
    {
      /** logger */
     private static final Logger LOGGER = Logger.getLogger(FriendlyUrlGenerationFilter.class.getName());
      /** the path separator / */
     private static final String PATH_SEPARATOR = "/";
      /** regular expression pattern to split a path into segments */
     private static final Pattern PATH_SEPARATOR_PATTERN = Pattern.compile(PATH_SEPARATOR);
      /** friendly selection service */
     private final FriendlySelectionService friendlySelectionService;
      /** content model */
     private final ContentModel<ContentNode> contentModel;
      /** WCM workspace */
     private final Workspace workspace;
      /** identifcation service */
     private final Identification identification;
      /** state manager */
     private final PortletStateManager stateManager;
      /** url mapping model */
     private final MappingURLTreeModel urlMappingModel;
      /** content mapping info home */
     private final ContentMappingInfoHome contentMappingInfoHome;
      /** selection accessor */
     private final SelectionAccessorFactory selectionFactory;
      /** factory for friendly URLs */
     private final FriendlyURLFactory friendlyUrlFactory;
      /** the currently selected page */
     private ObjectID currentPage;
      /**
      * Create a new filter instance. This should be called once per render   
      * request   
      *    
      * @param friendlySelectionService
      * The friendly selection service   
      * @param contentModel
      * The content model 
      * @param workspace   
      * The WCM workspace 
      * @param identification   
      * The identificaton service   
      * @param stateManager
      * The state manager service   
      * @param urlMappingTreeModel
      * The url mapping model   
      * @param contentMappingInfoHome
      * The content mapping home interface   
      *    
      * @throws CannotInstantiateAccessorException
      *             If instantiation of state selection accessor factory fails   
      * @throws UnknownAccessorTypeException
      *             If instantiation of state selection accessor factory fails 
      */
    
     public FriendlyUrlGenerationFilter(final FriendlySelectionService 
        friendlySelectionService, 
        final ContentModel<ContentNode> contentModel,
        final Workspace workspace,
        final Identification identification,
        final PortletStateManager stateManager,
        final MappingURLTreeModel urlMappingModel,
        final ContentMappingInfoHome contentMappingInfoHome)
       throws UnknownAccessorTypeException, CannotInstantiateAccessorException 
    {
      final boolean isLogging = LOGGER.isLoggable(Level.FINEST);
      if (isLogging) 
      {
       LOGGER.entering(getClass().getName(), "<init>", new Object[] 
       { 
           friendlySelectionService,
           contentModel,
           workspace,
           identification,
           stateManager,
           urlMappingModel,
           contentMappingInfoHome });
      }
      this.friendlySelectionService = friendlySelectionService;
      this.friendlyUrlFactory = friendlySelectionService.getURLFactory();
      this.contentModel = contentModel;
      this.workspace = workspace;
      this.identification = identification;
      this.stateManager = stateManager;
      this.urlMappingModel = urlMappingModel;
      this.contentMappingInfoHome = contentMappingInfoHome;
      this.selectionFactory = stateManager.getAccessorFactory(SelectionAccessorFactory.class);
    
      if (isLogging) 
      {
        LOGGER.exiting(getClass().getName(), "<init>");
      }
     }
    
     @Override
     public void dispose() 
     {
      final boolean isLogging = LOGGER.isLoggable(Level.FINEST);
      if (isLogging) 
      {
       LOGGER.entering(getClass().getName(), "dispose");
      }
    
      // dispose all request specific services   this.friendlySelectionService.dispose();
      this.stateManager.dispose();
      if (isLogging) 
      {
        LOGGER.exiting(getClass().getName(), "dispose");
      }
     }
    
     @Override
     public void writeURL(final ContentUrlGenerationRequest request,
                          final ContentUrlGenerationResponse response,
                          final ContentUrlGenerationFilterChain chain) throws ContentUrlGenerationException,
                          IOException 
    {
       final boolean isLogging = LOGGER.isLoggable(Level.FINEST);
       if (isLogging) 
       {
         LOGGER.entering(getClass().getName(), "writeURL", new Object[] { request.getContentPath(false) });
       }
    
       // As use the path to lookup the item in WCM we need the  decoded version   
       final String contentPath = request.getContentPath(false);
       if (contentPath != null) 
       {
         // Check if we should generate a URL that publishes to the current or another page or uses the dynamic publishing    
         final PortletContextSharingConfig ctxSharingConfig = request.getPortletContextSharingConfig();
         final PublishConfig publishConfig = ctxSharingConfig.getPublishConfig();
         final PortletRequest portletRequest = request.getPortletRenderRequest();
         final PortletResponse portletResponse = request.getPortletRenderResponse();
         ObjectID targetPageId = null;
    
         try 
         {
         // Determine the target page. The target page is determined     
         // from either a dynamic target page override (i.e. on the UrlCmpnt tag), 
         // a web content mapping on a page or from the portlet configuration      
         // Check if a dynamic page target as been set as it can be set on the WCM UrlCmpnt tag     
    
         final TargetPageConfig targetPageDynamic = request.getDynamicTargetPageOverride();
         if (targetPageDynamic != null) 
         {
            // lookup the page from the dynamic target page override      
            targetPageId = getTargetPage(portletRequest, portletResponse, targetPageDynamic);
         } 
         else 
         {
            if (publishConfig.getMode() == PublishConfig.MODE_DYNAMIC) 
            {
             // lookup the target page from content mappings       
             targetPageId = lookupTargetPage(portletRequest, portletResponse, contentPath);
          } 
          else 
          {
             // target page is determined from portlet configuration       
             final TargetPageConfig targetPagePortletConfig = publishConfig.getTargetPage();
             if (targetPagePortletConfig != null) 
             {
              // lookup the page from the portlet target page configuration        
              targetPageId = getTargetPage(portletRequest, portletResponse, targetPagePortletConfig);
             }
          }
        }
    
        if (targetPageId != null) 
        {
          // check if the path of the content is a children of the     
          // site area mapped to the page and get the path relative to      
          // this site area      
          final String relativePathInfo = getRelativePathInfo(contentPath, targetPageId);
          if (relativePathInfo != null && !relativePathInfo.isEmpty()) 
          {
            // write the friendly URL to the page and the      // relative path information added       
            final FriendlyURL url = this.friendlyUrlFactory.newURL(com.ibm.portal.state.Constants.Clone.EMPTY_COPY);
            url.setSelection(targetPageId);
            url.setPathInfo(relativePathInfo);
            url.writeDispose(response.getWriter());
         } 
         else 
         {
           if (isLogging) 
           {
             LOGGER.logp(Level.FINEST,
                         getClass().getName(),
                         "writeURL",
                         "Content [{0}] is not a children of the site area mapped to page with ID [{1}]",      
                         new Object[] { contentPath, targetPageId });
             }
             // the content is not a children of the site area       
             // mapped to the target page so forward the request to the chain 
             chain.writeURL(request, response);
         }
        } 
        else 
        {
          if (isLogging) 
          {
          LOGGER.logp(Level.FINEST, 
                      getClass().getName(),
                      "writeURL",     
                      "No target page could be determined for content [{0}]",
                      new Object[] { contentPath });
         }
          // no target page could be determined      
          // let the content URL generation chain handle the request      
          chain.writeURL(request, response);
        }
       } catch (SerializationException e) {
        throw new ContentUrlGenerationException(e);
       } catch (ModelException e) {
        throw new ContentUrlGenerationException(e);
       } catch (StateException e) {
        throw new ContentUrlGenerationException(e);
       } catch (ContentMappingException e) {
        throw new ContentUrlGenerationException(e);
       } catch (WCMException e) {
        throw new ContentUrlGenerationException(e);
       }
      } 
      else 
      {
       // no content path was given   
       // let the content URL generation chain handle the request    
       chain.writeURL(request, response);
      }
      if (isLogging) 
      {
       LOGGER.exiting(getClass().getName(), "writeURL");
      }
    }
    
    /**
    * Lookup the best matching target web content page for the content   
    *    
    * @param portletRequest   
    * The current portlet request   
    * @param portletResponse   
    * The current portlet request   
    * @param contentPath
    * The path of the content   
    * @return The {@link ObjectID} of page found or <code>null</code>
    *    
    * @throws ContentMappingException
    * If an error occurred loading a content mapping  
    * @throws ModelException
    * If an exception occurred while accessing a model object 
    * @throws WCMException
    * If an exception occurred while accessing the WCM repository 
    * @throws StateException
    * If an error occurred working with the portal state objects 
    */
    
     protected ObjectID lookupTargetPage(final PortletRequest portletRequest, 
                                         final PortletResponse portletResponse,    
                                         final String contentPath) 
     throws ContentMappingException, ModelException, WCMException, StateException 
     {
       final boolean isLogging = LOGGER.isLoggable(Level.FINEST);
      if (isLogging) 
      {
       LOGGER.entering(getClass().getName(), "lookupTargetPage", new Object[] 
       { 
          contentPath });
       }
       ObjectID result = null;
       // get the ID of the published item addressed by the content path   
       final DocumentIdIterator documentsIt = this.workspace.findByPath(contentPath, Workspace.WORKFLOWSTATUS_PUBLISHED);
       if (documentsIt.hasNext()) {
    
       // get the IDs of the content and all its parents    
       final LinkedList<String> resourceIds = new LinkedList<String>();
       final DocumentId documentId = documentsIt.next();
       resourceIds.push(documentId.getId());
    
       // load the IDs of the parents of the item    
       DocumentId parentId = documentId;
       do 
       {
        Document doc = this.workspace.getById(parentId);
        parentId = null;
        if (doc instanceof Content) 
        {
         parentId = ((Content) doc).getDirectParent();
        } else if (doc instanceof ContentLink) 
        {
         parentId = ((ContentLink) doc).getParentId();
        } else if (doc instanceof SiteFrameworkContainer) 
        {
         parentId = ((SiteFrameworkContainer) doc).getParent();
        }
        if (parentId != null) 
        {
         resourceIds.push(parentId.getId());
        }
       } 
       while (parentId != null);
        // add the library of the content to the beginning    
        resourceIds.push(documentId.getContainingLibrary().getId());
        if (isLogging) 
        {
        LOGGER.logp(Level.FINEST,
                    getClass().getName(),
                    "lookupTargetPage",
                    "Lookup up best matching web content page for resources [{0}] using the following IDs [{1}]",
                    new Object[] {contentPath, resourceIds });}
    
        // lookup the best matching web content page    
        final ContentMappingLocator contentMappinglocator = this.contentMappingInfoHome.getContentMappingLocator();
        final LongestPathMatch match = contentMappinglocator.getLongestPathMatch(resourceIds,
                                                                                 getCurrentPage(portletRequest, 
                                                                                 portletResponse), 
                                                                                 new ContentMappingFilter() 
        {
          public void filterEntitledMappings(List<? extends ContentMapping> mappings) 
          {
    
          // filter out pages we cannot locate e.g. the       
          // user doesn't have access to or if the page is disabled        
          final Locator<ContentNode> contentNodeLocator = FriendlyUrlGenerationFilter.this.contentModel.getLocator();
          final Iterator<? extends ContentMapping> mappingsIt = mappings.iterator();
    
          while (mappingsIt.hasNext()) 
            {
              if (contentNodeLocator.findByID(mappingsIt.next().getResourceID()) == null) 
              {
               mappingsIt.remove();
              }
          }
         }
        });
        // if at least one match was found take the suggest content    
        // mapping further candidates might be found    final ContentMapping contentMapping = match.getContentMapping();
        if (contentMapping != null) 
        {
        result = contentMapping.getResourceID();
        }
      }
      if (isLogging) 
      {
       LOGGER.exiting(getClass().getName(), "lookupTargetPage", result);
      }
      return result;
     }
    
      /**
      * Get the {@link ObjectID} of the target page from a target page configuration.
      * 
      * @param portletRequest 
      * The current portlet request 
      * @param portletResponse 
      * The current portlet request 
      * @param targetPageConfig
      * The target page configuration 
      * @return The {@link ObjectID} of the target page 
      *  
      * @throws SerializationException
      * If the a page ID given as a character string cannot be serialized to an {@link ObjectID}
      * @throws ModelException
      * If an exception occurred while accessing a model object 
      * @throws StateException
      * if an error occurred working with the portal state objects 
      */
     protected ObjectID getTargetPage(final PortletRequest portletRequest,
         final PortletResponse portletResponse,
         final TargetPageConfig targetPageConfig) throws SerializationException,
         ModelException, StateException 
    {
      final boolean isLogging = LOGGER.isLoggable(Level.FINEST);
      if (isLogging) 
      {
       LOGGER.entering(getClass().getName(), "getTargetPage", new Object[] { targetPageConfig });
      }
      ObjectID result = null;
      if (targetPageConfig != null) 
      {
       if (targetPageConfig.useCurrentPage()) 
       {
        result = getCurrentPage(portletRequest, portletResponse);
       } 
       else 
       {
        final String pagePath = targetPageConfig.getPagePath();
        if (pagePath != null && !pagePath.isEmpty()) 
        {
         // try to lookup the page treating the path as a URL mapping      
         result = getPageByUrlMapping(portletRequest, portletResponse, pagePath);
         if (result == null) 
         {
          // if no mapping was found, check if the path is a valid friendly URL       
          final List<ObjectID> pages = getPagesByFriendlyUrl(portletRequest, portletResponse, pagePath);
          if (pages != null && !pages.isEmpty()) 
          {
           // if multiple pages are found for simplicity use the first page more advance URL generation filter        
           // could do a disambiguation here and e.g. let the user choose what page to use result = pages.get(0);
          }
         }
        } 
        else 
        {
         result = getPageById(targetPageConfig.getPageId());
        }
       }
      }
    
      if (isLogging) 
      {
       LOGGER.exiting(getClass().getName(), "getTargetPage", result);
      }
      return result;
     }
    
      /**
      * Get the {@link ObjectID} of the page with the given ID or unique name 
      * 
      * @param pageId  
      *  The ID or unique name of the page 
      * @return The {@link ObjectID} of the page 
      *  
      * @throws SerializationException
      * If the a page ID given as a character string cannot be 
      * serialized to an {@link ObjectID}
      */
    
     protected ObjectID getPageById(final String pageId) throws SerializationException 
     {
       final boolean isLogging = LOGGER.isLoggable(Level.FINEST);
      if (isLogging) 
      {
       LOGGER.entering(getClass().getName(), "getPageById", new Object[] { pageId });
      }
       ObjectID result = null;
      if (pageId != null && !pageId.isEmpty()) 
      {
       // de-serialize the ID    
       result = this.identification.deserialize(pageId);
      }
       if (isLogging) 
       {
         LOGGER.exiting(getClass().getName(), "getPageById", result);
       }
      return result;
     }
      /**
      * Get the {@link ObjectID} of the current page 
      *  
      * @param portletRequest 
      * The current portlet request 
      * @param portletResponse 
      * The current portlet request 
      *  
      * @return The {@link ObjectID} of the current page 
      *  
      * @throws StateException
      * if an error occurred working with the portal state objects 
      */
     protected ObjectID getCurrentPage(final PortletRequest portletRequest, final PortletResponse portletResponse)
       throws StateException 
    {
      final boolean isLogging = LOGGER.isLoggable(Level.FINEST);
      if (isLogging) 
      {
       LOGGER.entering(getClass().getName(), "getCurrentPage");
      }
       if (currentPage == null) 
       {
       final SelectionAccessor selectionAcc = this.selectionFactory.getSelectionAccessor(this.stateManager.getStateHolder());
       try 
       {
        currentPage = selectionAcc.getSelection();
       } 
       finally 
       {
        selectionAcc.dispose();
       }
      }
      if (isLogging) 
      {
         LOGGER.exiting(getClass().getName(), "getCurrentPage", currentPage);
      }
      return currentPage;
     }
      /**
      * Get the list of {@link ObjectID} of all page that are addressed by the  * passed friendly name 
      *  
      * @param portletRequest 
      * The current portlet request 
      * @param portletResponse 
      * The current portlet request 
      * @param friendlyName
      * The friendly name   * @return List of all pages that are addressed by the passed friendly name 
      *  
      * @throws ModelException
      * If looking up the page from a friendly URL fails 
      * @throws StateException
      * if the state could not be accessed 
      */
     protected List<ObjectID> getPagesByFriendlyUrl(final PortletRequest portletRequest,
           final PortletResponse portletResponse,
           final String friendlyName) throws ModelException,
           StateException 
    {
      final boolean isLogging = LOGGER.isLoggable(Level.FINEST);
      if (isLogging) 
      {
       LOGGER.entering(getClass().getName(), "getPagesByFriendlyUrl", new Object[] { friendlyName });
      }
       List<ObjectID> result = null;
       if (friendlyName != null && !friendlyName.isEmpty()) 
       {
       final SelectionResult bean = new DefaultSelectionResult();
       this.friendlySelectionService.resolve(bean, friendlyName);
       // the resulting node list is already AC filtered as a   
       // result of using a performing navigation model.
       final List<ObjectID> nodelist = bean.getNodes();
    
       if (nodelist != null && !nodelist.isEmpty() && bean.getFriendlyPath() != null) 
       {
        result = nodelist;
       }
      }
       if (isLogging) 
       {
       LOGGER.exiting(getClass().getName(), "getPagesByFriendlyUrl", result);
      }
      return result;
     }
    
      /**
      * Get the {@link ObjectID} of the page addressed by the passed compound   
      * name of a url mapping or <code>null</code> if no corresponding URL   
      * mapping or page exists or if the current user does not have access to it.
      *    
      * @param portletRequest   
      *            The current portlet request   
      * @param portletResponse   
      *            The current portlet request   
      * @param urlMapping   
      *            The compound name of the url mapping   
      * @return {@link ObjectID} of the page or <code>null</code>
      * @throws ModelException
      *             If an exception occurred while accessing the url mapping   
      *             model   
      */
     protected ObjectID getPageByUrlMapping(final PortletRequest request, final PortletResponse response,    final String urlMapping) throws ModelException 
     {
      final boolean isLogging = LOGGER.isLoggable(Level.FINEST);
      if (isLogging) 
      {
        LOGGER.entering(getClass().getName(), "getPageByUrlMapping", new Object[] { urlMapping }); }
        ObjectID result = null;
      if (urlMapping != null && !urlMapping.isEmpty()) 
      {
       final BestMatchResult searchResult;
       // different to friendly names a URL mapping must not begin with a 
       if (urlMapping.charAt(0) == PATH_SEPARATOR.charAt(0)) 
       {
        searchResult = this.urlMappingModel.getLocator().findBestMatch(urlMapping.substring(1));
       } else 
       {
        searchResult = this.urlMappingModel.getLocator().findBestMatch(urlMapping);
       }
        if (searchResult != null) 
       {
        final Context mappingCtx = searchResult.getContext();
        if (ObjectTypeConstants.PORTAL_URL.getType().equals(mappingCtx.getAssignedObjectType())) 
       {
         final PortalURL url = (PortalURL) mappingCtx.getAssignedObject();
         result = url.getReferencedResourceID();
        }
       }
      }
       if (isLogging) 
       {
       LOGGER.exiting(getClass().getName(), "getPageByUrlMapping", result);
      }
      return result;
     }
      /**
      * Returns the path for the given content path relative to the site area   
      * mapped to the target page.
      *    
      * Returns <code>null</code> if there is no content mapping set for the  
      * target page that is appropriate for the targeted content item.
      *    
      * @param contentPath
      * The fully qualified path of the target content item. Must not   
      * be <code>null</code>.
      * @param pageId   
      * The object ID of the target page. Must not be   
      * <code>null</code>.
      * @return The relative path which is the remainder of the content path   
      * after cutting off the content mapping prefix. May return   
      * <code>null</code>.
      *    
      * @throws ContentMappingException
      *             If an exception occurred during lookup of the content mapping   
      * @throws WCMException
      *             If an exception occurred while accessing the WCM repository   
      * @throws UnsupportedEncodingException
      *             A requested character encoding is not supported   
      */
    
     protected String getRelativePathInfo(final String contentPath, final ObjectID pageId)
       throws ContentMappingException, WCMException, UnsupportedEncodingException 
    {
       final boolean isLogging = LOGGER.isLoggable(Level.FINEST);
       if (isLogging) 
       {
          LOGGER.entering(getClass().getName(), "getRelativePathInfo", new Object[] { contentPath, pageId });
       }
    
       String result = null;
       final ContentMapping contentMapping = getDefaultContentMapping(pageId);
       if (contentMapping != null) 
       {
          // lookup the path of the site area mapped to the page    
          String pathMapping = contentMapping.getContentPath();
          if (pathMapping == null || pathMapping.isEmpty()) 
          {
            // lets lookup the path from the id     
            final String mappedId = contentMapping.getContentID();
            if (mappedId != null && !mappedId.isEmpty()) 
            {
               pathMapping = this.workspace.getPathById(this.workspace.createDocumentId(mappedId), false, true);
            }
          }
          if (isLogging) 
          {
              LOGGER.logp(Level.FINEST, getClass().getName(), 
                          "getRelativePathInfo",   
                          "Page with ID [{0}] is mapped to [{1}]", 
                          new Object[] { pageId, pathMapping });
          }
          // calculate relative path = contentPath - mappingPath    
          if (pathMapping != null && !pathMapping.isEmpty()) 
          {
          // check if the content path is a children of the mapped path     
          // to do this split the path into its segments     
          if (pathMapping.charAt(0) == PATH_SEPARATOR.charAt(0)) 
          {
           pathMapping = pathMapping.substring(1);
          }
          final String[] partsPathMapping = PATH_SEPARATOR_PATTERN.split(pathMapping);
    
          // also split path of content     
          String pathContent = contentPath;
          if (pathContent.charAt(0) == PATH_SEPARATOR.charAt(0)) 
          {
           pathContent = pathContent.substring(1);
          }
    
          final String[] partsPathContent = PATH_SEPARATOR_PATTERN.split(pathContent);
          // check if the content is a children of the mapped path     
          if (partsPathMapping.length <= partsPathContent.length) 
          {
           boolean isDescendant = true;
           for (int i = 0; i < partsPathMapping.length && isDescendant; i++) 
           {
            if (!partsPathMapping[i].equalsIgnoreCase(partsPathContent[i])) 
            {
             isDescendant = false;
            }
           }
    
           if (isDescendant) 
           {
            // determine how many descendant levels are between the      
            // content and the mapped site area       
            final int descendantLevels = partsPathContent.length - partsPathMapping.length;
            if (descendantLevels > 0) 
            {
             // build children path which is everything after the parent        
             final StringBuilder tmp = new StringBuilder();
             for (int i = 0; i < descendantLevels; i++) {
              tmp.append(PATH_SEPARATOR);
              tmp.append(URLEncoder.encode(partsPathContent[partsPathMapping.length + i], "UTF-8"));
             }
             result = tmp.toString();
           }
         }
        }
       }
      }
       if (isLogging) 
       {
         LOGGER.exiting(getClass().getName(), "getRelativePathInfo", result);
       }
       return result;
     }
    
      /**
      * Get the default content mapping of a page or <code>null</code> if no such   
      * mapping exists   
      *    
      * @param pageId * The {@link ObjectID} of the page * @return The default mapping of the page or <code>null</code> if no   
      *         default mapping could be determined.
      *    
      * @throws ContentMappingDataBackendException
      * If an exception occurred during lookup of the content mapping   
    */
    
    protected ContentMapping getDefaultContentMapping(final ObjectID pageId) throws ContentMappingDataBackendException 
    {
       final boolean isLogging = LOGGER.isLoggable(Level.FINEST);
       if (isLogging) 
       {
        LOGGER.entering(getClass().getName(), "getDefaultContentMapping", new Object[] { pageId });
       }
       // get the page default content mapping as friendly url path info is  
       // only set for default or system content mapping   
       final ContentMappingInfo contentMappingInfo = this.contentMappingInfoHome.getContentMappingInfo(pageId);
       ContentMapping result = contentMappingInfo.getDefaultContentMapping();
    
       if(result == null) 
       {
         // use system mapping as default       
         result = contentMappingInfo.getSystemContentMapping();
       }
       if (isLogging) 
       {
           LOGGER.exiting(getClass().getName(), "getDefaultContentMapping", result);
       }
      return result;
    }
    }


Parent Example 2: Generate a friendly URL for web content