cancel
Showing results for 
Search instead for 
Did you mean: 

Webscript to export nodes to Excel: cannot call getWriter() after getOutputStream()

sorin_postelnic
Confirmed Champ
Confirmed Champ
Hello everybody,

I have created a Spring Surf presentation webscript that queries the Alfresco repository for some list of nodes, and then exports a selection of node properties to an Excel file, to be downloaded by the user.
The Excel is generated on-the-fly, without being saved to the repository, and it is being streamed directly to the browser.

I have used the following XML webscript description:

<webscript>
  <shortname>ElasticSearch search and export as Excel</shortname>
  <description>ElasticSearch search and export as Excel</description>
  <url>/es/exportexcel</url>
  <authentication>none</authentication>
  <format default=""></format>
</webscript>

and the webscript looks like this:


public class PublicSearchExportExcelWebScript extends AbstractWebScript {
   @Override
   public void execute(WebScriptRequest req, WebScriptResponse res) throws IOException {
      res.setContentType("application/vnd.ms-excel");
      res.setHeader("Content-Disposition", "attachment; filename=\"export.xls\"");
      Workbook wb = new HSSFWorkbook();
      //….. build Excel
      OutputStream contentOutputStream = res.getOutputStream();
      wb.write(contentOutputStream);
   }
}

But whenever I try to download the Excel file by using this webscript, I get this error in the logs (even if the file is downloaded successfully):


ERROR [org.springframework.extensions.webscripts.AbstractRuntime] Exception from executeScript - redirecting to status template error: strict servlet API: cannot call getWriter() after getOutputStream()
java.lang.IllegalStateException: strict servlet API: cannot call getWriter() after getOutputStream()
        at weblogic.servlet.internal.ServletResponseImpl.getWriter(ServletResponseImpl.java:324)
        at javax.servlet.ServletResponseWrapper.getWriter(ServletResponseWrapper.java:148)
        at org.springframework.extensions.webscripts.servlet.WebScriptServletResponse.getWriter(WebScriptServletResponse.java:198)
        at org.springframework.extensions.webscripts.LocalWebScriptRuntimeContainer.executeScript(LocalWebScriptRuntimeContainer.java:241)
        at org.springframework.extensions.webscripts.AbstractRuntime.executeScript(AbstractRuntime.java:377)
        at org.springframework.extensions.webscripts.AbstractRuntime.executeScript(AbstractRuntime.java:209)
        at org.springframework.extensions.webscripts.servlet.mvc.WebScriptView.renderMergedOutputModel(WebScriptView.java:104)
        at org.springframework.web.servlet.view.AbstractView.render(AbstractView.java:250)
        at org.springframework.web.servlet.DispatcherServlet.render(DispatcherServlet.java:1060)
        at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:798)
        at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:716)
        at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:647)
        at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:552)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:731)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:844)
        at weblogic.servlet.internal.StubSecurityHelper$ServletServiceAction.run(StubSecurityHelper.java:280)
        at weblogic.servlet.internal.StubSecurityHelper$ServletServiceAction.run(StubSecurityHelper.java:254)
        at weblogic.servlet.internal.StubSecurityHelper.invokeServlet(StubSecurityHelper.java:136)
        at weblogic.servlet.internal.ServletStubImpl.execute(ServletStubImpl.java:341)
        at weblogic.servlet.internal.ServletStubImpl.execute(ServletStubImpl.java:238)
        at weblogic.servlet.internal.RequestDispatcherImpl.invokeServlet(RequestDispatcherImpl.java:573)
        at weblogic.servlet.internal.RequestDispatcherImpl.forward(RequestDispatcherImpl.java:272)
        at org.tuckey.web.filters.urlrewrite.NormalRewrittenUrl.doRewrite(NormalRewrittenUrl.java:213)
        at org.tuckey.web.filters.urlrewrite.RuleChain.handleRewrite(RuleChain.java:171)
        at org.tuckey.web.filters.urlrewrite.RuleChain.doRules(RuleChain.java:145)
        at org.tuckey.web.filters.urlrewrite.UrlRewriter.processRequest(UrlRewriter.java:92)
        at org.tuckey.web.filters.urlrewrite.UrlRewriteFilter.doFilter(UrlRewriteFilter.java:381)
        at weblogic.servlet.internal.FilterChainImpl.doFilter(FilterChainImpl.java:79)
        at weblogic.servlet.internal.WebAppServletContext$ServletInvocationAction.wrapRun(WebAppServletContext.java:3367)
        at weblogic.servlet.internal.WebAppServletContext$ServletInvocationAction.run(WebAppServletContext.java:3333)
        at weblogic.security.acl.internal.AuthenticatedSubject.doAs(AuthenticatedSubject.java:321)
        at weblogic.security.service.SecurityManager.runAs(SecurityManager.java:120)
        at weblogic.servlet.provider.WlsSubjectHandle.run(WlsSubjectHandle.java:57)
        at weblogic.servlet.internal.WebAppServletContext.doSecuredExecute(WebAppServletContext.java:2220)
        at weblogic.servlet.internal.WebAppServletContext.securedExecute(WebAppServletContext.java:2146)
        at weblogic.servlet.internal.WebAppServletContext.execute(WebAppServletContext.java:2124)
        at weblogic.servlet.internal.ServletRequestImpl.run(ServletRequestImpl.java:1564)
        at weblogic.servlet.provider.ContainerSupportProviderImpl$WlsRequestExecutor.run(ContainerSupportProviderImpl.java:254)
        at weblogic.work.ExecuteThread.execute(ExecuteThread.java:295)
        at weblogic.work.ExecuteThread.run(ExecuteThread.java:254)


