+++ /dev/null
-/**
- * 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
--- /dev/null
+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() {}
+
+}
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;
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) {
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) {
<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
</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>
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;
<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>
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;
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
// 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();
//
//
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();
}
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 =
package org.collectionspace.services.common.authorization_mgt;
-import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.List;
+import java.util.Map;
import java.util.UUID;
import javax.naming.NamingException;
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
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;
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
}
}
+ /*
+ * 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,
// 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);
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();
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);
}
}
- 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 {
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();
+ }
+ }
}
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 =
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;
+ 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);
}
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);
}
import java.util.List;
import javax.persistence.RollbackException;
+import javax.xml.datatype.XMLGregorianCalendar;
import java.sql.BatchUpdateException;
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;
<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"/>
<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>