javaee-samples / javaee8-samples

Java EE 8 Samples
Other
376 stars 238 forks source link

Add Thorntail support #34

Closed juangon closed 5 years ago

juangon commented 5 years ago

So, after release of 2.3.0.Final, I can push this pull request.

There are some changes I want to highlight:

/cc @Ladicek

Ladicek commented 5 years ago

Looking good to me!

juangon commented 5 years ago

Hi @arjantijms . I guess you are waiting for https://github.com/eclipse-ee4j/servlet-api/issues/233 in order to merge this?

arjantijms commented 5 years ago

@juangon That was indeed the idea. I guess this PR and that one should be able to be merged soon.

Thanks again for looking at this!

arjantijms commented 5 years ago

https://github.com/eclipse-ee4j/servlet-api/pull/234 has been merged, fixing https://github.com/eclipse-ee4j/servlet-api/issues/233

arjantijms commented 5 years ago

@juangon I did spot one potential issue with the PR though:

com.sun.faces.config.ConfigureListener

This is only valid for Mojarra, and not for MyFaces. You could try if a system event listener to do the mapping in works better.

For an example see:

juangon commented 5 years ago

Thanks for pointing that out. Yes I saw it too when implementing but, as servers that works on these tests are all using Mojarra I didn't consider that necessary, althought it should be the right fix indeed.

Will check using System even listeners rigth now.

Thanks!

juangon commented 5 years ago

@arjantijms doesn't work. It isn't possible to add mappings or any servlet registration programatically outside a ServletContext listener:

arjantijms commented 5 years ago

@juangon the failure is probably not exactly due to that, as the PostConstructApplicationEvent is thrown from a ServletContextListener (in Mojarra, it's the ConfigureListener that throws it).

The problem is probably that the ConfigureListener itself is programmatically registered (in FacesInitialiser), and that for some reason things like getServletRegistrations and getVirtualServer are not allowed to be called from programmatically registered listeners.

arjantijms commented 5 years ago

Also interesting here is that StandardContext in GF and Payara contains special exception code for Mojarra:

 if (t instanceof ServletContextListener) {
            ServletContextListener proxy = (ServletContextListener) t;
            if (isProgrammatic) {
                proxy = new RestrictedServletContextListener(
                    (ServletContextListener) t);
            }
            // Always add the JSF listener as the first element,
            // see GlassFish Issue 2563 for details
            boolean isFirst =
                "com.sun.faces.config.ConfigureListener".equals(
                    t.getClass().getName());
            if (isFirst) {
                contextListeners.add(0, proxy);
            } else {
                contextListeners.add(proxy);
            }
            if (!added) {
                added = true;
            }
        }

Referenced issue is https://github.com/eclipse-ee4j/glassfish/issues/2563

arjantijms commented 5 years ago

@juangon after thinking about this a little, the following should work for the case the JSF configure/init listener is last

Collect the registrations in a statically declared listener:

@WebListener
public class MappingInit implements ServletContextListener {

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        sce.getServletContext().setAttribute("mappings", sce.getServletContext().getServletRegistrations());
    }

}

Then use them in the JSF system event listener:

    @Override
    public void processEvent(SystemEvent event) {
        try {
            ServletContext servletContext = (ServletContext) FacesContext.getCurrentInstance().getExternalContext().getContext();

             FacesContext context = FacesContext.getCurrentInstance();

             Map<String, ? extends ServletRegistration> servletRegistrations = (Map<String, ? extends ServletRegistration> ) servletContext.getAttribute("mappings");
             if (servletRegistrations == null) {
                 return;
             }

             servletRegistrations
                   .values()
                   .stream()
                   .filter(e -> e.getClassName().equals(FacesServlet.class.getName()))
                   .findAny()
                   .ifPresent(
                       reg -> context.getApplication()
                                     .getViewHandler()
                                     .getViews(context, "/", RETURN_AS_MINIMAL_IMPLICIT_OUTCOME)
                                     .forEach(e -> reg.addMapping(e)));

        }
        catch (Exception | LinkageError e) {
            throw e;
        }
    }

This should only still take into account the situation that the JSF listener is first, which can be handled by having a regular context listener still there, that checks if the JSF system event listener hasn't run already.

juangon commented 5 years ago

@arjantijms wow, "interesting" hack for making it first listener in Glassfish/Payara:-).

About your suggestion, thanks for that! It now works in Thorntail but not using Payara, as it can't found the new mappings. This is caused mainly by ServletRegistration Map obtained from the ServletContext attribute in SystemEvent being null

juangon commented 5 years ago

@arjantijms I pushed changes for your JSF suggestion here: https://github.com/juangon/javaee8-samples/tree/thorntail_JSF . There you can see it doesn't work in Payara due to mappings being empty. It works in Thorntail. If you need anything from me, just tell me. Thanks!!

arjantijms commented 5 years ago

There you can see it doesn't work in Payara due to mappings being empty. It works in Thorntail.

My suggestion would be too have the regular static listener that does the mappings there and keep a flag in the application attributes to check if mappings have already been applied. If mappings have been applied AND JSF has been initialised, the listener can go ahead and do its regular work (as it does in master now), if not it does nothing.

That would, I think, work for both cases.

juangon commented 5 years ago

Thanks @arjantijms ! Now it works, although looks a bit "hacky".

Just pushed all changes.

arjantijms commented 5 years ago

@juangon thank you too!

Hopefully I can add something to the JSF 3.0 spec or Servlet.Next spec to make this less hacky in the future, but for EE 8 it seems that this is the way then.

Payara is of course failing now, so I should fix that too in Payara.

Thx again!