ERROR org.springframework.extensions.webscripts.WebScriptException: 03260002 Internal error
        at org.springframework.extensions.webscripts.AbstractRuntime.executeScript(AbstractRuntime.java:335)
        at org.springframework.extensions.webscripts.servlet.mvc.WebScriptView.renderMergedOutputModel(WebScriptView.java:104)
        at org.springframework.web.servlet.view.AbstractView.render(AbstractView.java:250)
        at org.springframework.web.servlet.DispatcherServlet.render(DispatcherServlet.java:1060)
        at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:798)
        Truncated. see log file for complete stacktrace
Caused By: java.lang.IllegalStateException: strict servlet API: cannot call getWriter() after getOutputStream()
        at weblogic.servlet.internal.ServletResponseImpl.getWriter(ServletResponseImpl.java:324)
        at javax.servlet.ServletResponseWrapper.getWriter(ServletResponseWrapper.java:148)
        at org.springframework.extensions.webscripts.servlet.WebScriptServletResponse.getWriter(WebScriptServletResponse.java:198)
        at org.springframework.extensions.webscripts.AbstractRuntime.executeScript(AbstractRuntime.java:330)
        at org.springframework.extensions.webscripts.servlet.mvc.WebScriptView.renderMergedOutputModel(WebScriptView.java:104)
        Truncated. see log file for complete stacktrace

I investigated a bit inside the Spring Surf sources, and I identified that the error occurs at the following lines inside LocalWebScriptRuntimeContainer:


        // call through to the parent container to perform the WebScript processing
        ExtensibilityModel extModel = openExtensibilityModel();
        super.executeScript(scriptReq, scriptRes, auth);
        closeExtensibilityModel(extModel, scriptRes.getWriter());

and then again inside AbstractRuntime when trying to redirect to the error status template:


        String validTemplatePath = container.getTemplateProcessorRegistry().findValidTemplatePath(statusTemplate.getPath());               
        TemplateProcessor statusProcessor = container.getTemplateProcessorRegistry().getTemplateProcessor(validTemplatePath);
        statusProcessor.process(validTemplatePath, statusModel, res.getWriter());

Is there a way for me to suppress the calling of getWriter after I have generated the binary content and streamed it to the response output stream?
6 REPLIES 6

steven_okennedy
Star Contributor
Star Contributor
Hi

I think the problem here may be that the Spring Surf presentation webscript framework is not expecting you to be writing back to the response stream at this time.  As far as it's concerned, it is expecting to process a template and then use the result of that to eventually return something as the response.  I don't know how easy/possible it is to change the way that this works as I don't know if "template-less" webscripts are possible in Surf (maybe they are using a different kind of webscript?)

