]> git.aero2k.de Git - tmp/jakarta-migration.git/commitdiff
DRYD-220: Normalize AuthN/AuthZ objects to have UTC times in out going payloads.
authorremillet <remillet@yahoo.com>
Thu, 21 Dec 2017 23:04:15 +0000 (15:04 -0800)
committerremillet <remillet@yahoo.com>
Thu, 21 Dec 2017 23:04:15 +0000 (15:04 -0800)
14 files changed:
services/JaxRsServiceProvider/src/main/java/org/collectionspace/services/aspect/HyperJaxb3TimezoneAspect.java [deleted file]
services/JaxRsServiceProvider/src/main/java/org/collectionspace/services/aspect/JaxbXMLGregorianCalendarMarshal.java [new file with mode: 0644]
services/JaxRsServiceProvider/src/main/java/org/collectionspace/services/jaxrs/CSpaceResteasyBootstrap.java
services/JaxRsServiceProvider/src/main/resources/META-INF/aop.xml
services/account/jaxb/src/main/resources/accounts_common.xsd
services/account/pstore/src/main/resources/db/postgresql/account.sql
services/authorization-mgt/import/pom.xml
services/common-api/src/main/java/org/collectionspace/services/common/api/GregorianCalendarDateTimeUtils.java
services/common/src/main/java/org/collectionspace/services/common/ServiceMain.java
services/common/src/main/java/org/collectionspace/services/common/authorization_mgt/AuthorizationCommon.java
services/common/src/main/java/org/collectionspace/services/common/config/ServiceConfigUtils.java
services/common/src/main/java/org/collectionspace/services/common/config/TenantBindingConfigReaderImpl.java
services/common/src/main/java/org/collectionspace/services/common/storage/jpa/JpaStorageClientImpl.java
services/config/src/main/resources/tenant.xsd

diff --git a/services/JaxRsServiceProvider/src/main/java/org/collectionspace/services/aspect/HyperJaxb3TimezoneAspect.java b/services/JaxRsServiceProvider/src/main/java/org/collectionspace/services/aspect/HyperJaxb3TimezoneAspect.java
deleted file mode 100644 (file)
index 34e2bf7..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-/**
- * An AOP (AspectJ) aspect to resolve the timezone related issue https://issues.collectionspace.org/browse/DRYD-182.
- * 
- * See related config in files: src/main/resources/META-INF/aop.xml, src/main/webapp/WEB-INF/applicationContext-security.xml
- * 
- */
-package org.collectionspace.services.aspect;
-
-import java.util.Date;
-import javax.xml.datatype.XMLGregorianCalendar;
-
-import org.aspectj.lang.ProceedingJoinPoint;
-import org.aspectj.lang.annotation.Aspect;
-import org.aspectj.lang.annotation.Around;
-import org.aspectj.lang.annotation.Pointcut;
-
-@Aspect
-public class HyperJaxb3TimezoneAspect {
-
-       @Around("methodsToBeProfiled()")
-    public Object profile(ProceedingJoinPoint pjp) throws Throwable {
-        try {
-               Date fromDate = (Date)pjp.getArgs()[0];
-               XMLGregorianCalendar toDate = (XMLGregorianCalendar)pjp.getArgs()[1];
-                        
-            Object result = pjp.proceed();
-            //
-            // Marshal the timezone info from the 'fromDate' Date instance into the XMLGregorianCalendar 'toDate' instance
-            //
-            toDate.setTimezone(fromDate.getTimezoneOffset());
-
-            return result;
-        } finally {
-            // No cleanup needed.
-        }
-    }
-
-    /**
-     * Intercept all calls to the createCalendar() method of the XMLGregorianCalendarAsDateTime class.  This is how HyperJaxb3 marshals datetime info from Hibernate/JPA into
-     * out AuthN/AuthZ class instances.
-     */
-    @Pointcut("execution(* org.jvnet.hyperjaxb3.xml.bind.annotation.adapters.XMLGregorianCalendarAsDateTime.createCalendar(java.util.Date, javax.xml.datatype.XMLGregorianCalendar))")
-    public void methodsToBeProfiled() {}
-}
\ No newline at end of file
diff --git a/services/JaxRsServiceProvider/src/main/java/org/collectionspace/services/aspect/JaxbXMLGregorianCalendarMarshal.java b/services/JaxRsServiceProvider/src/main/java/org/collectionspace/services/aspect/JaxbXMLGregorianCalendarMarshal.java
new file mode 100644 (file)
index 0000000..21c3346
--- /dev/null
@@ -0,0 +1,62 @@
+package org.collectionspace.services.aspect;
+
+import java.util.Date;
+
+import javax.xml.datatype.XMLGregorianCalendar;
+
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Pointcut;
+
+/**
+ * @author remillet
+ * 
+ * This method intercepts the all calls to the the setCreatedAt() method of the AuthN/AuthZ JaxB classes.  These classes
+ * are all derived from XML Schema by the HyperJaxB3 Maven build plugin, so we couldn't do this in classes themselved.
+ * 
+ * This method sets the timezone of the incoming XMLGregorianCalendar instance to the current JVM timezone and then
+ * normalized the instance to UTC time.  Doing this results in the correct marshaling of the instance into the convention
+ * used by the other CollectionSpace services.
+ *
+ */
+
+@Aspect
+public class JaxbXMLGregorianCalendarMarshal {
+
+       @Around("setDateMethods()")
+    public Object profile(ProceedingJoinPoint pjp) throws Throwable {
+               Object result = null;
+               
+        try {
+               Object[] args = pjp.getArgs(); // gets us a read-only copy of the argument(s)
+               XMLGregorianCalendar toDate = (XMLGregorianCalendar)args[0]; // get the incoming date argument
+               if (toDate != null) {
+                   toDate.setTimezone(new Date().getTimezoneOffset() * -1);    // set the incoming date's timezone to the current JVM timezone
+                   toDate = toDate.normalize(); // normalize to UTC time
+                   args[0] = toDate; // setup the new arguments
+                   result = pjp.proceed(args); // finish the call
+               } else {
+                   result = pjp.proceed();     // finish the call
+               }
+        } finally {
+            // No cleanup needed.
+        }
+        
+        return result;
+    }
+
+       /**
+        * Setup a pointcut for all CSpace classes with methods like setCreatedAt(javax.xml.datatype.XMLGregorianCalendar) and
+        * setUpdatedAt(javax.xml.datatype.XMLGregorianCalendar)
+        */
+    @Pointcut("execution(void org.collectionspace.services..setCreatedAt(javax.xml.datatype.XMLGregorianCalendar))")
+    public void setCreatedAtCutPoint() {}
+    
+    @Pointcut("execution(void org.collectionspace.services..setUpdatedAt(javax.xml.datatype.XMLGregorianCalendar))")
+    public void setUpdateatedAtCutPoint() {}
+    
+    @Pointcut("setCreatedAtCutPoint() || setUpdateatedAtCutPoint()")
+    public void setDateMethods() {}
+
+}
index 1f6e319d3b0b753edbdc864d30eabc034d9138ed..8147eefec5997f8398fc914e977c34d4e27cfa4b 100644 (file)
@@ -11,12 +11,14 @@ import org.collectionspace.services.account.Tenant;
 import org.collectionspace.services.account.TenantResource;
 import org.collectionspace.services.authorization.AuthZ;
 import org.collectionspace.services.client.AuthorityClient;
