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;
34 import javax.ws.rs.core.MediaType;
35 import javax.ws.rs.core.Response.ResponseBuilder;
37 import javax.naming.NamingException;
38 import javax.ws.rs.WebApplicationException;
39 import javax.ws.rs.core.Response;
41 import net.sf.jasperreports.engine.JRException;
42 import net.sf.jasperreports.engine.JRExporter;
43 import net.sf.jasperreports.engine.JRExporterParameter;
44 import net.sf.jasperreports.engine.JRParameter;
45 import net.sf.jasperreports.engine.JasperExportManager;
46 import net.sf.jasperreports.engine.JasperFillManager;
47 import net.sf.jasperreports.engine.JasperPrint;
48 import net.sf.jasperreports.engine.export.JRCsvExporter;
49 import net.sf.jasperreports.engine.export.JRCsvExporterParameter;
50 import net.sf.jasperreports.engine.export.JRHtmlExporter;
51 import net.sf.jasperreports.engine.export.JRPdfExporter;
52 import net.sf.jasperreports.engine.export.JRXmlExporter;
54 import org.collectionspace.services.ReportJAXBSchema;
55 import org.collectionspace.services.report.ReportsCommon;
56 import org.collectionspace.services.client.PoxPayloadIn;
57 import org.collectionspace.services.client.PoxPayloadOut;
58 import org.collectionspace.services.client.ReportClient;
59 import org.collectionspace.services.common.ServiceMain;
60 import org.collectionspace.services.common.api.Tools;
61 import org.collectionspace.services.common.config.ConfigReader;
62 import org.collectionspace.services.common.context.ServiceContext;
63 import org.collectionspace.services.common.document.BadRequestException;
64 import org.collectionspace.services.common.document.DocumentException;
65 import org.collectionspace.services.common.document.DocumentWrapper;
66 import org.collectionspace.services.common.invocable.Invocable;
67 import org.collectionspace.services.common.invocable.InvocationContext;
68 import org.collectionspace.services.common.storage.JDBCTools;
69 import org.collectionspace.services.jaxb.InvocableJAXBSchema;
70 import org.collectionspace.services.nuxeo.client.java.DocHandlerBase;
71 import org.collectionspace.services.nuxeo.client.java.RepositoryJavaClientImpl;
72 import org.jfree.util.Log;
73 import org.nuxeo.ecm.core.api.DocumentModel;
74 import org.nuxeo.ecm.core.api.model.PropertyException;
75 import org.nuxeo.ecm.core.api.repository.RepositoryInstance;
76 import org.slf4j.Logger;
77 import org.slf4j.LoggerFactory;
80 * ReportDocumentModelHandler
82 * $LastChangedRevision: $
85 public class ReportDocumentModelHandler extends DocHandlerBase<ReportsCommon> {
86 private final Logger logger = LoggerFactory.getLogger(ReportDocumentModelHandler.class);
87 private static String REPORTS_FOLDER = "reports";
88 private static String CSID_LIST_SEPARATOR = ",";
90 private static String REPORTS_STD_CSID_PARAM = "csid";
91 private static String REPORTS_STD_GROUPCSID_PARAM = "groupcsid";
92 private static String REPORTS_STD_CSIDLIST_PARAM = "csidlist";
93 private static String REPORTS_STD_TENANTID_PARAM = "tenantid";
95 public Response invokeReport(
96 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
98 InvocationContext invContext) throws Exception {
99 RepositoryInstance repoSession = null;
100 boolean releaseRepoSession = false;
102 String invocationMode = invContext.getMode();
103 String modeProperty = null;
104 String outputMimeType = ReportClient.DEFAULT_REPORT_OUTPUT_MIME;
105 HashMap<String, Object> params = new HashMap<String, Object>();
106 params.put(REPORTS_STD_TENANTID_PARAM, ctx.getTenantId());
107 boolean checkDocType = true;
108 if(Invocable.INVOCATION_MODE_SINGLE.equalsIgnoreCase(invocationMode)) {
109 modeProperty = InvocableJAXBSchema.SUPPORTS_SINGLE_DOC;
110 params.put(REPORTS_STD_CSID_PARAM, invContext.getSingleCSID());
111 } else if(Invocable.INVOCATION_MODE_LIST.equalsIgnoreCase(invocationMode)) {
112 modeProperty = InvocableJAXBSchema.SUPPORTS_DOC_LIST;
113 List<String> csids = null;
114 InvocationContext.ListCSIDs listThing = invContext.getListCSIDs();
115 if(listThing!=null) {
116 csids = listThing.getCsid();
118 if(csids==null||csids.isEmpty()){
119 throw new BadRequestException(
120 "ReportResource: Report invoked in list mode, with no csids in list." );
122 StringBuilder sb = new StringBuilder();
123 boolean first = true;
124 for(String csidItem : csids) {
128 sb.append(CSID_LIST_SEPARATOR);
131 params.put(REPORTS_STD_CSIDLIST_PARAM, sb.toString());
132 } else if(Invocable.INVOCATION_MODE_GROUP.equalsIgnoreCase(invocationMode)) {
133 modeProperty = InvocableJAXBSchema.SUPPORTS_GROUP;
134 params.put(REPORTS_STD_GROUPCSID_PARAM, invContext.getGroupCSID());
135 } else if(Invocable.INVOCATION_MODE_NO_CONTEXT.equalsIgnoreCase(invocationMode)) {
136 modeProperty = InvocableJAXBSchema.SUPPORTS_NO_CONTEXT;
137 checkDocType = false;
139 throw new BadRequestException("ReportResource: unknown Invocation Mode: "
144 RepositoryJavaClientImpl repoClient = (RepositoryJavaClientImpl)this.getRepositoryClient(ctx);
145 repoSession = this.getRepositorySession();
146 if (repoSession == null) {
147 repoSession = repoClient.getRepositorySession(ctx);
148 releaseRepoSession = true;
151 String reportFileName = null;
152 // Get properties from the batch docModel, and release the session
154 DocumentWrapper<DocumentModel> wrapper = repoClient.getDoc(repoSession, ctx, csid);
155 DocumentModel docModel = wrapper.getWrappedObject();
156 Boolean supports = (Boolean)docModel.getPropertyValue(modeProperty);
157 if(supports == null || !supports) {
158 throw new BadRequestException(
159 "ReportResource: This Report does not support Invocation Mode: "
163 List<String> forDocTypeList =
164 (List<String>)docModel.getPropertyValue(InvocableJAXBSchema.FOR_DOC_TYPES);
165 if(forDocTypeList==null
166 || !forDocTypeList.contains(invContext.getDocType())) {
167 throw new BadRequestException(
168 "ReportResource: Invoked with unsupported document type: "
169 +invContext.getDocType());
172 reportFileName = (String)docModel.getPropertyValue(ReportJAXBSchema.FILENAME);
173 String reportOutputMime = (String)docModel.getPropertyValue(ReportJAXBSchema.OUTPUT_MIME);
174 if(!Tools.isEmpty(reportOutputMime)) {
175 outputMimeType = reportOutputMime;
177 } catch (PropertyException pe) {
178 if (logger.isDebugEnabled()) {
179 logger.debug("Property exception getting batch values: ", pe);
182 } catch (DocumentException de) {
183 if (logger.isDebugEnabled()) {
184 logger.debug("Problem getting batch doc: ", de);
187 } catch (Exception e) {
188 if (logger.isDebugEnabled()) {
189 logger.debug("Caught exception ", e);
191 throw new DocumentException(e);
193 if (releaseRepoSession && repoSession != null) {
194 repoClient.releaseRepositorySession(ctx, repoSession);
197 return buildReportResponse(csid, params, reportFileName, outputMimeType);
201 private Response buildReportResponse(String reportCSID,
202 HashMap<String, Object> params, String reportFileName, String outputMimeType)
204 Connection conn = null;
205 Response response = null;
207 String fullPath = ServiceMain.getInstance().getServerRootDir() +
208 File.separator + ConfigReader.CSPACE_DIR_NAME +
209 File.separator + REPORTS_FOLDER +
210 // File.separator + tenantName +
211 File.separator + reportFileName;
212 conn = getConnection();
214 if (logger.isTraceEnabled()) {
215 logger.trace("ReportResource for csid=" + reportCSID
216 +" output as "+outputMimeType+" using report file: "+fullPath);
218 FileInputStream fileStream = new FileInputStream(fullPath);
220 // export report to pdf and build a response with the bytes
221 //JasperExportManager.exportReportToPdf(jasperprint);
223 // Report will be to a byte output array.
224 ByteArrayOutputStream baos = new ByteArrayOutputStream();
226 JRExporter exporter = null;
227 // Strip extension from report filename.
228 String outputFilename = reportFileName;
229 // Strip extension from report filename.
230 int idx = outputFilename.lastIndexOf(".");
232 outputFilename = outputFilename.substring(0, idx);
233 // Strip any sub-dir from report filename.
234 idx = outputFilename.lastIndexOf(File.separator);
236 outputFilename = outputFilename.substring(idx+1);
237 if(outputMimeType.equals(MediaType.APPLICATION_XML)) {
238 params.put(JRParameter.IS_IGNORE_PAGINATION, Boolean.TRUE);
239 exporter = new JRXmlExporter();
240 outputFilename = outputFilename+".xml";
241 } else if(outputMimeType.equals(MediaType.TEXT_HTML)) {
242 exporter = new JRHtmlExporter();
243 outputFilename = outputFilename+".html";
244 } else if(outputMimeType.equals(ReportClient.PDF_MIME_TYPE)) {
245 exporter = new JRPdfExporter();
246 outputFilename = outputFilename+".pdf";
247 } else if(outputMimeType.equals(ReportClient.CSV_MIME_TYPE)) {
248 params.put(JRParameter.IS_IGNORE_PAGINATION, Boolean.TRUE);
249 exporter = new JRCsvExporter();
250 exporter.setParameter(JRCsvExporterParameter.FIELD_DELIMITER, ",");
251 outputFilename = outputFilename+".csv";
252 } else if(outputMimeType.equals(ReportClient.TSV_MIME_TYPE)) {
253 params.put(JRParameter.IS_IGNORE_PAGINATION, Boolean.TRUE);
254 exporter = new JRCsvExporter();
255 exporter.setParameter(JRCsvExporterParameter.FIELD_DELIMITER, "\t");
256 outputFilename = outputFilename+".csv";
259 JasperPrint jasperPrint = JasperFillManager.fillReport(fileStream, params,conn);
261 exporter.setParameter(JRExporterParameter.JASPER_PRINT, jasperPrint);
262 exporter.setParameter(JRExporterParameter.OUTPUT_STREAM, baos);
263 exporter.exportReport();
264 byte[] reportAsBytes = baos.toByteArray();
266 // Need to set response type for what is requested...
267 ResponseBuilder builder = Response.ok(reportAsBytes, outputMimeType);
268 builder = builder.header("Content-Disposition","inline;filename=\""+outputFilename+"\"");
269 response = builder.build();
272 } catch (SQLException sqle) {
273 // SQLExceptions can be chained. We have at least one exception, so
274 // set up a loop to make sure we let the user know about all of them
275 // if there happens to be more than one.
276 if (logger.isDebugEnabled()) {
277 SQLException tempException = sqle;
278 while (null != tempException) {
279 logger.debug("SQL Exception: " + sqle.getLocalizedMessage());
281 // loop to the next exception
282 tempException = tempException.getNextException();
285 response = Response.status(
286 Response.Status.INTERNAL_SERVER_ERROR).entity(
287 "Invoke failed (SQL problem) on Report csid=" + reportCSID).type("text/plain").build();
288 throw new WebApplicationException(response);
289 } catch (JRException jre) {
290 if (logger.isDebugEnabled()) {
291 logger.debug("JR Exception: " + jre.getLocalizedMessage() + " Cause: "+jre.getCause());
293 response = Response.status(
294 Response.Status.INTERNAL_SERVER_ERROR).entity(
295 "Invoke failed (Jasper problem) on Report csid=" + reportCSID).type("text/plain").build();
296 throw new WebApplicationException(response);
297 } catch (FileNotFoundException fnfe) {
298 if (logger.isDebugEnabled()) {
299 logger.debug("FileNotFoundException: " + fnfe.getLocalizedMessage());
301 response = Response.status(
302 Response.Status.INTERNAL_SERVER_ERROR).entity(
303 "Invoke failed (SQL problem) on Report csid=" + reportCSID).type("text/plain").build();
304 throw new WebApplicationException(response);
309 } catch (SQLException sqle) {
310 // SQLExceptions can be chained. We have at least one exception, so
311 // set up a loop to make sure we let the user know about all of them
312 // if there happens to be more than one.
313 if (logger.isDebugEnabled()) {
314 logger.debug("SQL Exception closing connection: "
315 + sqle.getLocalizedMessage());
317 } catch (Exception e) {
318 if (logger.isDebugEnabled()) {
319 logger.debug("Exception closing connection", e);
326 private Connection getConnection() throws NamingException, SQLException {
327 Connection result = null;
329 ServiceContext ctx = this.getServiceContext();
331 String repositoryName = ctx.getRepositoryName();
332 if (repositoryName != null && repositoryName.trim().isEmpty() == false) {
333 result = JDBCTools.getConnection(JDBCTools.NUXEO_DATASOURCE_NAME, repositoryName);
335 } catch (Exception e) {
337 throw new NamingException();