The easier way to do this (and the way that's standard for this sort of thing for Alfresco OOTB stuff) is to do this kind of work on the Alfresco repository side - i.e. a repository webscript.  Extending from org.alfresco.repo.web.scripts.content.StreamContent.java allows you to easily create a webscript that doesn't have a freemarker template and will never attempt to call a template processor after your controller is finished.

Have a look at the implementation of org.alfresco.repo.web.scripts.content.ContentGet for a concrete example of how this is used to allow the Open in Browser/Download actions in Alfresco.

Regards

Steven

Please keep in mind that in the end I still need to have a Spring Surf webscript which will be used by the public users to download the dynamically-generated files.
This is because the Alfresco repository is not accessible to the outside public, but we have a special web application (based on Spring Surf) which is accessing the repository in the background and it's then returning results to the outside public.

So even if I create a webscript in the Alfresco repository side, I will still need to stream that content from the Alfresco webscript to the Surf webscript.
And then the same problem will apply: the Surf webscript will complain about the getWriter called after getOutputStream.

You can potentially use the Share proxy for that, which allows you to proxy your request through Share to Alfresco - effectively allowing a user to make a call to Alfresco through Share.  Basically your url looks like this:

http://localhost:8080/share/proxy/alfresco/<my-alfresco-webscript-url>

but it actually gets mapped to a call to Alfresco that looks like

http://localhost:8080/alfresco/service/<my-alfresco-webscript-url>


This is a mechanism commonly used by Share javscript components that exectue on the client side - they need to call backend Alfresco webscripts, but obviously they don't want to know anything about Alfresco and don't want to call it directly, so they use this proxy mechanism. You don't need to do anything else except call it using this proxy style url.  Generally you would use this kind of approach when your interaction is kicked off from a client-side action, e.g. Share action, custom button or even from a custom client.  It won't be useful if you're providing a url to users as the start point

Another option if you really need the surf webscript (e.g. as the entry point or to do some logic before you call Alfresco), is to use a redirect response instead.  Not as clean and more traffic, but it will work.  You have the user call your surf webscript url, it goes and does whatever it needs to do as well as calculating the correct Alfresco url to call (again use the proxy style here) and then sends back a HTTP response code 302 and a Location header with your url in it.  This response gets sent back to the user's browser and the browser transparently goes and calls your Alfresco webscript - it will appear seemless to the user, and as long as you're not doing massive amounts of calculation, won't really cause a noticeable delay (e.g. Share does this every time you call localhost:8080/share - it redirects you to your user dashboard using 302 responses).

I'm sure there's probably a way to get around the problem using the Surf framework as well although I haven't poked around enough to know what kind of webscript you'd need to setup to get this to work the way you want it to.

Regards

Steven

Thank you again for your responses, Steven.

Unfortunately it has nothing to do with Alfresco Repository or Share.

I just wanted this feature to work for a simple web application using the Spring Surf library, without any connection to Share or Alfresco.

In my case it is a WebQuickStart application, but it could be any application using the Spring Surf and Spring Webscripts libraries.

Apparently the same problem has been reported by these people here:

Exception while using the response outputstream in a Java-backed Alfresco-Share webscript - Stack Ov... 

[ALF-21949] Conflict between Surf extensibility handling and streaming web script - Alfresco JIRA 

https://stackoverflow.com/questions/14275961/exception-while-using-the-response-outputstream-in-a-ja...

I will try to have a look whether I can find a fix for this problem (preferably a generic solution).

Finally I have fixed this problem by patching Alfresco Surf, like this:

1) I created a new class inside spring-webscripts-1.2.0.jar :

package org.springframework.extensions.webscripts;

/**
* Represents a type of {@link WebScript} which directly streams the content (such as a binary file) to the {@link WebScriptResponse#getOutputStream()}.
* <p>
* If you want to implement the streaming of the content directly to the OutputStream obtained from the {@link WebScriptResponse},
* then subclass this abstract class and override the method {@link AbstractWebScript#execute(WebScriptRequest, WebScriptResponse)}.
*/

public abstract class OutputStreamWebScript extends AbstractWebScript {
}

As you can see, this is just an empty "marker class".

2) I have modified the following class inside spring-surf-1.2.0.jar :   org.springframework.extensions.webscripts.LocalWebScriptRuntimeContainer   (the one which was causing the exception):

2a) I have added a new method after executeScript(WebScriptRequest scriptReq, WebScriptResponse scriptRes, Authenticator auth) :

     private void executeScriptWithExtensibilityModel(WebScriptRequest scriptReq, WebScriptResponse scriptRes, Authenticator auth) throws IOException
     {
          WebScript script = scriptReq.getServiceMatch().getWebScript();

          if (script instanceof OutputStreamWebScript)
          {
               // This type of WebScript streams directly the content to the OutputStream of the WebScriptResponse,
               // so we must not apply any extensibility model, but call through to the parent container to perform the WebScript processing
               super.executeScript(scriptReq, scriptRes, auth);
          }
          else
          {
               // For all the other types of WebScripts, apply the extensibility model as needed
               ExtensibilityModel extModel = openExtensibilityModel();
               super.executeScript(scriptReq, scriptRes, auth);
               closeExtensibilityModel(extModel, scriptRes.getWriter());
          }
     }

2b) I replaced the following lines inside executeScript(WebScriptRequest scriptReq, WebScriptResponse scriptRes, Authenticator auth):

            try
            {
                // call through to the parent container to perform the WebScript processing
                ExtensibilityModel extModel = openExtensibilityModel();
                super.executeScript(scriptReq, scriptRes, auth);
                closeExtensibilityModel(extModel, scriptRes.getWriter());
            }

with these lines:

               try
               {
                    // call through to the parent container to perform the WebScript processing, applying any ExtensibilityModel
                    executeScriptWithExtensibilityModel(scriptReq, scriptRes, auth);
               }

I will try to see if someone with access to the spring-surf and spring-webscripts repository can commit these improvements, so anyone else can use them in the future.

krutik_jayswal
Elite Collaborator
Elite Collaborator

There is one generic example using which you can export any kind of data in excel.Below link will help you understanding that stuff.

Alfresco Webscript : Export Data In Excel | Krutik Jayswal