+
 import org.collectionspace.services.common.CSWebApplicationException;
 import org.collectionspace.services.common.ResourceMap;
 import org.collectionspace.services.common.ServiceMain;
 import org.collectionspace.services.common.api.RefName;
 import org.collectionspace.services.common.config.TenantBindingConfigReaderImpl;
 import org.collectionspace.services.common.vocabulary.AuthorityResource;
+
 import org.collectionspace.services.config.service.AuthorityInstanceType;
 import org.collectionspace.services.config.service.ServiceBindingType;
 import org.collectionspace.services.config.service.ServiceBindingType.AuthorityInstanceList;
@@ -34,6 +36,7 @@ public class CSpaceResteasyBootstrap extends ResteasyBootstrap {
        
        java.util.logging.Logger logger = java.util.logging.Logger.getAnonymousLogger();
        static final String RESET_AUTHORITIES_PROPERTY = "org.collectionspace.services.authorities.reset";
+       private static final String QUICK_BOOT_PROPERTY = "org.collectionspace.services.quickboot";
        
        @Override
        public void  contextInitialized(ServletContextEvent event) {
@@ -48,8 +51,11 @@ public class CSpaceResteasyBootstrap extends ResteasyBootstrap {
                        Dispatcher disp = deployment.getDispatcher();
                        disp.getDefaultContextObjects().put(ResourceMap.class, app.getResourceMap());
                        
-                       String resetAuthsString = System.getProperty(RESET_AUTHORITIES_PROPERTY, Boolean.FALSE.toString()); // Property can be set in the tomcat/bin/setenv.sh (or setenv.bat) file
-                       initializeAuthorities(app.getResourceMap(), Boolean.valueOf(resetAuthsString));
+                       String quickBoot = System.getProperty(QUICK_BOOT_PROPERTY, Boolean.FALSE.toString()); // Property can be set in the tomcat/bin/setenv.sh (or setenv.bat) file
+                       if (Boolean.valueOf(quickBoot) == false) {
+                               String resetAuthsString = System.getProperty(RESET_AUTHORITIES_PROPERTY, Boolean.FALSE.toString()); // Property can be set in the tomcat/bin/setenv.sh (or setenv.bat) file
+                               initializeAuthorities(app.getResourceMap(), Boolean.valueOf(resetAuthsString));
+                       }
                        
                        logger.log(Level.INFO, String.format("%tc [INFO] CollectionSpace Services' JAX-RS application started.", new Date()));
                } catch (Exception e) {
index c1069b69802df4c61c69c613ffd05a0869e7ae01..2ef53fb0cd010286b1f3e2626b97487f9c7b06f0 100644 (file)
@@ -3,12 +3,12 @@
 <aspectj>
     <weaver>
         <!-- only weave classes in our application-specific packages -->
-        <include within="org.jvnet.hyperjaxb3.xml.bind.annotation.adapters.*"/> 
+        <include within="org.collectionspace.services..*"/>
         <include within="org.collectionspace.services.aspect.*"/>
     </weaver>
     
     <aspects>        
-        <!-- weave in just this aspect -->        
-        <aspect name="org.collectionspace.services.aspect.HyperJaxb3TimezoneAspect"/>
+        <!-- weave in just this aspect --> 
+        <aspect name="org.collectionspace.services.aspect.JaxbXMLGregorianCalendarMarshal"/>
     </aspects>
 </aspectj>
\ No newline at end of file
index e4c5daa5eae67455107750670aaaf121b450858d..cdd208f3a96945cbb0a9a9e0ed1e050321cd0700 100644 (file)
                                                </xs:appinfo>
                                        </xs:annotation>
                                </xs:element>
+                               <xs:element name="configMD5Hash" type="xs:string">
+                                       <xs:annotation>
+                                               <xs:appinfo>
+                                                       <hj:basic>
+                                                               <orm:column name="config_md5hash"/>
+                                                       </hj:basic>
+                                               </xs:appinfo>
+                                       </xs:annotation>
+                               </xs:element>                           
                                <xs:element name="authoritiesInitialized" type="xs:boolean">
                                        <xs:annotation>
                                                <xs:appinfo>
index 7f1564e81819c60e2fcbc64c2020fbc0a086d5a8..74f75df2385427e2afa5b023c2af392bc7cc28fc 100644 (file)
@@ -5,6 +5,6 @@ DROP TABLE IF EXISTS tenants CASCADE;
 DROP SEQUENCE IF EXISTS hibernate_sequence;
 create table accounts_common (csid varchar(128) not null, created_at timestamp not null, email varchar(255) not null, mobile varchar(255), person_ref_name varchar(255), phone varchar(255), screen_name varchar(128) not null, status varchar(15) not null, updated_at timestamp, userid varchar(128) not null, metadata_protection varchar(255), roles_protection varchar(255), primary key (csid));
 create table accounts_tenants (HJID int8 not null, tenant_id varchar(128) not null, TENANTS_ACCOUNTS_COMMON_CSID varchar(128), primary key (HJID));
-create table tenants (id varchar(128) not null, created_at timestamp not null, name varchar(255) not null, authorities_initialized boolean not null, disabled boolean not null, updated_at timestamp, primary key (id));
+create table tenants (id varchar(128) not null, created_at timestamp not null, name varchar(255) not null, config_md5hash varchar(255), authorities_initialized boolean not null, disabled boolean not null, updated_at timestamp, primary key (id));
 alter table accounts_tenants add constraint FKFDA649B05A9CEEB5 foreign key (TENANTS_ACCOUNTS_COMMON_CSID) references accounts_common;
 create sequence hibernate_sequence;
index 77b43e9b6b8614f50604ba2cd0d1ffa32cba8742..d31186c2ef5e29bb9666f6199d4d4803f4fe82a4 100644 (file)
                        <groupId>org.collectionspace.services</groupId>
                        <artifactId>org.collectionspace.services.account.client</artifactId>
                        <version>${project.version}</version>
+                       <exclusions>
+                               <exclusion>
+                                       <groupId>commons-codec</groupId>
+                                       <artifactId>commons-codec</artifactId>
+                               </exclusion>
+                       </exclusions>
                </dependency>
                <dependency>
                        <groupId>org.collectionspace.services</groupId>
                        <version>${project.version}</version>
                </dependency>
                <!-- 3rd Pary dependencies -->
+               <dependency>
+                       <groupId>commons-codec</groupId>
+                       <artifactId>commons-codec</artifactId>
+                       <version>1.4</version>
+               </dependency>
                <dependency>
                        <groupId>org.testng</groupId>
                        <artifactId>testng</artifactId>
                        <groupId>ch.elca.el4j.modules</groupId>
                        <artifactId>module-xml_merge-common</artifactId>
                        <version>3.1</version>
+                       <exclusions>
+                               <exclusion>
+                                       <groupId>commons-codec</groupId>
+                                       <artifactId>commons-codec</artifactId>
+                               </exclusion>
+                       </exclusions>
                </dependency>
                <dependency>
                        <groupId>org.collectionspace.services</groupId>
                                        <artifactId>nuxeo-core-storage-sql</artifactId>
                                        <groupId>org.nuxeo.ecm.core</groupId>
                                </exclusion>
+                               <exclusion>
+                                       <groupId>commons-codec</groupId>
+                                       <artifactId>commons-codec</artifactId>
+                               </exclusion>
                        </exclusions>
                </dependency>
        </dependencies>
index 5ebca7b5ddeabcaf9abec4a809e987bd0d3e1524..8198d43009748e892cfbbbcf707b9e5a66ce2f17 100644 (file)
@@ -22,6 +22,9 @@ import java.util.Date;
 import java.util.GregorianCalendar;
 import java.util.TimeZone;
 
+import javax.xml.datatype.DatatypeConfigurationException;
+import javax.xml.datatype.XMLGregorianCalendar;
+
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -66,6 +69,10 @@ public class GregorianCalendarDateTimeUtils {
     public static GregorianCalendar currentDateAndTimeUTC() {
         return currentDateAndTime(DateUtils.UTCTimeZone());
     }
+    
+    public static XMLGregorianCalendar currentXMLGregorianCalendarUTC() throws DatatypeConfigurationException {
+       return javax.xml.datatype.DatatypeFactory.newInstance().newXMLGregorianCalendar(currentDateAndTimeUTC());
+    }
 
    /**
     * Returns a calendar date, representing the current date and time instance
index ccb118ef9364bd17342393be727012b3912cd0cc..263e01774c64e4474890a6adbf42f05ebedf0460 100644 (file)
@@ -171,12 +171,12 @@ public class ServiceMain {
        // set our root directory
        setServerRootDir();
        
-       // read in and set our Services config
+               // read in and set our Services config
        readAndSetServicesConfig();
-       
+
        // Set our AuthN's datasource to for the cspaceDataSource
        AuthN.setDataSource(JDBCTools.getDataSource(JDBCTools.CSPACE_DATASOURCE_NAME));
-       
+               
        // In each tenant, set properties that don't already have values
        // to their default values.
         propagateConfiguredProperties();
@@ -219,39 +219,42 @@ public class ServiceMain {
         //
         //
         initializeEventListeners();        
-                
+        
+        //
+        // Mark if a tenant's bindings have changed since the last time we started, by comparing the MD5 hash of each tenant's bindings with that of
+        // the bindings the last time we started/launch.
         //
-        // Create all the default user accounts and permissions.  Since some of our "cspace" database config files
+        String cspaceDatabaseName = getCspaceDatabaseName();
+        Hashtable<String, TenantBindingType> tenantBindings = tenantBindingConfigReader.getTenantBindings();
+        for (String tenantId : tenantBindings.keySet()) {
+               TenantBindingType tenantBinding = tenantBindings.get(tenantId);
+               String persistedMD5Hash = AuthorizationCommon.getPersistedMD5Hash(tenantId, cspaceDatabaseName);
+               String currentMD5Hash = tenantBinding.getConfigMD5Hash();
+               AuthorizationCommon.setTenantConfigMD5Hash(tenantId, currentMD5Hash); // store this for later.  We'll persist this info with the tenant record.
+               tenantBinding.setConfigChangedSinceLastStart(hasConfigChanged(tenantBinding, persistedMD5Hash, currentMD5Hash));
+        }
+        
+        //
+        // Create all the tenant records, default user accounts, roles, 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();
                        DatabaseProductType databaseProductType = JDBCTools.getDatabaseProductType(JDBCTools.CSPACE_DATASOURCE_NAME,
-                                       cspaceDatabaseName);
-                       AuthorizationCommon.createDefaultAccounts(tenantBindingConfigReader, databaseProductType,
-                                       cspaceDatabaseName);
+                                       cspaceDatabaseName);            
+
+                       AuthorizationCommon.createTenants(tenantBindingConfigReader, databaseProductType, cspaceDatabaseName);
+                       AuthorizationCommon.createDefaultWorkflowPermissions(tenantBindingConfigReader, databaseProductType, cspaceDatabaseName);
+                       AuthorizationCommon.createDefaultAccounts(tenantBindingConfigReader, databaseProductType, cspaceDatabaseName);
+                       AuthorizationCommon.persistTenantBindingsMD5Hash(tenantBindingConfigReader, databaseProductType, cspaceDatabaseName);
                } catch (Exception e) {
-                       logger.error("Default accounts and permissions setup failed with exception(s): " +
+                       logger.error("Default create/update of tenants, accounts, roles and permissions setup failed with exception(s): " +
                                        e.getLocalizedMessage(), e);
                        throw e;
                }
-               
                //
-               // Ensure default vocabulary and authority instances and their corresponding terms exist.
+               // Log tenant status -shows all tenants' info and active status.
                //
-//             initializeVocabularies();
-//             initializeAuthorities();
-        
-        /*
-         * This might be useful for something, but the reader grants are better handled in the ReportPostInitHandler.
-        try {
-               handlePostNuxeoInitDBTasks();
-        } catch(Throwable e) {         
-               logger.error("handlePostNuxeoInitDBTasks failed with exception(s): " + e.getLocalizedMessage(), e);
-        }
-        */
                showTenantStatus();
     }
         
@@ -398,6 +401,25 @@ public class ServiceMain {
         tenantBindingConfigReader = new TenantBindingConfigReaderImpl(getServerRootDir()); 
         tenantBindingConfigReader.read(useAppGeneratedBindings);
     }
+    
+    /*
+     * Returns 'true' if the tenant bindings have change since we last started up the Services layer of if the 'forceUpdate' field in the bindings
+     * has been set to 'true'
+     * 
+     */
+       private static boolean hasConfigChanged(TenantBindingType tenantBinding, String persistedMD5Hash,
+                       String currentMD5Hash) {
+               boolean result = false;
+               
+               if (persistedMD5Hash == null || tenantBinding.isForceUpdate() == true) {
+                       result = true;
+               } else {
+                       result = !persistedMD5Hash.equals(currentMD5Hash);  // if the two hashes don't match, the tenant bindings have changed so we'll return 'true'
+               }
+               
+               return result;
+       }
+    
 
     private void propagateConfiguredProperties() {
         List<PropertyType> repoPropListHolder =
index 6e686f06416791634c2810ea71decf0da2f5fbed..af328f7c1db706a8ede3cb174e009e575aa030c7 100644 (file)
@@ -1,6 +1,5 @@
 package org.collectionspace.services.common.authorization_mgt;
 
-import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 import java.sql.Connection;
 import java.sql.PreparedStatement;
@@ -13,6 +12,7 @@ import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Hashtable;
 import java.util.List;
+import java.util.Map;
 import java.util.UUID;
 
 import javax.naming.NamingException;
@@ -77,6 +77,11 @@ public class AuthorizationCommon {
        final private static String tokensalt = "74102328UserDetailsReset";
        final private static int TIME_SCALAR = 100000;
        private static final String DEFAULT_PASSWORD_RESET_EMAIL_SUBJECT = "Password reset for CollectionSpace account";
+       
+       //
+       // Keep track of the MD5 hash value for the tenant bindings
+       //
+       private static final Map<String, String> tenantConfigMD5HashTable = new HashMap<String, String>();
 
     //
     // ActionGroup labels/constants
@@ -149,6 +154,14 @@ public class AuthorizationCommon {
        public static final String IGNORE_TENANT_ID = null; // A null constant to indicate an empty/unused value for the tenant ID
 
 
+       public static String getTenantConfigMD5Hash(String tenantId) {
+               return tenantConfigMD5HashTable.get(tenantId);
+       }
+       
+       public static String setTenantConfigMD5Hash(String tenantId, String md5hash) {
+               return tenantConfigMD5HashTable.put(tenantId, md5hash);
+       }       
+       
     public static Role getRole(String tenantId, String displayName) {
        Role role = null;
        
@@ -427,39 +440,7 @@ public class AuthorizationCommon {
 
        return existingTenants;
     }
-    
-    private static void createMissingTenants(Connection conn, Hashtable<String, String> tenantInfo,
-               ArrayList<String> existingTenants) throws SQLException, Exception {
-               // Need to define and look for a createDisabled attribute in tenant config
-       final String insertTenantSQL = 
-               "INSERT INTO tenants (id,name,authorities_initialized,disabled,created_at) VALUES (?,?,FALSE,FALSE,now())";
-        PreparedStatement pstmt = null;
-       try {
-               pstmt = conn.prepareStatement(insertTenantSQL); // create a statement
-               for(String tId : tenantInfo.keySet()) {
-                       if(existingTenants.contains(tId)) {
-                               if (logger.isDebugEnabled()) {
-                                       logger.debug("createMissingTenants: tenant exists (skipping): "
-                                                       +tenantInfo.get(tId));
-                               }
-                               continue;
-                       }
-                       pstmt.setString(1, tId);                                        // set id param
-                       pstmt.setString(2, tenantInfo.get(tId));        // set name param
-                       if (logger.isDebugEnabled()) {
-                               logger.debug("createMissingTenants adding entry for tenant: "+tId);
-                       }
-                       pstmt.executeUpdate();
-               }
-               pstmt.close();
-       } catch(Exception e) {
-               throw e;
-       } finally {
-               if(pstmt!=null)
-                       pstmt.close();
-       }
-    }
-    
+        
     private static ArrayList<String> findOrCreateDefaultUsers(Connection conn, Hashtable<String, String> tenantInfo) 
                throws SQLException, Exception {
        // Second find or create the users
@@ -915,6 +896,37 @@ public class AuthorizationCommon {
        }
     }
     
+    /*
+     * Using the tenant bindings, ensure there are corresponding Tenant records (db columns).
+     */
+    public static void createTenants(
+               TenantBindingConfigReaderImpl tenantBindingConfigReader,
+               DatabaseProductType databaseProductType,
+                       String cspaceDatabaseName) throws Exception {
+               logger.debug("ServiceMain.createTenants starting...");
+               Hashtable<String, String> tenantInfo = getTenantNamesFromConfig(tenantBindingConfigReader);
+               Connection conn = null;
+               try {
+                       conn = getConnection(cspaceDatabaseName);
+                       ArrayList<String> existingTenants = compileExistingTenants(conn, tenantInfo);
+
+                       // Note that this only creates tenants not marked as "createDisabled"
+                       createMissingTenants(conn, tenantInfo, existingTenants);
+               } catch (Exception e) {
+                       logger.debug("Exception in createTenants: " + e.getLocalizedMessage());
+                       throw e;
+               } finally {
+                       try {
+                               if (conn != null)
+                                       conn.close();
+                       } catch (SQLException sqle) {
+                               if (logger.isDebugEnabled()) {
+                                       logger.debug("SQL Exception closing statement/connection: " + sqle.getLocalizedMessage());
+                               }
+                       }
+               }
+       }
+    
     public static void createDefaultAccounts(
                TenantBindingConfigReaderImpl tenantBindingConfigReader,
                DatabaseProductType databaseProductType,
@@ -932,10 +944,6 @@ public class AuthorizationCommon {
         // accounts, users, account-tenants, account-roles, and start over.
         try {
                conn = getConnection(cspaceDatabaseName);
-               ArrayList<String> existingTenants = compileExistingTenants(conn, tenantInfo);
-               
-               // Note that this only creates tenants not marked as "createDisabled"
-               createMissingTenants(conn, tenantInfo, existingTenants);
                
                ArrayList<String> usersInRepo = findOrCreateDefaultUsers(conn, tenantInfo);
                
@@ -1069,7 +1077,16 @@ public class AuthorizationCommon {
                return result;
        }
        
-    public static void createDefaultWorkflowPermissions(TenantBindingConfigReaderImpl tenantBindingConfigReader) throws Exception //FIXME: REM - 4/11/2012 - Rename to createWorkflowPermissions
+       /**
+        * 
+        * @param tenantBindingConfigReader
+        * @param databaseProductType
+        * @param cspaceDatabaseName
+        * @throws Exception
+        */
+    public static void createDefaultWorkflowPermissions(TenantBindingConfigReaderImpl tenantBindingConfigReader,
+               DatabaseProductType databaseProductType, 
+               String cspaceDatabaseName) throws Exception //FIXME: REM - 4/11/2012 - Rename to createWorkflowPermissions
     {
        java.util.logging.Logger logger = java.util.logging.Logger.getAnonymousLogger();
 
@@ -1081,11 +1098,14 @@ public class AuthorizationCommon {
         try {
             em = emf.createEntityManager();
 
-               Hashtable<String, TenantBindingType> tenantBindings =
-                       tenantBindingConfigReader.getTenantBindings();
+               Hashtable<String, TenantBindingType> 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);
+                       if (tenantBinding.isConfigChangedSinceLastStart() == false) {
+                               continue; // skip the rest of the loop and go to the next tenant
+                       }
+                       
                        Role adminRole = AuthorizationCommon.getRole(em, tenantBinding.getId(), ROLE_TENANT_ADMINISTRATOR);
                        Role readonlyRole = AuthorizationCommon.getRole(em, tenantBinding.getId(), ROLE_TENANT_READER);
                        
@@ -1136,7 +1156,74 @@ public class AuthorizationCommon {
         }
     }
     
-    private static PermissionRoleRel findPermRoleRel(EntityManager em, String permissionId, String RoleId) {
+       private static void createMissingTenants(Connection conn, Hashtable<String, String> tenantInfo,
+               ArrayList<String> existingTenants) throws SQLException, Exception {
+               // Need to define and look for a createDisabled attribute in tenant config
+       final String insertTenantSQL = 
+               "INSERT INTO tenants (id,name,authorities_initialized,disabled,created_at) VALUES (?,?,FALSE,FALSE,now())";
+        PreparedStatement pstmt = null;
+       try {
+               pstmt = conn.prepareStatement(insertTenantSQL); // create a statement
+               for(String tId : tenantInfo.keySet()) {
+                       if(existingTenants.contains(tId)) {
+                               if (logger.isDebugEnabled()) {
+                                       logger.debug("createMissingTenants: tenant exists (skipping): "
+                                                       +tenantInfo.get(tId));
+                               }
+                               continue;
+                       }
+                       pstmt.setString(1, tId);                                        // set id param
+                       pstmt.setString(2, tenantInfo.get(tId));        // set name param
+                       if (logger.isDebugEnabled()) {
+                               logger.debug("createMissingTenants adding entry for tenant: "+tId);
+                       }
+                       pstmt.executeUpdate();
+               }
+               pstmt.close();
+       } catch(Exception e) {
+               throw e;
+       } finally {
+               if(pstmt!=null)
+                       pstmt.close();
+       }
+    }
+    
+    public static String getPersistedMD5Hash(String tenantId, String cspaceDatabaseName) throws Exception {
+       String result = null;
+       
+       ArrayList<String> existingTenants = new ArrayList<String>();
+       // First find or create the tenants
+       final String queryTenantSQL = String.format("SELECT id, name, config_md5hash FROM tenants WHERE id = '%s'", tenantId);
+       
+       Statement stmt = null;
+       Connection conn;
+       int rowCount = 0;
+       try {
+                       conn = getConnection(cspaceDatabaseName);
+               stmt = conn.createStatement();
+               ResultSet rs = stmt.executeQuery(queryTenantSQL);
+               while (rs.next()) {
+                       if (rowCount > 0) {
+                               String errMsg = String.format("Unable to configure tenant ID='%s'.  There appears to be more than one tenant with that ID in the AuthN/AuthZ database named '%s'.",
+                                               tenantId, cspaceDatabaseName);
+                               throw new Exception(errMsg);
+                       }
+                       String tId = rs.getString("id");                // for debugging only
+                       String tName = rs.getString("name");    // for debugging only
+                       result = rs.getString("config_md5hash");
+                       rowCount++;
+               }
+               rs.close();
+       } catch(Exception e) {
+               throw e;
+       } finally {
+               if (stmt != null) stmt.close();
+       }
+       
+       return result;
+    }
+
+       private static PermissionRoleRel findPermRoleRel(EntityManager em, String permissionId, String RoleId) {
        PermissionRoleRel result = null;
        
        try {
@@ -1270,4 +1357,30 @@ public class AuthorizationCommon {
                
                return result;
        }
+
+       public static void persistTenantBindingsMD5Hash(TenantBindingConfigReaderImpl tenantBindingConfigReader,
+                       DatabaseProductType databaseProductType, String cspaceDatabaseName) throws Exception {
+               // Need to define and look for a createDisabled attribute in tenant config
+               String updateTableSQL = "UPDATE tenants SET config_md5hash = ? WHERE id = ?";
+
+       Connection conn;
+        PreparedStatement pstmt = null;
+       try {
+                       conn = getConnection(cspaceDatabaseName);
+               pstmt = conn.prepareStatement(updateTableSQL); // create a statement
+               for (String tId : AuthorizationCommon.tenantConfigMD5HashTable.keySet()) {
+                       pstmt.setString(1, AuthorizationCommon.getTenantConfigMD5Hash(tId));
+                       pstmt.setString(2, tId);
+                       if (logger.isDebugEnabled()) {
+                               logger.debug("createMissingTenants adding entry for tenant: " + tId);
+                       }
+                       pstmt.executeUpdate();
+               }
+               pstmt.close();
+       } catch(Exception e) {
+               throw e;
+       } finally {
+               if (pstmt!=null) pstmt.close();
+       }
+    }
 }
index 978d2ba544c7be8c4cfa305887700ecadae688f2..eb6d80565652a869d8dfd5de4750539bcd83e02b 100644 (file)
@@ -47,6 +47,8 @@ import org.slf4j.LoggerFactory;
 public class ServiceConfigUtils {
 
     final static Logger logger = LoggerFactory.getLogger(ServiceConfigUtils.class);
+    
+       public static final String FORCE_BINDINGS_UPDATE = "-1";
 
        public static DocHandlerParams.Params getDocHandlerParams(String tenantId, String serviceName) throws DocumentException {
         TenantBindingConfigReaderImpl tReader =
index b55073769bbab7ae330f39890bf21d2e135efd1f..3147500e24ccca00815b6c9577e237536fdbf0f4 100644 (file)
@@ -27,11 +27,16 @@ import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.security.DigestInputStream;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
 import java.util.ArrayList;
 import java.util.Enumeration;
 import java.util.Hashtable;
 import java.util.List;
 
+import org.apache.commons.codec.binary.Hex;
+import org.apache.commons.codec.digest.DigestUtils;
 import org.apache.commons.io.FileUtils;
 import org.collectionspace.services.common.api.JEEServerDeployment;
 import org.collectionspace.services.common.api.Tools;
@@ -159,7 +164,7 @@ public class TenantBindingConfigReaderImpl extends AbstractConfigReaderImpl<List
                                        + JEEServerDeployment.TENANT_BINDINGS_FILENAME_PREFIX + MERGED_SUFFIX;
                        File mergedOutFile = new File(mergedFileName);
                        try {
-                               FileUtils.copyInputStreamToFile(result, mergedOutFile);
+                               FileUtils.copyInputStreamToFile(result, mergedOutFile); // Save the merge file for debugging
                        } catch (IOException e) {
                                logger.warn("Could not create a copy of the merged tenant configuration at: " + mergedFileName, e);
                        }
@@ -293,6 +298,13 @@ public class TenantBindingConfigReaderImpl extends AbstractConfigReaderImpl<List
                                        try {
                                                tenantBindingConfig = (TenantBindingConfig) parse(tenantBindingsStream,
                                                                TenantBindingConfig.class);
+                                               //
+                                               // Compute the MD5 hash of the tenant's binding file.  We'll persist this a little later during startup.  If the value hasn't
+                                               // changed since we last startedup, we can skip some of the startup steps.
+                                               //
+                                               tenantBindingsStream.reset();
+                                               String md5hash = new String(Hex.encodeHex(DigestUtils.md5(tenantBindingsStream)));
+                                               tenantBindingConfig.getTenantBinding().setConfigMD5Hash(md5hash); // use this to compare with the last persisted one and to persist as the new hash
                                        } catch (Exception e) {
                                                logger.error("Could not parse the merged tenant bindings.", e);
                                        }
index b5349389b275b0f2b60465563d1d68c95fdf03b8..5ade3641c5a39a2846772ca4f8635c6eeb6f23c6 100644 (file)
@@ -21,6 +21,7 @@ import java.util.Date;
 import java.util.List;
 
 import javax.persistence.RollbackException;
+import javax.xml.datatype.XMLGregorianCalendar;
 
 import java.sql.BatchUpdateException;
 
@@ -43,6 +44,7 @@ import org.collectionspace.services.common.storage.StorageClient;
 import org.collectionspace.services.common.vocabulary.RefNameServiceUtils.AuthorityItemSpecifier;
 import org.collectionspace.services.common.vocabulary.RefNameServiceUtils.Specifier;
 import org.collectionspace.services.common.context.ServiceContextProperties;
+import org.collectionspace.services.common.api.GregorianCalendarDateTimeUtils;
 import org.collectionspace.services.common.authorization_mgt.AuthorizationStore;
 import org.collectionspace.services.common.context.ServiceContext;
 import org.collectionspace.services.common.query.QueryContext;
index 7e81de2e22db421d0a0f5a4eeb0131c55fb02f81..a38278ba923b40c631c841ff9d7fd43316d508ce 100644 (file)
                <xs:element name="remoteClientConfigurations" type="RemoteClientConfigurations" minOccurs="0" maxOccurs="1"/>
                <xs:element name="emailConfig" type="EmailConfig" minOccurs="0" maxOccurs="1"/>
             <xs:element name="serviceBindings" type="service:ServiceBindingType" minOccurs="0" maxOccurs="unbounded"/>
+                       <!--
+                               Processing all of the tenant bindings at startup involves several sub-tasks and is expensive (slow), so we'll compute the MD5 hash of the config and persist it in the corresponding tenant record.
+                               Some of the startup sub-tasks can compare the MD5 hash of the current tenant bindings with that of the last startup to see if anything has changed.  If the hashes match, there's been no changes so
+                               a sub-task can decide whether to run or not.                              
+                       -->             
+               <xs:element name="configMD5Hash" type="xs:string" minOccurs="0" maxOccurs="1"/>                                 <!-- Transient, only used during startup.  Not part of config file. -->
+               <xs:element name="configChangedSinceLastStart" type="xs:boolean" minOccurs="0" maxOccurs="1"/>  <!-- Transient, only used during startup.  Not part of config file. -->
         </xs:sequence>
         <!-- tenant id, a UUID -->
         <xs:attribute name="id" type="xs:ID" use="required"/>
@@ -62,6 +69,7 @@
         <xs:attribute name="displayName" type="xs:string" use="required"/>
         <xs:attribute name="version" type="types:VersionType" use="required"/>
         <xs:attribute name="createDisabled" type="xs:boolean" use="optional" default="false"/>
+        <xs:attribute name="forceUpdate" type="xs:boolean" use="optional" default="false"/>
 
     </xs:complexType>