]> git.aero2k.de Git - tmp/jakarta-migration.git/blob
adbb388888f798a14887d648ed66f4e53b4261b7
[tmp/jakarta-migration.git] /
1 /**
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:
5
6  *  http://www.collectionspace.org
7  *  http://wiki.collectionspace.org
8
9  *  Copyright 2009 University of California at Berkeley
10
11  *  Licensed under the Educational Community License (ECL), Version 2.0.
12  *  You may not use this file except in compliance with this License.
13
14  *  You may obtain a copy of the ECL 2.0 License at
15
16  *  https://source.collectionspace.org/collection-space/LICENSE.txt
17
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.
23  */
24 package org.collectionspace.services.report.nuxeo;
25
26 import java.io.File;
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;
36 import java.util.Map;
37
38 import javax.ws.rs.core.MediaType;
39 import javax.naming.NamingException;
40 import javax.ws.rs.core.Response;
41
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;
57
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;
87
88 import org.jfree.util.Log;
89
90 import org.nuxeo.ecm.core.api.model.PropertyException;
91 import org.nuxeo.ecm.core.api.DocumentModel;
92
93 import org.slf4j.Logger;
94 import org.slf4j.LoggerFactory;
95
96 /**
97  * ReportDocumentModelHandler
98  *
99  * $LastChangedRevision: $
100  * $LastChangedDate: $
101  */
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 = ",";
106     
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";
111     
112     //
113     // Map the MIME types from the service bindings to our payload output
114     //
115     public ReportsOuputMimeList getSupportMIMETypes(
116                 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx) {
117         //
118         // Create a new payload response instance and initialize it
119         //
120         ReportsOuputMimeList result = new ReportsOuputMimeList();
121         MIMEType resultMIMEType = result.getMIMETypeList();
122         if (resultMIMEType == null) {
123                 result.setMIMETypeList(resultMIMEType = new MIMEType());
124         }
125         List<MIMETypeItemType> resultMIMETypeItemList = resultMIMEType.getMIMEType();
126         
127         //
128         // Read the MIME type values from the service bindings and put into our response payload
129         //
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);
134         
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);
141                         } else {
142                         resultMimeTypeItem.setKey(bindingItemMimeType.getValue());
143                         }
144                         resultMimeTypeItem.setValue(bindingItemMimeType.getValue());
145                         resultMIMETypeItemList.add(resultMimeTypeItem);
146                 }
147         }
148
149         return result;
150     }
151     
152         public InputStream invokeReport(
153                         ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
154                         String csid,
155                         InvocationContext invContext,
156                         StringBuffer outMimeType,
157                         StringBuffer outReportFileName) throws Exception {
158                 CoreSessionInterface repoSession = null;
159                 boolean releaseRepoSession = false;
160
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;
166                 
167                 // Note we set before we put in the default ones, so they cannot override tenant or CSID.
168                 setParamsFromContext(params, invContext);
169                 
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();
179                                 }
180                                 if (csids==null||csids.isEmpty()){
181                                 throw new BadRequestException(
182                                                 "ReportResource: Report invoked in list mode, with no csids in list." );
183                                 }
184                                 StringBuilder sb = new StringBuilder();
185                                 boolean first = true;
186                                 for(String csidItem : csids) {
187                                         if(first)
188                                                 first = false;
189                                         else
190                                                 sb.append(CSID_LIST_SEPARATOR);
191                                         sb.append(csidItem);
192                                 }
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;
200                 } else {
201                         throw new BadRequestException("ReportResource: unknown Invocation Mode: "
202                                 +invocationMode);
203                 }
204                 
205                 NuxeoRepositoryClientImpl repoClient = (NuxeoRepositoryClientImpl)this.getRepositoryClient(ctx);
206                 repoSession = this.getRepositorySession();
207                 if (repoSession == null) {
208                         repoSession = repoClient.getRepositorySession(ctx);
209                         releaseRepoSession = true;
210                 }
211
212                 // Get properties from the batch docModel, and release the session
213                 String reportFileNameProperty;
214                 try {
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: "
221                                         +invocationMode);
222                         }
223                 if (checkDocType) {
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());
230                         }
231                 }
232                 reportFileNameProperty = (String) NuxeoUtils.getProperyValue(docModel, ReportJAXBSchema.FILENAME); //docModel.getPropertyValue(ReportJAXBSchema.FILENAME)); // Set the outgoing param with the report file name
233                         //
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.
236                 //
237                 if (!Tools.isEmpty(invContext.getOutputMIME())) {
238                         outMimeType.append(invContext.getOutputMIME());
239                 }
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);
244                         } else {
245                                 outMimeType.append(ReportClient.DEFAULT_REPORT_OUTPUT_MIME);
246                         }
247                 }
248                 } catch (PropertyException pe) {
249                         if (logger.isDebugEnabled()) {
250                                 logger.debug("Property exception getting batch values: ", pe);
251                         }
252                         throw pe;
253                 } catch (DocumentException de) {
254                         if (logger.isDebugEnabled()) {
255                                 logger.debug("Problem getting batch doc: ", de);
256                         }
257                         throw de;
258                 } catch (Exception e) {
259                         if (logger.isDebugEnabled()) {
260                                 logger.debug("Caught exception ", e);
261                         }
262                         throw new DocumentException(e);
263                 } finally {
264                         if (releaseRepoSession && repoSession != null) {
265                                 repoClient.releaseRepositorySession(ctx, repoSession);
266                         }
267                 }
268                 
269         return buildReportResult(csid, params, reportFileNameProperty, outMimeType.toString(), outReportFileName);
270         }
271         
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);
282                                         }
283                                 }
284                         }
285                 }
286                 
287         }
288
289     private InputStream buildReportResult(String reportCSID, 
290                 HashMap<String, Object> params, String reportFileName, String outputMimeType, StringBuffer outReportFileName)
291                                 throws Exception {
292                 Connection conn = null;
293                 InputStream result = null;
294                 
295         try {
296                 String fileNameBase = Tools.getFilenameBase(reportFileName);
297                 String compiledReportFilename = fileNameBase+ReportClient.COMPILED_REPORT_EXTENSION;
298                 String reportDescriptionFilename = fileNameBase+ReportClient.REPORT_DECSRIPTION_EXTENSION;
299                 
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;
305                         
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!");
316                                 }
317                 logger.info("Report for csid={} is not compiled. Compiling first, and saving to: {}",
318                                 reportCSID, compiledFilePath);
319                 JasperCompileManager.compileReportToFile(sourceFilePath, compiledFilePath);
320                         }                               
321                                 
322                         conn = getConnection();
323         
324             if (logger.isTraceEnabled()) {
325                 logger.trace("ReportResource for csid=" + reportCSID
326                                 +" output as "+outputMimeType+" using report file: "+compiledFilePath);
327             }
328                         FileInputStream fileStream = new FileInputStream(compiledFilePath);
329         
330                         // export report to pdf and build a response with the bytes
331                         //JasperExportManager.exportReportToPdf(jasperprint);
332                         
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("."); 
338                         if(idx>0)
339                                 outputFilename = outputFilename.substring(0, idx);
340                         // Strip any sub-dir from report filename.
341                         idx = outputFilename.lastIndexOf(File.separator); 
342                         if(idx>0)
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";
376                         } else {
377                                 logger.error("Reporting: unsupported output MIME type - defaulting to PDF");
378                                 exporter = new JRPdfExporter();
379                                 outputFilename = outputFilename+"-default-to.pdf";
380                         }
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());
386                         }
387                         // fill the report
388                         JasperPrint jasperPrint = JasperFillManager.fillReport(fileStream, params,conn);
389                                 
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();
397                         
398                         result = new FileInputStream(tempOutputFile);
399                 return result;
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());
408         
409                         // loop to the next exception
410                         tempException = tempException.getNextException();
411                     }
412             }
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());
420             }
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());
428             }
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);
433                 } finally {
434                 if (conn!=null) {
435                         try {
436                                 conn.close();
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());
444                     }
445                 } catch (Exception e) {
446                     if (logger.isDebugEnabled()) {
447                         logger.debug("Exception closing connection", e);
448                     }
449                 }
450                 }
451         }
452     }
453
454     private Connection getConnection() throws NamingException, SQLException {
455         Connection result = null;
456         
457         ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
458         try {
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);
464                 }
465                 } catch (Exception e) {
466                         Log.error(e);
467                         throw new NamingException();
468                 }
469         
470         return result;
471     }
472
473 }
474