a72036e1 by Adam Heath

Merge branch 'servlet-3.1-async' of /home/git/repositories/brainfood/ofbiz-directcontrolservlet

2 parents f830cd02 94c3a2d7
1 package com.brainfood.ofbiz; 1 package com.brainfood.ofbiz;
2 2
3 import java.io.ByteArrayOutputStream;
3 import java.io.IOException; 4 import java.io.IOException;
4 import java.io.PrintWriter; 5 import java.io.PrintWriter;
6 import java.io.BufferedOutputStream;
5 import java.io.BufferedReader; 7 import java.io.BufferedReader;
6 import java.io.StringReader; 8 import java.io.StringReader;
7 import java.io.InputStreamReader; 9 import java.io.InputStreamReader;
8 import java.io.InputStream; 10 import java.io.InputStream;
9 import java.io.File; 11 import java.io.File;
12 import java.io.FileInputStream;
13 import java.io.FileOutputStream;
14 import java.io.FileReader;
15 import java.io.OutputStream;
10 import java.util.Collections; 16 import java.util.Collections;
11 import java.util.Collection; 17 import java.util.Collection;
12 import java.util.Map; 18 import java.util.Map;
13 import java.net.URL; 19 import java.net.URL;
14 import java.net.MalformedURLException; 20 import java.net.MalformedURLException;
21 import java.security.DigestOutputStream;
22 import java.security.MessageDigest;
23 import java.security.NoSuchAlgorithmException;
15 import java.util.ArrayList; 24 import java.util.ArrayList;
16 import java.util.Set; 25 import java.util.Set;
17 import java.util.Date; 26 import java.util.Date;
...@@ -26,10 +35,17 @@ import java.util.Iterator; ...@@ -26,10 +35,17 @@ import java.util.Iterator;
26 import java.sql.Timestamp; 35 import java.sql.Timestamp;
27 36
28 import javax.script.ScriptContext; 37 import javax.script.ScriptContext;
38 import javax.servlet.AsyncContext;
39 import javax.servlet.AsyncEvent;
40 import javax.servlet.AsyncListener;
41 import javax.servlet.ReadListener;
29 import javax.servlet.RequestDispatcher; 42 import javax.servlet.RequestDispatcher;
30 import javax.servlet.ServletConfig; 43 import javax.servlet.ServletConfig;
31 import javax.servlet.ServletContext; 44 import javax.servlet.ServletContext;
32 import javax.servlet.ServletException; 45 import javax.servlet.ServletException;
46 import javax.servlet.ServletInputStream;
47 import javax.servlet.ServletOutputStream;
48 import javax.servlet.WriteListener;
33 import javax.servlet.http.HttpServlet; 49 import javax.servlet.http.HttpServlet;
34 import javax.servlet.http.HttpServletRequest; 50 import javax.servlet.http.HttpServletRequest;
35 import javax.servlet.http.HttpServletResponse; 51 import javax.servlet.http.HttpServletResponse;
...@@ -38,6 +54,7 @@ import javax.servlet.http.Cookie; ...@@ -38,6 +54,7 @@ import javax.servlet.http.Cookie;
38 import javax.servlet.http.Part; 54 import javax.servlet.http.Part;
39 55
40 import org.ofbiz.base.lang.JSON; 56 import org.ofbiz.base.lang.JSON;
57 import org.ofbiz.base.util.Base64;
41 import org.ofbiz.base.util.Debug; 58 import org.ofbiz.base.util.Debug;
42 import org.ofbiz.base.util.GroovyUtil; 59 import org.ofbiz.base.util.GroovyUtil;
43 import org.ofbiz.base.util.ScriptHelper; 60 import org.ofbiz.base.util.ScriptHelper;
...@@ -62,11 +79,11 @@ import org.ofbiz.service.ServiceContainer; ...@@ -62,11 +79,11 @@ import org.ofbiz.service.ServiceContainer;
62 79
63 import org.apache.commons.csv.CSVFormat; 80 import org.apache.commons.csv.CSVFormat;
64 import org.apache.commons.csv.CSVRecord; 81 import org.apache.commons.csv.CSVRecord;
65 82 import org.apache.commons.fileupload.MultipartStream;
66 import org.apache.commons.fileupload.*; 83 import org.apache.commons.fileupload.ParameterParser;
67 import org.apache.commons.fileupload.disk.DiskFileItemFactory; 84 import org.apache.http.HeaderElement;
68 import org.apache.commons.fileupload.disk.DiskFileItem; 85 import org.apache.http.NameValuePair;
69 import org.apache.commons.fileupload.servlet.ServletFileUpload; 86 import org.apache.http.message.BasicHeaderValueParser;
70 87
71 import groovy.lang.GroovyClassLoader; 88 import groovy.lang.GroovyClassLoader;
72 import groovy.lang.Script; 89 import groovy.lang.Script;
...@@ -157,9 +174,98 @@ public class DirectControlServlet extends HttpServlet { ...@@ -157,9 +174,98 @@ public class DirectControlServlet extends HttpServlet {
157 return; 174 return;
158 } 175 }
159 } 176 }
160 Debug.logInfo("Service name " + serviceName, module); 177 Debug.logInfo("Service name %s" +serviceName + " mapped for " + pathInfo + "#" + method, module, serviceName, pathInfo);
178
179 AsyncContext context = request.startAsync();
180 AsyncHandler asyncHandler = new AsyncHandler(serviceName, request, response, context);
181 context.addListener(asyncHandler);
182 asyncHandler.sin.setReadListener(asyncHandler);
183 } catch (Throwable t) {
184 response.setStatus(500);
185 PrintWriter writer = response.getWriter();
186 Debug.logInfo("Exception processing request", module);
187 Debug.logInfo(t, module);
188 while (t != null) {
189 t.printStackTrace(writer);
190 t = t.getCause();
191 }
192 writer.flush();
193 writer.close();
194 }
195 }
196
197 protected interface Cleanup {
198 void cleanup();
199 }
200
201 protected class AsyncHandler implements ReadListener, WriteListener, AsyncListener {
202 protected final String serviceName;
203 protected final String outputHandler;
204
205 protected final File directory;
206 protected final HttpServletRequest request;
207 protected final HttpServletResponse response;
208
209 protected final ServletInputStream sin;
210 protected final AsyncContext context;
211
212 protected final File tmpFile;
213 protected final OutputStream tmpOut;
214
215 protected final List<Cleanup> cleanups = new ArrayList<Cleanup>();
216
217 protected AsyncHandler(String serviceName, HttpServletRequest request, HttpServletResponse response, AsyncContext context) throws IOException, NoSuchAlgorithmException {
218 this.directory = (File) request.getServletContext().getAttribute("javax.servlet.context.tempdir");
219 this.request = request;
220 this.response = response;
221
222 this.sin = request.getInputStream();
223 this.context = context;
224
225 this.tmpFile = deleteFileWhenDone(File.createTempFile("dcs", ".body", directory));
226 this.tmpOut = new BufferedOutputStream(new FileOutputStream(tmpFile));
227
228 String outputHandler = "JSON";
229 if (serviceName.indexOf("|") != -1) {
230 String[] parts = serviceName.split("\\|");
231 serviceName = parts[0];
232 outputHandler = parts[1];
233 }
234 this.serviceName = serviceName;
235 this.outputHandler = outputHandler;
236 }
237
238
239 public void onAllDataRead() throws IOException {
240 //System.err.println("onAllDataRead");
241 sin.close();
242
243 tmpOut.flush();
244 tmpOut.close();
245
246 try {
247 processRequest();
248 } catch (IOException e) {
249 throw e;
250 } catch (Exception e) {
251 e.printStackTrace();
252 throw (IOException) new IOException(e.getMessage()).initCause(e);
253 } finally {
254 context.complete();
255 }
256 }
161 257
162 // Load context 258 protected File deleteFileWhenDone(final File file) {
259 file.deleteOnExit();
260 cleanups.add(new Cleanup() {
261 public void cleanup() {
262 file.delete();
263 }
264 });
265 return file;
266 }
267
268 protected void processRequest() throws Exception {
163 Map<String, Object> context = new HashMap<String, Object>(); 269 Map<String, Object> context = new HashMap<String, Object>();
164 270
165 // Directly copy request parameters into context. 271 // Directly copy request parameters into context.
...@@ -172,21 +278,23 @@ public class DirectControlServlet extends HttpServlet { ...@@ -172,21 +278,23 @@ public class DirectControlServlet extends HttpServlet {
172 278
173 // Determine type of request and load the context from JSON content or 279 // Determine type of request and load the context from JSON content or
174 // parameter values accordingly 280 // parameter values accordingly
175 if (contentType != null) { 281 String rawContentType = request.getContentType();
176 int semi = contentType.indexOf(";"); 282 String contentType = rawContentType;
283 if (rawContentType != null) {
284 int semi = rawContentType.indexOf(";");
177 if (semi != -1) { 285 if (semi != -1) {
178 contentType = contentType.substring(0, semi); 286 contentType = rawContentType.substring(0, semi);
179 } 287 }
180 } 288 }
181 289
182 if ("application/json".equals(contentType)) { 290 if ("application/json".equals(contentType)) {
183 // Read request body as JSON and insert into the context 291 // Read request body as JSON and insert into the context
184 Map<String, Object> items = UtilGenerics.cast(JSON.from(UtilIO.readString(request.getReader())).toObject(Map.class)); 292 Map<String, Object> items = UtilGenerics.cast(JSON.from(UtilIO.readString(new FileReader(tmpFile))).toObject(Map.class));
185 for (Object key : items.keySet()) { 293 for (Object key : items.keySet()) {
186 context.put((String) key, items.get(key)); 294 context.put((String) key, items.get(key));
187 } 295 }
188 } else if ("text/csv".equals(contentType)) { 296 } else if ("text/csv".equals(contentType)) {
189 Iterable<CSVRecord> records = CSVFormat.EXCEL.parse(request.getReader()); 297 Iterable<CSVRecord> records = CSVFormat.EXCEL.parse(new FileReader(tmpFile));
190 List<List<String>> data = new ArrayList<List<String>>(); 298 List<List<String>> data = new ArrayList<List<String>>();
191 for (CSVRecord record : records) { 299 for (CSVRecord record : records) {
192 List<String> row = new ArrayList<String>(); 300 List<String> row = new ArrayList<String>();
...@@ -198,30 +306,55 @@ public class DirectControlServlet extends HttpServlet { ...@@ -198,30 +306,55 @@ public class DirectControlServlet extends HttpServlet {
198 data.add(row); 306 data.add(row);
199 } 307 }
200 context.put("data", data); 308 context.put("data", data);
201 } else if (contentType != null && contentType.indexOf("multipart/form-data") != -1) { 309 } else if (rawContentType != null && rawContentType.indexOf("multipart/form-data") != -1) {
202 // Create a factory for disk-based file items 310 //System.err.println("contentType[" + rawContentType + "]");
203 DiskFileItemFactory factory = new DiskFileItemFactory(); 311 String boundary = rawContentType.substring("multipart/form-data; boundary=".length());
204 312 MessageDigest digest = MessageDigest.getInstance("SHA-256");
205 // Configure a repository (to ensure a secure temp location is used) 313 MultipartStream multipartStream = new MultipartStream(new FileInputStream(tmpFile), boundary.getBytes(), 65536, null);
206 ServletContext servletContext = config.getServletContext(); 314 ParameterParser parameterParser = new ParameterParser();
207 File repository = (File) servletContext 315
208 .getAttribute("javax.servlet.context.tempdir"); 316 boolean hasNext = multipartStream.skipPreamble();
209 factory.setRepository(repository); 317 while (hasNext) {
210 318 String[] headerLines = multipartStream.readHeaders().split("[\r\n]+(?! )");
211 ServletFileUpload upload = new ServletFileUpload(factory); 319 Map<String, String> headerData = new HashMap<String, String>();
212 320 for (String headerLine: headerLines) {
213 //Map<String, FileItem> itemMap = new HashMap<String, FileItem>(); 321 //System.err.println("headerLine[" + headerLine + "]");
214 322 String[] headerNameAndRawValue = headerLine.split(": ", 2);
215 List<FileItem> items = (List<FileItem>)upload.parseRequest(request); 323 headerData.put(headerNameAndRawValue[0].toLowerCase(), headerNameAndRawValue[1]);
216 for (FileItem item : items) { 324 HeaderElement[] headerElements = BasicHeaderValueParser.INSTANCE.parseElements(headerNameAndRawValue[1], null);
217 Debug.logInfo("PART: " + item.getFieldName(), module); 325 for (HeaderElement headerElement: headerElements) {
218 context.put(item.getFieldName(), item.getString()); 326 String headerName = headerElement.getName();
327 String prefix = (headerNameAndRawValue[0] + "." + headerName).toLowerCase();
328 headerData.put(prefix, headerElement.getValue());
329 for (NameValuePair nameValuePair: headerElement.getParameters()) {
330 headerData.put(prefix + "." + nameValuePair.getName(), nameValuePair.getValue());
331 }
332 }
333 }
334 //System.err.println("header=" + JSON.from(headerData).toString());
335 String fieldName = headerData.get("content-disposition.form-data.name");
336 String fileName = headerData.get("content-disposition.form-data.filename");
337 if (fileName == null) {
338 ByteArrayOutputStream baos = new ByteArrayOutputStream();
339 multipartStream.readBodyData(baos);
340 context.put(fieldName, baos.toString());
341 } else {
342 File tmpFilePart = deleteFileWhenDone(File.createTempFile("dcs", ".part", directory));
343 digest.reset();
344 DigestOutputStream digestOut = new DigestOutputStream(new FileOutputStream(tmpFilePart), digest);
345 multipartStream.readBodyData(digestOut);
346 context.put(fieldName, tmpFilePart);
347 String sha256 = new String(Base64.base64Encode(digest.digest()));
348 context.put(fieldName + ".contentType", headerData.get("content-type"));
349 context.put(fieldName + ".sha256", sha256);
350 }
351 hasNext = multipartStream.readBoundary();
219 } 352 }
220 } else { 353 } else {
221 // Check if the request is a backbone style "emulateJSON" request 354 // Check if the request is a backbone style "emulateJSON" request
222 if (contentType != null && contentType.indexOf("x-www-form-urlencoded") != -1 && request.getParameter("model") != null) { 355 if (contentType != null && contentType.indexOf("x-www-form-urlencoded") != -1 && request.getParameter("model") != null) {
223 Debug.logInfo("MODEL: " + request.getParameter("model"), module); 356 Debug.logInfo("MODEL: " + request.getParameter("model"), module);
224 Map<String, Object> items = UtilGenerics.cast(JSON.from(UtilIO.readString(request.getReader())).toObject(Map.class)); 357 Map<String, Object> items = UtilGenerics.cast(JSON.from(UtilIO.readString(new FileReader(tmpFile))).toObject(Map.class));
225 for (Object key : items.keySet()) { 358 for (Object key : items.keySet()) {
226 if (!"sessionId".equals(key)) { 359 if (!"sessionId".equals(key)) {
227 context.put((String) key, items.get(key)); 360 context.put((String) key, items.get(key));
...@@ -234,14 +367,6 @@ public class DirectControlServlet extends HttpServlet { ...@@ -234,14 +367,6 @@ public class DirectControlServlet extends HttpServlet {
234 LocalDispatcher dispatcher = getDispatcher(request.getServletContext()); 367 LocalDispatcher dispatcher = getDispatcher(request.getServletContext());
235 368
236 // Check if there is an output handler 369 // Check if there is an output handler
237 String outputHandler = "JSON";
238 if (serviceName.indexOf("|") != -1) {
239 String[] parts = serviceName.split("\\|");
240 serviceName = parts[0];
241 outputHandler = parts[1];
242 }
243
244 Debug.logInfo("Service name" +serviceName + " mapped for " + pathInfo + "#" + method, module);
245 370
246 // If the sessionId parameter is set, attempt to look up the corresponding 371 // If the sessionId parameter is set, attempt to look up the corresponding
247 // UserLogin and apply it to the service context 372 // UserLogin and apply it to the service context
...@@ -293,10 +418,13 @@ public class DirectControlServlet extends HttpServlet { ...@@ -293,10 +418,13 @@ public class DirectControlServlet extends HttpServlet {
293 Map<String, Object> result = dispatcher.runSync(serviceName, context); 418 Map<String, Object> result = dispatcher.runSync(serviceName, context);
294 419
295 result.remove("responseMessage"); 420 result.remove("responseMessage");
421 Integer httpStatusCode = (Integer) result.remove("httpStatusCode");
422 int statusCode = httpStatusCode == null ? 200 : httpStatusCode.intValue();
296 423
297 if (result.get("errorMessage") != null) { 424 if (result.get("errorMessage") != null) {
298 response.setStatus(400); 425 statusCode = 400;
299 } 426 }
427 response.setStatus(statusCode);
300 428
301 // Set to expire far in the past. 429 // Set to expire far in the past.
302 response.setHeader("Expires", "Sat, 6 May 1995 12:00:00 GMT"); 430 response.setHeader("Expires", "Sat, 6 May 1995 12:00:00 GMT");
...@@ -373,17 +501,45 @@ public class DirectControlServlet extends HttpServlet { ...@@ -373,17 +501,45 @@ public class DirectControlServlet extends HttpServlet {
373 if ("PDF".equals(outputHandler)) { 501 if ("PDF".equals(outputHandler)) {
374 LibreOfficeRenderer.service(request, response, result); 502 LibreOfficeRenderer.service(request, response, result);
375 } 503 }
376 } catch (Throwable t) { 504
377 response.setStatus(500); 505 //sout.setWriteListener(this);
378 PrintWriter writer = response.getWriter();
379 Debug.logInfo("Exception processing request", module);
380 Debug.logInfo(t, module);
381 while (t != null) {
382 t.printStackTrace(writer);
383 t = t.getCause();
384 } 506 }
385 writer.flush(); 507
386 writer.close(); 508
509 public void onDataAvailable() throws IOException {
510 byte[] buffer = new byte[65536];
511 int numRead;
512 while (sin.isReady() && (numRead = sin.read(buffer)) > 0) {
513 tmpOut.write(buffer, 0, numRead);
514 }
515 }
516
517 public void onWritePossible() throws IOException {
518 context.complete();
519 }
520
521 public void onError(Throwable t) {
522 t.printStackTrace();
523 context.complete();
524 }
525
526 public void onComplete(AsyncEvent event) throws IOException {
527 //System.err.println("onComplete");
528 for (Cleanup cleanup: cleanups) {
529 cleanup.cleanup();
530 }
531 }
532
533 public void onError(AsyncEvent event) {
534 event.getThrowable().printStackTrace();
535 }
536
537 public void onStartAsync(AsyncEvent event) {
538 //System.err.println("onStartAsync");
539 }
540
541 public void onTimeout(AsyncEvent event) {
542 ////System.err.println("onTimeout");
387 } 543 }
388 } 544 }
389 545
......