import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
+import java.io.FileReader;
import java.io.InputStream;
+import java.math.BigInteger;
+import java.nio.file.Path;
+import java.nio.file.Paths;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.*;
+import java.util.regex.Pattern;
import javax.naming.NamingException;
import javax.servlet.ServletContext;
* @author
*/
public class ServiceMain {
+ final static Logger logger = LoggerFactory.getLogger(ServiceMain.class);
+
+ public static final String VER_DISPLAY_NAME = "CollectionSpace Services v6.0";
+ public static final String VER_MAJOR = "6";
+ public static final String VER_MINOR = "0";
+ public static final String VER_PATCH = "0";
+ public static final String VER_BUILD = "1";
- final static Logger logger = LoggerFactory.getLogger(ServiceMain.class);
private static final int PRIMARY_REPOSITORY_DOMAIN = 0;
/**
}
}
+ private String applyRepositoryUpgradeScripts(Connection conn, String dataSourceName, String repositoryName, String fromVersion, String stage) throws Exception {
+ Map<String, List<File>> upgradeScriptFiles = getRepositoryUpgradeScripts(dataSourceName, repositoryName, fromVersion, stage);
+ Set<String> versions = upgradeScriptFiles.keySet();
+
+ String upgradedToVersion = null;
+
+ if (versions.size() > 0) {
+ for (String version : versions) {
+ logger.info(String.format("upgrading %s repository to version %s", repositoryName, version));
+
+ List<File> scriptFiles = upgradeScriptFiles.get(version);
+
+ for (File file : scriptFiles) {
+ if (file.getName().endsWith(".sql")) {
+ logger.info(String.format("Running %s", file.getName()));
+
+ JDBCTools.runScript(conn, file);
+ }
+ }
+
+ upgradedToVersion = version;
+ }
+ }
+
+ return upgradedToVersion;
+ }
+
+ private void upgradeRepository(String dataSourceName, String repositoryName, String cspaceInstanceId) throws Exception {
+ // Install the pgcrypto extension so that the gen_random_uuid function will be available
+ // to upgrade scripts.
+
+ JDBCTools.executeUpdate(JDBCTools.CSADMIN_NUXEO_DATASOURCE_NAME, repositoryName, cspaceInstanceId, "CREATE EXTENSION IF NOT EXISTS \"pgcrypto\"");
+
+ String stage = "post-init";
+ Connection conn = null;
+
+ try {
+ conn = JDBCTools.getConnection(dataSourceName, repositoryName, cspaceInstanceId);
+
+ conn.setAutoCommit(false);
+
+ String version = JDBCTools.getRepositoryDatabaseVersion(conn);
+
+ logger.info(String.format("%s repository current version is %s", repositoryName, version));
+
+ String upgradedToVersion = applyRepositoryUpgradeScripts(conn, dataSourceName, repositoryName, version, stage);
+
+ if (upgradedToVersion != null) {
+ logger.info(String.format("%s repository upgraded to version %s", repositoryName, upgradedToVersion));
+
+ JDBCTools.setRepositoryDatabaseVersion(conn, upgradedToVersion);
+ }
+
+ conn.commit();
+ }
+ catch (Exception e) {
+ if (conn != null) {
+ conn.rollback();
+ }
+ }
+ finally {
+ if (conn != null) {
+ conn.close();
+ }
+ }
+ }
+
+ void upgradeDatabase() throws Exception {
+ Hashtable<String, TenantBindingType> tenantBindingTypeMap = tenantBindingConfigReader.getTenantBindings();
+
+ // Loop through all tenants in tenant-bindings.xml
+
+ String cspaceInstanceId = getCspaceInstanceId();
+
+ for (TenantBindingType tbt : tenantBindingTypeMap.values()) {
+ List<String> repositoryNameList = ConfigUtils.getRepositoryNameList(tbt);
+
+ if (repositoryNameList != null && repositoryNameList.isEmpty() == false) {
+ // Loop through each repo/DB defined in a tenant bindings file
+
+ for (String repositoryName : repositoryNameList) {
+ upgradeRepository(JDBCTools.NUXEO_DATASOURCE_NAME, repositoryName, cspaceInstanceId);
+ }
+ } else {
+ String errMsg = "repositoryNameList was empty or null.";
+
+ logger.error(errMsg);
+
+ throw new Exception(errMsg);
+ }
+ }
+ }
+
+ public static Map<String, List<File>> getRepositoryUpgradeScripts(String dataSourceName, String repositoryName, String fromVersion, String stage) throws Exception {
+ Map<String, List<File>> upgradeScriptFiles = new LinkedHashMap<>();
+
+ Path upgradesPath = Paths.get(
+ ServiceMain.getInstance().getServerRootDir(),
+ JEEServerDeployment.DATABASE_SCRIPTS_DIR_PATH,
+ JDBCTools.getDatabaseProductType(dataSourceName, repositoryName).toString(),
+ "upgrade"
+ );
+
+ File upgradesDirectory = upgradesPath.toFile();
+
+ if (!upgradesDirectory.isDirectory() || !upgradesDirectory.canRead()) {
+ return upgradeScriptFiles;
+ }
+
+ File[] upgradesDirectoryFiles = upgradesDirectory.listFiles();
+ List<File> versionDirectories = new ArrayList<>();
+ VersionComparator versionComparator = new VersionComparator();
+
+ for (File file : upgradesDirectoryFiles) {
+ if (
+ file.isDirectory()
+ && file.canRead()
+ && file.getName().matches("^\\d+\\.\\d+(\\.\\d+)?$")
+ && versionComparator.compare(fromVersion, file.getName()) < 0
+ ) {
+ versionDirectories.add(file);
+ }
+ }
+
+ versionDirectories.sort(new VersionFileNameComparator());
+
+ for (File versionDir : versionDirectories) {
+ Path versionStagePath = versionDir.toPath().resolve(stage);
+ File versionStageDirectory = versionStagePath.toFile();
+
+ if (versionStageDirectory.isDirectory()) {
+ File[] versionStageFiles = versionStageDirectory.listFiles();
+
+ Arrays.sort(versionStageFiles);
+
+ List<File> scriptFiles = new ArrayList<>();
+
+ for (File file : versionStageFiles) {
+ if (
+ file.isFile()
+ && file.canRead()
+ ) {
+ scriptFiles.add(file);
+ }
+ }
+
+ if (scriptFiles.size() > 0) {
+ upgradeScriptFiles.put(versionDir.getName(), scriptFiles);
+ }
+ }
+ }
+
+ return upgradeScriptFiles;
+ }
+
+ /**
+ * From https://dzone.com/articles/semantically-ordering-versioned-file-names-in-java
+ */
+ public static class VersionComparator implements Comparator<String> {
+ private static final Pattern NUMBERS = Pattern.compile("(?<=\\D)(?=\\d)|(?<=\\d)(?=\\D)");
+
+ @Override
+ public final int compare(String o1, String o2) {
+ // Optional "NULLS LAST" semantics:
+ if (o1 == null || o2 == null) {
+ return o1 == null ? o2 == null ? 0 : -1 : 1;
+ }
+
+ // Splitting both input strings by the above patterns
+ String[] split1 = NUMBERS.split(o1);
+ String[] split2 = NUMBERS.split(o2);
+ int length = Math.min(split1.length, split2.length);
+
+ // Looping over the individual segments
+ for (int i = 0; i < length; i++) {
+ char c1 = split1[i].charAt(0);
+ char c2 = split2[i].charAt(0);
+ int cmp = 0;
+
+ // If both segments start with a digit, sort them
+ // numerically using BigInteger to stay safe
+ if (c1 >= '0' && c1 <= '9' && c2 >= 0 && c2 <= '9')
+ cmp = new BigInteger(split1[i]).compareTo(
+ new BigInteger(split2[i]));
+
+ // If we haven't sorted numerically before, or if
+ // numeric sorting yielded equality (e.g 007 and 7)
+ // then sort lexicographically
+ if (cmp == 0)
+ cmp = split1[i].compareTo(split2[i]);
+
+ // Abort once some prefix has unequal ordering
+ if (cmp != 0)
+ return cmp;
+ }
+
+ // If we reach this, then both strings have equally
+ // ordered prefixes, but maybe one string is longer than
+ // the other (i.e. has more segments)
+ return split1.length - split2.length;
+ }
+ }
+
+ public static class VersionFileNameComparator implements Comparator<File> {
+ private final VersionComparator versionComparator = new VersionComparator();
+
+ @Override
+ public final int compare(File o1, File o2) {
+ return versionComparator.compare(o1.getName(), o2.getName());
+ }
+ }
+
/**
* Create required indexes (aka indices) in database tables not associated
* with any specific tenant.
}
return result;
- }
- /*
- * Look through the tenant bindings and create the required Nuxeo databases -each tenant can declare
- * their own Nuxeo repository/database.
- * Get the NuxeoDS info and create the necessary databases.
- * Consider the tenant bindings to find and get the data sources for each tenant.
- * There may be only one, one per tenant, or something in between.
- *
- */
- private HashSet<String> createNuxeoDatabases() throws Exception {
- String nuxeoUser = getBasicDataSourceUsername(JDBCTools.NUXEO_DATASOURCE_NAME);
- String nuxeoPW = getBasicDataSourcePassword(JDBCTools.NUXEO_DATASOURCE_NAME);
-
- String readerUser = getBasicDataSourceUsername(JDBCTools.NUXEO_READER_DATASOURCE_NAME);
- String readerPW = getBasicDataSourcePassword(JDBCTools.NUXEO_READER_DATASOURCE_NAME);
-
- DatabaseProductType dbType = JDBCTools.getDatabaseProductType(JDBCTools.CSADMIN_DATASOURCE_NAME,
- getServiceConfig().getDbCsadminName());
-
- Hashtable<String, TenantBindingType> tenantBindings =
- tenantBindingConfigReader.getTenantBindings();
- HashSet<String> nuxeoDBsChecked = new HashSet<String>();
+ }
- // First check and create the roles as needed. (nuxeo and reader)
- for (TenantBindingType tenantBinding : tenantBindings.values()) {
- String tId = tenantBinding.getId();
- String tName = tenantBinding.getName();
-
- List<RepositoryDomainType> repoDomainList = tenantBinding.getRepositoryDomain();
- for (RepositoryDomainType repoDomain : repoDomainList) {
- String repoDomainName = repoDomain.getName();
- String repositoryName = repoDomain.getRepositoryName();
- String cspaceInstanceId = getCspaceInstanceId();
- String dbName = JDBCTools.getDatabaseName(repositoryName, cspaceInstanceId);
- if (nuxeoDBsChecked.contains(dbName)) {
- if (logger.isDebugEnabled()) {
- logger.debug("Another user of db: " + dbName + ": Repo: " + repoDomainName
- + " and tenant: " + tName + " (id:" + tId + ")");
- }
- } else {
- if (logger.isDebugEnabled()) {
- logger.debug("Need to prepare db: " + dbName + " for Repo: " + repoDomainName
- + " and tenant: " + tName + " (id:" + tId + ")");
- }
- boolean dbExists = JDBCTools.hasDatabase(dbType, dbName);
- if (dbExists) {
- if (logger.isDebugEnabled()) {
- logger.debug("Database: " + dbName + " already exists.");
- }
- } else {
- // Create the user as needed
- JDBCTools.createNewDatabaseUser(JDBCTools.CSADMIN_DATASOURCE_NAME, repositoryName, cspaceInstanceId, dbType, nuxeoUser, nuxeoPW);
- if (readerUser != null) {
- JDBCTools.createNewDatabaseUser(JDBCTools.CSADMIN_DATASOURCE_NAME, repositoryName, cspaceInstanceId, dbType, readerUser, readerPW);
- }
- // Create the database
- createDatabaseWithRights(dbType, dbName, nuxeoUser, nuxeoPW, readerUser, readerPW);
- }
- nuxeoDBsChecked.add(dbName);
- }
- } // Loop on repos for tenant
- } // Loop on tenants
+ /*
+ * Look through the tenant bindings and create the required Nuxeo databases -each tenant can declare
+ * their own Nuxeo repository/database.
+ * Get the NuxeoDS info and create the necessary databases.
+ * Consider the tenant bindings to find and get the data sources for each tenant.
+ * There may be only one, one per tenant, or something in between.
+ *
+ */
+ private HashSet<String> createNuxeoDatabases() throws Exception {
+ String nuxeoUser = getBasicDataSourceUsername(JDBCTools.NUXEO_DATASOURCE_NAME);
+ String nuxeoPW = getBasicDataSourcePassword(JDBCTools.NUXEO_DATASOURCE_NAME);
+
+ String readerUser = getBasicDataSourceUsername(JDBCTools.NUXEO_READER_DATASOURCE_NAME);
+ String readerPW = getBasicDataSourcePassword(JDBCTools.NUXEO_READER_DATASOURCE_NAME);
+
+ DatabaseProductType dbType = JDBCTools.getDatabaseProductType(JDBCTools.CSADMIN_DATASOURCE_NAME,
+ getServiceConfig().getDbCsadminName());
+
+ Hashtable<String, TenantBindingType> tenantBindings =
+ tenantBindingConfigReader.getTenantBindings();
+ HashSet<String> nuxeoDBsChecked = new HashSet<String>();
+
+ // First check and create the roles as needed. (nuxeo and reader)
+ for (TenantBindingType tenantBinding : tenantBindings.values()) {
+ String tId = tenantBinding.getId();
+ String tName = tenantBinding.getName();
+
+ List<RepositoryDomainType> repoDomainList = tenantBinding.getRepositoryDomain();
+ for (RepositoryDomainType repoDomain : repoDomainList) {
+ String repoDomainName = repoDomain.getName();
+ String repositoryName = repoDomain.getRepositoryName();
+ String cspaceInstanceId = getCspaceInstanceId();
+ String dbName = JDBCTools.getDatabaseName(repositoryName, cspaceInstanceId);
+ if (nuxeoDBsChecked.contains(dbName)) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Another user of db: " + dbName + ": Repo: " + repoDomainName
+ + " and tenant: " + tName + " (id:" + tId + ")");
+ }
+ } else {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Need to prepare db: " + dbName + " for Repo: " + repoDomainName
+ + " and tenant: " + tName + " (id:" + tId + ")");
+ }
+ boolean dbExists = JDBCTools.hasDatabase(dbType, dbName);
+ if (dbExists) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Database: " + dbName + " already exists.");
+ }
+ } else {
+ // Create the user as needed
+ JDBCTools.createNewDatabaseUser(JDBCTools.CSADMIN_DATASOURCE_NAME, repositoryName, cspaceInstanceId, dbType, nuxeoUser, nuxeoPW);
+ if (readerUser != null) {
+ JDBCTools.createNewDatabaseUser(JDBCTools.CSADMIN_DATASOURCE_NAME, repositoryName, cspaceInstanceId, dbType, readerUser, readerPW);
+ }
+ // Create the database
+ createDatabaseWithRights(dbType, dbName, nuxeoUser, nuxeoPW, readerUser, readerPW);
+ initRepositoryDatabaseVersion(JDBCTools.NUXEO_DATASOURCE_NAME, repositoryName, cspaceInstanceId);
+ }
+ nuxeoDBsChecked.add(dbName);
+ }
+ } // Loop on repos for tenant
+ } // Loop on tenants
- return nuxeoDBsChecked;
+ return nuxeoDBsChecked;
- }
+ }
- /**
- * Creates a Nuxeo-managed database, sets up an owner for that
- * database, and adds (at least) connection privileges to a reader
- * of that database.
- *
- * @param conn
- * @param dbType
- * @param dbName
- * @param ownerName
- * @param ownerPW
- * @param readerName
- * @param readerPW
- * @throws Exception
- */
+ /**
+ * Creates a Nuxeo-managed database, sets up an owner for that
+ * database, and adds (at least) connection privileges to a reader
+ * of that database.
+ *
+ * @param conn
+ * @param dbType
+ * @param dbName
+ * @param ownerName
+ * @param ownerPW
+ * @param readerName
+ * @param readerPW
+ * @throws Exception
+ */
private void createDatabaseWithRights(DatabaseProductType dbType, String dbName, String ownerName,
String ownerPW, String readerName, String readerPW) throws Exception {
- Connection conn = null;
+ Connection conn = null;
Statement stmt = null;
try {
DataSource csadminDataSource = JDBCTools.getDataSource(JDBCTools.CSADMIN_DATASOURCE_NAME);
if (stmt != null) {
stmt.close();
}
- if (conn != null) {
- conn.close();
- }
+ if (conn != null) {
+ conn.close();
+ }
} catch (SQLException se) {
se.printStackTrace();
}
}
+ }
+
+ private void initRepositoryDatabaseVersion(String dataSourceName, String repositoryName, String cspaceInstanceId) throws Exception {
+ String version = ServiceMain.VER_MAJOR + "." + ServiceMain.VER_MINOR + "." + ServiceMain.VER_PATCH;
+ Connection conn = null;
+
+ try {
+ conn = JDBCTools.getConnection(dataSourceName, repositoryName, cspaceInstanceId);
+ JDBCTools.setRepositoryDatabaseVersion(conn, version);
+ }
+ finally {
+ if (conn != null) {
+ conn.close();
+ }
+ }
}
private BasicDataSource getBasicDataSource(String dataSourceName) {
*/
package org.collectionspace.services.common.storage;
+import org.collectionspace.services.common.ServiceMain;
+import org.collectionspace.services.common.api.JEEServerDeployment;
import org.collectionspace.services.common.api.Tools;
import org.collectionspace.services.common.config.ConfigUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java_cup.version;
+
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;
import java.sql.DatabaseMetaData;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.Reader;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
import java.util.HashMap;
+import java.util.LinkedHashMap;
import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
import javax.sql.rowset.CachedRowSet;
import javax.sql.rowset.RowSetFactory;
import javax.sql.rowset.RowSetProvider;
+import org.apache.ibatis.jdbc.ScriptRunner;
import org.apache.tomcat.dbcp.dbcp2.BasicDataSource;
/**
public static String DEFAULT_NUXEO_REPOSITORY_NAME = ConfigUtils.DEFAULT_NUXEO_REPOSITORY_NAME;
public static String DEFAULT_NUXEO_DATABASE_NAME = ConfigUtils.DEFAULT_NUXEO_DATABASE_NAME;
public static String CSADMIN_DATASOURCE_NAME = "CsadminDS";
+ public static String CSADMIN_NUXEO_DATASOURCE_NAME = "Csadmin_NuxeoDS";
public static String NUXEO_READER_DATASOURCE_NAME = "NuxeoReaderDS";
public static String NUXEO_USER_NAME = "nuxeo";
public static String SQL_WILDCARD = "%";
public static String DATABASE_SELECT_PRIVILEGE_NAME = "SELECT";
public static String POSTGRES_UNIQUE_VIOLATION = "23505";
+ private final static String DATABASE_RESOURCE_DIRECTORY_NAME = "db";
//
// Private constants
//
private static String DBProductName = null;
+ private static String serverResourcesPath;
//todo: make sure this will get instantiated in the right order
final static Logger logger = LoggerFactory.getLogger(JDBCTools.class);
}
}
+ public static boolean cspaceMetaTableExists(Connection conn) throws SQLException {
+ Statement stmt = conn.createStatement();
+ ResultSet rs = stmt.executeQuery("SELECT true FROM pg_tables WHERE schemaname = 'cspace' AND tablename = 'meta'");
+
+ boolean exists = false;
+
+ if (rs.next()) {
+ exists = rs.getBoolean(1);
+ };
+
+ rs.close();
+ stmt.close();
+
+ return exists;
+ }
+
+ public static void createCspaceMetaTable(Connection conn) throws SQLException {
+ Statement stmt = conn.createStatement();
+
+ stmt.executeUpdate("CREATE SCHEMA cspace");
+ stmt.executeUpdate("CREATE TABLE cspace.meta (version varchar(32))");
+ stmt.executeUpdate("INSERT INTO cspace.meta (version) values (null)");
+
+ stmt.close();
+ }
+
+ public static String getRepositoryDatabaseVersion(Connection conn) throws SQLException {
+ String version = "0";
+
+ if (cspaceMetaTableExists(conn)) {
+ Statement stmt = conn.createStatement();
+ ResultSet rs = stmt.executeQuery("SELECT version FROM cspace.meta");
+
+ if (rs.next()) {
+ version = rs.getString(1);
+ }
+
+ rs.close();
+ stmt.close();
+ }
+
+ return version;
+ }
+
+ public static void setRepositoryDatabaseVersion(Connection conn, String version) throws SQLException {
+ if (!cspaceMetaTableExists(conn)) {
+ createCspaceMetaTable(conn);
+ }
+
+ PreparedStatement stmt = conn.prepareStatement("UPDATE cspace.meta SET version = ?");
+
+ stmt.setString(1, version);
+ stmt.executeUpdate();
+
+ stmt.close();
+ }
+
+ public static void runScript(Connection conn, File scriptFile) throws FileNotFoundException {
+ ScriptRunner scriptRunner = new ScriptRunner(conn);
+ Reader reader = new BufferedReader(new FileReader(scriptFile));
+
+ scriptRunner.runScript(reader);
+ }
}