View Javadoc

1   package pl.psnc.dl.ege.webapp.servlet;
2   
3   import java.io.ByteArrayInputStream;
4   import java.io.File;
5   import java.io.FileInputStream;
6   import java.io.FileNotFoundException;
7   import java.io.FileOutputStream;
8   import java.io.IOException;
9   import java.io.InputStream;
10  import java.io.OutputStream;
11  import java.io.PrintWriter;
12  import java.util.List;
13  import java.util.Properties;
14  import java.util.Set;
15  import java.util.UUID;
16  import java.util.zip.ZipException;
17  import java.util.zip.ZipFile;
18  
19  import javax.servlet.ServletException;
20  import javax.servlet.http.HttpServlet;
21  import javax.servlet.http.HttpServletRequest;
22  import javax.servlet.http.HttpServletResponse;
23  
24  import org.apache.commons.fileupload.FileItemIterator;
25  import org.apache.commons.fileupload.FileItemStream;
26  import org.apache.commons.fileupload.FileUploadException;
27  import org.apache.commons.fileupload.servlet.ServletFileUpload;
28  import org.apache.log4j.Logger;
29  
30  import pl.psnc.dl.ege.EGE;
31  import pl.psnc.dl.ege.EGEImpl;
32  import pl.psnc.dl.ege.configuration.EGEConfigurationManager;
33  import pl.psnc.dl.ege.configuration.EGEConstants;
34  import pl.psnc.dl.ege.exception.ConverterException;
35  import pl.psnc.dl.ege.exception.EGEException;
36  import pl.psnc.dl.ege.exception.ValidatorException;
37  import pl.psnc.dl.ege.types.ConversionAction;
38  import pl.psnc.dl.ege.types.ConversionsPath;
39  import pl.psnc.dl.ege.types.DataType;
40  import pl.psnc.dl.ege.types.ValidationResult;
41  import pl.psnc.dl.ege.utils.DataBuffer;
42  import pl.psnc.dl.ege.utils.EGEIOUtils;
43  import pl.psnc.dl.ege.utils.IOResolver;
44  import pl.psnc.dl.ege.webapp.config.LabelProvider;
45  import pl.psnc.dl.ege.webapp.config.MimeExtensionProvider;
46  import pl.psnc.dl.ege.webapp.config.PreConfig;
47  import pl.psnc.dl.ege.webapp.request.ConversionRequestResolver;
48  import pl.psnc.dl.ege.webapp.request.ConversionsPropertiesHandler;
49  import pl.psnc.dl.ege.webapp.request.Method;
50  import pl.psnc.dl.ege.webapp.request.OperationId;
51  import pl.psnc.dl.ege.webapp.request.RequestResolver;
52  import pl.psnc.dl.ege.webapp.request.RequestResolvingException;
53  
54  /**
55   * EGE RESTful WebService interface.
56   * 
57   * Conversion web service servlet, accepting requests in REST WS manner.
58   * 
59   * @author mariuszs
60   */
61  /*
62   * TODO : Metody tworzace XML wrzucic do osobnej klasy lub uzyc Apache Velocity.
63   */
64  public class ConversionServlet extends HttpServlet {
65  
66  	private static final String EZP_EXT = ".ezp";
67  
68  	private static final String APPLICATION_MSWORD = "application/msword";
69  
70  	private static final String APPLICATION_OCTET_STREAM = "application/octet-stream";
71  
72  	private static final Logger LOGGER = Logger
73  			.getLogger(ConversionServlet.class);
74  
75  	private static final long serialVersionUID = 1L;
76  
77  	public static final String SLASH = "/";
78  
79  	public static final String COMMA = ",";
80  
81  	public static final String SEMICOLON = ";";
82  
83  	public static final String R_WRONG_METHOD = "Wrong method: GET, expected: POST.";
84  
85  	public static final String CONVERSIONS_SLICE_BASE = "Conversions/";
86  
87  	public static final String ZIP_EXT = ".zip";
88  
89  	public static final String DOCX_EXT = ".docx";
90  
91  	/**
92  	 * @see HttpServlet#HttpServlet()
93  	 */
94  	public ConversionServlet() {
95  		super();
96  	}
97  
98  	/**
99  	 * Serves GET requests - responses are : list of input data types and lists
100 	 * of possible conversions paths.
101 	 */
102 	protected void doGet(HttpServletRequest request,
103 			HttpServletResponse response) throws ServletException, IOException {
104 		try {
105 			RequestResolver rr = new ConversionRequestResolver(request,
106 					Method.GET);
107 			if (rr.getOperationId().equals(OperationId.PRINT_CONVERSIONS_PATHS)) {
108 				DataType idt = (DataType) rr.getData();
109 				EGE ege = new EGEImpl();
110 				List<ConversionsPath> paths = ege.findConversionPaths(idt);
111 				printConversionsPaths(response, rr, paths);
112 			} else if (rr.getOperationId()
113 					.equals(OperationId.PRINT_INPUT_TYPES)) {
114 				EGE ege = new EGEImpl();
115 				Set<DataType> inpfo = ege.returnSupportedInputFormats();
116 				if (inpfo.size() == 0) {
117 					response.setStatus(HttpServletResponse.SC_NO_CONTENT);
118 					return;
119 				}
120 				printConversionPossibilities(response, rr, inpfo);
121 			}
122 
123 		} catch (RequestResolvingException ex) {
124 			if (ex.getStatus().equals(
125 					RequestResolvingException.Status.WRONG_METHOD)) {
126 				response.sendError(405, R_WRONG_METHOD);
127 			} else {
128 				throw new ServletException(ex);
129 			}
130 		}
131 
132 	}
133 
134 	/*
135 	 * Send in response xml data of possible conversions paths.
136 	 */
137 	protected void printConversionsPaths(HttpServletResponse response,
138 			RequestResolver rr, List<ConversionsPath> paths) throws IOException {
139 		LabelProvider lp = getLabelProvider();
140 		PrintWriter out = response.getWriter();
141 		if (paths.isEmpty()) {
142 			response.setStatus(HttpServletResponse.SC_NO_CONTENT);
143 			return;
144 		}
145 		response.setContentType("text/xml");
146 		StringBuffer resp = new StringBuffer();
147 		StringBuffer sbpath = new StringBuffer();
148 		StringBuffer pathopt = new StringBuffer();
149 		resp.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
150 		resp
151 				.append("<conversions-paths xmlns:xlink=\"http://www.w3.org/1999/xlink\">");
152 		int counter = 0;
153 		String reqTransf;
154 		for (ConversionsPath cp : paths) {
155 			resp.append("<conversions-path xlink:href=\"");
156 			reqTransf = rr.getRequest().getRequestURL().toString();
157 			if (reqTransf.endsWith(SLASH)) {
158 				reqTransf = reqTransf.substring(0, reqTransf.length() - 2);
159 				resp.append(reqTransf
160 						.substring(0, reqTransf.lastIndexOf(SLASH))
161 						+ SLASH
162 						+ rr.encodeDataType((DataType) rr.getData())
163 						+ SLASH);
164 			} else {
165 				resp.append(reqTransf
166 						.substring(0, reqTransf.lastIndexOf(SLASH))
167 						+ SLASH
168 						+ rr.encodeDataType((DataType) rr.getData())
169 						+ SLASH);
170 			}
171 			sbpath.delete(0, sbpath.length());
172 			pathopt.delete(0, pathopt.length());
173 			counter = 0;
174 			for (ConversionAction ca : cp.getPath()) {
175 				sbpath.append(rr.encodeDataType(ca.getConversionOutputType())
176 						+ SLASH);
177 				pathopt.append("<conversion id=\"" + ca.toString()
178 						+ "\" index=\"" + counter + "\" >");
179 				String paramsDefs = ca.getConversionActionArguments()
180 						.getPropertiesDefinitions();
181 				if (paramsDefs.length() > 0) {
182 					Properties props = new Properties();
183 					props.loadFromXML(new ByteArrayInputStream(paramsDefs
184 							.getBytes()));
185 					for (Object key : props.keySet()) {
186 						if (!key.toString().endsWith(".type")) {
187 							pathopt.append("<property id=\"" + key
188 									+ "\"><value>");
189 							pathopt.append("<![CDATA[" + props.get(key)
190 									+ "]]></value>");
191 							pathopt.append("<type>"
192 									+ props.get(key.toString() + ".type")
193 									+ "</type>");
194 							pathopt.append("<property-name>"
195 									+ lp.getLabel(key.toString())
196 									+ "</property-name></property>");
197 						}
198 					}
199 				}
200 				pathopt.append("</conversion>");
201 				counter++;
202 			}
203 			resp.append(sbpath);
204 			resp.append("\" ><path-name><![CDATA[ \n " + cp.toString()
205 					+ " \n ]]></path-name>");
206 			resp.append(pathopt);
207 			resp.append("</conversions-path>");
208 		}
209 		resp.append("</conversions-paths>");
210 		out.print(resp.toString());
211 		out.close();
212 	}
213 
214 	/*
215 	 * Sends to response xml data of supported input data types.
216 	 */
217 	protected void printConversionPossibilities(HttpServletResponse response,
218 			RequestResolver rr, Set<DataType> inputDataTypes)
219 			throws IOException {
220 		PrintWriter out = response.getWriter();
221 		response.setContentType("text/xml");
222 		out.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
223 		out
224 				.println("<input-data-types xmlns:xlink=\"http://www.w3.org/1999/xlink\">");
225 		String prefix = rr.getRequest().getRequestURL().toString()
226 				+ (rr.getRequest().getRequestURL().toString().endsWith(SLASH) ? ""
227 						: "/");
228 		for (DataType dt : inputDataTypes) {
229 			out.println("<input-data-type id=\"" + dt.toString()
230 					+ "\" xlink:href=\"" + prefix + rr.encodeDataType(dt)
231 					+ "/\" />");
232 		}
233 		out.println("</input-data-types>");
234 		out.close();
235 	}
236 
237 	/**
238 	 * Servers POST method - performs conversions over specified within URL
239 	 * conversions path.
240 	 */
241 	protected void doPost(HttpServletRequest request,
242 			HttpServletResponse response) throws ServletException, IOException {
243 		try {
244 			ConversionRequestResolver rr = new ConversionRequestResolver(
245 					request, Method.POST);
246 			List<DataType> pathFrame = (List<DataType>) rr.getData();
247 			performConversion(response, rr, pathFrame);
248 		} catch (RequestResolvingException ex) {
249 			if (ex.getStatus().equals(
250 					RequestResolvingException.Status.BAD_REQUEST)) {
251 				response.sendError(HttpServletResponse.SC_BAD_REQUEST);
252 			} else if (ex.getStatus().equals(
253 					RequestResolvingException.Status.WRONG_METHOD)) {
254 				response.sendError(405, R_WRONG_METHOD);
255 			} else {
256 				throw new ServletException(ex);
257 			}
258 		} catch (Exception ex) {
259 			throw new ServletException(ex);
260 		}
261 	}
262 
263 	/*
264 	 * Performs conversion.
265 	 */
266 	protected void performConversion(HttpServletResponse response,
267 			ConversionRequestResolver rr, List<DataType> pathFrame)
268 			throws IOException, FileUploadException, EGEException,
269 			ConverterException, RequestResolvingException {
270 		EGE ege = new EGEImpl();
271 		List<ConversionsPath> cp = ege.findConversionPaths(pathFrame.get(0));
272 		ConversionsPath cpath = null;
273 		boolean found = false;
274 		for (ConversionsPath path : cp) {
275 			if ((pathFrame.size() - 1) != path.getPath().size()) {
276 				continue;
277 			}
278 			found = true;
279 			int count = 1;
280 			for (ConversionAction ca : path.getPath()) {
281 				if (!ca.getConversionOutputType().equals(pathFrame.get(count))) {
282 					found = false;
283 					break;
284 				}
285 				count++;
286 			}
287 			if (found) {
288 				cpath = path;
289 				break;
290 			}
291 		}
292 		if (!found) {
293 			response.sendError(HttpServletResponse.SC_BAD_REQUEST);
294 			return;
295 		} else {
296 			doConvert(response, rr, ege, cpath);
297 		}
298 	}
299 
300 	private void doConvert(HttpServletResponse response,
301 			ConversionRequestResolver rr, EGE ege, ConversionsPath cpath)
302 			throws FileUploadException, IOException, RequestResolvingException,
303 			EGEException, FileNotFoundException, ConverterException,
304 			ZipException {
305 		InputStream is = null;
306 		OutputStream os = null;
307 		if (ServletFileUpload.isMultipartContent(rr.getRequest())) {
308 			ServletFileUpload upload = new ServletFileUpload();
309 			FileItemIterator iter = upload.getItemIterator(rr.getRequest());
310 			while (iter.hasNext()) {
311 				FileItemStream item = iter.next();
312 				if (!item.isFormField()) {
313 					is = item.openStream();
314 					applyConversionsProperties(
315 							rr.getConversionProperties(), cpath);
316 					// creating temporary data buffer
317 					DataBuffer buffer = new DataBuffer(0,
318 							EGEConstants.BUFFER_TEMP_PATH);
319 					String alloc = buffer.allocate(is);
320 					InputStream ins = buffer.getDataAsStream(alloc);
321 					is.close();
322 					// input validation - print result if fatal error
323 					// occurs.
324 					try {
325 						ValidationResult vRes = ege.performValidation(ins,
326 								cpath.getInputDataType());
327 						if (vRes.getStatus().equals(
328 								ValidationResult.Status.FATAL)) {
329 							ValidationServlet valServ = new ValidationServlet();
330 							valServ.printValidationResult(response, vRes);
331 							try {
332 								ins.close();
333 							} finally {
334 								buffer.removeData(alloc, true);
335 							}
336 							return;
337 						}
338 					} catch (ValidatorException vex) {
339 						LOGGER.warn(vex.getMessage());
340 					} finally {
341 						try {
342 							ins.close();
343 						} catch (Exception ex) {
344 							// do nothing
345 						}
346 					}
347 					File zipFile = null;
348 					FileOutputStream fos = null;
349 					String newTemp = UUID.randomUUID().toString();
350 					IOResolver ior = EGEConfigurationManager.getInstance()
351 							.getStandardIOResolver();
352 					File buffDir = new File(buffer.getDataDir(alloc));
353 					zipFile = new File(EGEConstants.BUFFER_TEMP_PATH
354 							+ File.separator + newTemp + EZP_EXT);
355 					fos = new FileOutputStream(zipFile);
356 					ior.compressData(buffDir, fos);
357 					ins = new FileInputStream(zipFile);
358 					File szipFile = new File(EGEConstants.BUFFER_TEMP_PATH
359 							+ File.separator + newTemp + ZIP_EXT);
360 					fos = new FileOutputStream(szipFile);
361 					try {
362 						try {
363 							ege.performConversion(ins, fos, cpath);
364 						} finally {
365 							fos.close();
366 						}
367 						boolean isComplex = EGEIOUtils
368 								.isComplexZip(szipFile);
369 						response.setContentType(APPLICATION_OCTET_STREAM);
370 						String fN = item.getName().substring(0,
371 								item.getName().lastIndexOf("."));
372 						if (isComplex) {
373 							String fileExt;
374 							if (cpath.getOutputDataType().getMimeType()
375 									.equals(APPLICATION_MSWORD)) {
376 								fileExt = DOCX_EXT;
377 							} else {
378 								fileExt = ZIP_EXT;
379 							}
380 							response.setHeader("Content-Disposition",
381 									"attachment; filename=\"" + fN
382 											+ fileExt + "\"");
383 							FileInputStream fis = new FileInputStream(
384 									szipFile);
385 							os = response.getOutputStream();
386 							try {
387 								EGEIOUtils.copyStream(fis, os);
388 							} finally {
389 								fis.close();
390 							}
391 						} else {
392 							String fileExt = getMimeExtensionProvider()
393 									.getFileExtension(
394 											cpath.getOutputDataType()
395 													.getMimeType());
396 							response.setHeader("Content-Disposition",
397 									"attachment; filename=\"" + fN
398 											+ fileExt + "\"");
399 							os = response.getOutputStream();
400 							EGEIOUtils.unzipSingleFile(
401 									new ZipFile(szipFile), os);
402 						}
403 					} finally {
404 						ins.close();
405 						if (os != null) {
406 							os.flush();
407 							os.close();
408 						}
409 						buffer.clear(true);
410 						szipFile.delete();
411 						if (zipFile != null) {
412 							zipFile.delete();
413 						}
414 					}
415 				}
416 			}
417 		} else {
418 			response.sendError(HttpServletResponse.SC_BAD_REQUEST);
419 			return;
420 		}
421 	}
422 
423 	private void applyConversionsProperties(String properties,
424 			ConversionsPath cP) throws RequestResolvingException {
425 		ConversionsPropertiesHandler cpp = new ConversionsPropertiesHandler(
426 				properties);
427 		cpp.applyPathProperties(cP);
428 	}
429 
430 	/**
431 	 * Returns local names provider.
432 	 * 
433 	 * @return
434 	 */
435 	public LabelProvider getLabelProvider() {
436 		return (LabelProvider) this.getServletContext().getAttribute(
437 				PreConfig.LABEL_PROVIDER);
438 	}
439 
440 	/**
441 	 * Returns map that contains mapping of mime type to file extension.
442 	 * 
443 	 * @return
444 	 */
445 	public MimeExtensionProvider getMimeExtensionProvider() {
446 		return (MimeExtensionProvider) this.getServletContext().getAttribute(
447 				PreConfig.MIME_EXTENSION_PROVIDER);
448 	}
449 
450 }