Uploaded image for project: 'Confluence Data Center'
  1. Confluence Data Center
  2. CONFSERVER-8280

Export code fails when executed in non-servlet context

    XMLWordPrintable

Details

    Description

      Short intro follows. I've written a Quartz job which exports all spaces of a wiki to HTML and PDF each night. In order to get this working, I had to hack Confluence source a bit because the renderer depends on an instance of javax.servlet.ServletContext. the getResourceAsStream() method of ServletContext is used to fetch external resources for rendering. However, when running as a Quartz job, the thread doesn't have ServletContext available and bad things will happen. My suggestion is to either a) make resource loading independent of ServletContext and use this new code when loading resources or b) ensure there's a ServletContext available regardless if the running thread is a servlet or not and continue using current implementation. a) is probably the right thing to do but probably much harder to implement. I quickly solved the problem by going the b) route. Next I'm going to describe this ugly hack in more detail.

      Here is the bit from my export job class which calls the Confluence export code:

      private File[] exportSpace(Space space, User user) throws ImportExportException, IOException {
      log.info("Exporting space " + space.getKey() + " as user " + user.getName());
      ContentTree contentTree = importExportManager.getContentTree(user, space);
      DefaultExportContext context = new DefaultExportContext();
      context.addWorkingEntity(space);
      context.setScope(ImportExportManager.EXPORT_XML_SCOPE_SPACE);
      context.setExportComments(true);
      context.setExportAttachments(true);
      context.setDateFormatter(userAccessor.getConfluenceUserPreferences(user).getDateFormatter(formatSettingsManager));
      context.setContentTree(contentTree);
      context.setType(ImportExportManager.TYPE_HTML);
      String exportPathHtml = importExportManager.exportAs(context);

      And when the job is executed with unpatched Confluence 2.4.5, NullPointerException shown in export-null-pointer-exception.log will be thrown.

      At line 12 of java/com/atlassian/core/filters/ServletContextThreadLocal.java getRequest() returns null and getSession() on that null results in NullPointerException. I think line 12 should be replaced by something like this line:

      return getRequest() != null ? getRequest().getSession().getServletContext() : null;

      However, I wanted to continue using atlassian-core jar from Atlassian Maven2 repository so I didn't want directly patch ServletContextThreadLocal.java. Instead, I created attached HackedServletContextThreadLocal and HackedServletContextThreadLocalFilter classes and replaced all occurrences of ServletContextThreadLocal and ServletContextThreadLocalFilter in Confluence code and descriptors with these classes.

      Then I created a new servlet (SaveServletContextServlet), which is load on startup in web.xml. The servlet just saves its servlet context as a property of Confluence bootstrap manager.

      Then I patched java/com/atlassian/confluence/importexport/impl/AbstractRendererExporterImpl.java with patch found in safer-AbstractRendererExporterImpl.java.diff. Now servlet context is retrieved using the servlet contex thread local hack class. If it returns null (NullPointerException is thrown anymore), it tries to retrieve a servlet context from the bootstrap manager property saved by the servlet described above. Now we should have a working servlet context whether the running thread is a servlet or Quartz job. It's ugly but it does the trick for me.

      Attachments

        Issue Links

          Activity

            People

              Unassigned Unassigned
              744135a9a5ef Tuomas Jormola
              Votes:
              0 Vote for this issue
              Watchers:
              2 Start watching this issue

              Dates

                Created:
                Updated:
                Resolved: