2 * This document is a part of the source code and related artifacts
3 * for CollectionSpace, an open source collections management system
4 * for museums and related institutions:
6 * http://www.collectionspace.org
7 * http://wiki.collectionspace.org
9 * Copyright 2009 University of California at Berkeley
11 * Licensed under the Educational Community License (ECL), Version 2.0.
12 * You may not use this file except in compliance with this License.
14 * You may obtain a copy of the ECL 2.0 License at
16 * https://source.collectionspace.org/collection-space/LICENSE.txt
18 * Unless required by applicable law or agreed to in writing, software
19 * distributed under the License is distributed on an "AS IS" BASIS,
20 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21 * See the License for the specific language governing permissions and
22 * limitations under the License.
24 package org.collectionspace.services.report.nuxeo;
26 import java.io.ByteArrayInputStream;
27 import java.io.ByteArrayOutputStream;
29 import java.io.FileInputStream;
30 import java.io.FileNotFoundException;
31 import java.io.InputStream;
32 import java.sql.Connection;
33 import java.sql.SQLException;
34 import java.util.HashMap;
35 import java.util.List;
38 import javax.ws.rs.core.MediaType;
40 import javax.naming.NamingException;
41 import javax.ws.rs.WebApplicationException;
42 import javax.ws.rs.core.Response;
44 import net.sf.jasperreports.engine.JRException;
45 import net.sf.jasperreports.engine.JRExporter;
46 import net.sf.jasperreports.engine.JRExporterParameter;
47 import net.sf.jasperreports.engine.JRParameter;
48 import net.sf.jasperreports.engine.JasperCompileManager;
49 import net.sf.jasperreports.engine.JasperExportManager;
50 import net.sf.jasperreports.engine.JasperFillManager;
51 import net.sf.jasperreports.engine.JasperPrint;
52 import net.sf.jasperreports.engine.export.JRCsvExporter;
53 import net.sf.jasperreports.engine.export.JRCsvExporterParameter;
54 import net.sf.jasperreports.engine.export.JRHtmlExporter;
55 import net.sf.jasperreports.engine.export.JRPdfExporter;
56 import net.sf.jasperreports.engine.export.JRXmlExporter;
57 import net.sf.jasperreports.engine.export.ooxml.JRDocxExporter;
58 import net.sf.jasperreports.engine.export.ooxml.JRPptxExporter;
59 import net.sf.jasperreports.engine.export.ooxml.JRXlsxExporter;
61 import org.bouncycastle.crypto.RuntimeCryptoException;
62 import org.collectionspace.services.ReportJAXBSchema;
63 import org.collectionspace.services.report.ReportsCommon;
64 import org.collectionspace.services.client.PoxPayloadIn;
65 import org.collectionspace.services.client.PoxPayloadOut;
66 import org.collectionspace.services.client.ReportClient;
67 import org.collectionspace.services.common.ServiceMain;
68 import org.collectionspace.services.common.api.JEEServerDeployment;
69 import org.collectionspace.services.common.api.FileTools;
70 import org.collectionspace.services.common.api.Tools;
71 import org.collectionspace.services.common.config.ConfigReader;
72 import org.collectionspace.services.common.context.ServiceContext;
73 import org.collectionspace.services.common.document.BadRequestException;
74 import org.collectionspace.services.common.document.DocumentException;
75 import org.collectionspace.services.common.document.DocumentWrapper;
76 import org.collectionspace.services.common.invocable.Invocable;
77 import org.collectionspace.services.common.invocable.InvocationContext;
78 import org.collectionspace.services.common.storage.JDBCTools;
79 import org.collectionspace.services.jaxb.InvocableJAXBSchema;
80 import org.collectionspace.services.nuxeo.client.java.DocHandlerBase;
81 import org.collectionspace.services.nuxeo.client.java.RepositoryJavaClientImpl;
82 import org.jfree.util.Log;
83 import org.nuxeo.ecm.core.api.DocumentModel;
84 import org.nuxeo.ecm.core.api.model.PropertyException;
85 import org.nuxeo.ecm.core.api.repository.RepositoryInstance;
86 import org.slf4j.Logger;
87 import org.slf4j.LoggerFactory;
90 * ReportDocumentModelHandler
92 * $LastChangedRevision: $
95 public class ReportDocumentModelHandler extends DocHandlerBase<ReportsCommon> {
96 private final Logger logger = LoggerFactory.getLogger(ReportDocumentModelHandler.class);
97 private static String REPORTS_FOLDER = "reports";
98 private static String CSID_LIST_SEPARATOR = ",";
100 private static String REPORTS_STD_CSID_PARAM = "csid";
101 private static String REPORTS_STD_GROUPCSID_PARAM = "groupcsid";
102 private static String REPORTS_STD_CSIDLIST_PARAM = "csidlist";
103 private static String REPORTS_STD_TENANTID_PARAM = "tenantid";
105 public InputStream invokeReport(
106 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
108 InvocationContext invContext,
109 StringBuffer outMimeType,
110 StringBuffer outReportFileName) throws Exception {
111 RepositoryInstance repoSession = null;
112 boolean releaseRepoSession = false;
114 String invocationMode = invContext.getMode();
115 String modeProperty = null;
116 HashMap<String, Object> params = new HashMap<String, Object>();
117 params.put(REPORTS_STD_TENANTID_PARAM, ctx.getTenantId());
118 boolean checkDocType = true;
120 // Note we set before we put in the default ones, so they cannot override tenant or CSID.
121 setParamsFromContext(params, invContext);
123 if(Invocable.INVOCATION_MODE_SINGLE.equalsIgnoreCase(invocationMode)) {
124 modeProperty = InvocableJAXBSchema.SUPPORTS_SINGLE_DOC;
125 params.put(REPORTS_STD_CSID_PARAM, invContext.getSingleCSID());
126 } else if(Invocable.INVOCATION_MODE_LIST.equalsIgnoreCase(invocationMode)) {
127 modeProperty = InvocableJAXBSchema.SUPPORTS_DOC_LIST;
128 List<String> csids = null;
129 InvocationContext.ListCSIDs listThing = invContext.getListCSIDs();
130 if(listThing!=null) {
131 csids = listThing.getCsid();
133 if(csids==null||csids.isEmpty()){
134 throw new BadRequestException(
135 "ReportResource: Report invoked in list mode, with no csids in list." );
137 StringBuilder sb = new StringBuilder();
138 boolean first = true;
139 for(String csidItem : csids) {
143 sb.append(CSID_LIST_SEPARATOR);
146 params.put(REPORTS_STD_CSIDLIST_PARAM, sb.toString());
147 } else if(Invocable.INVOCATION_MODE_GROUP.equalsIgnoreCase(invocationMode)) {
148 modeProperty = InvocableJAXBSchema.SUPPORTS_GROUP;
149 params.put(REPORTS_STD_GROUPCSID_PARAM, invContext.getGroupCSID());
150 } else if(Invocable.INVOCATION_MODE_NO_CONTEXT.equalsIgnoreCase(invocationMode)) {
151 modeProperty = InvocableJAXBSchema.SUPPORTS_NO_CONTEXT;
152 checkDocType = false;
154 throw new BadRequestException("ReportResource: unknown Invocation Mode: "
159 RepositoryJavaClientImpl repoClient = (RepositoryJavaClientImpl)this.getRepositoryClient(ctx);
160 repoSession = this.getRepositorySession();
161 if (repoSession == null) {
162 repoSession = repoClient.getRepositorySession(ctx);
163 releaseRepoSession = true;
166 // Get properties from the batch docModel, and release the session
167 String reportFileNameProperty;
169 DocumentWrapper<DocumentModel> wrapper = repoClient.getDoc(repoSession, ctx, csid);
170 DocumentModel docModel = wrapper.getWrappedObject();
171 Boolean supports = (Boolean)docModel.getPropertyValue(modeProperty);
172 if(supports == null || !supports) {
173 throw new BadRequestException(
174 "ReportResource: This Report does not support Invocation Mode: "
178 List<String> forDocTypeList =
179 (List<String>)docModel.getPropertyValue(InvocableJAXBSchema.FOR_DOC_TYPES);
180 if(forDocTypeList==null
181 || !forDocTypeList.contains(invContext.getDocType())) {
182 throw new BadRequestException(
183 "ReportResource: Invoked with unsupported document type: "
184 +invContext.getDocType());
187 reportFileNameProperty = ((String)docModel.getPropertyValue(ReportJAXBSchema.FILENAME)); // Set the outgoing param with the report file name
188 String reportOutputMime = (String)docModel.getPropertyValue(ReportJAXBSchema.OUTPUT_MIME);
189 if(!Tools.isEmpty(reportOutputMime)) {
190 outMimeType.append(reportOutputMime);
192 outMimeType.append(ReportClient.DEFAULT_REPORT_OUTPUT_MIME);
194 } catch (PropertyException pe) {
195 if (logger.isDebugEnabled()) {
196 logger.debug("Property exception getting batch values: ", pe);
199 } catch (DocumentException de) {
200 if (logger.isDebugEnabled()) {
201 logger.debug("Problem getting batch doc: ", de);
204 } catch (Exception e) {
205 if (logger.isDebugEnabled()) {
206 logger.debug("Caught exception ", e);
208 throw new DocumentException(e);
210 if (releaseRepoSession && repoSession != null) {
211 repoClient.releaseRepositorySession(ctx, repoSession);
215 return buildReportResult(csid, params, reportFileNameProperty, outMimeType.toString(), outReportFileName);
218 private void setParamsFromContext(Map<String, Object> params, InvocationContext invContext) {
219 InvocationContext.Params icParams = invContext.getParams();
220 if(icParams!= null) {
221 List<InvocationContext.Params.Param> icParamList = icParams.getParam();
222 if(icParamList != null) {
223 for(InvocationContext.Params.Param param:icParamList) {
224 String key = param.getKey();
225 String value = param.getValue();
226 if(!Tools.isEmpty(key) && !Tools.isEmpty(value)) {
227 params.put(key, value);
235 private InputStream buildReportResult(String reportCSID,
236 HashMap<String, Object> params, String reportFileName, String outputMimeType, StringBuffer outReportFileName)
238 Connection conn = null;
239 InputStream result = null;
242 String fileNameBase = Tools.getFilenameBase(reportFileName);
243 String compiledReportFilename = fileNameBase+ReportClient.COMPILED_REPORT_EXTENSION;
244 String reportDescriptionFilename = fileNameBase+ReportClient.REPORT_DECSRIPTION_EXTENSION;
246 String basePath = ServiceMain.getInstance().getServerRootDir() +
247 File.separator + JEEServerDeployment.CSPACE_DIR_NAME +
248 File.separator + REPORTS_FOLDER +
249 // File.separator + tenantName +
250 File.separator; // + reportFileName;
252 String compiledFilePath = basePath+compiledReportFilename;
253 File f = new File(compiledFilePath);
254 if(!f.exists()) { // Need to compile the file
255 // First verify that there is a source file.
256 String sourceFilePath = basePath+reportDescriptionFilename;
257 File f2 = new File(sourceFilePath);
258 if(!f2.exists()) { // Missing source file - error!
259 logger.error("Report for csid={} is missing the specified source file: {}",
260 reportCSID, sourceFilePath);
261 throw new RuntimeException("Report is missing the specified source file!");
263 logger.info("Report for csid={} is not compiled. Compiling first, and saving to: {}",
264 reportCSID, compiledFilePath);
265 JasperCompileManager.compileReportToFile(sourceFilePath, compiledFilePath);
268 conn = getConnection();
270 if (logger.isTraceEnabled()) {
271 logger.trace("ReportResource for csid=" + reportCSID
272 +" output as "+outputMimeType+" using report file: "+compiledFilePath);
274 FileInputStream fileStream = new FileInputStream(compiledFilePath);
276 // export report to pdf and build a response with the bytes
277 //JasperExportManager.exportReportToPdf(jasperprint);
279 // Report will be to a byte output array.
280 ByteArrayOutputStream baos = new ByteArrayOutputStream();
282 JRExporter exporter = null;
283 // Strip extension from report filename.
284 String outputFilename = reportFileName;
285 // Strip extension from report filename.
286 int idx = outputFilename.lastIndexOf(".");
288 outputFilename = outputFilename.substring(0, idx);
289 // Strip any sub-dir from report filename.
290 idx = outputFilename.lastIndexOf(File.separator);
292 outputFilename = outputFilename.substring(idx+1);
293 if(outputMimeType.equals(MediaType.APPLICATION_XML)) {
294 params.put(JRParameter.IS_IGNORE_PAGINATION, Boolean.TRUE);
295 exporter = new JRXmlExporter();
296 outputFilename = outputFilename+".xml";
297 } else if(outputMimeType.equals(MediaType.TEXT_HTML)) {
298 exporter = new JRHtmlExporter();
299 outputFilename = outputFilename+".html";
300 } else if(outputMimeType.equals(ReportClient.PDF_MIME_TYPE)) {
301 exporter = new JRPdfExporter();
302 outputFilename = outputFilename+".pdf";
303 } else if(outputMimeType.equals(ReportClient.CSV_MIME_TYPE)) {
304 params.put(JRParameter.IS_IGNORE_PAGINATION, Boolean.TRUE);
305 exporter = new JRCsvExporter();
306 exporter.setParameter(JRCsvExporterParameter.FIELD_DELIMITER, ",");
307 outputFilename = outputFilename+".csv";
308 } else if(outputMimeType.equals(ReportClient.TSV_MIME_TYPE)) {
309 params.put(JRParameter.IS_IGNORE_PAGINATION, Boolean.TRUE);
310 exporter = new JRCsvExporter();
311 exporter.setParameter(JRCsvExporterParameter.FIELD_DELIMITER, "\t");
312 outputFilename = outputFilename+".csv";
313 } else if(outputMimeType.equals(ReportClient.MSWORD_MIME_TYPE) // Understand msword as docx
314 || outputMimeType.equals(ReportClient.OPEN_DOCX_MIME_TYPE)) {
315 exporter = new JRDocxExporter();
316 outputFilename = outputFilename+".docx";
317 } else if(outputMimeType.equals(ReportClient.MSEXCEL_MIME_TYPE) // Understand msexcel as xlsx
318 || outputMimeType.equals(ReportClient.OPEN_XLSX_MIME_TYPE)) {
319 exporter = new JRXlsxExporter();
320 outputFilename = outputFilename+".xlsx";
321 } else if(outputMimeType.equals(ReportClient.MSPPT_MIME_TYPE) // Understand msppt as xlsx
322 || outputMimeType.equals(ReportClient.OPEN_PPTX_MIME_TYPE)) {
323 exporter = new JRPptxExporter();
324 outputFilename = outputFilename+".pptx";
326 logger.error("Reporting: unsupported output MIME type - defaulting to PDF");
327 exporter = new JRPdfExporter();
328 outputFilename = outputFilename+"-default-to.pdf";
330 outReportFileName.append(outputFilename); // Set the out going param to the report's final file name
331 // FIXME: Logging temporarily set to INFO level for CSPACE-5766;
332 // can change to TRACE or DEBUG level as warranted thereafter
333 if (logger.isInfoEnabled()) {
334 logger.info(FileTools.getJavaTmpDirInfo());
337 JasperPrint jasperPrint = JasperFillManager.fillReport(fileStream, params,conn);
339 exporter.setParameter(JRExporterParameter.JASPER_PRINT, jasperPrint);
340 exporter.setParameter(JRExporterParameter.OUTPUT_STREAM, baos);
341 exporter.exportReport();
342 result = new ByteArrayInputStream(baos.toByteArray());
345 } catch (SQLException sqle) {
346 // SQLExceptions can be chained. We have at least one exception, so
347 // set up a loop to make sure we let the user know about all of them
348 // if there happens to be more than one.
349 if (logger.isDebugEnabled()) {
350 SQLException tempException = sqle;
351 while (null != tempException) {
352 logger.debug("SQL Exception: " + sqle.getLocalizedMessage());
354 // loop to the next exception
355 tempException = tempException.getNextException();
358 Response response = Response.status(
359 Response.Status.INTERNAL_SERVER_ERROR).entity(
360 "Invoke failed (SQL problem) on Report csid=" + reportCSID).type("text/plain").build();
361 throw new WebApplicationException(response);
362 } catch (JRException jre) {
363 if (logger.isDebugEnabled()) {
364 logger.debug("JR Exception: " + jre.getLocalizedMessage() + " Cause: "+jre.getCause());
366 Response response = Response.status(
367 Response.Status.INTERNAL_SERVER_ERROR).entity(
368 "Invoke failed (Jasper problem) on Report csid=" + reportCSID).type("text/plain").build();
369 throw new WebApplicationException(response);
370 } catch (FileNotFoundException fnfe) {
371 if (logger.isDebugEnabled()) {
372 logger.debug("FileNotFoundException: " + fnfe.getLocalizedMessage());
374 Response response = Response.status(
375 Response.Status.INTERNAL_SERVER_ERROR).entity(
376 "Invoke failed (SQL problem) on Report csid=" + reportCSID).type("text/plain").build();
377 throw new WebApplicationException(response);
382 } catch (SQLException sqle) {
383 // SQLExceptions can be chained. We have at least one exception, so
384 // set up a loop to make sure we let the user know about all of them
385 // if there happens to be more than one.
386 if (logger.isDebugEnabled()) {
387 logger.debug("SQL Exception closing connection: "
388 + sqle.getLocalizedMessage());
390 } catch (Exception e) {
391 if (logger.isDebugEnabled()) {
392 logger.debug("Exception closing connection", e);
399 private Connection getConnection() throws NamingException, SQLException {
400 Connection result = null;
402 ServiceContext ctx = this.getServiceContext();
404 String repositoryName = ctx.getRepositoryName();
405 if (repositoryName != null && repositoryName.trim().isEmpty() == false) {
406 result = JDBCTools.getConnection(JDBCTools.NUXEO_DATASOURCE_NAME, repositoryName);
408 } catch (Exception e) {
410 throw new NamingException();