ICEfaces
  1. ICEfaces
  2. ICE-8715

Cached resources for ace:richTextEntry can lead to problems with portlets

    Details

    • Type: Bug Bug
    • Status: Closed
    • Priority: Major Major
    • Resolution: Fixed
    • Affects Version/s: 3.2
    • Fix Version/s: EE-3.2.0.GA, 3.3
    • Component/s: ACE-Components
    • Labels:
      None
    • Environment:
      Portlets, ACE comps.
    • Assignee Priority:
      P1
    • Affects:
      Compatibility/Configuration

      Description

      In doing work for ICE-8710, I noticed that the strategy of caching resource URLs during the initial creation of the RichTextEntryResourceHandler can lead to problems when running in portlets. Specifically, when the RichTextEntryResourceHandler is first created, it will try to determine and use the URL of the first portal page that loads with an ace:richTextEntry component. For example, a Liferay URL might look like:

           http://localhost:8080/web/guest/page1....

       It will then try to create mappings for all the resources used by the CKEditor using that URL. If you add another ace:richTextEntry instance to another page:
           http://localhost:8080/web/guest/page2....
       it will attempt to load the resources from the original page. This may work or it may fail depending on the current state of the application. However, we should not be relying on this strategy anyway for portlets as it can either use the incorrect URL or it may fail completely in environments (e.g. WebSphere) were the URLs might be more opaque.

        Activity

        Hide
        Ken Fyten added a comment - - edited

        Assigning to Art to review the resource handling and caching strategy for CKEditor resources.

        Show
        Ken Fyten added a comment - - edited Assigning to Art to review the resource handling and caching strategy for CKEditor resources.
        Hide
        Arturo Zambrano added a comment -

        The resource dependencies for ace:richTextEntry have to be prepared in a special way in order to load the CKEditor resources lazily while still using correct JSF URLs to access each of them. The way this works is as follows. A resource handler instance (extending ResourceHandlerWrapper) is registered in the application via faces-config. This handler, in turn, registers a SystemEventListener on PreRenderViewEvent, which is called at every page view or partial request. The actual set up of resources is initiated by this listener and is done only once, at the very first request in the application of a view that requires the ace:richTextEntry resources. The most important part of this set up is creating a mapping from real paths of the CKEditor resources to valid JSF URLs, in order to allow access to each of those resources from the browser. This mapping is sent to the client, so that it can map a canonical resource name to its corresponding valid JSF URL in the current application. Also, all CSS files have to be processed to replace all occurrences of url() expressions with correct JSF URLs for the specific application.

        In the case of portlets, this is more complicated since the URLs to access required resources (any kind of resources, not just ace:richTextEntry resources) contain information about the page, the war file, the portlet location within the page and other information. So, the mapping described above will contain URLs designed for the very first portlet that requested the resources. This can lead to problems loading the ace:richTextEntry component.

        The first thing I attempted was to prepare a set of these resources per portlet, instead of per application. However, this wouldn't work since the mapping in question is actually a Javascript function, which uses a global variable (window.CKEDITOR_GETURL). This is where the CKEditor looks first to see if it exists in order to use that function, instead of using a naive approach based on base paths and relative paths to access the resources. So, this global function would be overriden by the portlets (containing an ace:richTextEntry component) that are loaded on the page at a later time (i.e. the one loaded at the end would override the mapping loaded by the others). Therefore, in this scenario, some portlets would be still accessing resources with information in their URLs belonging to another portlet. Moreover, the SystemEventListener only fires once per page, not per portlet. Trying to set up a set of resources per page wouldn't work either because they would still be associated with a particular portlet. It's not possible to serve resources that are associated with the page itself; they always have to be associated with a particular portlet. Also, we can't really rely on URLs to determine this, since all portlet environments create their own kind of URLs, and the WebSphere Portal environment encrypts its URLs.

        Another thing I tried was to make a separate mapping per ace:richTextEntry instance and put some logic in the global mapping function to use different maps depending on the instance that was requesting the resources (lazily). This wasn't possible, since there's no way to tell from the global mapping function (window.CKEDITOR_GETURL) which editor instance initiated the request, and also some resources are being requested even before any editor has been initialized. So this possibility had to be ruled out.

        I kept trying different things to prepare a set of resources per portlet, per page or per user. It's just not possible to do this per user because the resource URLs are the same for all users, so there's no way to differentiate among resources prepared for one user or another user. Besides, it would be very innefficient and memory-consuming to have to create a set of resources per user.

        After this, I realized that there can't be multiple versions of the same resource (only varying in the portlet URLs they reference in their bodies), either per user or per page or per portlet, because the resources have a single way of being identified in the server/application. Even if the URL to access a given resource is slightly different (i.e. containing different page/portlet information), the portlet server interprets such URL and identifies the resource it is requesting, of which there's only one instance in the application. For example, independently of the portlet URL used to access 'config.js', it will be internally and uniquely identified as '/showcase/javax.faces.resource/richtextentry/ckeditor/config.js.jsf'. There's a single instance of each resource: either a physical file in the hard drive or a Resource object. So, the only way to have multiple versions of the same resource (either per portlet or per page) would be to create unique internal identifiers, one per version. For example, a version of the above resource for 'page1' in the portlet could be '/showcase/javax.faces.resource/richtextentry/ckeditor/config.page1.js.jsf'. However, in order to do this, we would have to create Resource objects for each of the 213 CKEditor resources, in order to keep things separate per portlet or per page.

        Even if the above was feasible or acceptable, it wouldn't work simply because the mapping resource is declared in a @ResourceDependency annotation ("richtextentry/ckeditor/ckeditor.mapping.js"). This means that there can only be one version of such resource, since this is the first resource the browser will ask for to access the rest of them. The only possibility is to prepare this resource for a specific page every time the SystemEventListener is fired (before rendering the page), and replace its contents with the URLs to access the resources from a specific portlet/page. However, this will only update the contents of the Resource object, and the actual resorce request will be made by the browser at a later moment, once the page loads. This can lead to problems in a multi-threaded system. It will be very likely that the Resource object gets updated again by another thread just before the first client had a chance to request the resource that had been prepared for the page it was viewing. The more users are accessing different pages in the portlet server, the more this is likely to happen.

        Show
        Arturo Zambrano added a comment - The resource dependencies for ace:richTextEntry have to be prepared in a special way in order to load the CKEditor resources lazily while still using correct JSF URLs to access each of them. The way this works is as follows. A resource handler instance (extending ResourceHandlerWrapper) is registered in the application via faces-config. This handler, in turn, registers a SystemEventListener on PreRenderViewEvent, which is called at every page view or partial request. The actual set up of resources is initiated by this listener and is done only once, at the very first request in the application of a view that requires the ace:richTextEntry resources. The most important part of this set up is creating a mapping from real paths of the CKEditor resources to valid JSF URLs, in order to allow access to each of those resources from the browser. This mapping is sent to the client, so that it can map a canonical resource name to its corresponding valid JSF URL in the current application. Also, all CSS files have to be processed to replace all occurrences of url() expressions with correct JSF URLs for the specific application. In the case of portlets, this is more complicated since the URLs to access required resources (any kind of resources, not just ace:richTextEntry resources) contain information about the page, the war file, the portlet location within the page and other information. So, the mapping described above will contain URLs designed for the very first portlet that requested the resources. This can lead to problems loading the ace:richTextEntry component. The first thing I attempted was to prepare a set of these resources per portlet, instead of per application. However, this wouldn't work since the mapping in question is actually a Javascript function, which uses a global variable (window.CKEDITOR_GETURL). This is where the CKEditor looks first to see if it exists in order to use that function, instead of using a naive approach based on base paths and relative paths to access the resources. So, this global function would be overriden by the portlets (containing an ace:richTextEntry component) that are loaded on the page at a later time (i.e. the one loaded at the end would override the mapping loaded by the others). Therefore, in this scenario, some portlets would be still accessing resources with information in their URLs belonging to another portlet. Moreover, the SystemEventListener only fires once per page, not per portlet. Trying to set up a set of resources per page wouldn't work either because they would still be associated with a particular portlet. It's not possible to serve resources that are associated with the page itself; they always have to be associated with a particular portlet. Also, we can't really rely on URLs to determine this, since all portlet environments create their own kind of URLs, and the WebSphere Portal environment encrypts its URLs. Another thing I tried was to make a separate mapping per ace:richTextEntry instance and put some logic in the global mapping function to use different maps depending on the instance that was requesting the resources (lazily). This wasn't possible, since there's no way to tell from the global mapping function (window.CKEDITOR_GETURL) which editor instance initiated the request, and also some resources are being requested even before any editor has been initialized. So this possibility had to be ruled out. I kept trying different things to prepare a set of resources per portlet, per page or per user. It's just not possible to do this per user because the resource URLs are the same for all users, so there's no way to differentiate among resources prepared for one user or another user. Besides, it would be very innefficient and memory-consuming to have to create a set of resources per user. After this, I realized that there can't be multiple versions of the same resource (only varying in the portlet URLs they reference in their bodies), either per user or per page or per portlet, because the resources have a single way of being identified in the server/application. Even if the URL to access a given resource is slightly different (i.e. containing different page/portlet information), the portlet server interprets such URL and identifies the resource it is requesting, of which there's only one instance in the application. For example, independently of the portlet URL used to access 'config.js', it will be internally and uniquely identified as '/showcase/javax.faces.resource/richtextentry/ckeditor/config.js.jsf'. There's a single instance of each resource: either a physical file in the hard drive or a Resource object. So, the only way to have multiple versions of the same resource (either per portlet or per page) would be to create unique internal identifiers, one per version. For example, a version of the above resource for 'page1' in the portlet could be '/showcase/javax.faces.resource/richtextentry/ckeditor/config.page1.js.jsf'. However, in order to do this, we would have to create Resource objects for each of the 213 CKEditor resources, in order to keep things separate per portlet or per page. Even if the above was feasible or acceptable, it wouldn't work simply because the mapping resource is declared in a @ResourceDependency annotation ("richtextentry/ckeditor/ckeditor.mapping.js"). This means that there can only be one version of such resource, since this is the first resource the browser will ask for to access the rest of them. The only possibility is to prepare this resource for a specific page every time the SystemEventListener is fired (before rendering the page), and replace its contents with the URLs to access the resources from a specific portlet/page. However, this will only update the contents of the Resource object, and the actual resorce request will be made by the browser at a later moment, once the page loads. This can lead to problems in a multi-threaded system. It will be very likely that the Resource object gets updated again by another thread just before the first client had a chance to request the resource that had been prepared for the page it was viewing. The more users are accessing different pages in the portlet server, the more this is likely to happen.
        Hide
        Arturo Zambrano added a comment -

        Committed fix to trunk at revision 32921.

        Modified RichTextResourceHandler to only take the existing ckeditor resource mapping script and serve it by means of a special Resource implementation that processes the contents and converts EL resource expressions to correct JSF URLs in portlets and non-portlets environments; also added the pre-processed ckeditor resource mapping script file and pre-processed CSS files with EL resource expressions instead of simple relative paths to images.

        Show
        Arturo Zambrano added a comment - Committed fix to trunk at revision 32921. Modified RichTextResourceHandler to only take the existing ckeditor resource mapping script and serve it by means of a special Resource implementation that processes the contents and converts EL resource expressions to correct JSF URLs in portlets and non-portlets environments; also added the pre-processed ckeditor resource mapping script file and pre-processed CSS files with EL resource expressions instead of simple relative paths to images.
        Hide
        Arturo Zambrano added a comment -

        Added tool (ckeditorurlmapper) to automate the modification of CSS files and the generation of the mapping script from relative paths to EL resource expressions. Committed to trunk at revision 33045.

        Show
        Arturo Zambrano added a comment - Added tool (ckeditorurlmapper) to automate the modification of CSS files and the generation of the mapping script from relative paths to EL resource expressions. Committed to trunk at revision 33045.
        Hide
        Ken Fyten added a comment -

        This commit is causing resources to fail to load in showcase, in Production project_stage.

        Show
        Ken Fyten added a comment - This commit is causing resources to fail to load in showcase, in Production project_stage.
        Hide
        Arturo Zambrano added a comment -

        That commit wasn't causing the problem. A commit related to ICE-8812 caused the problem because of a syntax error in a Javascript resource that prevented the code from being compressed. This was fixed at revision 33055.

        Show
        Arturo Zambrano added a comment - That commit wasn't causing the problem. A commit related to ICE-8812 caused the problem because of a syntax error in a Javascript resource that prevented the code from being compressed. This was fixed at revision 33055.

          People

          • Assignee:
            Arturo Zambrano
            Reporter:
            Deryk Sinotte
          • Votes:
            0 Vote for this issue
            Watchers:
            3 Start watching this issue

            Dates

            • Created:
              Updated:
              Resolved: