From 771d802165c639f83d01dc0407c70d517d62912b Mon Sep 17 00:00:00 2001 From: Ray Lee Date: Fri, 13 Mar 2020 19:52:36 -0700 Subject: [PATCH] DRYD-835: Upgrade database after nuxeo initialization. --- .../src/main/webapp/META-INF/context.xml | 13 + services/common/pom.xml | 5 + ...CollectionSpaceServiceContextListener.java | 13 +- .../services/common/ServiceMain.java | 404 ++++++++++++++---- .../services/common/storage/JDBCTools.java | 82 ++++ .../systeminfo/SystemInfoResource.java | 10 +- .../6.0.0/post-init/01_organization.sql | 10 + .../upgrade/6.0.0/post-init/02_intake.sql | 17 + .../03_annotation-collectionobject.sql | 14 + .../post-init/04_anthro-collectionobject.sql | 11 + 10 files changed, 487 insertions(+), 92 deletions(-) create mode 100644 src/main/resources/db/postgresql/upgrade/6.0.0/post-init/01_organization.sql create mode 100644 src/main/resources/db/postgresql/upgrade/6.0.0/post-init/02_intake.sql create mode 100644 src/main/resources/db/postgresql/upgrade/6.0.0/post-init/03_annotation-collectionobject.sql create mode 100644 src/main/resources/db/postgresql/upgrade/6.0.0/post-init/04_anthro-collectionobject.sql diff --git a/services/JaxRsServiceProvider/src/main/webapp/META-INF/context.xml b/services/JaxRsServiceProvider/src/main/webapp/META-INF/context.xml index 4cdf658f8..e0026b701 100644 --- a/services/JaxRsServiceProvider/src/main/webapp/META-INF/context.xml +++ b/services/JaxRsServiceProvider/src/main/webapp/META-INF/context.xml @@ -106,6 +106,19 @@ validationQuery="SELECT 1" logAbandoned="true"/> + + jackson-core 2.8.0 + + org.mybatis + mybatis + 3.5.4 + org.easymock diff --git a/services/common/src/main/java/org/collectionspace/services/common/CollectionSpaceServiceContextListener.java b/services/common/src/main/java/org/collectionspace/services/common/CollectionSpaceServiceContextListener.java index 07deb97a1..527bc06cf 100644 --- a/services/common/src/main/java/org/collectionspace/services/common/CollectionSpaceServiceContextListener.java +++ b/services/common/src/main/java/org/collectionspace/services/common/CollectionSpaceServiceContextListener.java @@ -17,15 +17,18 @@ public class CollectionSpaceServiceContextListener implements ServletContextList @Override public void contextInitialized(ServletContextEvent event) { - try { + try { // // Initialize/start the Nuxeo EP server instance and create/retrieve the service workspaces // ServletContext servletContext = event.getServletContext(); ServiceMain svcMain = ServiceMain.getInstance(servletContext); - + svcMain.retrieveAllWorkspaceIds(); - + + // Upgrade database schema + svcMain.upgradeDatabase(); + // Create required indexes (aka indices) in tables not associated // with any specific tenant. svcMain.createRequiredIndices(); @@ -34,7 +37,7 @@ public class CollectionSpaceServiceContextListener implements ServletContextList // Typically, these handlers modify column types and add indexes to the Nuxeo db schema. // svcMain.firePostInitHandlers(); - + } catch (Throwable e) { e.printStackTrace(); //fail here @@ -48,7 +51,7 @@ public class CollectionSpaceServiceContextListener implements ServletContextList @Override public void contextDestroyed(ServletContextEvent event) { ServiceMain instance = null; - + try { ServiceMain.getInstance(); } catch (Throwable t) { 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 4b21b2406..27b3aab8b 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 @@ -6,11 +6,16 @@ package org.collectionspace.services.common; 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; @@ -65,8 +70,14 @@ import org.slf4j.LoggerFactory; * @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; /** @@ -438,6 +449,218 @@ public class ServiceMain { } } + private String applyRepositoryUpgradeScripts(Connection conn, String dataSourceName, String repositoryName, String fromVersion, String stage) throws Exception { + Map> upgradeScriptFiles = getRepositoryUpgradeScripts(dataSourceName, repositoryName, fromVersion, stage); + Set 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 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 tenantBindingTypeMap = tenantBindingConfigReader.getTenantBindings(); + + // Loop through all tenants in tenant-bindings.xml + + String cspaceInstanceId = getCspaceInstanceId(); + + for (TenantBindingType tbt : tenantBindingTypeMap.values()) { + List 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> getRepositoryUpgradeScripts(String dataSourceName, String repositoryName, String fromVersion, String stage) throws Exception { + Map> 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 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 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 { + 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 { + 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. @@ -667,90 +890,92 @@ public class ServiceMain { } 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 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 tenantBindings = - tenantBindingConfigReader.getTenantBindings(); - HashSet nuxeoDBsChecked = new HashSet(); + } - // First check and create the roles as needed. (nuxeo and reader) - for (TenantBindingType tenantBinding : tenantBindings.values()) { - String tId = tenantBinding.getId(); - String tName = tenantBinding.getName(); - - List repoDomainList = tenantBinding.getRepositoryDomain(); - for (RepositoryDomainType repoDomain : repoDomainList) { - String repoDomainName = repoDomain.getName(); - String repositoryName = repoDomain.getRepositoryName(); - String cspaceInstanceId = getCspaceInstanceId(); - String dbName = JDBCTools.getDatabaseName(repositoryName, cspaceInstanceId); - if (nuxeoDBsChecked.contains(dbName)) { - if (logger.isDebugEnabled()) { - logger.debug("Another user of db: " + dbName + ": Repo: " + repoDomainName - + " and tenant: " + tName + " (id:" + tId + ")"); - } - } else { - if (logger.isDebugEnabled()) { - logger.debug("Need to prepare db: " + dbName + " for Repo: " + repoDomainName - + " and tenant: " + tName + " (id:" + tId + ")"); - } - boolean dbExists = JDBCTools.hasDatabase(dbType, dbName); - if (dbExists) { - if (logger.isDebugEnabled()) { - logger.debug("Database: " + dbName + " already exists."); - } - } else { - // Create the user as needed - JDBCTools.createNewDatabaseUser(JDBCTools.CSADMIN_DATASOURCE_NAME, repositoryName, cspaceInstanceId, dbType, nuxeoUser, nuxeoPW); - if (readerUser != null) { - JDBCTools.createNewDatabaseUser(JDBCTools.CSADMIN_DATASOURCE_NAME, repositoryName, cspaceInstanceId, dbType, readerUser, readerPW); - } - // Create the database - createDatabaseWithRights(dbType, dbName, nuxeoUser, nuxeoPW, readerUser, readerPW); - } - nuxeoDBsChecked.add(dbName); - } - } // 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 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 tenantBindings = + tenantBindingConfigReader.getTenantBindings(); + HashSet nuxeoDBsChecked = new HashSet(); + + // First check and create the roles as needed. (nuxeo and reader) + for (TenantBindingType tenantBinding : tenantBindings.values()) { + String tId = tenantBinding.getId(); + String tName = tenantBinding.getName(); + + List repoDomainList = tenantBinding.getRepositoryDomain(); + for (RepositoryDomainType repoDomain : repoDomainList) { + String repoDomainName = repoDomain.getName(); + String repositoryName = repoDomain.getRepositoryName(); + String cspaceInstanceId = getCspaceInstanceId(); + String dbName = JDBCTools.getDatabaseName(repositoryName, cspaceInstanceId); + if (nuxeoDBsChecked.contains(dbName)) { + if (logger.isDebugEnabled()) { + logger.debug("Another user of db: " + dbName + ": Repo: " + repoDomainName + + " and tenant: " + tName + " (id:" + tId + ")"); + } + } else { + if (logger.isDebugEnabled()) { + logger.debug("Need to prepare db: " + dbName + " for Repo: " + repoDomainName + + " and tenant: " + tName + " (id:" + tId + ")"); + } + boolean dbExists = JDBCTools.hasDatabase(dbType, dbName); + if (dbExists) { + if (logger.isDebugEnabled()) { + logger.debug("Database: " + dbName + " already exists."); + } + } else { + // Create the user as needed + JDBCTools.createNewDatabaseUser(JDBCTools.CSADMIN_DATASOURCE_NAME, repositoryName, cspaceInstanceId, dbType, nuxeoUser, nuxeoPW); + if (readerUser != null) { + JDBCTools.createNewDatabaseUser(JDBCTools.CSADMIN_DATASOURCE_NAME, repositoryName, cspaceInstanceId, dbType, readerUser, readerPW); + } + // Create the database + createDatabaseWithRights(dbType, dbName, nuxeoUser, nuxeoPW, readerUser, readerPW); + 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); @@ -800,14 +1025,29 @@ public class ServiceMain { 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) { diff --git a/services/common/src/main/java/org/collectionspace/services/common/storage/JDBCTools.java b/services/common/src/main/java/org/collectionspace/services/common/storage/JDBCTools.java index 8b68ca3d2..ba715cf52 100644 --- a/services/common/src/main/java/org/collectionspace/services/common/storage/JDBCTools.java +++ b/services/common/src/main/java/org/collectionspace/services/common/storage/JDBCTools.java @@ -17,17 +17,26 @@ */ 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; @@ -36,13 +45,20 @@ import java.sql.ResultSetMetaData; 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; /** @@ -59,16 +75,19 @@ public class JDBCTools { 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); @@ -755,4 +774,67 @@ public class JDBCTools { } } + 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); + } } diff --git a/services/systeminfo/service/src/main/java/org/collectionspace/services/systeminfo/SystemInfoResource.java b/services/systeminfo/service/src/main/java/org/collectionspace/services/systeminfo/SystemInfoResource.java index 07ba009b0..24e45547a 100644 --- a/services/systeminfo/service/src/main/java/org/collectionspace/services/systeminfo/SystemInfoResource.java +++ b/services/systeminfo/service/src/main/java/org/collectionspace/services/systeminfo/SystemInfoResource.java @@ -59,12 +59,12 @@ public class SystemInfoResource extends AbstractCollectionSpaceResourceImpl