5 Replies Latest reply on Apr 5, 2010 10:46 PM by kasper110382

    Upload seam mail page and send it

    kasper110382

      Hi everyone,


      I'm trying to upgrade my Seam application from JBoss AS 4.x to 5.x and Seam Mail is giving me a headache because I'm seeing different behaviour on the different app. servers. I think this is possibly a bug and I think I have the solution. Let me first tell you about the app:



      • It's packaged as an .ear with two .war files in it.

      • The app is designed to allow upload of .xhtml files to be rendered as Seam Mail pages.

      • The way we do this is something like this (simplified):



      @In
      Renderer renderer;
      
      public void sendMail(byte[] uploadedBytes, String filename) {
        ServletContext servletContext = (ServletContext) FacesContext.getCurrentInstance().getExternalContext().getContext();
        File uploadDir = new File(servletContext.getRealPath("/uploads"));
        File uploadedFile = new File(uploadDir, filename);
        // write uploadedBytes to 'uploadedFile'
        renderer.render("/uploads/" + filename);
      }
      


      This works fine in JBoss 4.x but it doesn't in JBoss 5.x or 6.x. It throws an exception because the resource (

      "/uploads/" + filename

      ) cannot be found.


      I've done some debugging and found a couple of hints as to what goes wrong:



      • The faceletForViewId(...) method in RendererRequest uses ResourceLoader to resolve the resource.

      • The ResourceLoader uses ServletLifecycle.getCurrentServletContext() to resolve the resource.

      • The ServletContext returned ServletLifecycle.getCurrentServletContext() is not the same servlet context as FacesContext.getCurrentInstance().getExternalContext().getContext(). I think this is perhaps because the code above is placed in a Stateless EJB so a ServletContext for the whole .ear is returned by ServletLifecycle? Anyways - looking into catalina's source code there are some if's based on isFileSystemBased() which return true on JBoss 4 and false on JBoss 5/6 - depending on this you get null or a real url when calling getResource(...).

      • I created this replacement ResourceLoader which seems to work, but it's a messy solution. I think maybe this code should be pushed down to Seam's source:



      @Scope(ScopeType.STATELESS)
      @Name("org.jboss.seam.core.resourceLoader")
      @Install(precedence=Install.APPLICATION)
      public class MyResourceLoader extends ResourceLoader {
           @Override
           public URL getResource(String resource) {
                URL result = super.getResource(resource);
                if (result == null) {
                     ServletContext servletContext = (ServletContext) FacesContext
                               .getCurrentInstance().getExternalContext().getContext();
                     try {
                          result = servletContext.getResource(resource);
                     } catch (MalformedURLException e) {
                          throw new IllegalArgumentException(e);
                     }
                }
                return result;
           }
      }
      


      Also - this propagates to other parts of Seam Mail, such as UIAttachment, where the encodeEnd(...) method uses the FacesResources class (which is not a Seam component, so I cannot hack it by replacing it with my own alternative). In other words: Although I managed to hack ResourceLoader, I cannot support uploaded attachments in JBoss 5/6 as I could in JBoss 4! :-(


      Help would be VERY MUCH appreciated as I have tried fixing this issue for several weeks now.

        • 1. Re: Upload seam mail page and send it
          kasper110382

          bump


          Also - if anyone succesfully did something similar, I would be interested in alternative solutions, eg. other ways to upload the files.

          • 2. Re: Upload seam mail page and send it
            kasper110382

            Okay ... I've done some more investigation and found that this is most definately a bug.


            It appears that ServletLifecycle.getCurrentServletContext() will return the servlet context of a wrong .war in my .ear. This makes sense as the Servlet Context is stored in the application scope which seems to be bad design because several servlet contexts can exist in an .ear application!


            To summarize, I believe that the problem is: Several servlet contexts exist, but only one is available in app. scope!


            I've turned on remote debugging and called this piece of code:


            ServletContext servletLifecycleServletContext = ServletLifecycle.getCurrentServletContext();
            ServletContext facesContextServletContext = (ServletContext) FacesContext.getCurrentInstance().getExternalContext().getContext();
            



            When taking a look at these objects I find that not the same context is returned. Take a look at the screenshot from eclipse's debug-view below (the two .war files are named Yacs-intranet and Yacs-website respectively):


            • 3. Re: Upload seam mail page and send it
              kasper110382

              I've reported this as a JIRA issue and supplied a patch that I believe fixes the issue.


              JBSEAM-4623


              Will someone please react to this soon? :)

              • 4. Re: Upload seam mail page and send it
                swd847

                The seam mail issue should be fixed in the next release:


                https://jira.jboss.org/jira/browse/JBSEAM-4590


                The issue was that the mail subsystem was using getServletContext() instead of getCurrentServletContext().

                • 5. Re: Upload seam mail page and send it
                  kasper110382

                  That in deed seems to fix both issues (ie. it also makes my ResourceLoader hack unnescesary).


                  Next (minor) issue: Was there ever any solution to assigning a servlet context to a MDB's context? In JIRA there is mention of an annotation to select which application scope to use. Was this ever implemented? My current hack is to save static references to the servlet contexts and send along the name of the context to use in my JMS message. But it would be nicer to be able to just declaratively select it through an annotation...