From: Patrick Schmitz Date: Fri, 15 Jul 2011 20:47:29 +0000 (+0000) Subject: CSPACE-2391, 2503, 4201, 4202, 4204, 4206 More work on reporting to handle parameters... X-Git-Url: https://git.aero2k.de/?a=commitdiff_plain;h=c5ab618ecae8c2379c7ec2bbca8e6b7b893aabeb;p=tmp%2Fjakarta-migration.git CSPACE-2391, 2503, 4201, 4202, 4204, 4206 More work on reporting to handle parameters, manage repository connections more cleanly, add support for sample reports that are installed as part of the build. Added one example of a report that can run with no context, or with a single CSID context. Added a post init handler for reports to more cleanly manage the DB permissions for the reader user. --- diff --git a/services/common/src/main/cspace/config/services/tenants/tenant-bindings-proto.xml b/services/common/src/main/cspace/config/services/tenants/tenant-bindings-proto.xml index 703926ed1..c596d3748 100644 --- a/services/common/src/main/cspace/config/services/tenants/tenant-bindings-proto.xml +++ b/services/common/src/main/cspace/config/services/tenants/tenant-bindings-proto.xml @@ -837,6 +837,15 @@ default-domain org.collectionspace.services.report.nuxeo.ReportDocumentModelHandler org.collectionspace.services.report.nuxeo.ReportValidatorHandler + + org.collectionspace.services.report.nuxeo.ReportPostInitHandler + + + readerRoleName + reader + + + diff --git a/services/common/src/main/java/org/collectionspace/services/common/storage/JDBCTools.java b/services/common/src/main/java/org/collectionspace/services/common/storage/JDBCTools.java index e145b2ef5..125bc65b0 100755 --- a/services/common/src/main/java/org/collectionspace/services/common/storage/JDBCTools.java +++ b/services/common/src/main/java/org/collectionspace/services/common/storage/JDBCTools.java @@ -79,6 +79,7 @@ public class JDBCTools { } + /* THIS IS BROKEN - If you close the statement, it closes the ResultSet!!! public static ResultSet executeQuery(String repoName, String sql) throws Exception { Connection conn = null; Statement stmt = null; @@ -108,7 +109,7 @@ public class JDBCTools { return null; } } - } + } */ public static int executeUpdate(String repoName, String sql) throws Exception { Connection conn = null; diff --git a/services/report/3rdparty/build.xml b/services/report/3rdparty/build.xml index a8e694700..4929ee806 100644 --- a/services/report/3rdparty/build.xml +++ b/services/report/3rdparty/build.xml @@ -108,9 +108,18 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <band height="102" splitType="Stretch"> + <frame> + <reportElement mode="Opaque" x="0" y="0" width="721" height="66" backcolor="#FFFFFF"/> + <staticText> + <reportElement style="SubTitle" x="370" y="37" width="316" height="29"/> + <textElement textAlignment="Right"> + <font fontName="Arial" size="22" isBold="false"/> + </textElement> + <text><![CDATA[Acquisitions]]></text> + </staticText> + <image> + <reportElement x="11" y="2" width="366" height="61"/> + <imageExpression class="java.lang.String"><![CDATA["http://www.collectionspace.org/sites/all/themes/CStheme/images/CSpaceLogo.png"]]></imageExpression> + </image> + </frame> + <frame> + <reportElement mode="Opaque" x="0" y="70" width="721" height="32" forecolor="#000000" backcolor="#66FFFF"/> + <textField pattern="EEEEE dd MMMMM yyyy"> + <reportElement x="553" y="12" width="144" height="20" forecolor="#FFFFFF"/> + <textElement textAlignment="Right"> + <font size="12"/> + </textElement> + <textFieldExpression class="java.util.Date"><![CDATA[new java.util.Date()]]></textFieldExpression> + </textField> + </frame> + </band> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/services/report/service/src/main/java/org/collectionspace/services/report/ReportResource.java b/services/report/service/src/main/java/org/collectionspace/services/report/ReportResource.java index 58c23b3e2..0de359d8f 100644 --- a/services/report/service/src/main/java/org/collectionspace/services/report/ReportResource.java +++ b/services/report/service/src/main/java/org/collectionspace/services/report/ReportResource.java @@ -68,6 +68,7 @@ import javax.ws.rs.core.Context; import javax.ws.rs.core.Response; import java.io.File; import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.sql.Connection; import java.sql.SQLException; import java.util.HashMap; @@ -120,87 +121,26 @@ public class ReportResource extends ResourceBase { @Produces("application/pdf") public Response invokeReport( @PathParam("csid") String csid) { - if (logger.isDebugEnabled()) { - logger.debug("invokeReport with csid=" + csid); - } - Response response = null; if (csid == null || "".equals(csid)) { logger.error("invokeReport: missing csid!"); - response = Response.status(Response.Status.BAD_REQUEST).entity( + Response response = Response.status(Response.Status.BAD_REQUEST).entity( "invoke failed on Report csid=" + csid).type( "text/plain").build(); throw new WebApplicationException(response); } + if (logger.isTraceEnabled()) { + logger.trace("invokeReport with csid=" + csid); + } try { ServiceContext ctx = createServiceContext(); DocumentWrapper docWrapper = getRepositoryClient(ctx).getDoc(ctx, csid); DocumentModel docModel = docWrapper.getWrappedObject(); String reportFileName = (String)docModel.getPropertyValue(ReportJAXBSchema.FILENAME); - String fullPath = ServiceMain.getInstance().getServerRootDir() + - File.separator + ConfigReader.CSPACE_DIR_NAME + - File.separator + REPORTS_FOLDER + - File.separator + reportFileName; - Connection conn = getConnection(); HashMap params = new HashMap(); - FileInputStream fileStream = new FileInputStream(fullPath); - - // fill the report - JasperPrint jasperprint = JasperFillManager.fillReport(fileStream, params,conn); - // export report to pdf and build a response with the bytes - byte[] pdfasbytes = JasperExportManager.exportReportToPdf(jasperprint); - response = Response.ok(pdfasbytes, "application/pdf").build(); - } catch (UnauthorizedException ue) { - response = Response.status( - Response.Status.UNAUTHORIZED).entity("Invoke failed reason " + ue.getErrorReason()).type("text/plain").build(); - throw new WebApplicationException(response); - } catch (DocumentNotFoundException dnfe) { - if (logger.isDebugEnabled()) { - logger.debug("invokeReport", dnfe); - } - response = Response.status(Response.Status.NOT_FOUND).entity( - "Invoke failed on Report csid=" + csid).type( - "text/plain").build(); - throw new WebApplicationException(response); - } catch (SQLException sqle) { - // SQLExceptions can be chained. We have at least one exception, so - // set up a loop to make sure we let the user know about all of them - // if there happens to be more than one. - if (logger.isDebugEnabled()) { - SQLException tempException = sqle; - while (null != tempException) { - logger.debug("SQL Exception: " + sqle.getLocalizedMessage()); - - // loop to the next exception - tempException = tempException.getNextException(); - } - } - response = Response.status( - Response.Status.INTERNAL_SERVER_ERROR).entity( - "Invoke failed (SQL problem) on Report csid=" + csid).type("text/plain").build(); - throw new WebApplicationException(response); - } catch (JRException jre) { - if (logger.isDebugEnabled()) { - logger.debug("JR Exception: " + jre.getLocalizedMessage() + " Cause: "+jre.getCause()); - } - response = Response.status( - Response.Status.INTERNAL_SERVER_ERROR).entity( - "Invoke failed (Jasper problem) on Report csid=" + csid).type("text/plain").build(); - throw new WebApplicationException(response); + return buildReportResponse(csid, params, reportFileName); } catch (Exception e) { - if (logger.isDebugEnabled()) { - logger.debug("invokeReport", e); - } - response = Response.status( - Response.Status.INTERNAL_SERVER_ERROR).entity("Invoke failed").type("text/plain").build(); - throw new WebApplicationException(response); - } - if (response == null) { - response = Response.status(Response.Status.NOT_FOUND).entity( - "Invoke failed, the requested Report CSID:" + csid + ": was not found.").type( - "text/plain").build(); - throw new WebApplicationException(response); + throw bigReThrow(e, ServiceMessages.POST_FAILED); } - return response; } @POST @@ -256,26 +196,92 @@ public class ReportResource extends ResourceBase { +invocationMode); } String reportFileName = (String)docModel.getPropertyValue(ReportJAXBSchema.FILENAME); - String fullPath = ServiceMain.getInstance().getServerRootDir() + - File.separator + ConfigReader.CSPACE_DIR_NAME + - File.separator + REPORTS_FOLDER + - // File.separator + tenantName + - File.separator + reportFileName; - Connection conn = getConnection(); - - FileInputStream fileStream = new FileInputStream(fullPath); - // fill the report - JasperPrint jasperprint = JasperFillManager.fillReport(fileStream, params,conn); - // export report to pdf and build a response with the bytes - byte[] pdfasbytes = JasperExportManager.exportReportToPdf(jasperprint); - - // Need to set response type for what is requested... - Response response = Response.ok(pdfasbytes, "application/pdf").build(); - - return response; + return buildReportResponse(csid, params, reportFileName); + } catch (Exception e) { + throw bigReThrow(e, ServiceMessages.POST_FAILED); + } + } + + private Response buildReportResponse(String reportCSID, HashMap params, String reportFileName) { + Connection conn = null; + Response response = null; + try { + String fullPath = ServiceMain.getInstance().getServerRootDir() + + File.separator + ConfigReader.CSPACE_DIR_NAME + + File.separator + REPORTS_FOLDER + + // File.separator + tenantName + + File.separator + reportFileName; + conn = getConnection(); + + if (logger.isTraceEnabled()) { + logger.trace("ReportResource for Report csid=" + reportCSID + +" opening report file: "+fullPath); + } + FileInputStream fileStream = new FileInputStream(fullPath); + + // fill the report + JasperPrint jasperprint = JasperFillManager.fillReport(fileStream, params,conn); + // export report to pdf and build a response with the bytes + byte[] pdfasbytes = JasperExportManager.exportReportToPdf(jasperprint); + + // Need to set response type for what is requested... + response = Response.ok(pdfasbytes, "application/pdf").build(); + + return response; + } catch (SQLException sqle) { + // SQLExceptions can be chained. We have at least one exception, so + // set up a loop to make sure we let the user know about all of them + // if there happens to be more than one. + if (logger.isDebugEnabled()) { + SQLException tempException = sqle; + while (null != tempException) { + logger.debug("SQL Exception: " + sqle.getLocalizedMessage()); + + // loop to the next exception + tempException = tempException.getNextException(); + } + } + response = Response.status( + Response.Status.INTERNAL_SERVER_ERROR).entity( + "Invoke failed (SQL problem) on Report csid=" + reportCSID).type("text/plain").build(); + throw new WebApplicationException(response); + } catch (JRException jre) { + if (logger.isDebugEnabled()) { + logger.debug("JR Exception: " + jre.getLocalizedMessage() + " Cause: "+jre.getCause()); + } + response = Response.status( + Response.Status.INTERNAL_SERVER_ERROR).entity( + "Invoke failed (Jasper problem) on Report csid=" + reportCSID).type("text/plain").build(); + throw new WebApplicationException(response); + } catch (FileNotFoundException fnfe) { + if (logger.isDebugEnabled()) { + logger.debug("FileNotFoundException: " + fnfe.getLocalizedMessage()); + } + response = Response.status( + Response.Status.INTERNAL_SERVER_ERROR).entity( + "Invoke failed (SQL problem) on Report csid=" + reportCSID).type("text/plain").build(); + throw new WebApplicationException(response); } catch (Exception e) { throw bigReThrow(e, ServiceMessages.POST_FAILED); + } finally { + if(conn!=null) { + try { + conn.close(); + } catch (SQLException sqle) { + // SQLExceptions can be chained. We have at least one exception, so + // set up a loop to make sure we let the user know about all of them + // if there happens to be more than one. + if (logger.isDebugEnabled()) { + logger.debug("SQL Exception closing connection: " + + sqle.getLocalizedMessage()); + } + } catch (Exception e) { + if (logger.isDebugEnabled()) { + logger.debug("Exception closing connection", e); + } + } + } } } diff --git a/services/report/service/src/main/java/org/collectionspace/services/report/nuxeo/ReportConstants.java b/services/report/service/src/main/java/org/collectionspace/services/report/nuxeo/ReportConstants.java index bf22acb74..f8b3ee238 100644 --- a/services/report/service/src/main/java/org/collectionspace/services/report/nuxeo/ReportConstants.java +++ b/services/report/service/src/main/java/org/collectionspace/services/report/nuxeo/ReportConstants.java @@ -31,5 +31,6 @@ public class ReportConstants { public final static String NUXEO_DOCTYPE = "Report"; public final static String NUXEO_SCHEMA_NAME = "report"; + public final static String DB_COMMON_PART_TABLE_NAME = "reports_common"; public final static String NUXEO_DC_TITLE = "CollectionSpace-Report"; } diff --git a/services/report/service/src/main/java/org/collectionspace/services/report/nuxeo/ReportPostInitHandler.java b/services/report/service/src/main/java/org/collectionspace/services/report/nuxeo/ReportPostInitHandler.java new file mode 100644 index 000000000..9945742b9 --- /dev/null +++ b/services/report/service/src/main/java/org/collectionspace/services/report/nuxeo/ReportPostInitHandler.java @@ -0,0 +1,123 @@ +/** + * This document is a part of the source code and related artifacts + * for CollectionSpace, an open source collections management system + * for museums and related institutions: + + * http://www.collectionspace.org + * http://wiki.collectionspace.org + + * Copyright 2009 University of California at Berkeley + + * Licensed under the Educational Community License (ECL), Version 2.0. + * You may not use this file except in compliance with this License. + + * You may obtain a copy of the ECL 2.0 License at + + * https://source.collectionspace.org/collection-space/LICENSE.txt + */ +package org.collectionspace.services.report.nuxeo; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.List; + +import org.collectionspace.services.common.api.Tools; +import org.collectionspace.services.common.service.ServiceBindingType; +import org.collectionspace.services.common.init.IInitHandler; +import org.collectionspace.services.common.init.InitHandler; +import org.collectionspace.services.common.service.InitHandler.Params.Field; +import org.collectionspace.services.common.service.InitHandler.Params.Property; +import org.collectionspace.services.common.storage.DatabaseProductType; +import org.collectionspace.services.common.storage.JDBCTools; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * ReportPostInitHandler, post-init action to add grant reader access to DB + * + * In the configuration file, looks for a single Field declaration + * with a param value that has the name of the reader account/role. + * If not specified, it will assume 'reader'; + * + * $LastChangedRevision: 5103 $ + * $LastChangedDate: 2011-06-23 16:50:06 -0700 (Thu, 23 Jun 2011) $ + */ +public class ReportPostInitHandler extends InitHandler implements IInitHandler { + + final Logger logger = LoggerFactory.getLogger(ReportPostInitHandler.class); + public static final String READ_ROLE_NAME_KEY = "readerRoleName"; + private String readerRoleName = "reader"; + + /** See the class javadoc for this class: it shows the syntax supported in the configuration params. + */ + @Override + public void onRepositoryInitialized(ServiceBindingType sbt, List fields, + List properties) throws Exception { + //Check for existing privileges, and if not there, grant them + for(Property prop:properties) { + if(READ_ROLE_NAME_KEY.equals(prop.getKey())) { + String value = prop.getValue(); + if(Tools.notEmpty(value) && !readerRoleName.equals(value)){ + readerRoleName = value; + logger.debug("ReportPostInitHandler: overriding readerRoleName to use: " + + value); + } + } + } + Connection conn = null; + Statement stmt = null; + String sql = ""; + try { + DatabaseProductType databaseProductType = JDBCTools.getDatabaseProductType(); + if (databaseProductType == DatabaseProductType.MYSQL) { + // Nothing to do: MYSQL already does wildcard grants in init_db.sql + } else if(databaseProductType != DatabaseProductType.POSTGRESQL) { + throw new Exception("Unrecognized database system " + databaseProductType); + } else { + boolean hasRights = false; + // Check for rights on report_common, and infer rights from that + sql = "SELECT has_table_privilege('"+readerRoleName + +"', '"+ReportConstants.DB_COMMON_PART_TABLE_NAME+"', 'SELECT')"; + conn = JDBCTools.getConnection(JDBCTools.NUXEO_REPOSITORY_NAME); + stmt = conn.createStatement(); + ResultSet rs = stmt.executeQuery(sql); + if(rs.next()) { + hasRights = rs.getBoolean(1); + } + rs.close(); + if(!hasRights) { + sql = "REVOKE SELECT ON ALL TABLES IN SCHEMA public FROM "+readerRoleName; + stmt.execute(sql); + sql = "GRANT SELECT ON ALL TABLES IN SCHEMA public TO "+readerRoleName; + stmt.execute(sql); + } + } + + } catch (SQLException sqle) { + SQLException tempException = sqle; + while (null != tempException) { // SQLExceptions can be chained. Loop to log all. + logger.debug("SQL Exception: " + sqle.getLocalizedMessage()); + tempException = tempException.getNextException(); + } + logger.debug("ReportPostInitHandler: SQL problem in executeQuery: ", sqle); + } catch (Throwable e) { + logger.debug("ReportPostInitHandler: problem checking/adding grant for reader: "+readerRoleName+") SQL: "+sql+" ERROR: "+e); + } finally { + try { + if (conn != null) { + conn.close(); + } + if (stmt != null) { + stmt.close(); + } + } catch (SQLException sqle) { + logger.debug("SQL Exception closing statement/connection in executeQuery: " + sqle.getLocalizedMessage()); + } + } + } + + +}