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.ByteArrayOutputStream;
28 import java.io.FileInputStream;
29 import java.io.FileNotFoundException;
30 import java.sql.Connection;
31 import java.sql.SQLException;
32 import java.util.HashMap;
33 import java.util.List;
36 import javax.ws.rs.core.MediaType;
37 import javax.ws.rs.core.Response.ResponseBuilder;
39 import javax.naming.NamingException;
40 import javax.ws.rs.WebApplicationException;
41 import javax.ws.rs.core.Response;
43 import net.sf.jasperreports.engine.JRException;
44 import net.sf.jasperreports.engine.JRExporter;
45 import net.sf.jasperreports.engine.JRExporterParameter;
46 import net.sf.jasperreports.engine.JRParameter;
47 import net.sf.jasperreports.engine.JasperCompileManager;
48 import net.sf.jasperreports.engine.JasperExportManager;
49 import net.sf.jasperreports.engine.JasperFillManager;
50 import net.sf.jasperreports.engine.JasperPrint;
51 import net.sf.jasperreports.engine.export.JRCsvExporter;
52 import net.sf.jasperreports.engine.export.JRCsvExporterParameter;
53 import net.sf.jasperreports.engine.export.JRHtmlExporter;
54 import net.sf.jasperreports.engine.export.JRPdfExporter;
55 import net.sf.jasperreports.engine.export.JRXmlExporter;
57 import org.bouncycastle.crypto.RuntimeCryptoException;
58 import org.collectionspace.services.ReportJAXBSchema;
59 import org.collectionspace.services.report.ReportsCommon;
60 import org.collectionspace.services.client.PoxPayloadIn;
61 import org.collectionspace.services.client.PoxPayloadOut;
62 import org.collectionspace.services.client.ReportClient;
63 import org.collectionspace.services.common.ServiceMain;
64 import org.collectionspace.services.common.api.Tools;
65 import org.collectionspace.services.common.config.ConfigReader;
66 import org.collectionspace.services.common.context.ServiceContext;
67 import org.collectionspace.services.common.document.BadRequestException;
68 import org.collectionspace.services.common.document.DocumentException;
69 import org.collectionspace.services.common.document.DocumentWrapper;
70 import org.collectionspace.services.common.invocable.Invocable;
71 import org.collectionspace.services.common.invocable.InvocationContext;
72 import org.collectionspace.services.common.storage.JDBCTools;
73 import org.collectionspace.services.jaxb.InvocableJAXBSchema;
74 import org.collectionspace.services.nuxeo.client.java.DocHandlerBase;
75 import org.collectionspace.services.nuxeo.client.java.RepositoryJavaClientImpl;
76 import org.jfree.util.Log;
77 import org.nuxeo.ecm.core.api.DocumentModel;
78 import org.nuxeo.ecm.core.api.model.PropertyException;
79 import org.nuxeo.ecm.core.api.repository.RepositoryInstance;
80 import org.slf4j.Logger;
81 import org.slf4j.LoggerFactory;
84 * ReportDocumentModelHandler
86 * $LastChangedRevision: $
89 public class ReportDocumentModelHandler extends DocHandlerBase<ReportsCommon> {
90 private final Logger logger = LoggerFactory.getLogger(ReportDocumentModelHandler.class);
91 private static String REPORTS_FOLDER = "reports";
92 private static String CSID_LIST_SEPARATOR = ",";
94 private static String REPORTS_STD_CSID_PARAM = "csid";
95 private static String REPORTS_STD_GROUPCSID_PARAM = "groupcsid";
96 private static String REPORTS_STD_CSIDLIST_PARAM = "csidlist";
97 private static String REPORTS_STD_TENANTID_PARAM = "tenantid";
99 public Response invokeReport(
100 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
102 InvocationContext invContext) throws Exception {
103 RepositoryInstance repoSession = null;
104 boolean releaseRepoSession = false;
106 String invocationMode = invContext.getMode();
107 String modeProperty = null;
108 String outputMimeType = ReportClient.DEFAULT_REPORT_OUTPUT_MIME;
109 HashMap<String, Object> params = new HashMap<String, Object>();
110 params.put(REPORTS_STD_TENANTID_PARAM, ctx.getTenantId());
111 boolean checkDocType = true;
113 // Note we set before we put in the default ones, so they cannot override tenant or CSID.
114 setParamsFromContext(params, invContext);
116 if(Invocable.INVOCATION_MODE_SINGLE.equalsIgnoreCase(invocationMode)) {
117 modeProperty = InvocableJAXBSchema.SUPPORTS_SINGLE_DOC;
118 params.put(REPORTS_STD_CSID_PARAM, invContext.getSingleCSID());
119 } else if(Invocable.INVOCATION_MODE_LIST.equalsIgnoreCase(invocationMode)) {
120 modeProperty = InvocableJAXBSchema.SUPPORTS_DOC_LIST;
121 List<String> csids = null;
122 InvocationContext.ListCSIDs listThing = invContext.getListCSIDs();
123 if(listThing!=null) {
124 csids = listThing.getCsid();
126 if(csids==null||csids.isEmpty()){
127 throw new BadRequestException(
128 "ReportResource: Report invoked in list mode, with no csids in list." );
130 StringBuilder sb = new StringBuilder();
131 boolean first = true;
132 for(String csidItem : csids) {
136 sb.append(CSID_LIST_SEPARATOR);
139 params.put(REPORTS_STD_CSIDLIST_PARAM, sb.toString());
140 } else if(Invocable.INVOCATION_MODE_GROUP.equalsIgnoreCase(invocationMode)) {
141 modeProperty = InvocableJAXBSchema.SUPPORTS_GROUP;
142 params.put(REPORTS_STD_GROUPCSID_PARAM, invContext.getGroupCSID());
143 } else if(Invocable.INVOCATION_MODE_NO_CONTEXT.equalsIgnoreCase(invocationMode)) {
144 modeProperty = InvocableJAXBSchema.SUPPORTS_NO_CONTEXT;
145 checkDocType = false;
147 throw new BadRequestException("ReportResource: unknown Invocation Mode: "
152 RepositoryJavaClientImpl repoClient = (RepositoryJavaClientImpl)this.getRepositoryClient(ctx);
153 repoSession = this.getRepositorySession();
154 if (repoSession == null) {
155 repoSession = repoClient.getRepositorySession(ctx);
156 releaseRepoSession = true;
159 String reportFileName = null;
160 // Get properties from the batch docModel, and release the session
162 DocumentWrapper<DocumentModel> wrapper = repoClient.getDoc(repoSession, ctx, csid);
163 DocumentModel docModel = wrapper.getWrappedObject();
164 Boolean supports = (Boolean)docModel.getPropertyValue(modeProperty);
165 if(supports == null || !supports) {
166 throw new BadRequestException(
167 "ReportResource: This Report does not support Invocation Mode: "
171 List<String> forDocTypeList =
172 (List<String>)docModel.getPropertyValue(InvocableJAXBSchema.FOR_DOC_TYPES);
173 if(forDocTypeList==null
174 || !forDocTypeList.contains(invContext.getDocType())) {
175 throw new BadRequestException(
176 "ReportResource: Invoked with unsupported document type: "
177 +invContext.getDocType());
180 reportFileName = (String)docModel.getPropertyValue(ReportJAXBSchema.FILENAME);
181 String reportOutputMime = (String)docModel.getPropertyValue(ReportJAXBSchema.OUTPUT_MIME);
182 if(!Tools.isEmpty(reportOutputMime)) {
183 outputMimeType = reportOutputMime;
185 } catch (PropertyException pe) {
186 if (logger.isDebugEnabled()) {
187 logger.debug("Property exception getting batch values: ", pe);
190 } catch (DocumentException de) {
191 if (logger.isDebugEnabled()) {
192 logger.debug("Problem getting batch doc: ", de);
195 } catch (Exception e) {
196 if (logger.isDebugEnabled()) {
197 logger.debug("Caught exception ", e);
199 throw new DocumentException(e);
201 if (releaseRepoSession && repoSession != null) {
202 repoClient.releaseRepositorySession(ctx, repoSession);
205 return buildReportResponse(csid, params, reportFileName, outputMimeType);
208 private void setParamsFromContext(Map<String, Object> params, InvocationContext invContext) {
209 InvocationContext.Params icParams = invContext.getParams();
210 if(icParams!= null) {
211 List<InvocationContext.Params.Param> icParamList = icParams.getParam();
212 if(icParamList != null) {
213 for(InvocationContext.Params.Param param:icParamList) {
214 String key = param.getKey();
215 String value = param.getValue();
216 if(!Tools.isEmpty(key) && !Tools.isEmpty(value)) {
217 params.put(key, value);
227 private Response buildReportResponse(String reportCSID,
228 HashMap<String, Object> params, String reportFileName, String outputMimeType)
230 Connection conn = null;
231 Response response = null;
233 String fileNameBase = Tools.getFilenameBase(reportFileName);
234 String compiledReportFilename = fileNameBase+ReportClient.COMPILED_REPORT_EXTENSION;
235 String reportDescriptionFilename = fileNameBase+ReportClient.REPORT_DECSRIPTION_EXTENSION;
237 String basePath = ServiceMain.getInstance().getServerRootDir() +
238 File.separator + ConfigReader.CSPACE_DIR_NAME +
239 File.separator + REPORTS_FOLDER +
240 // File.separator + tenantName +
241 File.separator; // + reportFileName;
243 String compiledFilePath = basePath+compiledReportFilename;
244 File f = new File(compiledFilePath);
245 if(!f.exists()) { // Need to compile the file
246 // First verify that there is a source file.
247 String sourceFilePath = basePath+reportDescriptionFilename;
248 File f2 = new File(sourceFilePath);
249 if(!f2.exists()) { // Missing source file - error!
250 logger.error("Report for csid={} is missing the specified source file: {}",
251 reportCSID, sourceFilePath);
252 throw new RuntimeException("Report is missing the specified source file!");
254 logger.info("Report for csid={} is not compiled. Compiling first, and saving to: {}",
255 reportCSID, compiledFilePath);
256 JasperCompileManager.compileReportToFile(sourceFilePath, compiledFilePath);
259 conn = getConnection();
261 if (logger.isTraceEnabled()) {
262 logger.trace("ReportResource for csid=" + reportCSID
263 +" output as "+outputMimeType+" using report file: "+compiledFilePath);
265 FileInputStream fileStream = new FileInputStream(compiledFilePath);
267 // export report to pdf and build a response with the bytes
268 //JasperExportManager.exportReportToPdf(jasperprint);
270 // Report will be to a byte output array.
271 ByteArrayOutputStream baos = new ByteArrayOutputStream();
273 JRExporter exporter = null;
274 // Strip extension from report filename.
275 String outputFilename = reportFileName;
276 // Strip extension from report filename.
277 int idx = outputFilename.lastIndexOf(".");
279 outputFilename = outputFilename.substring(0, idx);
280 // Strip any sub-dir from report filename.
281 idx = outputFilename.lastIndexOf(File.separator);
283 outputFilename = outputFilename.substring(idx+1);
284 if(outputMimeType.equals(MediaType.APPLICATION_XML)) {
285 params.put(JRParameter.IS_IGNORE_PAGINATION, Boolean.TRUE);
286 exporter = new JRXmlExporter();
287 outputFilename = outputFilename+".xml";
288 } else if(outputMimeType.equals(MediaType.TEXT_HTML)) {
289 exporter = new JRHtmlExporter();
290 outputFilename = outputFilename+".html";
291 } else if(outputMimeType.equals(ReportClient.PDF_MIME_TYPE)) {
292 exporter = new JRPdfExporter();
293 outputFilename = outputFilename+".pdf";
294 } else if(outputMimeType.equals(ReportClient.CSV_MIME_TYPE)) {
295 params.put(JRParameter.IS_IGNORE_PAGINATION, Boolean.TRUE);
296 exporter = new JRCsvExporter();
297 exporter.setParameter(JRCsvExporterParameter.FIELD_DELIMITER, ",");
298 outputFilename = outputFilename+".csv";
299 } else if(outputMimeType.equals(ReportClient.TSV_MIME_TYPE)) {
300 params.put(JRParameter.IS_IGNORE_PAGINATION, Boolean.TRUE);
301 exporter = new JRCsvExporter();
302 exporter.setParameter(JRCsvExporterParameter.FIELD_DELIMITER, "\t");
303 outputFilename = outputFilename+".csv";
306 JasperPrint jasperPrint = JasperFillManager.fillReport(fileStream, params,conn);
308 exporter.setParameter(JRExporterParameter.JASPER_PRINT, jasperPrint);
309 exporter.setParameter(JRExporterParameter.OUTPUT_STREAM, baos);
310 exporter.exportReport();
311 byte[] reportAsBytes = baos.toByteArray();
313 // Need to set response type for what is requested...
314 ResponseBuilder builder = Response.ok(reportAsBytes, outputMimeType);
315 builder = builder.header("Content-Disposition","inline;filename=\""+outputFilename+"\"");
316 response = builder.build();
319 } catch (SQLException sqle) {
320 // SQLExceptions can be chained. We have at least one exception, so
321 // set up a loop to make sure we let the user know about all of them
322 // if there happens to be more than one.
323 if (logger.isDebugEnabled()) {
324 SQLException tempException = sqle;
325 while (null != tempException) {
326 logger.debug("SQL Exception: " + sqle.getLocalizedMessage());
328 // loop to the next exception
329 tempException = tempException.getNextException();
332 response = Response.status(
333 Response.Status.INTERNAL_SERVER_ERROR).entity(
334 "Invoke failed (SQL problem) on Report csid=" + reportCSID).type("text/plain").build();
335 throw new WebApplicationException(response);
336 } catch (JRException jre) {
337 if (logger.isDebugEnabled()) {
338 logger.debug("JR Exception: " + jre.getLocalizedMessage() + " Cause: "+jre.getCause());
340 response = Response.status(
341 Response.Status.INTERNAL_SERVER_ERROR).entity(
342 "Invoke failed (Jasper problem) on Report csid=" + reportCSID).type("text/plain").build();
343 throw new WebApplicationException(response);
344 } catch (FileNotFoundException fnfe) {
345 if (logger.isDebugEnabled()) {
346 logger.debug("FileNotFoundException: " + fnfe.getLocalizedMessage());
348 response = Response.status(
349 Response.Status.INTERNAL_SERVER_ERROR).entity(
350 "Invoke failed (SQL problem) on Report csid=" + reportCSID).type("text/plain").build();
351 throw new WebApplicationException(response);
356 } catch (SQLException sqle) {
357 // SQLExceptions can be chained. We have at least one exception, so
358 // set up a loop to make sure we let the user know about all of them
359 // if there happens to be more than one.
360 if (logger.isDebugEnabled()) {
361 logger.debug("SQL Exception closing connection: "
362 + sqle.getLocalizedMessage());
364 } catch (Exception e) {
365 if (logger.isDebugEnabled()) {
366 logger.debug("Exception closing connection", e);
373 private Connection getConnection() throws NamingException, SQLException {
374 Connection result = null;
376 ServiceContext ctx = this.getServiceContext();
378 String repositoryName = ctx.getRepositoryName();
379 if (repositoryName != null && repositoryName.trim().isEmpty() == false) {
380 result = JDBCTools.getConnection(JDBCTools.NUXEO_DATASOURCE_NAME, repositoryName);
382 } catch (Exception e) {
384 throw new NamingException();