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;
27 import java.io.FileInputStream;
28 import java.io.FileNotFoundException;
29 import java.io.FileOutputStream;
30 import java.io.InputStream;
31 import java.nio.file.Files;
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;
39 import javax.naming.NamingException;
40 import javax.ws.rs.core.Response;
42 import net.sf.jasperreports.engine.JRException;
43 import net.sf.jasperreports.engine.JRExporter;
44 import net.sf.jasperreports.engine.JRExporterParameter;
45 import net.sf.jasperreports.engine.JRParameter;
46 import net.sf.jasperreports.engine.JasperCompileManager;
47 import net.sf.jasperreports.engine.JasperFillManager;
48 import net.sf.jasperreports.engine.JasperPrint;
49 import net.sf.jasperreports.engine.export.JRCsvExporter;
50 import net.sf.jasperreports.engine.export.JRCsvExporterParameter;
51 import net.sf.jasperreports.engine.export.JRHtmlExporter;
52 import net.sf.jasperreports.engine.export.JRPdfExporter;
53 import net.sf.jasperreports.engine.export.JRXmlExporter;
54 import net.sf.jasperreports.engine.export.ooxml.JRDocxExporter;
55 import net.sf.jasperreports.engine.export.ooxml.JRPptxExporter;
56 import net.sf.jasperreports.engine.export.ooxml.JRXlsxExporter;
58 import org.collectionspace.services.ReportJAXBSchema;
59 import org.collectionspace.services.report.MIMEType;
60 import org.collectionspace.services.report.MIMETypeItemType;
61 import org.collectionspace.services.report.ReportsCommon;
62 import org.collectionspace.services.report.ReportsOuputMimeList;
63 import org.collectionspace.services.client.PoxPayloadIn;
64 import org.collectionspace.services.client.PoxPayloadOut;
65 import org.collectionspace.services.client.ReportClient;
66 import org.collectionspace.services.common.CSWebApplicationException;
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.TenantBindingConfigReaderImpl;
72 import org.collectionspace.services.common.context.ServiceBindingUtils;
73 import org.collectionspace.services.common.context.ServiceContext;
74 import org.collectionspace.services.common.document.BadRequestException;
75 import org.collectionspace.services.common.document.DocumentException;
76 import org.collectionspace.services.common.document.DocumentWrapper;
77 import org.collectionspace.services.common.invocable.Invocable;
78 import org.collectionspace.services.common.invocable.InvocationContext;
79 import org.collectionspace.services.common.storage.JDBCTools;
80 import org.collectionspace.services.config.service.ServiceBindingType;
81 import org.collectionspace.services.config.types.PropertyItemType;
82 import org.collectionspace.services.jaxb.InvocableJAXBSchema;
83 import org.collectionspace.services.nuxeo.client.java.NuxeoDocumentModelHandler;
84 import org.collectionspace.services.nuxeo.client.java.CoreSessionInterface;
85 import org.collectionspace.services.nuxeo.client.java.NuxeoRepositoryClientImpl;
86 import org.collectionspace.services.nuxeo.util.NuxeoUtils;
88 import org.jfree.util.Log;
90 import org.nuxeo.ecm.core.api.model.PropertyException;
91 import org.nuxeo.ecm.core.api.DocumentModel;
93 import org.slf4j.Logger;
94 import org.slf4j.LoggerFactory;
97 * ReportDocumentModelHandler
99 * $LastChangedRevision: $
100 * $LastChangedDate: $
102 public class ReportDocumentModelHandler extends NuxeoDocumentModelHandler<ReportsCommon> {
103 private final Logger logger = LoggerFactory.getLogger(ReportDocumentModelHandler.class);
104 private static String REPORTS_FOLDER = "reports";
105 private static String CSID_LIST_SEPARATOR = ",";
107 private static String REPORTS_STD_CSID_PARAM = "csid";
108 private static String REPORTS_STD_GROUPCSID_PARAM = "groupcsid";
109 private static String REPORTS_STD_CSIDLIST_PARAM = "csidlist";
110 private static String REPORTS_STD_TENANTID_PARAM = "tenantid";
113 // Map the MIME types from the service bindings to our payload output
115 public ReportsOuputMimeList getSupportMIMETypes(
116 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx) {
118 // Create a new payload response instance and initialize it
120 ReportsOuputMimeList result = new ReportsOuputMimeList();
121 MIMEType resultMIMEType = result.getMIMETypeList();
122 if (resultMIMEType == null) {
123 result.setMIMETypeList(resultMIMEType = new MIMEType());
125 List<MIMETypeItemType> resultMIMETypeItemList = resultMIMEType.getMIMEType();
128 // Read the MIME type values from the service bindings and put into our response payload
130 TenantBindingConfigReaderImpl tReader =
131 ServiceMain.getInstance().getTenantBindingConfigReader();
132 ServiceBindingType reportServiceBinding = tReader.getServiceBinding(ctx.getTenantId(), ctx.getServiceName());
133 List<PropertyItemType> bindingsMIMETypeList = ServiceBindingUtils.getPropertyValueList(reportServiceBinding, ServiceBindingUtils.OUTPUT_MIME_PROP);
135 if (bindingsMIMETypeList != null) {
136 for (PropertyItemType bindingItemMimeType : bindingsMIMETypeList) {
137 MIMETypeItemType resultMimeTypeItem = new MIMETypeItemType();
138 String displayName = bindingItemMimeType.getDisplayName();
139 if (displayName != null && displayName.trim().isEmpty() == false) {
140 resultMimeTypeItem.setKey(displayName);
142 resultMimeTypeItem.setKey(bindingItemMimeType.getValue());
144 resultMimeTypeItem.setValue(bindingItemMimeType.getValue());
145 resultMIMETypeItemList.add(resultMimeTypeItem);
152 public InputStream invokeReport(
153 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
155 InvocationContext invContext,
156 StringBuffer outMimeType,
157 StringBuffer outReportFileName) throws Exception {
158 CoreSessionInterface repoSession = null;
159 boolean releaseRepoSession = false;
161 String invocationMode = invContext.getMode();
162 String modeProperty = null;
163 HashMap<String, Object> params = new HashMap<String, Object>();
164 params.put(REPORTS_STD_TENANTID_PARAM, ctx.getTenantId());
165 boolean checkDocType = true;
167 // Note we set before we put in the default ones, so they cannot override tenant or CSID.
168 setParamsFromContext(params, invContext);
170 if(Invocable.INVOCATION_MODE_SINGLE.equalsIgnoreCase(invocationMode)) {
171 modeProperty = InvocableJAXBSchema.SUPPORTS_SINGLE_DOC;
172 params.put(REPORTS_STD_CSID_PARAM, invContext.getSingleCSID());
173 } else if(Invocable.INVOCATION_MODE_LIST.equalsIgnoreCase(invocationMode)) {
174 modeProperty = InvocableJAXBSchema.SUPPORTS_DOC_LIST;
175 List<String> csids = null;
176 InvocationContext.ListCSIDs listThing = invContext.getListCSIDs();
177 if (listThing!=null) {
178 csids = listThing.getCsid();
180 if (csids==null||csids.isEmpty()){
181 throw new BadRequestException(
182 "ReportResource: Report invoked in list mode, with no csids in list." );
184 StringBuilder sb = new StringBuilder();
185 boolean first = true;
186 for(String csidItem : csids) {
190 sb.append(CSID_LIST_SEPARATOR);
193 params.put(REPORTS_STD_CSIDLIST_PARAM, sb.toString());
194 } else if(Invocable.INVOCATION_MODE_GROUP.equalsIgnoreCase(invocationMode)) {
195 modeProperty = InvocableJAXBSchema.SUPPORTS_GROUP;
196 params.put(REPORTS_STD_GROUPCSID_PARAM, invContext.getGroupCSID());
197 } else if(Invocable.INVOCATION_MODE_NO_CONTEXT.equalsIgnoreCase(invocationMode)) {
198 modeProperty = InvocableJAXBSchema.SUPPORTS_NO_CONTEXT;
199 checkDocType = false;
201 throw new BadRequestException("ReportResource: unknown Invocation Mode: "
205 NuxeoRepositoryClientImpl repoClient = (NuxeoRepositoryClientImpl)this.getRepositoryClient(ctx);
206 repoSession = this.getRepositorySession();
207 if (repoSession == null) {
208 repoSession = repoClient.getRepositorySession(ctx);
209 releaseRepoSession = true;
212 // Get properties from the batch docModel, and release the session
213 String reportFileNameProperty;
215 DocumentWrapper<DocumentModel> wrapper = repoClient.getDoc(repoSession, ctx, csid);
216 DocumentModel docModel = wrapper.getWrappedObject();
217 Boolean supports = (Boolean) NuxeoUtils.getProperyValue(docModel, modeProperty); //docModel.getPropertyValue(modeProperty);
218 if(supports == null || !supports) {
219 throw new BadRequestException(
220 "ReportResource: This Report does not support Invocation Mode: "
224 List<String> forDocTypeList =
225 (List<String>) NuxeoUtils.getProperyValue(docModel, InvocableJAXBSchema.FOR_DOC_TYPES); //docModel.getPropertyValue(InvocableJAXBSchema.FOR_DOC_TYPES);
226 if (forDocTypeList==null || !forDocTypeList.contains(invContext.getDocType())) {
227 throw new BadRequestException(
228 "ReportResource: Invoked with unsupported document type: "
229 +invContext.getDocType());
232 reportFileNameProperty = (String) NuxeoUtils.getProperyValue(docModel, ReportJAXBSchema.FILENAME); //docModel.getPropertyValue(ReportJAXBSchema.FILENAME)); // Set the outgoing param with the report file name
234 // If the invocation context contains a MIME type then use it. Otherwise, look in the report resource. If no MIME type in the report resource,
235 // use the default MIME type.
237 if (!Tools.isEmpty(invContext.getOutputMIME())) {
238 outMimeType.append(invContext.getOutputMIME());
240 if (outMimeType == null || Tools.isEmpty(outMimeType.toString())) {
241 String reportOutputMime = (String) NuxeoUtils.getProperyValue(docModel, ReportJAXBSchema.OUTPUT_MIME); //docModel.getPropertyValue(ReportJAXBSchema.OUTPUT_MIME);
242 if (!Tools.isEmpty(reportOutputMime)) {
243 outMimeType.append(reportOutputMime);
245 outMimeType.append(ReportClient.DEFAULT_REPORT_OUTPUT_MIME);
248 } catch (PropertyException pe) {
249 if (logger.isDebugEnabled()) {
250 logger.debug("Property exception getting batch values: ", pe);
253 } catch (DocumentException de) {
254 if (logger.isDebugEnabled()) {
255 logger.debug("Problem getting batch doc: ", de);
258 } catch (Exception e) {
259 if (logger.isDebugEnabled()) {
260 logger.debug("Caught exception ", e);
262 throw new DocumentException(e);
264 if (releaseRepoSession && repoSession != null) {
265 repoClient.releaseRepositorySession(ctx, repoSession);
269 return buildReportResult(csid, params, reportFileNameProperty, outMimeType.toString(), outReportFileName);
272 private void setParamsFromContext(Map<String, Object> params, InvocationContext invContext) {
273 InvocationContext.Params icParams = invContext.getParams();
274 if(icParams!= null) {
275 List<InvocationContext.Params.Param> icParamList = icParams.getParam();
276 if(icParamList != null) {
277 for(InvocationContext.Params.Param param:icParamList) {
278 String key = param.getKey();
279 String value = param.getValue();
280 if(!Tools.isEmpty(key) && !Tools.isEmpty(value)) {
281 params.put(key, value);
289 private InputStream buildReportResult(String reportCSID,
290 HashMap<String, Object> params, String reportFileName, String outputMimeType, StringBuffer outReportFileName)
292 Connection conn = null;
293 InputStream result = null;
296 String fileNameBase = Tools.getFilenameBase(reportFileName);
297 String compiledReportFilename = fileNameBase+ReportClient.COMPILED_REPORT_EXTENSION;
298 String reportDescriptionFilename = fileNameBase+ReportClient.REPORT_DECSRIPTION_EXTENSION;
300 String basePath = ServiceMain.getInstance().getServerRootDir() +
301 File.separator + JEEServerDeployment.CSPACE_DIR_NAME +
302 File.separator + REPORTS_FOLDER +
303 // File.separator + tenantName +
304 File.separator; // + reportFileName;
306 String compiledFilePath = basePath+compiledReportFilename;
307 File f = new File(compiledFilePath);
308 if(!f.exists()) { // Need to compile the file
309 // First verify that there is a source file.
310 String sourceFilePath = basePath+reportDescriptionFilename;
311 File f2 = new File(sourceFilePath);
312 if(!f2.exists()) { // Missing source file - error!
313 logger.error("Report for csid={} is missing the specified source file: {}",
314 reportCSID, sourceFilePath);
315 throw new RuntimeException("Report is missing the specified source file!");
317 logger.info("Report for csid={} is not compiled. Compiling first, and saving to: {}",
318 reportCSID, compiledFilePath);
319 JasperCompileManager.compileReportToFile(sourceFilePath, compiledFilePath);
322 conn = getConnection();
324 if (logger.isTraceEnabled()) {
325 logger.trace("ReportResource for csid=" + reportCSID
326 +" output as "+outputMimeType+" using report file: "+compiledFilePath);
328 FileInputStream fileStream = new FileInputStream(compiledFilePath);
330 // export report to pdf and build a response with the bytes
331 //JasperExportManager.exportReportToPdf(jasperprint);
333 JRExporter exporter = null;
334 // Strip extension from report filename.
335 String outputFilename = reportFileName;
336 // Strip extension from report filename.
337 int idx = outputFilename.lastIndexOf(".");
339 outputFilename = outputFilename.substring(0, idx);
340 // Strip any sub-dir from report filename.
341 idx = outputFilename.lastIndexOf(File.separator);
343 outputFilename = outputFilename.substring(idx+1);
344 if(outputMimeType.equals(MediaType.APPLICATION_XML)) {
345 params.put(JRParameter.IS_IGNORE_PAGINATION, Boolean.TRUE);
346 exporter = new JRXmlExporter();
347 outputFilename = outputFilename+".xml";
348 } else if(outputMimeType.equals(MediaType.TEXT_HTML)) {
349 exporter = new JRHtmlExporter();
350 outputFilename = outputFilename+".html";
351 } else if(outputMimeType.equals(ReportClient.PDF_MIME_TYPE)) {
352 exporter = new JRPdfExporter();
353 outputFilename = outputFilename+".pdf";
354 } else if(outputMimeType.equals(ReportClient.CSV_MIME_TYPE)) {
355 params.put(JRParameter.IS_IGNORE_PAGINATION, Boolean.TRUE);
356 exporter = new JRCsvExporter();
357 exporter.setParameter(JRCsvExporterParameter.FIELD_DELIMITER, ",");
358 outputFilename = outputFilename+".csv";
359 } else if(outputMimeType.equals(ReportClient.TSV_MIME_TYPE)) {
360 params.put(JRParameter.IS_IGNORE_PAGINATION, Boolean.TRUE);
361 exporter = new JRCsvExporter();
362 exporter.setParameter(JRCsvExporterParameter.FIELD_DELIMITER, "\t");
363 outputFilename = outputFilename+".csv";
364 } else if(outputMimeType.equals(ReportClient.MSWORD_MIME_TYPE) // Understand msword as docx
365 || outputMimeType.equals(ReportClient.OPEN_DOCX_MIME_TYPE)) {
366 exporter = new JRDocxExporter();
367 outputFilename = outputFilename+".docx";
368 } else if(outputMimeType.equals(ReportClient.MSEXCEL_MIME_TYPE) // Understand msexcel as xlsx
369 || outputMimeType.equals(ReportClient.OPEN_XLSX_MIME_TYPE)) {
370 exporter = new JRXlsxExporter();
371 outputFilename = outputFilename+".xlsx";
372 } else if(outputMimeType.equals(ReportClient.MSPPT_MIME_TYPE) // Understand msppt as xlsx
373 || outputMimeType.equals(ReportClient.OPEN_PPTX_MIME_TYPE)) {
374 exporter = new JRPptxExporter();
375 outputFilename = outputFilename+".pptx";
377 logger.error("Reporting: unsupported output MIME type - defaulting to PDF");
378 exporter = new JRPdfExporter();
379 outputFilename = outputFilename+"-default-to.pdf";
381 outReportFileName.append(outputFilename); // Set the out going param to the report's final file name
382 // FIXME: Logging temporarily set to INFO level for CSPACE-5766;
383 // can change to TRACE or DEBUG level as warranted thereafter
384 if (logger.isInfoEnabled()) {
385 logger.info(FileTools.getJavaTmpDirInfo());
388 JasperPrint jasperPrint = JasperFillManager.fillReport(fileStream, params,conn);
390 // Report will be to a temporary file.
391 File tempOutputFile = Files.createTempFile("report-", null).toFile();
392 FileOutputStream tempOutputStream = new FileOutputStream(tempOutputFile);
393 exporter.setParameter(JRExporterParameter.JASPER_PRINT, jasperPrint);
394 exporter.setParameter(JRExporterParameter.OUTPUT_STREAM, tempOutputStream);
395 exporter.exportReport();
396 tempOutputStream.close();
398 result = new FileInputStream(tempOutputFile);
400 } catch (SQLException sqle) {
401 // SQLExceptions can be chained. We have at least one exception, so
402 // set up a loop to make sure we let the user know about all of them
403 // if there happens to be more than one.
404 if (logger.isDebugEnabled()) {
405 SQLException tempException = sqle;
406 while (null != tempException) {
407 logger.debug("SQL Exception: " + sqle.getLocalizedMessage());
409 // loop to the next exception
410 tempException = tempException.getNextException();
413 Response response = Response.status(
414 Response.Status.INTERNAL_SERVER_ERROR).entity(
415 "Invoke failed (SQL problem) on Report csid=" + reportCSID).type("text/plain").build();
416 throw new CSWebApplicationException(sqle, response);
417 } catch (JRException jre) {
418 if (logger.isDebugEnabled()) {
419 logger.debug("JR Exception: " + jre.getLocalizedMessage() + " Cause: "+jre.getCause());
421 Response response = Response.status(
422 Response.Status.INTERNAL_SERVER_ERROR).entity(
423 "Invoke failed (Jasper problem) on Report csid=" + reportCSID).type("text/plain").build();
424 throw new CSWebApplicationException(jre, response);
425 } catch (FileNotFoundException fnfe) {
426 if (logger.isDebugEnabled()) {
427 logger.debug("FileNotFoundException: " + fnfe.getLocalizedMessage());
429 Response response = Response.status(
430 Response.Status.INTERNAL_SERVER_ERROR).entity(
431 "Invoke failed (SQL problem) on Report csid=" + reportCSID).type("text/plain").build();
432 throw new CSWebApplicationException(fnfe, response);
437 } catch (SQLException sqle) {
438 // SQLExceptions can be chained. We have at least one exception, so
439 // set up a loop to make sure we let the user know about all of them
440 // if there happens to be more than one.
441 if (logger.isDebugEnabled()) {
442 logger.debug("SQL Exception closing connection: "
443 + sqle.getLocalizedMessage());
445 } catch (Exception e) {
446 if (logger.isDebugEnabled()) {
447 logger.debug("Exception closing connection", e);
454 private Connection getConnection() throws NamingException, SQLException {
455 Connection result = null;
457 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
459 String repositoryName = ctx.getRepositoryName();
460 if (repositoryName != null && repositoryName.trim().isEmpty() == false) {
461 String cspaceInstanceId = ServiceMain.getInstance().getCspaceInstanceId();
462 String databaseName = JDBCTools.getDatabaseName(repositoryName, cspaceInstanceId);
463 result = JDBCTools.getConnection(JDBCTools.NUXEO_READER_DATASOURCE_NAME, databaseName);
465 } catch (Exception e) {
467 throw new NamingException();