3

1. Context

I want to implement an Alfresco-Share Java-backed webscript like the existing "I18N resources and messages Web Script". The main difference is that I want to use the response outputstream (not the writer).

Alfresco-Share version used: 4.1.1.

2. Test code used to reproduce the error

- Spring bean:

<bean id="webscript.test.content.get" parent="webscript" class="test.TestWebscript" />

- Java code:

package test;

import java.io.IOException;

import org.springframework.extensions.webscripts.AbstractWebScript;
import org.springframework.extensions.webscripts.WebScriptRequest;
import org.springframework.extensions.webscripts.WebScriptResponse;

public final class TestWebscript extends AbstractWebScript
{
    @Override
    public void execute(final WebScriptRequest request, final WebScriptResponse response) throws IOException
    {
        response.getOutputStream().write("test".getBytes());
    }
}

- Webscript desc file:

<?xml version="1.0" encoding="UTF-8"?>

<webscript>
  <shortname>Test webscript</shortname>
  <description>A webscript using the response outputstream</description>
  <url>/test/content</url>
  <format default="">extension</format>
  <lifecycle>draft_public_api</lifecycle>
  <authentication>guest</authentication>
  <transaction>required</transaction>
  <family>Tests</family>
</webscript>

3. Result

I have the exception below:

java.lang.IllegalStateException: getOutputStream() has already been called for this response
    at org.apache.catalina.connector.Response.getWriter(Response.java:611)
    at org.apache.catalina.connector.ResponseFacade.getWriter(ResponseFacade.java:198)
    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:1047)
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:817)
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:719)
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:644)
    at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:549)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:617)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:717)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:290)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
    at org.alfresco.web.site.servlet.MTAuthenticationFilter.doFilter(MTAuthenticationFilter.java:74)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
    at org.alfresco.web.site.servlet.SSOAuthenticationFilter.doFilter(SSOAuthenticationFilter.java:355)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:233)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:191)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:298)
    at org.apache.coyote.http11.Http11NioProcessor.process(Http11NioProcessor.java:886)
    at org.apache.coyote.http11.Http11NioProtocol$Http11ConnectionHandler.process(Http11NioProtocol.java:721)
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:2256)
    at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
    at java.lang.Thread.run(Thread.java:662)

4. Question

Is there any way to use the webscript outputstream response?

Illyr
  • 155
  • 7
  • It looks from the stacktrace like you might have both a java and javascript controller for your webscript. Might that be the case? If so, you can't write output from both! – Gagravarr Jan 11 '13 at 10:20
  • @Gagravarr No, I do not think so. Where did you see that? The example I gave is similar to http://wiki.alfresco.com/wiki/Java-backed_Web_Scripts_Samples#SimpleWebScript.java but I use `response.getOutputStream().write()` instead of `response.getWriter().write()` – Illyr Jan 11 '13 at 10:43

3 Answers3

5

This is a limitation of the WebScript Runtime on the Share web-tier. Share has a powerful component framework and extensibility model that wraps all WebScripts - enabling advanced customisations at various points in the JavaScript code, templates etc. Unfortunately treating the WebScripts as components that have their output merged together on a single page means that the Runtime controls the OutputStream and Writer - the Writer you retrieve is not the usual Servlet Writer at all but a wrapper one.

shkwav
  • 126
  • 2
  • For those who don't know, Share is in many ways Kev's baby, so this can be considered a canonical answer! – Gagravarr Jan 15 '13 at 09:28
  • Please have a look at my answer below, which provides a patch for this limitation of the WebScript Runtime in the Surf library. I have also posted it in the related Alfresco JIRA issue. – Sorin Postelnicu Jan 19 '18 at 13:40
0

I've subclassed org.alfresco.repo.web.scripts.content.StreamContent instead of AbstractWebScript in similiar situations.

billerby
  • 1,977
  • 12
  • 22
  • I cannot use this class in my context (`StreamContent` is in the Alfresco repository: not available/packaged with Share/ it uses the NodeService: I cannot re-use this code to make it works in Share). – Illyr Jan 14 '13 at 09:05
  • @Illyr, Please have a look at my answer below, which provides a patch for this limitation of the WebScript Runtime in the Surf library. I have also posted it in the related Alfresco JIRA issue. – Sorin Postelnicu Jan 19 '18 at 13:40
0

I had exactly the same problem (while using Spring Sur 1.2.0, but the same problem is still valid for the latest Trunk version),

and my solution was to patch Spring Surf as documented in the following JIRA issue: https://issues.alfresco.com/jira/browse/ALF-21949

and in the following forum thread: https://community.alfresco.com/thread/202736-webscript-to-export-nodes-to-excel-cannot-call-getwriter-after-getoutputstream


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);
           }

.

Maybe someone with write-access to the spring-surf and spring-webscripts repository will commit these improvements, so then anyone else can use them in the future.

Sorin Postelnicu
  • 1,271
  • 1
  • 10
  • 15