From: Michael Ritter Date: Tue, 9 Jan 2024 03:01:53 +0000 (-0700) Subject: DRYD-1254: UCB Per Tenant SQL (#371) X-Git-Url: https://git.aero2k.de/?a=commitdiff_plain;h=e965400c24eccd6c1d2ca956f556f77c45002426;p=tmp%2Fjakarta-migration.git DRYD-1254: UCB Per Tenant SQL (#371) Co-authored-by: Richard Millet Co-authored-by: Ray Lee --- diff --git a/services/JaxRsServiceProvider/src/main/resources/log4j2.xml b/services/JaxRsServiceProvider/src/main/resources/log4j2.xml index 96e2ea9bb..a2e7ee8d2 100644 --- a/services/JaxRsServiceProvider/src/main/resources/log4j2.xml +++ b/services/JaxRsServiceProvider/src/main/resources/log4j2.xml @@ -42,6 +42,7 @@ + diff --git a/services/common/src/main/cspace/config/services/tenants/tenant-bindings-proto-unified.xml b/services/common/src/main/cspace/config/services/tenants/tenant-bindings-proto-unified.xml index b38b4dc1b..6db0c9cd7 100644 --- a/services/common/src/main/cspace/config/services/tenants/tenant-bindings-proto-unified.xml +++ b/services/common/src/main/cspace/config/services/tenants/tenant-bindings-proto-unified.xml @@ -63,6 +63,17 @@ + + + + default-domain + + org.collectionspace.services.common.init.RunSqlScripts + + + + 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 2b9604064..dac61e730 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 @@ -116,6 +116,8 @@ public class ServiceMain { private static final String DROP_USER_IF_EXISTS_SQL_CMD = DROP_USER_SQL_CMD + " IF EXISTS %s;"; private static final String DROP_OBJECTS_SQL_COMMENT = "-- drop all the objects before dropping roles"; private static final String CSPACE_JEESERVER_HOME = "CSPACE_JEESERVER_HOME"; + private static final String CSPACE_UTILS_SCHEMANAME = "utils"; + private static final String RUNSQLSCRIPTS_SERVICE_NAME = "runsqlscripts"; private ServiceMain() { // Intentionally blank @@ -734,7 +736,7 @@ public class ServiceMain { // 1 fields.add(field); } - addindices.onRepositoryInitialized(JDBCTools.NUXEO_DATASOURCE_NAME, repositoryName, cspaceInstanceId, + addindices.onRepositoryInitialized(JDBCTools.NUXEO_DATASOURCE_NAME, repositoryName, cspaceInstanceId, tbt.getShortName(), null, fields, null); } } else { @@ -745,10 +747,73 @@ public class ServiceMain { } } + /** + * Search through the service bindings for the RUNSQLSCRIPTS_SERVICE_NAME service. Each tenant can add a set of SQL + * that will be run before the other Services' initHandlers. + * + * @param tenantBindingTypeMap + * @throws Exception + */ + private void firePostInitRunSQLScripts(Hashtable tenantBindingTypeMap) throws Exception { + String cspaceInstanceId = getCspaceInstanceId(); + for (TenantBindingType tbt : tenantBindingTypeMap.values()) { + // Loop through all the services in this tenant + List sbtList = tbt.getServiceBindings(); + for (ServiceBindingType sbt: sbtList) { + if (sbt.getName().equalsIgnoreCase(RUNSQLSCRIPTS_SERVICE_NAME)) { + runInitHandler(cspaceInstanceId, tbt, sbt); + return; + } + } + } + } + + private void runInitHandler(String cspaceInstanceId, TenantBindingType tbt, ServiceBindingType sbt) throws Exception { + String repositoryName = null; + if (!sbt.getType().equalsIgnoreCase(ServiceBindingUtils.SERVICE_TYPE_SECURITY)) { + // Each service can have a different repo domain + repositoryName = ConfigUtils.getRepositoryName(tbt, sbt.getRepositoryDomain()); + } + + // Get the list of InitHandler elements, extract the first one (only one supported right now) and fire it using reflection. + List list = sbt.getInitHandler(); + if (list != null && !list.isEmpty()) { + // REM - 12/2012: We might want to think about supporting multiple post-init handlers + org.collectionspace.services.config.service.InitHandler handlerType = list.get(0); + String initHandlerClassname = handlerType.getClassname(); + if (Tools.isEmpty(initHandlerClassname)) { + return; + } + logger.trace("Firing post-init handler {}...", initHandlerClassname); + + List + fields = handlerType.getParams().getField(); + + List + props = handlerType.getParams().getProperty(); + + Object o = instantiate(initHandlerClassname, IInitHandler.class); + if (o instanceof IInitHandler){ + // The InitHandler may be the default one, + // or specialized classes which still implement this interface and are registered in tenant-bindings.xm. + IInitHandler handler = (IInitHandler)o; + handler.onRepositoryInitialized(JDBCTools.NUXEO_DATASOURCE_NAME, repositoryName, cspaceInstanceId, tbt.getShortName(), + sbt, fields, props); + } + } + } + public void firePostInitHandlers() throws Exception { Hashtable tenantBindingTypeMap = tenantBindingConfigReader.getTenantBindings(); + // - //Loop through all tenants in tenant-bindings.xml + // We first need to run the init handler for the 'runsqlscripts' service to allow a tenant to perform + // any required tenant specific SQL setup. + // + firePostInitRunSQLScripts(tenantBindingTypeMap); + + // + // Loop through all tenants in tenant-bindings.xml and run each service's initHandler // String cspaceInstanceId = getCspaceInstanceId(); for (TenantBindingType tbt : tenantBindingTypeMap.values()) { @@ -757,39 +822,10 @@ public class ServiceMain { // List sbtList = tbt.getServiceBindings(); for (ServiceBindingType sbt: sbtList) { - String repositoryName = null; - if (sbt.getType().equalsIgnoreCase(ServiceBindingUtils.SERVICE_TYPE_SECURITY) == false) { - repositoryName = ConfigUtils.getRepositoryName(tbt, sbt.getRepositoryDomain()); // Each service can have a different repo domain + // skip the RUNSQLSCRIPTS_SERVICE_NAME since we ran it already + if (!sbt.getName().equalsIgnoreCase(RUNSQLSCRIPTS_SERVICE_NAME)) { + runInitHandler(cspaceInstanceId, tbt, sbt); } - //Get the list of InitHandler elements, extract the first one (only one supported right now) and fire it using reflection. - List list = sbt.getInitHandler(); - if (list != null && list.size() > 0) { - org.collectionspace.services.config.service.InitHandler handlerType = list.get(0); // REM - 12/2012: We might want to think about supporting multiple post-init handlers - String initHandlerClassname = handlerType.getClassname(); - if (Tools.isEmpty(initHandlerClassname)) { - continue; - } - if (ServiceMain.logger.isTraceEnabled()) { - ServiceMain.logger.trace(String.format("Firing post-init handler %s ...", initHandlerClassname)); - } - - List - fields = handlerType.getParams().getField(); - - List - props = handlerType.getParams().getProperty(); - - //org.collectionspace.services.common.service.InitHandler.Fields ft = handlerType.getFields(); - //List fields = ft.getField(); - Object o = instantiate(initHandlerClassname, IInitHandler.class); - if (o != null && o instanceof IInitHandler){ - IInitHandler handler = (IInitHandler)o; - handler.onRepositoryInitialized(JDBCTools.NUXEO_DATASOURCE_NAME, repositoryName, cspaceInstanceId, - sbt, fields, props); - //The InitHandler may be the default one, - // or specialized classes which still implement this interface and are registered in tenant-bindings.xml. - } - } } } } @@ -975,7 +1011,8 @@ public class ServiceMain { JDBCTools.createNewDatabaseUser(JDBCTools.CSADMIN_DATASOURCE_NAME, repositoryName, cspaceInstanceId, dbType, readerUser, readerPW); } // Create the database - createDatabaseWithRights(dbType, dbName, nuxeoUser, nuxeoPW, readerUser, readerPW); + createDatabaseWithRights(dbType, dbName, nuxeoUser, readerUser); + createUtilsSchemaWithRights(dbType, nuxeoUser, repositoryName, cspaceInstanceId); initRepositoryDatabaseVersion(JDBCTools.NUXEO_DATASOURCE_NAME, repositoryName, cspaceInstanceId); } nuxeoDBsChecked.add(dbName); @@ -1001,67 +1038,57 @@ public class ServiceMain { * @param readerPW * @throws Exception */ - private void createDatabaseWithRights(DatabaseProductType dbType, String dbName, String ownerName, - String ownerPW, String readerName, String readerPW) throws Exception { - Connection conn = null; - Statement stmt = null; - try { - DataSource csadminDataSource = JDBCTools.getDataSource(JDBCTools.CSADMIN_DATASOURCE_NAME); - conn = csadminDataSource.getConnection(); - stmt = conn.createStatement(); + private void createDatabaseWithRights(DatabaseProductType dbType, String dbName, String ownerName, String readerName) throws Exception { + String sql = null; + try (Connection conn = JDBCTools.getDataSource(JDBCTools.CSADMIN_DATASOURCE_NAME).getConnection(); + Statement stmt = conn.createStatement()) { if (dbType == DatabaseProductType.POSTGRESQL) { // PostgreSQL does not need passwords in grant statements. - String sql = "CREATE DATABASE " + dbName + " ENCODING 'UTF8' OWNER " + ownerName; + sql = "CREATE DATABASE " + dbName + " ENCODING 'UTF8' OWNER " + ownerName; stmt.executeUpdate(sql); - if (logger.isDebugEnabled()) { - logger.debug("Created db: '" + dbName + "' with owner: '" + ownerName + "'"); - } + logger.debug("Created db: '{}' with owner: '{}'", dbName, ownerName); if (readerName != null) { sql = "GRANT CONNECT ON DATABASE " + dbName + " TO " + readerName; stmt.executeUpdate(sql); - if (logger.isDebugEnabled()) { - logger.debug(" Granted connect rights on: '" + dbName + "' to reader: '" + readerName + "'"); - } + logger.debug("Granted connect rights on: '{}' to reader: '{}'", dbName, readerName); } // Note that select rights for reader must be granted after // Nuxeo startup. - } else if (dbType == DatabaseProductType.MYSQL) { - String sql = "CREATE database " + dbName + " DEFAULT CHARACTER SET utf8"; - stmt.executeUpdate(sql); - sql = "GRANT ALL PRIVILEGES ON " + dbName + ".* TO '" + ownerName + "'@'localhost' IDENTIFIED BY '" - + ownerPW + "' WITH GRANT OPTION"; - stmt.executeUpdate(sql); - if (logger.isDebugEnabled()) { - logger.debug("Created db: '" + dbName + "' with owner: '" + ownerName + "'"); - } - if (readerName != null) { - sql = "GRANT SELECT ON " + dbName + ".* TO '" + readerName + "'@'localhost' IDENTIFIED BY '" - + readerPW + "' WITH GRANT OPTION"; - stmt.executeUpdate(sql); - if (logger.isDebugEnabled()) { - logger.debug(" Granted SELECT rights on: '" + dbName + "' to reader: '" + readerName + "'"); - } - } } else { - throw new UnsupportedOperationException("createDatabaseWithRights only supports PSQL - MySQL NYI!"); + throw new UnsupportedOperationException(String.format("DB Type %s not supported", dbType)); } } catch (Exception e) { - logger.error("createDatabaseWithRights failed on exception: " + e.getLocalizedMessage()); - throw e; // propagate - } finally { // close resources - try { - if (stmt != null) { - stmt.close(); - } - if (conn != null) { - conn.close(); - } - } catch (SQLException se) { - se.printStackTrace(); - } + logger.error("createDatabaseWithRights failed on exception:", e); + logger.error("The following SQL statement failed using credentials from datasource '{}': {}", + JDBCTools.CSADMIN_DATASOURCE_NAME, sql); + throw e; } } + /* + * For a specific repo/db, create a schema for misc SQL functions + */ + private void createUtilsSchemaWithRights(DatabaseProductType dbType, String ownerName, + String repositoryName, String cspaceInstanceId) throws Exception { + String sql = null; + try (Connection conn = JDBCTools.getConnection(JDBCTools.NUXEO_DATASOURCE_NAME, + repositoryName, cspaceInstanceId); + Statement stmt = conn.createStatement()) { + if (dbType == DatabaseProductType.POSTGRESQL) { + sql = "CREATE SCHEMA IF NOT EXISTS " + CSPACE_UTILS_SCHEMANAME + " AUTHORIZATION " + ownerName; + stmt.executeUpdate(sql); + logger.debug("Created SCHEMA: '{}' with owner: '{}'", CSPACE_UTILS_SCHEMANAME, ownerName); + } else { + throw new UnsupportedOperationException("CollectionSpace supports only PostgreSQL database servers."); + } + } catch (Exception e) { + logger.error("createUtilsSchemaWithRights() failed with exception:", e); + logger.error("The following SQL statement failed using credentials from datasource '{}': {}", + JDBCTools.NUXEO_DATASOURCE_NAME, sql); + throw e; + } + } + private void initRepositoryDatabaseVersion(String dataSourceName, String repositoryName, String cspaceInstanceId) throws Exception { String version = getClass().getPackage().getImplementationVersion(); Connection conn = null; diff --git a/services/common/src/main/java/org/collectionspace/services/common/init/AddIndices.java b/services/common/src/main/java/org/collectionspace/services/common/init/AddIndices.java index 27b5f3608..6f5defa7d 100644 --- a/services/common/src/main/java/org/collectionspace/services/common/init/AddIndices.java +++ b/services/common/src/main/java/org/collectionspace/services/common/init/AddIndices.java @@ -95,6 +95,7 @@ public class AddIndices extends InitHandler implements IInitHandler { public void onRepositoryInitialized(String dataSourceName, String repositoryName, String cspaceInstanceId, + String tenantShortName, ServiceBindingType sbt, List fields, List properties) throws Exception { diff --git a/services/common/src/main/java/org/collectionspace/services/common/init/IInitHandler.java b/services/common/src/main/java/org/collectionspace/services/common/init/IInitHandler.java index d227cd392..c130d69bf 100644 --- a/services/common/src/main/java/org/collectionspace/services/common/init/IInitHandler.java +++ b/services/common/src/main/java/org/collectionspace/services/common/init/IInitHandler.java @@ -16,6 +16,7 @@ public interface IInitHandler { public void onRepositoryInitialized(String dataSourceName, String repositoryName, String cspaceInstanceId, + String tenantShortName, ServiceBindingType sbt, List fields, List property) throws Exception; diff --git a/services/common/src/main/java/org/collectionspace/services/common/init/InitHandler.java b/services/common/src/main/java/org/collectionspace/services/common/init/InitHandler.java index 56a6a4ba1..a92110024 100644 --- a/services/common/src/main/java/org/collectionspace/services/common/init/InitHandler.java +++ b/services/common/src/main/java/org/collectionspace/services/common/init/InitHandler.java @@ -53,6 +53,7 @@ public class InitHandler implements IInitHandler { public void onRepositoryInitialized(String dataSourceName, String repositoryName, String cspaceInstanceId, + String tenantShortName, ServiceBindingType sbt, List fields, List properties) throws Exception { diff --git a/services/common/src/main/java/org/collectionspace/services/common/init/ModifyFieldDatatypes.java b/services/common/src/main/java/org/collectionspace/services/common/init/ModifyFieldDatatypes.java index 6f8da421f..ea5fc29c3 100644 --- a/services/common/src/main/java/org/collectionspace/services/common/init/ModifyFieldDatatypes.java +++ b/services/common/src/main/java/org/collectionspace/services/common/init/ModifyFieldDatatypes.java @@ -57,6 +57,7 @@ public class ModifyFieldDatatypes extends InitHandler implements IInitHandler { public void onRepositoryInitialized(String dataSourceName, String repositoryName, String cspaceInstanceId, + String tenantShortName, ServiceBindingType sbt, List fields, List properties) throws Exception { diff --git a/services/common/src/main/java/org/collectionspace/services/common/init/RunSqlScripts.java b/services/common/src/main/java/org/collectionspace/services/common/init/RunSqlScripts.java index 51861d75e..b9b26a3d1 100644 --- a/services/common/src/main/java/org/collectionspace/services/common/init/RunSqlScripts.java +++ b/services/common/src/main/java/org/collectionspace/services/common/init/RunSqlScripts.java @@ -58,6 +58,7 @@ public class RunSqlScripts extends InitHandler implements IInitHandler { public void onRepositoryInitialized(String dataSourceName, String repositoryName, String cspaceInstanceId, + String tenantShortName, ServiceBindingType sbt, List fields, List properties) throws Exception { @@ -79,7 +80,7 @@ public class RunSqlScripts extends InitHandler implements IInitHandler { return; } for (String scriptName : scriptNames) { - String scriptPath = getSqlScriptPath(dataSourceName, repositoryName, scriptName); + String scriptPath = getSqlScriptPath(dataSourceName, repositoryName, tenantShortName, scriptName); if (Tools.isBlank(scriptPath)) { logger.warn("Could not get path to SQL script."); logger.warn(CANNOT_PERFORM_TASKS_MESSAGE); @@ -87,16 +88,28 @@ public class RunSqlScripts extends InitHandler implements IInitHandler { } scriptContents = getSqlScriptContents(scriptPath); if (Tools.isBlank(scriptContents)) { - logger.warn("Could not get contents of SQL script from resource " + scriptPath); - logger.warn(CANNOT_PERFORM_TASKS_MESSAGE); - continue; + // Since we couldn't find the script in the tenant qualified location, let's look in the shared/common + // (non-tenant qualified) location + String commonPath = getSqlScriptCommonPath(dataSourceName, repositoryName, scriptName); + scriptContents = getSqlScriptContents(commonPath); + logger.warn("Could not get contents of SQL script from resource '{}'. Looking here instead: '{}'", + scriptPath, commonPath); + if (Tools.isBlank(scriptContents)) { + logger.warn("Could not get contents of SQL script from resource {}", commonPath); + logger.warn(CANNOT_PERFORM_TASKS_MESSAGE); + continue; + } } + + logger.info("Running SQL script from Java class path '{}'", scriptPath); + logger.trace(scriptContents); + runScript(dataSourceName, repositoryName, cspaceInstanceId, scriptContents, "resource path " + scriptPath); } // Next, run a second sequence of SQL scripts, where those scripts may be // stored on disk, in a resources directory within the server directory. - List scriptFiles = getSqlScriptFiles(dataSourceName, repositoryName); + List scriptFiles = getSqlScriptFiles(dataSourceName, repositoryName, tenantShortName); // Run these scripts in a sequence based on the ascending order of their filenames. // FIXME: consider adding functionality to specify the locale for filename // sorting here. (The current sort order is based on the system's default locale.) @@ -135,22 +148,47 @@ public class RunSqlScripts extends InitHandler implements IInitHandler { return scriptNames; } - private String getSqlScriptPath(String dataSourceName, String repositoryName, String scriptName) throws Exception { + private String getSqlScriptCommonPath(String dataSourceName, + String repositoryName, + String scriptName) throws Exception { + String scriptPath = + DATABASE_RESOURCE_DIRECTORY_NAME + + RESOURCE_PATH_SEPARATOR + + JDBCTools.getDatabaseProductType(dataSourceName, repositoryName) + + RESOURCE_PATH_SEPARATOR + + scriptName; + return scriptPath; + } + + private String getSqlScriptPath(String dataSourceName, + String repositoryName, + String tenantShortName, + String scriptName) throws Exception { String scriptPath = DATABASE_RESOURCE_DIRECTORY_NAME + RESOURCE_PATH_SEPARATOR + JDBCTools.getDatabaseProductType(dataSourceName, repositoryName) + RESOURCE_PATH_SEPARATOR + + "tenants" + + RESOURCE_PATH_SEPARATOR + + tenantShortName + + RESOURCE_PATH_SEPARATOR + scriptName; return scriptPath; } - private String getSqlScriptDirectoryPath(String dataSourceName, String repositoryName) throws Exception { + private String getSqlScriptDirectoryPath(String dataSourceName, + String repositoryName, + String tenantShortName) throws Exception { String scriptDirectoryPath = getServerResourcesDirectoryPath() + DATABASE_RESOURCE_DIRECTORY_NAME + File.separator + JDBCTools.getDatabaseProductType(dataSourceName, repositoryName) + + File.separator + + "tenants" + + File.separator + + tenantShortName + File.separator; return scriptDirectoryPath; } @@ -162,9 +200,11 @@ public class RunSqlScripts extends InitHandler implements IInitHandler { return serverResourcesPath; } - private List getSqlScriptFiles(String dataSourceName, String repositoryName) throws Exception { + private List getSqlScriptFiles(String dataSourceName, + String repositoryName, + String tenantShortName) throws Exception { List sqlScriptFiles = new ArrayList<>(); - File folder = new File(getSqlScriptDirectoryPath(dataSourceName, repositoryName)); + File folder = new File(getSqlScriptDirectoryPath(dataSourceName, repositoryName, tenantShortName)); if (!folder.isDirectory() || !folder.canRead()) { return sqlScriptFiles; // Return an empty list of files } diff --git a/services/common/src/main/resources/db/postgresql/README.txt b/services/common/src/main/resources/db/postgresql/README.txt new file mode 100644 index 000000000..ebd91af63 --- /dev/null +++ b/services/common/src/main/resources/db/postgresql/README.txt @@ -0,0 +1 @@ +Place SQL scripts in this directory for Service initHandlers to find them. Placing SQL scripts in tenant "shortname" specific folders when appropriate. SQL scripts at this top-level will be available to all tenants. diff --git a/services/common/src/main/resources/db/postgresql/create_unaccent_text_search_configuration.sql b/services/common/src/main/resources/db/postgresql/create_unaccent_text_search_configuration.sql new file mode 100644 index 000000000..6cfd37f88 --- /dev/null +++ b/services/common/src/main/resources/db/postgresql/create_unaccent_text_search_configuration.sql @@ -0,0 +1,16 @@ +DO $$ +BEGIN + + IF NOT EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'unaccent') THEN + CREATE EXTENSION unaccent; + END IF; + + IF NOT EXISTS (SELECT 1 FROM pg_ts_config WHERE cfgname = 'unaccent_english') THEN + CREATE TEXT SEARCH CONFIGURATION unaccent_english ( COPY = english ); + + ALTER TEXT SEARCH CONFIGURATION unaccent_english + ALTER MAPPING FOR asciihword, asciiword, hword_asciipart, hword, hword_part, word + WITH unaccent, english_stem; + END IF; + +END $$; \ No newline at end of file diff --git a/services/common/src/main/resources/db/postgresql/tenants/README.txt b/services/common/src/main/resources/db/postgresql/tenants/README.txt new file mode 100644 index 000000000..ebd91af63 --- /dev/null +++ b/services/common/src/main/resources/db/postgresql/tenants/README.txt @@ -0,0 +1 @@ +Place SQL scripts in this directory for Service initHandlers to find them. Placing SQL scripts in tenant "shortname" specific folders when appropriate. SQL scripts at this top-level will be available to all tenants. 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 index 0f45f2fd1..f7c0a3e91 100644 --- 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 @@ -60,6 +60,7 @@ public class ReportPostInitHandler extends InitHandler implements IInitHandler { public void onRepositoryInitialized(String dataSourceName, String repositoryName, String cspaceInstanceId, + String tenantShortName, ServiceBindingType sbt, List fields, List propertyList) throws Exception {