From 531976376aff51248928d34c79cb28e504e991a3 Mon Sep 17 00:00:00 2001 From: remillet Date: Tue, 12 Jul 2016 12:43:14 -0700 Subject: [PATCH] CSPACE-6973: Reducing the number of workflow permissions to help speed up boot/restart times. --- .../jaxrs/CSpaceResteasyBootstrap.java | 5 +- ...CollectionSpaceServiceContextListener.java | 20 ++- .../services/common/ServiceMain.java | 131 ++++++++++++------ .../AuthorizationCommon.java | 53 ++++--- .../config/TenantBindingConfigReaderImpl.java | 9 ++ .../common/security/SecurityInterceptor.java | 20 ++- 6 files changed, 167 insertions(+), 71 deletions(-) diff --git a/services/JaxRsServiceProvider/src/main/java/org/collectionspace/services/jaxrs/CSpaceResteasyBootstrap.java b/services/JaxRsServiceProvider/src/main/java/org/collectionspace/services/jaxrs/CSpaceResteasyBootstrap.java index 48f79b626..6d32ab644 100644 --- a/services/JaxRsServiceProvider/src/main/java/org/collectionspace/services/jaxrs/CSpaceResteasyBootstrap.java +++ b/services/JaxRsServiceProvider/src/main/java/org/collectionspace/services/jaxrs/CSpaceResteasyBootstrap.java @@ -5,6 +5,7 @@ import javax.servlet.ServletContextEvent; import org.jboss.resteasy.core.Dispatcher; import org.jboss.resteasy.plugins.server.servlet.ResteasyBootstrap; import org.collectionspace.services.common.ResourceMap; +import java.util.Date; public class CSpaceResteasyBootstrap extends ResteasyBootstrap { @@ -14,13 +15,13 @@ public class CSpaceResteasyBootstrap extends ResteasyBootstrap { // This call to super instantiates and initializes our JAX-RS application class. // The application class is org.collectionspace.services.jaxrs.CollectionSpaceJaxRsApplication. // - System.out.println("[INFO] Starting up the CollectionSpace Services' JAX-RS application."); + System.out.println(String.format("%tc [INFO] Starting up the CollectionSpace Services' JAX-RS application.", new Date())); super.contextInitialized(event); CollectionSpaceJaxRsApplication app = (CollectionSpaceJaxRsApplication)deployment.getApplication(); Dispatcher disp = deployment.getDispatcher(); disp.getDefaultContextObjects().put(ResourceMap.class, app.getResourceMap()); - System.out.println("[INFO] CollectionSpace Services' JAX-RS application started."); + System.out.println(String.format("%tc [INFO] CollectionSpace Services' JAX-RS application started.", new Date())); } catch (Throwable e) { e.printStackTrace(); } diff --git a/services/common/src/main/java/org/collectionspace/services/common/CollectionSpaceServiceContextListener.java b/services/common/src/main/java/org/collectionspace/services/common/CollectionSpaceServiceContextListener.java index 794aa11e4..07deb97a1 100644 --- a/services/common/src/main/java/org/collectionspace/services/common/CollectionSpaceServiceContextListener.java +++ b/services/common/src/main/java/org/collectionspace/services/common/CollectionSpaceServiceContextListener.java @@ -38,14 +38,28 @@ public class CollectionSpaceServiceContextListener implements ServletContextList } catch (Throwable e) { e.printStackTrace(); //fail here + System.err.println("[ERROR] ***"); System.err.println("[ERROR] The CollectionSpace Services could not initialize. Please see the log files for details."); - throw new RuntimeException(e); + System.err.println("[ERROR] ***"); +// throw new RuntimeException(e); } } @Override public void contextDestroyed(ServletContextEvent event) { - //ServiceMain.getInstance().release(); - JpaStorageUtils.releaseEntityManagerFactories(); + ServiceMain instance = null; + + try { + ServiceMain.getInstance(); + } catch (Throwable t) { + // Do nothing. Error already logged by the Services layer + } finally { + if (instance != null) { + instance.release(); + } else { + System.err.println("ERROR: The CollectionSpace Services layer failed to startup successfully. Look in the tomcat logs and cspace-services logs for details."); + } + JpaStorageUtils.releaseEntityManagerFactories(); + } } } diff --git a/services/common/src/main/java/org/collectionspace/services/common/ServiceMain.java b/services/common/src/main/java/org/collectionspace/services/common/ServiceMain.java index 6100e1cee..cbf820b6e 100644 --- a/services/common/src/main/java/org/collectionspace/services/common/ServiceMain.java +++ b/services/common/src/main/java/org/collectionspace/services/common/ServiceMain.java @@ -42,6 +42,7 @@ import org.collectionspace.services.config.types.PropertyItemType; import org.collectionspace.services.config.types.PropertyType; import org.collectionspace.services.nuxeo.client.java.NuxeoConnectorEmbedded; import org.collectionspace.services.nuxeo.client.java.TenantRepository; + import org.apache.commons.io.FileUtils; import org.jboss.resteasy.spi.ResteasyProviderFactory; import org.dom4j.Document; @@ -55,7 +56,19 @@ import org.slf4j.LoggerFactory; */ public class ServiceMain { - final Logger logger = LoggerFactory.getLogger(ServiceMain.class); + final static Logger logger = LoggerFactory.getLogger(ServiceMain.class); + + /** + * For some reason, we have trouble getting logging from this class directed to + * the tomcat/catalina console log file. So we do it explicitly with this method. + * + * @param str + */ + private static void mirrorToStdOut(String str) { + System.out.println(str); + ServiceMain.logger.info(str); + } + /** * volatile is used here to assume about ordering (post JDK 1.5) */ @@ -179,21 +192,24 @@ public class ServiceMain { // if (getClientType().equals(ClientType.JAVA)) { nuxeoConnector = NuxeoConnectorEmbedded.getInstance(); + mirrorToStdOut("\nStarting Nuxeo platform..."); nuxeoConnector.initialize( getServerRootDir(), getServicesConfigReader().getConfiguration().getRepositoryClient(), ServiceMain.servletContext); + mirrorToStdOut("Nuxeo platform started successfully.\n"); } else { // // Exit if we don't have the correct/known client type // throw new RuntimeException("Unknown CollectionSpace services client type: " + getClientType()); } + // // Create all the default user accounts and permissions. Since some of our "cspace" database config files // for Spring need to be created at build time, the "cspace" database already will be suffixed with the // correct 'cspaceInstanceId' so we don't need to pass it to the JDBCTools methods. - // + // try { AuthorizationCommon.createDefaultWorkflowPermissions(tenantBindingConfigReader); String cspaceDatabaseName = getCspaceDatabaseName(); @@ -215,8 +231,42 @@ public class ServiceMain { logger.error("handlePostNuxeoInitDBTasks failed with exception(s): " + e.getLocalizedMessage(), e); } */ + showTenantStatus(); } - + + private void showTenantStatus() { + Hashtable tenantBindingsList = tenantBindingConfigReader.getTenantBindings(true); + mirrorToStdOut("++++++++++++++++ Summary - CollectionSpace tenant status. ++++++++++++++++++++++++"); + String headerTemplate = "%10s %10s %30s %60s %10s"; + String headerUnderscore = String.format(headerTemplate, + "______", + "__", + "____", + "____________", + "_______"); + String header = String.format(headerTemplate, + "Status", + "ID", + "Name", + "Display Name", + "Version"); + mirrorToStdOut(header); + mirrorToStdOut(headerUnderscore); + + for (String tenantId : tenantBindingsList.keySet()) { + TenantBindingType tenantBinding = tenantBindingsList.get(tenantId); + String statusLine = String.format(headerTemplate, + tenantBinding.isCreateDisabled() ? "Disabled" : "Active", + tenantBinding.getId(), + tenantBinding.getName(), + tenantBinding.getDisplayName(), + tenantBinding.getVersion()); + mirrorToStdOut(statusLine); + } + // footer + mirrorToStdOut("++++++++++++++++ ........................................ ++++++++++++++++++++++++"); + } + /** * release releases all resources occupied by service layer infrastructure * but not necessarily those occupied by individual services @@ -338,8 +388,8 @@ public class ServiceMain { if (Tools.isEmpty(initHandlerClassname)) { continue; } - if (logger.isInfoEnabled()) { - logger.info(String.format("Firing post-init handler %s ...", initHandlerClassname)); + if (ServiceMain.logger.isDebugEnabled()) { + ServiceMain.logger.debug(String.format("Firing post-init handler %s ...", initHandlerClassname)); } List @@ -508,41 +558,42 @@ public class ServiceMain { // First check and create the roles as needed. (nuxeo and reader) for (TenantBindingType tenantBinding : tenantBindings.values()) { - String tId = tenantBinding.getId(); - String tName = tenantBinding.getName(); - List repoDomainList = tenantBinding.getRepositoryDomain(); - for (RepositoryDomainType repoDomain : repoDomainList) { - String repoDomainName = repoDomain.getName(); - String repositoryName = repoDomain.getRepositoryName(); - String cspaceInstanceId = getCspaceInstanceId(); - String dbName = JDBCTools.getDatabaseName(repositoryName, cspaceInstanceId); - if (nuxeoDBsChecked.contains(dbName)) { - if (logger.isDebugEnabled()) { - logger.debug("Another user of db: " + dbName + ": Repo: " + repoDomainName - + " and tenant: " + tName + " (id:" + tId + ")"); - } - } else { - if (logger.isDebugEnabled()) { - logger.debug("Need to prepare db: " + dbName + " for Repo: " + repoDomainName - + " and tenant: " + tName + " (id:" + tId + ")"); - } - boolean dbExists = JDBCTools.hasDatabase(dbType, dbName); - if (dbExists) { - if (logger.isDebugEnabled()) { - logger.debug("Database: " + dbName + " already exists."); - } - } else { - // Create the user as needed - JDBCTools.createNewDatabaseUser(JDBCTools.CSADMIN_DATASOURCE_NAME, repositoryName, cspaceInstanceId, dbType, nuxeoUser, nuxeoPW); - if (readerUser != null) { - JDBCTools.createNewDatabaseUser(JDBCTools.CSADMIN_DATASOURCE_NAME, repositoryName, cspaceInstanceId, dbType, readerUser, readerPW); - } - // Create the database - createDatabaseWithRights(dbType, dbName, nuxeoUser, nuxeoPW, readerUser, readerPW); - } - nuxeoDBsChecked.add(dbName); + String tId = tenantBinding.getId(); + String tName = tenantBinding.getName(); + + List repoDomainList = tenantBinding.getRepositoryDomain(); + for (RepositoryDomainType repoDomain : repoDomainList) { + String repoDomainName = repoDomain.getName(); + String repositoryName = repoDomain.getRepositoryName(); + String cspaceInstanceId = getCspaceInstanceId(); + String dbName = JDBCTools.getDatabaseName(repositoryName, cspaceInstanceId); + if (nuxeoDBsChecked.contains(dbName)) { + if (logger.isDebugEnabled()) { + logger.debug("Another user of db: " + dbName + ": Repo: " + repoDomainName + + " and tenant: " + tName + " (id:" + tId + ")"); + } + } else { + if (logger.isDebugEnabled()) { + logger.debug("Need to prepare db: " + dbName + " for Repo: " + repoDomainName + + " and tenant: " + tName + " (id:" + tId + ")"); + } + boolean dbExists = JDBCTools.hasDatabase(dbType, dbName); + if (dbExists) { + if (logger.isDebugEnabled()) { + logger.debug("Database: " + dbName + " already exists."); + } + } else { + // Create the user as needed + JDBCTools.createNewDatabaseUser(JDBCTools.CSADMIN_DATASOURCE_NAME, repositoryName, cspaceInstanceId, dbType, nuxeoUser, nuxeoPW); + if (readerUser != null) { + JDBCTools.createNewDatabaseUser(JDBCTools.CSADMIN_DATASOURCE_NAME, repositoryName, cspaceInstanceId, dbType, readerUser, readerPW); } - } // Loop on repos for tenant + // Create the database + createDatabaseWithRights(dbType, dbName, nuxeoUser, nuxeoPW, readerUser, readerPW); + } + nuxeoDBsChecked.add(dbName); + } + } // Loop on repos for tenant } // Loop on tenants return nuxeoDBsChecked; @@ -721,7 +772,7 @@ public class ServiceMain { serverRootDir = "."; //assume server is started from server root, e.g. server/cspace String msg = String.format("System property '%s' was not set. Using '%s' instead.", SERVER_HOME_PROPERTY, serverRootDir); - logger.warn(msg); + mirrorToStdOut(msg); } } diff --git a/services/common/src/main/java/org/collectionspace/services/common/authorization_mgt/AuthorizationCommon.java b/services/common/src/main/java/org/collectionspace/services/common/authorization_mgt/AuthorizationCommon.java index e7e99b425..01c03ceb5 100644 --- a/services/common/src/main/java/org/collectionspace/services/common/authorization_mgt/AuthorizationCommon.java +++ b/services/common/src/main/java/org/collectionspace/services/common/authorization_mgt/AuthorizationCommon.java @@ -8,6 +8,7 @@ import java.sql.Statement; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; +import java.util.HashSet; import java.util.Hashtable; import java.util.List; import java.util.UUID; @@ -31,7 +32,6 @@ import org.collectionspace.services.authorization.perms.ActionType; import org.collectionspace.services.authorization.perms.EffectType; import org.collectionspace.services.authorization.perms.Permission; import org.collectionspace.services.authorization.perms.PermissionAction; - import org.collectionspace.services.client.Profiler; import org.collectionspace.services.client.RoleClient; import org.collectionspace.services.client.workflow.WorkflowClient; @@ -45,7 +45,6 @@ import org.collectionspace.services.common.storage.JDBCTools; import org.collectionspace.services.common.storage.jpa.JpaStorageUtils; import org.collectionspace.services.config.service.ServiceBindingType; import org.collectionspace.services.config.tenant.TenantBindingType; - import org.collectionspace.services.lifecycle.Lifecycle; import org.collectionspace.services.lifecycle.TransitionDef; import org.collectionspace.services.lifecycle.TransitionDefList; @@ -53,7 +52,6 @@ import org.collectionspace.services.lifecycle.TransitionDefList; //import org.mortbay.log.Log; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - import org.springframework.security.acls.model.AlreadyExistsException; @@ -78,6 +76,10 @@ public class AuthorizationCommon { public class ActionGroup { String name; ActionType[] actions; + + public String getName() { + return name; + } } static ActionGroup ACTIONGROUP_CRUDL; @@ -303,14 +305,14 @@ public class AuthorizationCommon { private static Permission createWorkflowPermission(TenantBindingType tenantBinding, ServiceBindingType serviceBinding, - TransitionDef transitionDef, + String transitionVerb, ActionGroup actionGroup) { Permission result = null; String workFlowServiceSuffix; String transitionName; - if (transitionDef != null) { - transitionName = transitionDef.getName(); + if (transitionVerb != null) { + transitionName = transitionVerb; workFlowServiceSuffix = WorkflowClient.SERVICE_AUTHZ_SUFFIX; } else { transitionName = ""; //since the transitionDef was null, we're assuming that this is the base workflow permission to be created @@ -1023,6 +1025,19 @@ public class AuthorizationCommon { return pa; } + private static HashSet getTransitionVerbList(TenantBindingType tenantBinding, ServiceBindingType serviceBinding) { + HashSet result = new HashSet(); + + TransitionDefList transitionDefList = getTransitionDefList(tenantBinding, serviceBinding); + for (TransitionDef transitionDef : transitionDefList.getTransitionDef()) { + String transitionVerb = transitionDef.getName(); + String[] tokens = transitionVerb.split("_"); // Split the verb into words. The workflow verbs are compound words combined with the '_' character. + result.add(tokens[0]); // We only care about the first word. + } + + return result; + } + private static TransitionDefList getTransitionDefList(TenantBindingType tenantBinding, ServiceBindingType serviceBinding) { TransitionDefList result = null; try { @@ -1069,6 +1084,7 @@ public class AuthorizationCommon { Hashtable tenantBindings = tenantBindingConfigReader.getTenantBindings(); for (String tenantId : tenantBindings.keySet()) { + logger.info(String.format("Creating/verifying workflow permissions for tenant ID=%s.", tenantId)); TenantBindingType tenantBinding = tenantBindings.get(tenantId); Role adminRole = AuthorizationCommon.getRole(em, tenantBinding.getId(), ROLE_TENANT_ADMINISTRATOR); Role readonlyRole = AuthorizationCommon.getRole(em, tenantBinding.getId(), ROLE_TENANT_READER); @@ -1078,25 +1094,16 @@ public class AuthorizationCommon { try { em.getTransaction().begin(); TransitionDefList transitionDefList = getTransitionDefList(tenantBinding, serviceBinding); - for (TransitionDef transitionDef : transitionDefList.getTransitionDef()) { + HashSet transitionVerbList = getTransitionVerbList(tenantBinding, serviceBinding); + for (String transitionVerb : transitionVerbList) { // // Create the permission for the admin role - Permission adminPerm = createWorkflowPermission(tenantBinding, serviceBinding, transitionDef, ACTIONGROUP_CRUDL); - persist(em, adminPerm, adminRole, true); + Permission adminPerm = createWorkflowPermission(tenantBinding, serviceBinding, transitionVerb, ACTIONGROUP_CRUDL); + persist(em, adminPerm, adminRole, true, ACTIONGROUP_CRUDL); // // Create the permission for the read-only role - Permission readonlyPerm = createWorkflowPermission(tenantBinding, serviceBinding, transitionDef, ACTIONGROUP_RL); - - Profiler profiler = new Profiler(AuthorizationCommon.class, 1); - profiler.start("createDefaultPermissions started:" + readonlyPerm.getCsid()); - persist(em, readonlyPerm, readonlyRole, true); // Persist/store the permission and permrole records and related Spring Security info - profiler.stop(); - logger.debug("Finished full perm generation for " - + ":" + tenantBinding.getId() - + ":" + serviceBinding.getName() - + ":" + transitionDef.getName() - + ":" + ACTIONGROUP_RL - + ":" + profiler.getCumulativeTime()); + Permission readonlyPerm = createWorkflowPermission(tenantBinding, serviceBinding, transitionVerb, ACTIONGROUP_RL); + persist(em, readonlyPerm, readonlyRole, true, ACTIONGROUP_RL); // Persist/store the permission and permrole records and related Spring Security info } em.getTransaction().commit(); } catch (IllegalStateException e) { @@ -1145,7 +1152,7 @@ public class AuthorizationCommon { /* * Persists the Permission, PermissionRoleRel, and Spring Security table entries all in one transaction */ - private static void persist(EntityManager em, Permission permission, Role role, boolean enforceTenancy) throws Exception { + private static void persist(EntityManager em, Permission permission, Role role, boolean enforceTenancy, ActionGroup actionGroup) throws Exception { AuthorizationStore authzStore = new AuthorizationStore(); // First persist the Permission record authzStore.store(em, permission); @@ -1168,7 +1175,7 @@ public class AuthorizationCommon { logger.debug("Finished full perm generation for " + ":" + permission.getTenantId() + ":" + permission.getResourceName() - + ":" + ACTIONGROUP_RL + + ":" + actionGroup.getName() + ":" + profiler.getCumulativeTime()); } diff --git a/services/common/src/main/java/org/collectionspace/services/common/config/TenantBindingConfigReaderImpl.java b/services/common/src/main/java/org/collectionspace/services/common/config/TenantBindingConfigReaderImpl.java index 48904f4cf..c60f8ae40 100644 --- a/services/common/src/main/java/org/collectionspace/services/common/config/TenantBindingConfigReaderImpl.java +++ b/services/common/src/main/java/org/collectionspace/services/common/config/TenantBindingConfigReaderImpl.java @@ -200,6 +200,8 @@ public class TenantBindingConfigReaderImpl extends AbstractConfigReaderImpl