MyFaces Core
  1. MyFaces Core
  2. MYFACES-2629

Accept abstract FaceletContext, do not force AbstractFaceletContext

    Details

    • Type: Improvement Improvement
    • Status: Open
    • Priority: Major Major
    • Resolution: Unresolved
    • Affects Version/s: 2.0.0-beta-3
    • Fix Version/s: None
    • Component/s: General, JSR-314
    • Labels:
      None
    • Environment:
      Tomcat 6.0+, MyFaces 2.0.0-beta3 API/Impl.

      Description

      I am the main coder on the Gracelets project (http://gracelets.sourceforge.net/) and have recently began integration of Groovy with JSF 2.0. In order for Gracelets to harness the already existing Facelets libraries it needs access to the TagLibrary class and the actual libraries loaded by the JSF 2.0 implementation. Since that library is not part of the JSF 2.0 public API, I have to write an extension for each different JSF 2.0 implementation in order to load them. I have been able to successfully integrate with the SUN RI with minimal code. However, in MyFaces Core implementation this code appears on line 135 of the org.apache.myfaces.view.facelets.tag.jsf.ComponentTagHandlerDelegate:

      AbstractFaceletContext actx = (AbstractFaceletContext) ctx;

      Gracelets has its own FaceletContext (which is part of the public API) in order to mimimize integration between different JSF 2.0 implementations. Since in MyFaces this is forced to be a particular sub class here, it breaks portability. Is there anyway this can be avoided?

      1. MYFACES-2629-1.patch
        91 kB
        Leonardo Uribe
      2. MYFACES-2629-2.patch
        87 kB
        Leonardo Uribe

        Activity

        Hide
        Jakob Korherr added a comment -

        Hi Lewis,

        We cast to the AbstractFaceletContext first, because "our" AbstractFaceletContext defines more methods than FaceletContext from the api, which we need internally. However if this is a big problem here, which it seems to me that it is, I'll take a look at those methods and maybe find another place to put them in order to not have to cast to AbstractFaceletContext.

        Show
        Jakob Korherr added a comment - Hi Lewis, We cast to the AbstractFaceletContext first, because "our" AbstractFaceletContext defines more methods than FaceletContext from the api, which we need internally. However if this is a big problem here, which it seems to me that it is, I'll take a look at those methods and maybe find another place to put them in order to not have to cast to AbstractFaceletContext.
        Hide
        Lewis Gass added a comment - - edited

        I imagined that you probably have "additional" functionality via the Abstract implementation, but could you use the wrapper pattern instead of casting?

        AbstractFaceletContext actx = new SomeASFFaceletContext(ctx);

        or

        AbstractFaceletContext actx = ctx instanceof AbstractFaceletContext ? (AbstractFaceletContext) ctx : new WrapperContext(ctx);

        And then pass on the calls to the original where possible and also allowing more functionality?

        Show
        Lewis Gass added a comment - - edited I imagined that you probably have "additional" functionality via the Abstract implementation, but could you use the wrapper pattern instead of casting? AbstractFaceletContext actx = new SomeASFFaceletContext(ctx); or AbstractFaceletContext actx = ctx instanceof AbstractFaceletContext ? (AbstractFaceletContext) ctx : new WrapperContext(ctx); And then pass on the calls to the original where possible and also allowing more functionality?
        Hide
        Leonardo Uribe added a comment -

        I never thought see one case like this.

        In theory, it is not expected to have a wrapper for FaceletContext. Probe of this fact are:

        • There is no FaceletContextFactory or something similar.
        • There is no documentation about when and where instance of this class are created.
        • The reason why FaceletContext is on the spec is to expose some methods required by FaceletHandler implementations.

        The reason why AbstractFaceletContext exists is provide a class that exposes some inner methods required by facelets implementation. This includes:

        • Handle TemplateClient class (used by "ui" components).
        • Composite component cases.
        • Partial State Saving methods.
        • Bean Validation cases.

        All those methods depends in one way or another of the state stored in DefaultFaceletContext. Obviously it is possible to move this to some place (an object stored on FacesContext or something similar), but I'm not very sure about that.

        If gracelets needs some specific code per impl to work, why don't create a wrapper there that extends from myfaces AbstractFaceletContext and delegate correctly, or even better, why don't we update myfaces code to expose in some way what gracelets needs. I think the right place to put that code is on that integration module. Other option is create a CustomFaceletContextFactory and do on myfaces what is missing on the spec (like in LifecycleProviderFactory to allow integration with web containers). In that place we can do something to wrap FaceletContext object in a "standard" way.

        Show
        Leonardo Uribe added a comment - I never thought see one case like this. In theory, it is not expected to have a wrapper for FaceletContext. Probe of this fact are: There is no FaceletContextFactory or something similar. There is no documentation about when and where instance of this class are created. The reason why FaceletContext is on the spec is to expose some methods required by FaceletHandler implementations. The reason why AbstractFaceletContext exists is provide a class that exposes some inner methods required by facelets implementation. This includes: Handle TemplateClient class (used by "ui" components). Composite component cases. Partial State Saving methods. Bean Validation cases. All those methods depends in one way or another of the state stored in DefaultFaceletContext. Obviously it is possible to move this to some place (an object stored on FacesContext or something similar), but I'm not very sure about that. If gracelets needs some specific code per impl to work, why don't create a wrapper there that extends from myfaces AbstractFaceletContext and delegate correctly, or even better, why don't we update myfaces code to expose in some way what gracelets needs. I think the right place to put that code is on that integration module. Other option is create a CustomFaceletContextFactory and do on myfaces what is missing on the spec (like in LifecycleProviderFactory to allow integration with web containers). In that place we can do something to wrap FaceletContext object in a "standard" way.
        Hide
        Leonardo Uribe added a comment -

        Maybe we need an alternative interface called MyfacesFaceletContext with the required methods, and cast to that interface instead of AbstractFaceletContext (really in that case AbstractFaceletContext should implement MyfacesFaceletContext), so in gracelets you can create a wrapper that extends from your custom FaceletContext wrapper and implement that interface, delegating all required methods.

        I'm just giving some ideas to make what looks better in this case.

        Show
        Leonardo Uribe added a comment - Maybe we need an alternative interface called MyfacesFaceletContext with the required methods, and cast to that interface instead of AbstractFaceletContext (really in that case AbstractFaceletContext should implement MyfacesFaceletContext), so in gracelets you can create a wrapper that extends from your custom FaceletContext wrapper and implement that interface, delegating all required methods. I'm just giving some ideas to make what looks better in this case.
        Hide
        Jakob Korherr added a comment -

        FYI, here is the code from Gracelets that uses the custom FaceletContext implementation (StandardGraceletsHandlerContext):

        public UIViewRoot build(FacesContext faces, GraceletsViewContext ctx, UIViewRoot root) {
        try {
        if (this.root == null || this.compiled < this.source.getLastModified())

        { this.compile(); }

        this.root.apply(new StandardGraceletsHandlerContext(faces, this, ctx), root);
        return root;
        } catch (IOException e)

        { throw ThrowableManagerRegistry.caught(e); }

        }

        Show
        Jakob Korherr added a comment - FYI, here is the code from Gracelets that uses the custom FaceletContext implementation (StandardGraceletsHandlerContext): public UIViewRoot build(FacesContext faces, GraceletsViewContext ctx, UIViewRoot root) { try { if (this.root == null || this.compiled < this.source.getLastModified()) { this.compile(); } this.root.apply(new StandardGraceletsHandlerContext(faces, this, ctx), root); return root; } catch (IOException e) { throw ThrowableManagerRegistry.caught(e); } }
        Hide
        Jakob Korherr added a comment -

        I think we should get rid of AbstractFaceletContext and define the additional methods somewhere else, because since FaceletContext is specified in the api we should also accept different FaceletContext implementations.

        Furthermore it won't be hard to relocate the methods, because:

        • all isXXX() methods like isRefreshingTransientBuild(), isMarkInitialState(), isBuildingCompositeComponentMetadata(),.. are just storing the values of the related static methods from FaceletViewDeclarationLanguage
        • all the push, pop, get method combinations are just accessing attributes on the FacesContext

        The only methods which are accessing instance variables here are pushClient(), extendClient(), includeDefinition() and I am sure we will be able to relocate those too!

        What do you think, Leonardo?

        Show
        Jakob Korherr added a comment - I think we should get rid of AbstractFaceletContext and define the additional methods somewhere else, because since FaceletContext is specified in the api we should also accept different FaceletContext implementations. Furthermore it won't be hard to relocate the methods, because: all isXXX() methods like isRefreshingTransientBuild(), isMarkInitialState(), isBuildingCompositeComponentMetadata(),.. are just storing the values of the related static methods from FaceletViewDeclarationLanguage all the push, pop, get method combinations are just accessing attributes on the FacesContext The only methods which are accessing instance variables here are pushClient(), extendClient(), includeDefinition() and I am sure we will be able to relocate those too! What do you think, Leonardo?
        Hide
        Leonardo Uribe added a comment -

        The most difficult part here is move this methods:

        public abstract void pushClient(TemplateClient client);

        public abstract void popClient(TemplateClient client);

        public abstract void extendClient(TemplateClient client);

        public abstract boolean includeDefinition(UIComponent parent, String name)
        throws IOException, FaceletException, FacesException, ELException;

        public abstract void applyCompositeComponent(UIComponent parent, Resource resource)
        throws IOException, FaceletException, FacesException, ELException;

        Really this is only "the tip of the iceberg". During the integration of facelets, I tried to do not change facelets code without a good reason, to prevent introduce bugs, after all, that code was not developed into our codebase, so we needed some time to understand how it works.

        I'll assign this issue to myself. I think the need to create a MyFaceletContext instance and a MyFacesContext instance is clear (I want to do something similar to RequestContext and RenderingContext on trinidad), and also it is necessary to organize better facelets code, keeping in mind preserve performance of the code. I thought I could continue with tomahawk for jsf 2.0 but all myfaces core issues has more priority.

        Show
        Leonardo Uribe added a comment - The most difficult part here is move this methods: public abstract void pushClient(TemplateClient client); public abstract void popClient(TemplateClient client); public abstract void extendClient(TemplateClient client); public abstract boolean includeDefinition(UIComponent parent, String name) throws IOException, FaceletException, FacesException, ELException; public abstract void applyCompositeComponent(UIComponent parent, Resource resource) throws IOException, FaceletException, FacesException, ELException; Really this is only "the tip of the iceberg". During the integration of facelets, I tried to do not change facelets code without a good reason, to prevent introduce bugs, after all, that code was not developed into our codebase, so we needed some time to understand how it works. I'll assign this issue to myself. I think the need to create a MyFaceletContext instance and a MyFacesContext instance is clear (I want to do something similar to RequestContext and RenderingContext on trinidad), and also it is necessary to organize better facelets code, keeping in mind preserve performance of the code. I thought I could continue with tomahawk for jsf 2.0 but all myfaces core issues has more priority.
        Hide
        Lewis Gass added a comment - - edited

        After more investigation, it turns out this problem also comes up in the SUN RI. I sincerely believe this should have been part of the public API in some way, but its probably too late for that. In any case, this just means that each integration library I write for each respective JSF implementation will be more involved. I have already did preliminary integration with the SUN RI FaceletContextImplBase class and its working now with the TemplateClient internal API, which seems to be replicated between both the SUN RI and MyFaces implementations. TemplateClient, or at least the basic concept of the insert() and define() UI tags seem to belong really in the public API.

        However, for the moment, unless you all just want to make the above proposed changes for other reasons, I will have to integrate by all means with your FaceletContext implementation. In fact if you put the TemplateClient methods (pushClient(), popClient() and extendClient(), includeDefinition()) somewhere else it will break the ability of Gracelets to get involved in that process, since Gracelets views can be used as TemplateClient's.

        One thing I would ask is if you made the DefaultFaceletContext public, and not package private, so that I do not have to reinvent the AJAX based extensions you have added to the AbstractFaceletContext.

        Show
        Lewis Gass added a comment - - edited After more investigation, it turns out this problem also comes up in the SUN RI. I sincerely believe this should have been part of the public API in some way, but its probably too late for that. In any case, this just means that each integration library I write for each respective JSF implementation will be more involved. I have already did preliminary integration with the SUN RI FaceletContextImplBase class and its working now with the TemplateClient internal API, which seems to be replicated between both the SUN RI and MyFaces implementations. TemplateClient, or at least the basic concept of the insert() and define() UI tags seem to belong really in the public API. However, for the moment, unless you all just want to make the above proposed changes for other reasons, I will have to integrate by all means with your FaceletContext implementation. In fact if you put the TemplateClient methods (pushClient(), popClient() and extendClient(), includeDefinition()) somewhere else it will break the ability of Gracelets to get involved in that process, since Gracelets views can be used as TemplateClient's. One thing I would ask is if you made the DefaultFaceletContext public, and not package private, so that I do not have to reinvent the AJAX based extensions you have added to the AbstractFaceletContext.
        Hide
        Jakob Korherr added a comment -

        I think it would be the best if you'd write an email to jsr-314-open@jcp.org and describe your particular case, so that the JSF 2.0 expert group and we can come up with a long term solution to this problem. I don't think that it should be your goal to use "private" MyFaces and Mojarra implementation classes in order to make your code work, because if our implementation code or Mojarra's implementation code changes by any reason (and this happens a lot), you will have to change your code too - and I am sure that this will cause lots of problems and will be totally annoying. Furthermore it could be possible that other projects, which are similar to Gracelets, will also have to use a custom FaceletContext.

        So we all should think about how this problem can be addressed in the best way and get some documentation about it into the JSF specification - and the jsr-314-open@jcp.org mailing list and also the spec issue tracker are the best ways to do so!

        Show
        Jakob Korherr added a comment - I think it would be the best if you'd write an email to jsr-314-open@jcp.org and describe your particular case, so that the JSF 2.0 expert group and we can come up with a long term solution to this problem. I don't think that it should be your goal to use "private" MyFaces and Mojarra implementation classes in order to make your code work, because if our implementation code or Mojarra's implementation code changes by any reason (and this happens a lot), you will have to change your code too - and I am sure that this will cause lots of problems and will be totally annoying. Furthermore it could be possible that other projects, which are similar to Gracelets, will also have to use a custom FaceletContext. So we all should think about how this problem can be addressed in the best way and get some documentation about it into the JSF specification - and the jsr-314-open@jcp.org mailing list and also the spec issue tracker are the best ways to do so!
        Hide
        Leonardo Uribe added a comment - - edited

        I have attached a patch with the possible refactoring we can do in this case (just a first attempt). The idea is create a class called FaceletCompositionContext, that works as a context for all Facelet instances. Actually, FaceletContext instances are created per each Facelet is traversed. This context is shared by all Facelet instances.

        The big problem is TemplateClient/TemplateManager code, in other words all code related to this methods:

        public abstract void pushClient(TemplateClient client);

        public abstract void popClient(TemplateClient client);

        public abstract void extendClient(TemplateClient client);

        public abstract boolean includeDefinition(UIComponent parent, String name)
        throws IOException, FaceletException, FacesException, ELException;

        There is a strong coupling between DefaultFacelet and DefaultFaceletContext. The constructor of DefaultFaceletContext expects a DefaultFacelet, TemplateManager only handle instances of DefaultFacelet ..... too bad, but that's the core of Facelets template mechanism.

        Jakob is right about this should be solved from the spec. I'm afraid if we refactor myfaces code more than the point proposed, it will cause more problems to gracelets, because in the future the code could change without previous warning. At this point I prefer let myfaces code as is without the patch, because if the patch does not solve anything why apply it?

        Ok, so what could we do? In this moment it is unknown what classes or which code api is required to be exposed by the spec and why. From my point of view I can't see it cleary, because I don't know what requires gracelets to work. Maybe you can make clear those points and send them to jsr-314-open mailing list.

        Show
        Leonardo Uribe added a comment - - edited I have attached a patch with the possible refactoring we can do in this case (just a first attempt). The idea is create a class called FaceletCompositionContext, that works as a context for all Facelet instances. Actually, FaceletContext instances are created per each Facelet is traversed. This context is shared by all Facelet instances. The big problem is TemplateClient/TemplateManager code, in other words all code related to this methods: public abstract void pushClient(TemplateClient client); public abstract void popClient(TemplateClient client); public abstract void extendClient(TemplateClient client); public abstract boolean includeDefinition(UIComponent parent, String name) throws IOException, FaceletException, FacesException, ELException; There is a strong coupling between DefaultFacelet and DefaultFaceletContext. The constructor of DefaultFaceletContext expects a DefaultFacelet, TemplateManager only handle instances of DefaultFacelet ..... too bad, but that's the core of Facelets template mechanism. Jakob is right about this should be solved from the spec. I'm afraid if we refactor myfaces code more than the point proposed, it will cause more problems to gracelets, because in the future the code could change without previous warning. At this point I prefer let myfaces code as is without the patch, because if the patch does not solve anything why apply it? Ok, so what could we do? In this moment it is unknown what classes or which code api is required to be exposed by the spec and why. From my point of view I can't see it cleary, because I don't know what requires gracelets to work. Maybe you can make clear those points and send them to jsr-314-open mailing list.
        Hide
        Jakob Korherr added a comment -

        Your patch looks good, Leonardo. However as you mentioned before, I would not apply it now, because it currently does not solve anything.. At first we should asked the EG about this.

        It would really be the best if Lewis could explain exactly what he needs in order to make Gracelets work. Then we all (expert group + developers) could come to a good and stable solution to this problem, which will certainly include moving some facelets code from impl to api.

        I want to add here that all the facelets code which is already in the api was considered to be enough by the EG for all custom facelets implementations. However this does not seem to be the case here for Gracelets, so there is the need to change something!

        Show
        Jakob Korherr added a comment - Your patch looks good, Leonardo. However as you mentioned before, I would not apply it now, because it currently does not solve anything.. At first we should asked the EG about this. It would really be the best if Lewis could explain exactly what he needs in order to make Gracelets work. Then we all (expert group + developers) could come to a good and stable solution to this problem, which will certainly include moving some facelets code from impl to api. I want to add here that all the facelets code which is already in the api was considered to be enough by the EG for all custom facelets implementations. However this does not seem to be the case here for Gracelets, so there is the need to change something!
        Hide
        Lewis Gass added a comment -

        I sent the following message to the jsr-314 expert group according to Jakob's recommendation:

        JSF 2.0 Expert Group,

        I am writing in relation to a particular use case which reveals that the
        current JSF 2.0 public API is defficient. This is in relation the open
        source project Gracelets (http://gracelets.sourceforge.net/) and the new
        effort to integrate JSF 2.0 with Groovy. In order for people to use Groovy
        as an alternative View Langauge they need
        to have access to the all the Facelets tag libraries and participate in the
        Templating framework that Facelets provides. Much of this is tied to the
        TagLibrary and
        TemplateClient API's. Before, with JSF 1.2, there was a single Facelets
        "API" and/or implementation. So integrating with it was much simpler, as is
        shown by previous Gracelets
        versions. With JSF 2.0, part of the Facelets library was divided into public
        API and another as JSF 2.0 specific implementation.

        However, basic concepts such as Templating (TemplateClient and
        TemplateManager) are not considered public API, which means that a
        technology such as Gracelets
        must rely on a per JSF implementation integration library which is volatile
        in nature. The FaceletContext class is public API, but implementations are
        not required to support
        third party implementations of such, and there is no standard way to access
        the TagLibrary used by facelets so that third part View Languages can
        harness them.

        Thus this message has the purpose of requesting such parts of the old
        Facelets library, namely, the TagLibrary, TemplateClient and the related
        FaceletContext methods (popClient(),
        pushClient(), extendClient() and applyDefinition()) to be part of the public
        JSF 2.0 API, while at the same time requiring JSF 2.0 implementors to
        support third party implementations
        of the same classes/API's.

        Respectfully,
        Lewis Gass
        Gracelets Coder
        sestechllc@gmail.com

        Show
        Lewis Gass added a comment - I sent the following message to the jsr-314 expert group according to Jakob's recommendation: JSF 2.0 Expert Group, I am writing in relation to a particular use case which reveals that the current JSF 2.0 public API is defficient. This is in relation the open source project Gracelets ( http://gracelets.sourceforge.net/ ) and the new effort to integrate JSF 2.0 with Groovy. In order for people to use Groovy as an alternative View Langauge they need to have access to the all the Facelets tag libraries and participate in the Templating framework that Facelets provides. Much of this is tied to the TagLibrary and TemplateClient API's. Before, with JSF 1.2, there was a single Facelets "API" and/or implementation. So integrating with it was much simpler, as is shown by previous Gracelets versions. With JSF 2.0, part of the Facelets library was divided into public API and another as JSF 2.0 specific implementation. However, basic concepts such as Templating (TemplateClient and TemplateManager) are not considered public API, which means that a technology such as Gracelets must rely on a per JSF implementation integration library which is volatile in nature. The FaceletContext class is public API, but implementations are not required to support third party implementations of such, and there is no standard way to access the TagLibrary used by facelets so that third part View Languages can harness them. Thus this message has the purpose of requesting such parts of the old Facelets library, namely, the TagLibrary, TemplateClient and the related FaceletContext methods (popClient(), pushClient(), extendClient() and applyDefinition()) to be part of the public JSF 2.0 API, while at the same time requiring JSF 2.0 implementors to support third party implementations of the same classes/API's. Respectfully, Lewis Gass Gracelets Coder sestechllc@gmail.com
        Hide
        Jakob Korherr added a comment -

        Great! I am curious what the EG will respond!

        Show
        Jakob Korherr added a comment - Great! I am curious what the EG will respond!
        Hide
        Leonardo Uribe added a comment -

        I resend the email, and It was created spec issue https://javaserverfaces-spec-public.dev.java.net/issues/show_bug.cgi?id=781

        Show
        Leonardo Uribe added a comment - I resend the email, and It was created spec issue https://javaserverfaces-spec-public.dev.java.net/issues/show_bug.cgi?id=781
        Hide
        Leonardo Uribe added a comment -

        I have attached another patch (MYFACES-2629-2.patch) with some changes.

        After study the code I changed my mind about apply this patch for the following reasons:

        • FaceletContext implementation works as a "page" context. Each time a call to include, or a template is applied, a DefaultFaceletContext instance is used to isolate the facelet id generation (see _prefix var).
        • We have 6 stacks used for different objectives (composite components, ajax, bean validation ....) and all stacks are stored on facesContext attribute map. Each time we access one of this stacks we need to do a lookup on facesContext attribute map. If we have a FaceletCompositionContext, we could store all six stacks there and prevent the lookup on facesContext attribute map (just sharing an instance of FaceletCompositionContext on DefaultFaceletContext).
        • All related code moved to FaceletCompositionContext comes from jsf 2.0, so if we move it, we'll keep the code close with the original facelets code.

        I'll do some other enhancements to the previous code and commit it. Anyway, this issue will be let open, because this patch does not solve the main problem, but help us organize the code.

        Show
        Leonardo Uribe added a comment - I have attached another patch ( MYFACES-2629 -2.patch) with some changes. After study the code I changed my mind about apply this patch for the following reasons: FaceletContext implementation works as a "page" context. Each time a call to include, or a template is applied, a DefaultFaceletContext instance is used to isolate the facelet id generation (see _prefix var). We have 6 stacks used for different objectives (composite components, ajax, bean validation ....) and all stacks are stored on facesContext attribute map. Each time we access one of this stacks we need to do a lookup on facesContext attribute map. If we have a FaceletCompositionContext, we could store all six stacks there and prevent the lookup on facesContext attribute map (just sharing an instance of FaceletCompositionContext on DefaultFaceletContext). All related code moved to FaceletCompositionContext comes from jsf 2.0, so if we move it, we'll keep the code close with the original facelets code. I'll do some other enhancements to the previous code and commit it. Anyway, this issue will be let open, because this patch does not solve the main problem, but help us organize the code.
        Hide
        Leonardo Uribe added a comment -

        Ok, I committed the patch and solve some bugs. Finally it was not moved ajax stack because is "per composite component page context", and also the call to applyCompositeComponent.

        Show
        Leonardo Uribe added a comment - Ok, I committed the patch and solve some bugs. Finally it was not moved ajax stack because is "per composite component page context", and also the call to applyCompositeComponent.

          People

          • Assignee:
            Leonardo Uribe
            Reporter:
            Lewis Gass
          • Votes:
            0 Vote for this issue
            Watchers:
            2 Start watching this issue

            Dates

            • Created:
              Updated:

              Development