portletspec3
  1. portletspec3
  2. PORTLETSPEC3-40

Add new javax.portlet.ResourceComparator annotation in order to identify duplicate resources

    Details

      Description

      I would like to propose the following annotation, which
      would allow portlet developers (and JSF portlet bridge implementors)
      to provide a way to inform the portlet container of duplicate
      resources. This is a common problem with JSF portlets, since each
      JSF portlet on the portal page needs the "jsf.js" resource:

      package javax.portlet;
      /**
      * Annotation for marking a class that compares resource URLs. Such classes are
      * discovered by the portlet container at startup and are required to
      * implement the {@link java.util.Comparator} interface in order to compare
      * resources represented as type {@link java.lang.String}. If the
      * {@link java.util.Comparator#compare(Object, Object)} method returns 0, then
      * the portlet container will consider the resources to be identical and will include
      * only the first resource in the <head/> section of the rendered portal page.
      *
      * @since 3.0
      */
      @Target(ElementType.TYPE)
      @Retention(RetentionPolicy.RUNTIME)
      public @interface ResourceComparator {
      }
      

      Here is an example that a JSF portlet bridge could use in order to prevent duplicate JSF resources:

      public interface FacesResource {
      
      	public enum TYPE {
      		HREF, LINK, SCRIPT
      	}
      	
      	public TYPE getType();
      	
      	public String getName();
      	
      	public String getLibrary();
      }
      
      @ResourceComparator
      public class FacesResourceComparator implements Comparator<String> {
      
      	public int compare(String resourceURL1, String resourceURL2) {
      
      		if (isFacesResource(resourceURL1) && isFacesResource(resourceURL2)) {
      				
      				FacesResource facesResource1 = new FacesResourceImpl(resourceURL1);
      				FacesResource facesResource2 = new FacesResourceImpl(resourceURL2);
      
      				if (facesResource1.equals(facesResource2)) {
      					return 0;
      				}
      				else {
      					return -1;
      				}
      		}
      		else {
      			return -1;
      		}
      	}
      
      	protected boolean isFacesResource(String url) {
      		return ((url != null) && url.contains("javax.faces.resource"));
      	}
      }
      

        Activity

        Hide
        kito75 added a comment -

        Sounds good to me. Would we need a default implementation?

        Show
        kito75 added a comment - Sounds good to me. Would we need a default implementation?
        Hide
        Ed Burns added a comment -

        This seems like a tough problem to solve in the absence of a component based rendering model.

        Show
        Ed Burns added a comment - This seems like a tough problem to solve in the absence of a component based rendering model.
        Hide
        Ed Burns added a comment -

        I would need to see more context to be able to critique this. I don't know enough about the portlet runtime to understand who would be instantiating the FacesResourceComparator and when it would be getting invoked, for example.

        Show
        Ed Burns added a comment - I would need to see more context to be able to critique this. I don't know enough about the portlet runtime to understand who would be instantiating the FacesResourceComparator and when it would be getting invoked, for example.
        Hide
        Neil Griffin added a comment -

        After each portlet on a page has undergone the RENDER_PHASE of the portlet lifecycle, the portal server is responsible for assembling a complete HTML document. When it comes time for the portal server to build up the contents of the <head>...</head> section, the portal server would invoke classes marked with @ResourceComparator in order to let individual portlets determine whether or not two resources are essentially identical. One example would be a portal page with two or more JSF portlets. Each JSF portlet would attempt to add jsf.js to the <head>...</head> section. The JSF Portlet Bridge could have a class annotated with @ResourceComparator that would be able to identify duplicate jsf.js inclusions. The portal server would then know that it should only include the first instance of jsf.js in the <head>...</head> section.

        Show
        Neil Griffin added a comment - After each portlet on a page has undergone the RENDER_PHASE of the portlet lifecycle, the portal server is responsible for assembling a complete HTML document. When it comes time for the portal server to build up the contents of the <head>...</head> section, the portal server would invoke classes marked with @ResourceComparator in order to let individual portlets determine whether or not two resources are essentially identical. One example would be a portal page with two or more JSF portlets. Each JSF portlet would attempt to add jsf.js to the <head>...</head> section. The JSF Portlet Bridge could have a class annotated with @ResourceComparator that would be able to identify duplicate jsf.js inclusions. The portal server would then know that it should only include the first instance of jsf.js in the <head>...</head> section.
        Hide
        msnicklous added a comment - - edited

        The current spec version (from 8 April 2016) calls for dependencies on page resources to be configured statically through annotations and through the portlet deployment descriptor - see section 28.2.8 "Dependencies".

        The portlet declares dependencies on the page resources it needs. The portal, as owner of the aggregated page, aggregates the dependencies of all portlets on the page, scrubs duplicates, and determines the correct version of each resource that would satisfy all portlet dependencies. The portal then assures that the correct set of page resources is placed on the page.

        I see the additional requirement for portlets to be able to dynamically declare dependencies on page resources at run time. I believe that the requirement could be satisfied by adding the following two methods to the HeaderResponse interface. The first allows a page resource to be dynamically declared, while the second allows a dependency to be declared and at the same time to provide the resource that would fultill the dependency.

        
        void addDependency(String name, String scope, String version)
        
        Adds a dependency on a page-level resource that is managed by the portal.
        
        When a portlet is aggregated onto a page, it may designate resources such as JavaScript files or style sheets that it needs to have on the page in order to operate correctly. These resources typically should be added to the aggregated document head section. Such resources are known as portlet dependencies.
        
        The portal implementation is responsible for adding the portlet dependencies to the page during aggregation. The resources declared as dependencies are typically such that they should be added to the page only a single time, but may be used by multiple portlets.
        
        Portlet dependencies may be configured statically either through annotations or through the portlet deployment descriptor. The resources to satisfy such dependencies will be made available on all pages on which the portlet is rendered.
        
        The dependency added dynamically through this method is added to the statically declared dependencies for the portlet for the current rendering cycle only.
        
        This method should only be used to add portlet dependencies that can potentially be required by or shared with other portlets as well. Strictly private dependencies, such as JavaScript code needed by this portlet only, should be added to to document head section using the PortletResponse#addProperty(String, Element) method or by using either the PrintWriter or OutputStream object obtained through the HeaderResponse.
        
        The manner in which the portal maps the specified name, scope, and version to specific resources is left to the portal implementation.
        
        Parameters:
        name - the resource name
        scope - the resource scope
        version - the resource version
        
        
        
        void addDependency(String name, String scope, String version, Element markup)
        
        Adds a dependency on a page-level resource that is managed by the portal and adds a resource for addition to the page for the dependency.
        
        See addDependency(String, String, String) for a discussion of dependencies.
        
        The specified markup element representing the resource must be suitable for inclusion in the aggregated portal document HEAD section. It will generally be either a LINK or SCRIPT tag.
        
        This method does not directly place markup into the aggregated page document. The aggregating portal will use the identifying information name, scope, and version along with identifying information for other available page resources to determine which resources will be added to the page.
        
        For example, if a portlet provides a resource with a version of 2.0.0 but another portlet on the same page provides a resource with the same name and scope but a version of 2.1.0, the aggregating portal may choose to include the later version of the resource on the page.
        
        Parameters:
        name - the resource name
        scope - the resource scope
        version - the resource version
        markup - the markup for adding the resource to the page
        

        The latter method could be used in the following manner:

        Element link = resp.createElement("link");
        link.setAttribute("rel", "stylesheet");
        link.setAttribute("type", "text/css");
        String contextRoot = req.getContextPath();
        link.setAttribute("href", contextRoot + "/resources/css/CompanyStyle.css");
        headerResponse.addDependency("CompanyStyle", "com.company", "2.3.4", link);
        

        Unfortunately, there is no real standard for module naming and versioning. With one exception, defining such a standard would be beyond the scope of the portlet specification. Portal vendors and portlet developers would be recommended to use a standard or de facto standard if one becomes available.

        The three-part naming and versioning scheme with name, scope, and version, is roughly modeled after the three-part maven coordinate systen with group ID, artifact ID, and version, and also on the node package manager scheme, which requires a package name and version and allows and additional scope identifier.

        For versioning, it would be recommended to adhere to the semantic versioning standard.

        However, if a portlet specifies a dependency with name="PortletHub", scope="javax.portlet", and version="3.0.0", the portal should assure that portlet hub JavaScript module is made available on the aggregated page.

        Show
        msnicklous added a comment - - edited The current spec version (from 8 April 2016) calls for dependencies on page resources to be configured statically through annotations and through the portlet deployment descriptor - see section 28.2.8 "Dependencies". The portlet declares dependencies on the page resources it needs. The portal, as owner of the aggregated page, aggregates the dependencies of all portlets on the page, scrubs duplicates, and determines the correct version of each resource that would satisfy all portlet dependencies. The portal then assures that the correct set of page resources is placed on the page. I see the additional requirement for portlets to be able to dynamically declare dependencies on page resources at run time. I believe that the requirement could be satisfied by adding the following two methods to the HeaderResponse interface. The first allows a page resource to be dynamically declared, while the second allows a dependency to be declared and at the same time to provide the resource that would fultill the dependency. void addDependency( String name, String scope, String version) Adds a dependency on a page-level resource that is managed by the portal. When a portlet is aggregated onto a page, it may designate resources such as JavaScript files or style sheets that it needs to have on the page in order to operate correctly. These resources typically should be added to the aggregated document head section. Such resources are known as portlet dependencies. The portal implementation is responsible for adding the portlet dependencies to the page during aggregation. The resources declared as dependencies are typically such that they should be added to the page only a single time, but may be used by multiple portlets. Portlet dependencies may be configured statically either through annotations or through the portlet deployment descriptor. The resources to satisfy such dependencies will be made available on all pages on which the portlet is rendered. The dependency added dynamically through this method is added to the statically declared dependencies for the portlet for the current rendering cycle only. This method should only be used to add portlet dependencies that can potentially be required by or shared with other portlets as well. Strictly private dependencies, such as JavaScript code needed by this portlet only, should be added to to document head section using the PortletResponse#addProperty( String , Element) method or by using either the PrintWriter or OutputStream object obtained through the HeaderResponse. The manner in which the portal maps the specified name, scope, and version to specific resources is left to the portal implementation. Parameters: name - the resource name scope - the resource scope version - the resource version void addDependency( String name, String scope, String version, Element markup) Adds a dependency on a page-level resource that is managed by the portal and adds a resource for addition to the page for the dependency. See addDependency( String , String , String ) for a discussion of dependencies. The specified markup element representing the resource must be suitable for inclusion in the aggregated portal document HEAD section. It will generally be either a LINK or SCRIPT tag. This method does not directly place markup into the aggregated page document. The aggregating portal will use the identifying information name, scope, and version along with identifying information for other available page resources to determine which resources will be added to the page. For example, if a portlet provides a resource with a version of 2.0.0 but another portlet on the same page provides a resource with the same name and scope but a version of 2.1.0, the aggregating portal may choose to include the later version of the resource on the page. Parameters: name - the resource name scope - the resource scope version - the resource version markup - the markup for adding the resource to the page The latter method could be used in the following manner: Element link = resp.createElement( "link" ); link.setAttribute( "rel" , "stylesheet" ); link.setAttribute( "type" , "text/css" ); String contextRoot = req.getContextPath(); link.setAttribute( "href" , contextRoot + "/resources/css/CompanyStyle.css" ); headerResponse.addDependency( "CompanyStyle" , "com.company" , "2.3.4" , link); Unfortunately, there is no real standard for module naming and versioning. With one exception, defining such a standard would be beyond the scope of the portlet specification. Portal vendors and portlet developers would be recommended to use a standard or de facto standard if one becomes available. The three-part naming and versioning scheme with name, scope, and version, is roughly modeled after the three-part maven coordinate systen with group ID, artifact ID, and version, and also on the node package manager scheme, which requires a package name and version and allows and additional scope identifier. For versioning, it would be recommended to adhere to the semantic versioning standard . However, if a portlet specifies a dependency with name="PortletHub", scope="javax.portlet", and version="3.0.0", the portal should assure that portlet hub JavaScript module is made available on the aggregated page.
        Hide
        msnicklous added a comment - - edited

        Changed the method signature for the method that provides a resource to:

        void addDependency(String name, String scope, String version, String markup)
        

        I initially thought that using the Element class similarly to PortletResponse#addProperty(String, Element) might make the interface more clear and less error-prone. However, in the mean time I came to the conclusion that using the Element class would mostly just make more work for the portlet developer and would not really save on error checking in the portal implementation.

        The modified method can be used like this:

        String contextRoot = headerRequest.getContextPath();
        StringBuilder txt = new StringBuilder(128);
        txt.append("<link href='").append(contextRoot);
        txt.append("/resources/css/infobox.css' rel='stylesheet' type='text/css'>");
        headerResponse.addDependency("infobox", "org.apache.pluto", "0.4.0", txt.toString());
        
        Show
        msnicklous added a comment - - edited Changed the method signature for the method that provides a resource to: void addDependency( String name, String scope, String version, String markup) I initially thought that using the Element class similarly to PortletResponse#addProperty(String, Element) might make the interface more clear and less error-prone. However, in the mean time I came to the conclusion that using the Element class would mostly just make more work for the portlet developer and would not really save on error checking in the portal implementation. The modified method can be used like this: String contextRoot = headerRequest.getContextPath(); StringBuilder txt = new StringBuilder(128); txt.append( "<link href='" ).append(contextRoot); txt.append( "/resources/css/infobox.css' rel='stylesheet' type='text/css'>" ); headerResponse.addDependency( "infobox" , "org.apache.pluto" , "0.4.0" , txt.toString());
        Hide
        msnicklous added a comment -

        Please see the API documentation describing these methods here.

        A reference implementation snapshot implementing the methods is located here.

        A portlet specification snapshot describing the new methods is available here.

        Show
        msnicklous added a comment - Please see the API documentation describing these methods here . A reference implementation snapshot implementing the methods is located here . A portlet specification snapshot describing the new methods is available here .

          People

          • Assignee:
            Unassigned
            Reporter:
            Neil Griffin
          • Votes:
            0 Vote for this issue
            Watchers:
            1 Start watching this issue

            Dates

            • Created:
              Updated:
              Resolved: