diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/openbisauth/OpenBISAuthenticationInterceptor.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/openbisauth/OpenBISAuthenticationInterceptor.java index 18571940cb5f9c1c8f47b1bcfddead315be4ff3e..d23d8e9b4013d497465a516ca4abedb96ed1a1ea 100644 --- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/openbisauth/OpenBISAuthenticationInterceptor.java +++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/openbisauth/OpenBISAuthenticationInterceptor.java @@ -30,6 +30,7 @@ import ch.systemsx.cisd.openbis.dss.generic.server.ConfigParameters; import ch.systemsx.cisd.openbis.dss.generic.server.EncapsulatedOpenBISService; import ch.systemsx.cisd.openbis.dss.generic.server.SessionTokenManager; import ch.systemsx.cisd.openbis.dss.generic.server.plugins.tasks.IPluginTaskInfoProvider; +import ch.systemsx.cisd.openbis.dss.generic.shared.IDataSourceProvider; import ch.systemsx.cisd.openbis.dss.generic.shared.ManagedAuthentication; import ch.systemsx.cisd.openbis.dss.generic.shared.utils.DssPropertyParametersUtil; import ch.systemsx.cisd.openbis.generic.shared.IETLLIMSService; @@ -87,10 +88,13 @@ public class OpenBISAuthenticationInterceptor implements MethodInterceptor private final IETLLIMSService service; + private final IDataSourceProvider dataSourceProvider; + public OpenBISAuthenticationInterceptor(SessionTokenManager sessionTokenManager, IETLLIMSService service, IPluginTaskInfoProvider pluginTaskParameters, - OpenBISSessionHolder sessionHolder) + IDataSourceProvider dataSourceProvider, OpenBISSessionHolder sessionHolder) { + this.dataSourceProvider = dataSourceProvider; assert sessionTokenManager != null : "Unspecified session token manager."; assert service != null : "Given IETLLIMSService implementation can not be null."; assert pluginTaskParameters != null : "Unspecified plugin tasks"; @@ -156,6 +160,8 @@ public class OpenBISAuthenticationInterceptor implements MethodInterceptor dataStoreServerInfo.setServicesDescriptions(pluginTaskDescriptions); dataStoreServerInfo.setArchiverConfigured(archiverConfigured); dataStoreServerInfo.setTimeoutInMinutes(timeoutInMinutes); + dataStoreServerInfo.setDataSourceDefinitions(dataSourceProvider.getAllDataSourceDefinitions()); + service.registerDataStoreServer(sessionToken, dataStoreServerInfo); } diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/DataSourceProvider.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/DataSourceProvider.java index d7e20b1b55f66a1c3b9a61a6ddcdd92dfe77d479..b3b48e0bd9e9f6246c6248a6715718cbf3a4bd1b 100644 --- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/DataSourceProvider.java +++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/DataSourceProvider.java @@ -16,7 +16,9 @@ package ch.systemsx.cisd.openbis.dss.generic.shared; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Properties; @@ -32,6 +34,9 @@ import ch.systemsx.cisd.common.properties.PropertyUtils; import ch.systemsx.cisd.common.properties.PropertyParametersUtil.SectionProperties; import ch.systemsx.cisd.common.reflection.ClassUtils; import ch.systemsx.cisd.openbis.dss.generic.shared.utils.DssPropertyParametersUtil; +import ch.systemsx.cisd.openbis.generic.shared.dto.DataSourceDefinition; +import ch.systemsx.cisd.openbis.generic.shared.dto.DataSourceWithDefinition; +import ch.systemsx.cisd.openbis.generic.shared.util.IDataSourceFactory; /** * Stores and provides access to data sources defined in properties file. @@ -75,14 +80,17 @@ public class DataSourceProvider implements IDataSourceProvider private static final Logger operationLog = LogFactory.getLogger(LogCategory.OPERATION, DataSourceProvider.class); - private final Map<String, DataSource> dataSources; + private final Map<String, DataSourceWithDefinition> dataSources; + + private final List<DataSourceDefinition> dataSourceDefinitions = + new ArrayList<DataSourceDefinition>(); public static final String DATA_SOURCE_KEY = "data-source"; private DataSourceProvider() { Properties properties = DssPropertyParametersUtil.loadServiceProperties(); - dataSources = new HashMap<String, DataSource>(); + dataSources = new HashMap<String, DataSourceWithDefinition>(); SectionProperties[] props = PropertyParametersUtil .extractSectionProperties(properties, Constants.DATA_SOURCES_KEY, false); @@ -97,7 +105,13 @@ public class DataSourceProvider implements IDataSourceProvider { IDataSourceFactory factory = ClassUtils.create(IDataSourceFactory.class, dataSourceFactoryClass); - DataSource dataSource = factory.create(dataSourceProperties); + DataSourceWithDefinition dataSource = factory.create(dataSourceProperties); + DataSourceDefinition definition = dataSource.getDefinitionOrNull(); + if (definition != null) + { + definition.setCode(dataSourceName); + dataSourceDefinitions.add(definition); + } dataSources.put(dataSourceName, dataSource); if (operationLog.isInfoEnabled()) { @@ -124,14 +138,14 @@ public class DataSourceProvider implements IDataSourceProvider @Override public DataSource getDataSource(String name) throws IllegalArgumentException { - DataSource result = dataSources.get(name); + DataSourceWithDefinition result = dataSources.get(name); if (result == null) { String message = "Data source '" + name + "' has not been configured."; throw new IllegalArgumentException(message); } else { - return result; + return result.getDataSource(); } } @@ -145,6 +159,12 @@ public class DataSourceProvider implements IDataSourceProvider return getDataSource(extractDataSourceName(properties)); } + @Override + public List<DataSourceDefinition> getAllDataSourceDefinitions() + { + return dataSourceDefinitions; + } + /** * Extracts data source name ({@link #DATA_SOURCE_KEY}) from properties. */ diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/DefaultDataSourceFactory.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/DefaultDataSourceFactory.java index ad60a328c613118ac15f1efa9be115c344a03a9f..420dd295b6945fdcad57efda3a75f84aa55657f3 100644 --- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/DefaultDataSourceFactory.java +++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/DefaultDataSourceFactory.java @@ -25,6 +25,9 @@ import ch.systemsx.cisd.common.reflection.BeanUtils; import ch.systemsx.cisd.common.reflection.ClassUtils; import ch.systemsx.cisd.dbmigration.DBMigrationEngine; import ch.systemsx.cisd.dbmigration.DatabaseConfigurationContext; +import ch.systemsx.cisd.openbis.generic.shared.dto.DataSourceDefinition; +import ch.systemsx.cisd.openbis.generic.shared.dto.DataSourceWithDefinition; +import ch.systemsx.cisd.openbis.generic.shared.util.IDataSourceFactory; /** * Creates a {@link DataSource} using {@link DatabaseConfigurationContext} and given properties. The @@ -39,7 +42,7 @@ public class DefaultDataSourceFactory implements IDataSourceFactory public static final String VERSION_HOLDER_CLASS_KEY = "version-holder-class"; @Override - public DataSource create(Properties dbProps) + public DataSourceWithDefinition create(Properties dbProps) { DatabaseConfigurationContext context = BeanUtils.createBean(DatabaseConfigurationContext.class, dbProps); @@ -59,6 +62,7 @@ public class DefaultDataSourceFactory implements IDataSourceFactory String version = versionHolder.getDatabaseVersion(); DBMigrationEngine.createOrMigrateDatabaseAndGetScriptProvider(context, version); } - return context.getDataSource(); + return new DataSourceWithDefinition(context.getDataSource(), + DataSourceDefinition.createFromContext(context)); } } diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/IDataSourceProvider.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/IDataSourceProvider.java index b0cf222503d4227e86875fa8cd33d36893faf172..bfc680bffc05982db06eca191f6ddd5a8079c7e6 100644 --- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/IDataSourceProvider.java +++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/IDataSourceProvider.java @@ -16,10 +16,13 @@ package ch.systemsx.cisd.openbis.dss.generic.shared; +import java.util.List; import java.util.Properties; import javax.sql.DataSource; +import ch.systemsx.cisd.openbis.generic.shared.dto.DataSourceDefinition; + /** * A provider for data sources. * @@ -39,5 +42,10 @@ public interface IDataSourceProvider * source by calling {@link #getDataSource(String)}. */ public DataSource getDataSource(Properties properties); + + /** + * Returns all data source definitions. Note, that not all data sources have a definition. + */ + public List<DataSourceDefinition> getAllDataSourceDefinitions(); } diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/SimpleDataSourceFactory.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/SimpleDataSourceFactory.java index dce0fa8d6af06d3d3b1138835d84a136b60b3f35..65c2632e9e8689ea7cd596e37a8ce38ac9cadb8c 100644 --- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/SimpleDataSourceFactory.java +++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/SimpleDataSourceFactory.java @@ -21,6 +21,8 @@ import java.util.Properties; import javax.sql.DataSource; import ch.systemsx.cisd.dbmigration.SimpleDatabaseConfigurationContext; +import ch.systemsx.cisd.openbis.generic.shared.dto.DataSourceWithDefinition; +import ch.systemsx.cisd.openbis.generic.shared.util.IDataSourceFactory; /** * Creates a commons-dbcp {@link DataSource} using its standard properties. @@ -31,11 +33,11 @@ public class SimpleDataSourceFactory implements IDataSourceFactory { @Override - public DataSource create(Properties dbProps) + public DataSourceWithDefinition create(Properties dbProps) { SimpleDatabaseConfigurationContext context = new SimpleDatabaseConfigurationContext(dbProps); - return context.getDataSource(); + return new DataSourceWithDefinition(context.getDataSource(), null); } } diff --git a/datastore_server/source/java/dssApplicationContext.xml b/datastore_server/source/java/dssApplicationContext.xml index 2471731203a85a2e34f509c25274a65d62f9f0be..8883e1790d43b17e8b12f82dd32b4ed9bb302ba5 100644 --- a/datastore_server/source/java/dssApplicationContext.xml +++ b/datastore_server/source/java/dssApplicationContext.xml @@ -61,6 +61,7 @@ <constructor-arg ref="etl-lims-service"/> <constructor-arg ref="plugin-tasks" /> <constructor-arg ref="sessionHolder" /> + <constructor-arg ref="data-source-provider" /> <property name="username" value="${username}"/> <property name="password" value="${password}"/> <property name="port" value="${port}"/> diff --git a/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/server/openbisauth/OpenBISAuthenticationInterceptorTest.java b/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/server/openbisauth/OpenBISAuthenticationInterceptorTest.java index 2a1bf6c06dd5045fcd71a686f1a9e350a6a967d1..727cd0e4b5a2739437cd2a19647f1d1867d4975f 100644 --- a/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/server/openbisauth/OpenBISAuthenticationInterceptorTest.java +++ b/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/server/openbisauth/OpenBISAuthenticationInterceptorTest.java @@ -19,6 +19,7 @@ package ch.systemsx.cisd.openbis.dss.generic.server.openbisauth; import static org.testng.AssertJUnit.assertEquals; import java.io.File; +import java.util.Arrays; import org.aopalliance.intercept.MethodInvocation; import org.hamcrest.BaseMatcher; @@ -32,9 +33,11 @@ import org.testng.annotations.Test; import ch.systemsx.cisd.common.exceptions.InvalidSessionException; import ch.systemsx.cisd.openbis.dss.generic.server.SessionTokenManager; +import ch.systemsx.cisd.openbis.dss.generic.shared.IDataSourceProvider; import ch.systemsx.cisd.openbis.dss.generic.shared.utils.PluginUtilTest; import ch.systemsx.cisd.openbis.generic.shared.IETLLIMSService; import ch.systemsx.cisd.openbis.generic.shared.dto.DataStoreServerInfo; +import ch.systemsx.cisd.openbis.generic.shared.dto.DataSourceDefinition; import ch.systemsx.cisd.openbis.generic.shared.dto.OpenBISSessionHolder; import ch.systemsx.cisd.openbis.generic.shared.dto.SessionContextDTO; @@ -66,18 +69,23 @@ public class OpenBISAuthenticationInterceptorTest private OpenBISSessionHolder sessionHolder; + private IDataSourceProvider dataSourceProvider; + @BeforeMethod public void setUp() { context = new Mockery(); limsService = context.mock(IETLLIMSService.class); methodInvocation = context.mock(MethodInvocation.class); + dataSourceProvider = context.mock(IDataSourceProvider.class); sessionHolder = new OpenBISSessionHolder(); sessionHolder.setDataStoreCode(DATA_STORE_CODE); - interceptor = new OpenBISAuthenticationInterceptor(new SessionTokenManager(), limsService, - PluginUtilTest.createPluginTaskProviders(new File(".")), sessionHolder); + interceptor = + new OpenBISAuthenticationInterceptor(new SessionTokenManager(), limsService, + PluginUtilTest.createPluginTaskProviders(new File(".")), + dataSourceProvider, sessionHolder); interceptor.setUsername(LIMS_USER); interceptor.setPassword(LIMS_PASSWORD); @@ -103,6 +111,9 @@ public class OpenBISAuthenticationInterceptorTest will(throwException(new InvalidSessionException("error"))); setUpAuthenticationExpectations(this); one(methodInvocation).proceed(); + + one(dataSourceProvider).getAllDataSourceDefinitions(); + will(returnValue(Arrays.asList(DataSourceDefinition.fromString("code=a")))); } }); @@ -121,6 +132,9 @@ public class OpenBISAuthenticationInterceptorTest { setUpAuthenticationExpectations(this); one(methodInvocation).proceed(); + + one(dataSourceProvider).getAllDataSourceDefinitions(); + will(returnValue(Arrays.asList(DataSourceDefinition.fromString("code=a")))); } }); @@ -171,7 +185,8 @@ public class OpenBISAuthenticationInterceptorTest && info.getServicesDescriptions() .getProcessingServiceDescriptions().size() == 0 && info.getServicesDescriptions().getReportingServiceDescriptions() - .size() == 0; + .size() == 0 + && info.getDataSourceDefinitions().toString().equals("[code=a\t]"); } return false; } diff --git a/dbmigration/source/java/ch/systemsx/cisd/dbmigration/DatabaseEngine.java b/dbmigration/source/java/ch/systemsx/cisd/dbmigration/DatabaseEngine.java index fc6f2e50fcca26aecf4bc727fbeb076a69f88e24..023387c73c5d2224ccc6ad72f2c71e19c14af41c 100644 --- a/dbmigration/source/java/ch/systemsx/cisd/dbmigration/DatabaseEngine.java +++ b/dbmigration/source/java/ch/systemsx/cisd/dbmigration/DatabaseEngine.java @@ -47,6 +47,8 @@ public enum DatabaseEngine "jdbc:h2:{0}{1};DB_CLOSE_DELAY=-1", "file:db/", "sa", null); private static Map<String, DatabaseEngine> engines = initEngineMap(); + + private static Map<String, DatabaseEngine> enginesByDriverClass = initEnginesByDriverClassMap(); private final String code; @@ -197,6 +199,30 @@ public enum DatabaseEngine } return engine; } + + /** + * Returns the database engine for specified driver class. + * + * @throws IllegalArgumentException if no engine could be found. + */ + public static DatabaseEngine getEngineForDriverClass(String driverClassName) + { + final DatabaseEngine engine = enginesByDriverClass.get(driverClassName); + if (engine == null) + { + throw new IllegalArgumentException("No database engine with driver class " + + driverClassName + " found."); + } + return engine; + } + + /** + * Returns <code>true</code> if a {@link DatabaseEngine} for specified driver class exists. + */ + public static boolean hasEngineForDriverClass(String driverClassName) + { + return enginesByDriverClass.get(driverClassName) != null; + } private static Map<String, DatabaseEngine> initEngineMap() { @@ -208,4 +234,14 @@ public enum DatabaseEngine return map; } + private static Map<String, DatabaseEngine> initEnginesByDriverClassMap() + { + final Map<String, DatabaseEngine> map = new HashMap<String, DatabaseEngine>(); + for (DatabaseEngine engine : values()) + { + map.put(engine.driverClass, engine); + } + return map; + } + } diff --git a/dbmigration/source/java/ch/systemsx/cisd/dbmigration/SimpleDatabaseConfigurationContext.java b/dbmigration/source/java/ch/systemsx/cisd/dbmigration/SimpleDatabaseConfigurationContext.java index ce8cc8419414b76a3cc47dca570c0fff66bf76db..a00f4ed142fa372186726a62a1edb9abd50239b2 100644 --- a/dbmigration/source/java/ch/systemsx/cisd/dbmigration/SimpleDatabaseConfigurationContext.java +++ b/dbmigration/source/java/ch/systemsx/cisd/dbmigration/SimpleDatabaseConfigurationContext.java @@ -47,13 +47,13 @@ public class SimpleDatabaseConfigurationContext implements DisposableBean private static final Logger operationLog = LogFactory.getLogger(LogCategory.OPERATION, SimpleDatabaseConfigurationContext.class); - static final String DRIVER_KEY = "database-driver"; + public static final String DRIVER_KEY = "database-driver"; - static final String URL_KEY = "database-url"; + public static final String URL_KEY = "database-url"; - static final String USER_KEY = "database-username"; + public static final String USER_KEY = "database-username"; - static final String PASSWORD_KEY = "database-password"; + public static final String PASSWORD_KEY = "database-password"; static final String MAX_IDLE_KEY = "database-max-idle-connections"; diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/ETLService.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/ETLService.java index ffdc4659f50133c54292c221658ec874b201c6b8..986506ae2b27884fde706ae1483f6ba506b45739 100644 --- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/ETLService.java +++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/ETLService.java @@ -100,6 +100,7 @@ import ch.systemsx.cisd.openbis.generic.server.dataaccess.IDAOFactory; import ch.systemsx.cisd.openbis.generic.server.dataaccess.IDataDAO; import ch.systemsx.cisd.openbis.generic.server.dataaccess.IDataSetTypeDAO; import ch.systemsx.cisd.openbis.generic.server.dataaccess.IDataStoreDAO; +import ch.systemsx.cisd.openbis.generic.server.dataaccess.IDataStoreDataSourceManager; import ch.systemsx.cisd.openbis.generic.server.dataaccess.IEntityPropertyTypeDAO; import ch.systemsx.cisd.openbis.generic.server.dataaccess.IEntityTypeDAO; import ch.systemsx.cisd.openbis.generic.server.dataaccess.IMetaprojectDAO; @@ -261,6 +262,8 @@ public class ETLService extends AbstractCommonServer<IETLLIMSService> implements private final IDataStoreServiceRegistrator dataStoreServiceRegistrator; + private final IDataStoreDataSourceManager dataSourceManager; + private IServiceConversationClientManagerLocal conversationClient; private IServiceConversationServerManagerLocal conversationServer; @@ -270,11 +273,12 @@ public class ETLService extends AbstractCommonServer<IETLLIMSService> implements ICommonBusinessObjectFactory boFactory, IDataStoreServiceFactory dssFactory, TrustedCrossOriginDomainsProvider trustedOriginDomainProvider, IETLEntityOperationChecker entityOperationChecker, - IDataStoreServiceRegistrator dataStoreServiceRegistrator) + IDataStoreServiceRegistrator dataStoreServiceRegistrator, + IDataStoreDataSourceManager dataSourceManager) { this(authenticationService, sessionManager, daoFactory, null, boFactory, dssFactory, trustedOriginDomainProvider, entityOperationChecker, dataStoreServiceRegistrator, - new DefaultSessionManager<Session>(new SessionFactory(), + dataSourceManager, new DefaultSessionManager<Session>(new SessionFactory(), new LogMessagePrefixGenerator(), new DummyAuthenticationService(), new RequestContextProviderAdapter(new IRequestContextProvider() { @@ -293,6 +297,7 @@ public class ETLService extends AbstractCommonServer<IETLLIMSService> implements TrustedCrossOriginDomainsProvider trustedOriginDomainProvider, IETLEntityOperationChecker entityOperationChecker, IDataStoreServiceRegistrator dataStoreServiceRegistrator, + IDataStoreDataSourceManager dataSourceManager, ISessionManager<Session> sessionManagerForEntityOperation) { super(authenticationService, sessionManager, daoFactory, propertiesBatchManager, boFactory); @@ -301,6 +306,7 @@ public class ETLService extends AbstractCommonServer<IETLLIMSService> implements this.trustedOriginDomainProvider = trustedOriginDomainProvider; this.entityOperationChecker = entityOperationChecker; this.dataStoreServiceRegistrator = dataStoreServiceRegistrator; + this.dataSourceManager = dataSourceManager; this.sessionManagerForEntityOperation = sessionManagerForEntityOperation; } @@ -354,6 +360,7 @@ public class ETLService extends AbstractCommonServer<IETLLIMSService> implements dataStoreDAO.createOrUpdateDataStore(dataStore); dataStoreServiceRegistrator.setServiceDescriptions(dataStore, info.getServicesDescriptions()); + dataSourceManager.handle(info.getDataStoreCode(), info.getDataSourceDefinitions()); conversationClient.setDataStoreInformation(dssURL, info.getTimeoutInMinutes()); conversationServer.setDataStoreInformation(info.getDataStoreCode(), dssURL, diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/DataStoreServerBasedDataSourceProvider.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/DataStoreServerBasedDataSourceProvider.java index a23dfc458ca7a68292051b848f814ee79d319ec1..7bf852bc58dee53f73070a08c7255287ac85f21f 100644 --- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/DataStoreServerBasedDataSourceProvider.java +++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/DataStoreServerBasedDataSourceProvider.java @@ -16,23 +16,48 @@ package ch.systemsx.cisd.openbis.generic.server.dataaccess; +import static ch.systemsx.cisd.dbmigration.SimpleDatabaseConfigurationContext.DRIVER_KEY; +import static ch.systemsx.cisd.dbmigration.SimpleDatabaseConfigurationContext.PASSWORD_KEY; +import static ch.systemsx.cisd.dbmigration.SimpleDatabaseConfigurationContext.URL_KEY; +import static ch.systemsx.cisd.dbmigration.SimpleDatabaseConfigurationContext.USER_KEY; + +import java.io.File; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.EnumMap; import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Properties; +import java.util.Set; +import java.util.regex.Pattern; import javax.annotation.Resource; import javax.sql.DataSource; +import org.apache.commons.lang.StringUtils; +import org.apache.log4j.Logger; import org.springframework.beans.factory.InitializingBean; import ch.systemsx.cisd.common.exceptions.ConfigurationFailureException; -import ch.systemsx.cisd.common.exceptions.UserFailureException; +import ch.systemsx.cisd.common.exceptions.EnvironmentFailureException; +import ch.systemsx.cisd.common.filesystem.FileUtilities; +import ch.systemsx.cisd.common.logging.LogCategory; +import ch.systemsx.cisd.common.logging.LogFactory; import ch.systemsx.cisd.common.properties.ExtendedProperties; import ch.systemsx.cisd.common.properties.PropertyParametersUtil; import ch.systemsx.cisd.common.properties.PropertyParametersUtil.SectionProperties; +import ch.systemsx.cisd.common.shared.basic.string.CommaSeparatedListBuilder; import ch.systemsx.cisd.common.spring.ExposablePropertyPlaceholderConfigurer; +import ch.systemsx.cisd.dbmigration.DatabaseEngine; +import ch.systemsx.cisd.dbmigration.MonitoringDataSource; import ch.systemsx.cisd.dbmigration.SimpleDatabaseConfigurationContext; -import ch.systemsx.cisd.openbis.generic.shared.dto.DataPE; +import ch.systemsx.cisd.openbis.generic.shared.dto.DataSourceDefinition; +import ch.systemsx.cisd.openbis.generic.shared.dto.DataSourceWithDefinition; +import ch.systemsx.cisd.openbis.generic.shared.dto.DataStorePE; +import ch.systemsx.cisd.openbis.generic.shared.util.IDataSourceFactory; /** * Data source provider based on configuration per Data Store Server. @@ -40,22 +65,50 @@ import ch.systemsx.cisd.openbis.generic.shared.dto.DataPE; * @author Franz-Josef Elmer */ public class DataStoreServerBasedDataSourceProvider implements IDataSourceProvider, - InitializingBean + IDataStoreDataSourceManager, InitializingBean { public static final String ROOT_KEY = "dss-based-data-source-provider"; public static final String DATA_STORE_SERVERS_KEY = "data-store-servers"; + private static final Logger operationLog = LogFactory.getLogger(LogCategory.OPERATION, + DataStoreServerBasedDataSourceProvider.class); + @Resource(name = ExposablePropertyPlaceholderConfigurer.PROPERTY_CONFIGURER_BEAN_NAME) private ExposablePropertyPlaceholderConfigurer configurer; + private Map<String, DataSource> dataSourcesByKey = new HashMap<String, DataSource>(); + + private Map<Mapping, DataSource> dataSourcesByMapping = new HashMap<Mapping, DataSource>(); + + private Map<String, Properties> configParametersByKeys = new HashMap<String, Properties>(); + private final IDAOFactory daoFactory; - private Map<String, DataSource> dataSources; + private final MappingManager mappingManager; + + private final IDataSourceFactory dataSourceFactory; + + public DataStoreServerBasedDataSourceProvider(IDAOFactory daoFactory, + String dssDataSourceMappingFilePath) + { + this(daoFactory, dssDataSourceMappingFilePath, new IDataSourceFactory() + { + @Override + public DataSourceWithDefinition create(Properties props) + { + return new DataSourceWithDefinition(new SimpleDatabaseConfigurationContext( + props).getDataSource(), null); + } + }); + } - public DataStoreServerBasedDataSourceProvider(IDAOFactory daoFactory) + DataStoreServerBasedDataSourceProvider(IDAOFactory daoFactory, + String dssDataSourceMappingFilePath, IDataSourceFactory dataSourceFactory) { this.daoFactory = daoFactory; + this.dataSourceFactory = dataSourceFactory; + mappingManager = new MappingManager(dssDataSourceMappingFilePath); } @Override @@ -66,47 +119,513 @@ public class DataStoreServerBasedDataSourceProvider implements IDataSourceProvid void init(Properties props) { - dataSources = new HashMap<String, DataSource>(); SectionProperties[] sectionsProperties = PropertyParametersUtil.extractSectionProperties(props, DATA_STORE_SERVERS_KEY, false); for (SectionProperties sectionProperties : sectionsProperties) { String key = sectionProperties.getKey().toUpperCase(); - Properties properties = sectionProperties.getProperties(); - SimpleDatabaseConfigurationContext context = - new SimpleDatabaseConfigurationContext(properties); - dataSources.put(key, context.getDataSource()); + configParametersByKeys.put(key, sectionProperties.getProperties()); + } + mappingManager.init(configParametersByKeys); + Map<String, List<DataSourceDefinition>> originalDataSourceDefinitions = + new HashMap<String, List<DataSourceDefinition>>(); + List<DataStorePE> dataStores = daoFactory.getDataStoreDAO().listDataStores(); + for (DataStorePE dataStore : dataStores) + { + String code = dataStore.getCode(); + List<DataSourceDefinition> definitions = + DataSourceDefinition.listFromString(dataStore + .getSerializedDataSourceDefinitions()); + originalDataSourceDefinitions.put(code, definitions); + } + for (Entry<String, List<DataSourceDefinition>> entry : originalDataSourceDefinitions + .entrySet()) + { + String dataStoreCode = entry.getKey(); + mappingManager.handle(dataStoreCode, entry.getValue()); + clearDataSourceCaches(dataStoreCode); } } @Override - public DataSource getDataSourceByDataSetCode(String dataSetCode, String technology) + public synchronized DataSource getDataSourceByDataStoreServerCode(String dssCode, + String technology) { - DataPE dataSet = daoFactory.getDataDAO().tryToFindDataSetByCode(dataSetCode); - if (dataSet == null) + String normalizedDssCode = dssCode.toUpperCase(); + String moduleCode = technology.toUpperCase(); + String key = normalizedDssCode + "[" + moduleCode + "]"; + DataSource dataSource = dataSourcesByKey.get(key); + if (dataSource == null) { - throw new UserFailureException("Unknown data set: " + dataSetCode); + dataSource = createDataSource(normalizedDssCode, moduleCode); + dataSourcesByKey.put(key, dataSource); } - return getDataSourceByDataStoreServerCode(dataSet.getDataStore().getCode(), technology); + return dataSource; } - @Override - public DataSource getDataSourceByDataStoreServerCode(String dssCode, String technology) + private DataSource createDataSource(String dataStoreCode, String moduleCode) { - DataSource dataSource = - dataSources.get(dssCode.toUpperCase() + "[" + technology.toUpperCase() + "]"); + Mapping mapping = mappingManager.getMapping(dataStoreCode, moduleCode); + DataSource dataSource = dataSourcesByMapping.get(mapping); if (dataSource == null) { - dataSource = dataSources.get(dssCode.toUpperCase()); + dataSource = createDataSource(mapping); + dataSourcesByMapping.put(mapping, dataSource); } - if (dataSource == null) + return dataSource; + } + + private DataSource createDataSource(Mapping mapping) + { + Properties properties = configParametersByKeys.get(mapping.configKey); + if (properties == null) { - throw new ConfigurationFailureException( - "No data source configured for Data Store Server '" + dssCode - + "' and technology '" + technology + "'."); + throw new ConfigurationFailureException("No data source configured for '" + + mapping.configKey + "'."); } - return dataSource; + properties = ExtendedProperties.createWith(properties); + DataSourceDefinition definitionOrNull = mapping.definitionOrNull; + Properties props = createMergedProperties(properties, definitionOrNull); + return dataSourceFactory.create(props).getDataSource(); + } + + private Properties createMergedProperties(Properties properties, + DataSourceDefinition definitionOrNull) + { + Properties props = ExtendedProperties.createWith(properties); + if (definitionOrNull != null) + { + if (properties.getProperty(DRIVER_KEY) == null) + { + props.setProperty(DRIVER_KEY, definitionOrNull.getDriverClassName()); + } + if (properties.getProperty(USER_KEY) == null) + { + props.setProperty(USER_KEY, definitionOrNull.getUsername()); + } + if (properties.getProperty(PASSWORD_KEY) == null) + { + props.setProperty(PASSWORD_KEY, definitionOrNull.getPassword()); + } + if (properties.getProperty(URL_KEY) == null) + { + DatabaseEngine engine = + DatabaseEngine.getEngineForDriverClass(definitionOrNull + .getDriverClassName()); + String url = + engine.getURL(definitionOrNull.getHostPart(), definitionOrNull.getSid()); + props.setProperty(URL_KEY, url); + } + } + return props; + } + + @Override + public synchronized void handle(String dataStoreCode, + List<DataSourceDefinition> dataSourceDefinitions) + { + IDataStoreDAO dataStoreDAO = daoFactory.getDataStoreDAO(); + DataStorePE dataStore = dataStoreDAO.tryToFindDataStoreByCode(dataStoreCode); + if (dataStore == null) + { + throw new EnvironmentFailureException("Unknown data store: " + dataStoreCode); + } + assertMandatoryAttributesDefined(dataSourceDefinitions); + dataStore.setSerializedDataSourceDefinitions(DataSourceDefinition + .toString(dataSourceDefinitions)); + dataStoreDAO.createOrUpdateDataStore(dataStore); + mappingManager.handle(dataStoreCode, dataSourceDefinitions); + clearDataSourceCaches(dataStoreCode); + } + + private void clearDataSourceCaches(String dataStoreCode) + { + LinkedList<Entry<Mapping, DataSource>> entries = + new LinkedList<Entry<Mapping, DataSource>>(dataSourcesByMapping.entrySet()); + dataSourcesByMapping.clear(); + for (Entry<Mapping, DataSource> entry : entries) + { + Mapping mapping = entry.getKey(); + DataSource dataSource = entry.getValue(); + if (dataStoreCode.equals(mapping.dataStoreCode)) + { + if (dataSource instanceof MonitoringDataSource) + { + try + { + ((MonitoringDataSource) dataSource).close(); + } catch (SQLException ex) + { + DataSourceDefinition definition = mapping.definitionOrNull; + if (definition == null) + { + operationLog.warn("Couldn't close data source for " + mapping.configKey + + "."); + } else + { + operationLog + .warn("Couldn't close data source for database " + + definition.getSid() + " on " + + definition.getHostPart() + "."); + } + } + } + } else + { + dataSourcesByMapping.put(mapping, dataSource); + } + } + dataSourcesByKey.clear(); + } + + private void assertMandatoryAttributesDefined(List<DataSourceDefinition> definitions) + { + StringBuilder errors = new StringBuilder(); + for (DataSourceDefinition definition : definitions) + { + CommaSeparatedListBuilder builder = new CommaSeparatedListBuilder(); + if (StringUtils.isBlank(definition.getCode())) + { + builder.append("code"); + } + if (StringUtils.isBlank(definition.getHostPart())) + { + builder.append("hostPart"); + } + if (StringUtils.isBlank(definition.getSid())) + { + builder.append("sid"); + } + String driverClassName = definition.getDriverClassName(); + if (driverClassName == null + || DatabaseEngine.hasEngineForDriverClass(driverClassName) == false) + { + builder.append("driverClassName"); + } + String error = builder.toString(); + if (error.length() > 0) + { + errors.append("\n").append(error).append("[").append(definition).append("]"); + } + } + if (errors.length() > 0) + { + throw new EnvironmentFailureException( + "Some data source definitions have missing or wrong mandatory attributes: " + + errors); + } + } + + private static final class MappingManager + { + private final String mappingFilePath; + + private List<MappingEntry> mappingEntries; + + private Map<String, Properties> configs; + + private Map<String, Map<String, DataSourceDefinition>> dataSourceDefinitionsByCodes = + new HashMap<String, Map<String, DataSourceDefinition>>(); + + public MappingManager(String mappingFilePath) + { + this.mappingFilePath = mappingFilePath; + } + + public void init(Map<String, Properties> propertiesByKey) + { + mappingEntries = parse(mappingFilePath); + configs = propertiesByKey; + } + + private static List<MappingEntry> parse(String mappingFilePath) + { + List<MappingEntry> mappingEntries = new ArrayList<MappingEntry>(); + File file = new File(mappingFilePath); + if (file.isFile()) + { + List<String> lines = FileUtilities.loadToStringList(file); + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < lines.size(); i++) + { + String line = lines.get(i); + if (StringUtils.isBlank(line) || line.startsWith("#")) + { + continue; + } + try + { + mappingEntries.add(parseLine(line)); + } catch (Exception e) + { + builder.append("\nLine ").append(i + 1).append(": ").append(e.getMessage()); + } + } + if (builder.length() > 0) + { + throw new ConfigurationFailureException("Error(s) in mapping file " + + mappingFilePath + ":" + builder); + } + } + return mappingEntries; + } + + private static MappingEntry parseLine(String line) + { + int indexOfEqualSign = line.indexOf('='); + if (indexOfEqualSign < 0) + { + throw new IllegalArgumentException("Missing '='"); + } + String[] description = line.substring(0, indexOfEqualSign).trim().split("\\."); + if (description.length != 3) + { + throw new IllegalArgumentException( + "Mapping description should have three parts separated by '.'"); + } + Pattern dssCodePattern = createPattern(description[0].toUpperCase()); + Pattern dataSourceCodePattern = createPattern(description[1].toUpperCase()); + String type = description[2]; + String value = line.substring(indexOfEqualSign + 1).trim(); + return new MappingEntry(dssCodePattern, dataSourceCodePattern, type, value); + } + + private static Pattern createPattern(String wildcardPattern) + { + String regex = wildcardPattern.replace("*", ".*"); + return Pattern.compile(regex); + } + + public Mapping getMapping(String dataStoreCode, String moduleCode) + { + String configKey = dataStoreCode + "[" + moduleCode + "]"; + String dataSourceCode = null; + Map<Type, String> replacementsByType = new EnumMap<Type, String>(Type.class); + for (MappingEntry mappingEntry : mappingEntries) + { + String value = mappingEntry.value; + if (mappingEntry.matches(dataStoreCode, moduleCode) && value != null) + { + Type type = mappingEntry.type; + switch (type) + { + case CONFIG: + configKey = + value.replace("[*]", "[" + moduleCode + "]").replace("*", + dataStoreCode); + break; + case DATA_SOURCE: + dataSourceCode = value; + break; + default: + replacementsByType.put(type, value); + } + } + } + DataSourceDefinition definition = + getDefinitionsOrNull(dataStoreCode, dataSourceCode, replacementsByType); + if (configs.containsKey(configKey) == false) + { + if (configs.containsKey(dataStoreCode) == false) + { + throw new EnvironmentFailureException("Couldn't find data source core plugin '" + + configKey + "' nor '" + dataStoreCode + "'."); + } + configKey = dataStoreCode; + } + return new Mapping(dataStoreCode, configKey, definition); + } + + private DataSourceDefinition getDefinitionsOrNull(String dataStoreCode, + String dataSourceCode, Map<Type, String> replacementsByType) + { + if (dataSourceCode == null) + { + return null; + } + Map<String, DataSourceDefinition> definitionsByCode = + dataSourceDefinitionsByCodes.get(dataStoreCode); + if (definitionsByCode == null) + { + return null; + } + DataSourceDefinition definition = definitionsByCode.get(dataSourceCode); + if (definition != null) + { + definition = definition.clone(); + Set<Entry<Type, String>> entrySet = replacementsByType.entrySet(); + for (Entry<Type, String> entry : entrySet) + { + entry.getKey().modify(definition, entry.getValue()); + } + } + return definition; + } + + public void handle(String dataStoreCode, List<DataSourceDefinition> dataSourceDefinitions) + { + Map<String, DataSourceDefinition> definitionsByCode = + dataSourceDefinitionsByCodes.get(dataStoreCode); + if (definitionsByCode == null) + { + definitionsByCode = new HashMap<String, DataSourceDefinition>(); + dataSourceDefinitionsByCodes.put(dataStoreCode, definitionsByCode); + } + for (DataSourceDefinition definition : dataSourceDefinitions) + { + definitionsByCode.put(definition.getCode(), definition); + } + } + + private static final class MappingEntry + { + private final Pattern dssCodePattern; + + private final Pattern moduleCodePattern; + + private final Type type; + + private final String value; + + public MappingEntry(Pattern dssCodePattern, Pattern moduleCodePattern, String typeName, + String value) + { + this.dssCodePattern = dssCodePattern; + this.moduleCodePattern = moduleCodePattern; + type = Type.getType(typeName); + this.value = type.valueInUpperCase ? value.toUpperCase() : value; + } + + public boolean matches(String dataStoreCode, String moduleCode) + { + if (dssCodePattern.matcher(dataStoreCode).matches() == false) + { + return false; + } + return moduleCodePattern.matcher(moduleCode).matches(); + } + } + + private static enum Type + { + HOST_PART("host-part") + { + @Override + public void modify(DataSourceDefinition definition, String value) + { + definition.setHostPart(value); + } + }, + USERNAME("username") + { + @Override + public void modify(DataSourceDefinition definition, String value) + { + definition.setUsername(value); + } + }, + PASSWORD("password") + { + @Override + public void modify(DataSourceDefinition definition, String value) + { + definition.setPassword(value); + } + }, + SID("sid") + { + @Override + public void modify(DataSourceDefinition definition, String value) + { + definition.setSid(value); + } + }, + CONFIG("config", true), DATA_SOURCE("data-source-code"); + + static Type getType(String typeName) + { + Type[] values = Type.values(); + CommaSeparatedListBuilder builder = new CommaSeparatedListBuilder(); + for (Type type : values) + { + if (type.name.equals(typeName)) + { + return type; + } + builder.append(type.name); + } + + throw new IllegalArgumentException("Unknown type '" + typeName + + "', possible values are: " + builder); + } + + private final String name; + + private final boolean valueInUpperCase; + + private Type(String name) + { + this(name, false); + } + + private Type(String name, boolean valueInUpperCase) + { + this.name = name; + this.valueInUpperCase = valueInUpperCase; + } + + public void modify(DataSourceDefinition definition, String value) + { + } + } + } + + private static final class Mapping + { + private final String dataStoreCode; + + private final String configKey; + + private final DataSourceDefinition definitionOrNull; + + public Mapping(String dataStoreCode, String configKey, DataSourceDefinition definitionOrNull) + { + this.dataStoreCode = dataStoreCode; + this.configKey = configKey; + this.definitionOrNull = definitionOrNull; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj instanceof Mapping == false) + { + return false; + } + Mapping that = (Mapping) obj; + return this.configKey.equals(that.configKey) + && (this.definitionOrNull == null ? null == that.definitionOrNull + : this.definitionOrNull.equals(that.definitionOrNull)); + } + + @Override + public int hashCode() + { + int sum = definitionOrNull == null ? 0 : definitionOrNull.hashCode(); + return 37 * sum + configKey.hashCode(); + } + + @Override + public String toString() + { + return configKey + "[" + (definitionOrNull == null ? "?" : definitionOrNull) + "]"; + } + } } diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/IDataSourceProvider.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/IDataSourceProvider.java index 1029fe68deed5a2041bcf209cf11f8faa74c6289..3113ab225ba6f18a0cc4fee04b1cdb07fade23a2 100644 --- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/IDataSourceProvider.java +++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/IDataSourceProvider.java @@ -28,15 +28,6 @@ import ch.systemsx.cisd.common.exceptions.UserFailureException; */ public interface IDataSourceProvider { - /** - * Returns an appropriated data source for specified data set code and technology. - * - * @throws IllegalArgumentException if getting data source by data set code isn't supported for - * the specified technology. - * @throws UserFailureException if the specified data set doesn't exist. - */ - public DataSource getDataSourceByDataSetCode(String dataSetCode, String technology); - /** * Returns an appropriated data source for specified data store server code and technology. * diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/IDataStoreDataSourceManager.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/IDataStoreDataSourceManager.java new file mode 100644 index 0000000000000000000000000000000000000000..e55215c9b1a717660e69edb8acdcb460ea582598 --- /dev/null +++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/IDataStoreDataSourceManager.java @@ -0,0 +1,35 @@ +/* + * Copyright 2012 ETH Zuerich, CISD + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.systemsx.cisd.openbis.generic.server.dataaccess; + +import java.util.List; + +import ch.systemsx.cisd.openbis.generic.shared.dto.DataSourceDefinition; + +/** + * Handles data sources provided by DSS. + * + * @author Franz-Josef Elmer + */ +public interface IDataStoreDataSourceManager +{ + /** + * Handles specified data source definitions received from specified data store. + */ + public void handle(String dataStoreCode, List<DataSourceDefinition> dataSourceDefinitions); + +} diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/DataSourceDefinition.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/DataSourceDefinition.java new file mode 100644 index 0000000000000000000000000000000000000000..23444811ea85c6c886e62a783c312c853f820973 --- /dev/null +++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/DataSourceDefinition.java @@ -0,0 +1,278 @@ +/* + * Copyright 2012 ETH Zuerich, CISD + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.systemsx.cisd.openbis.generic.shared.dto; + +import java.io.Serializable; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.lang.StringUtils; + +import ch.systemsx.cisd.base.exceptions.CheckedExceptionTunnel; +import ch.systemsx.cisd.dbmigration.DatabaseConfigurationContext; +import ch.systemsx.cisd.openbis.generic.shared.IServer; + +/** + * Data source definition. + * + * @author Franz-Josef Elmer + */ +public class DataSourceDefinition implements Serializable, Cloneable +{ + private static final String DEFINITIONS_DELIM = "\n"; + + private static final String ATTRIBUTE_DELIM = "\t"; + + private static final long serialVersionUID = IServer.VERSION; + + /** + * Creates an instance based on specified context object. + */ + public static DataSourceDefinition createFromContext(DatabaseConfigurationContext context) + { + DataSourceDefinition definition = new DataSourceDefinition(); + definition.setDriverClassName(context.getDatabaseEngine().getDriverClass()); + definition.setHostPart(context.getUrlHostPart()); + definition.setSid(context.getDatabaseName()); + definition.setUsername(context.getOwner()); + definition.setPassword(context.getPassword()); + return definition; + } + + /** + * Creates a list of definitions from specified string which could be the output of + * {@link #toString(List)}. + */ + public static List<DataSourceDefinition> listFromString(String serializedDefinitions) + { + List<DataSourceDefinition> result = new ArrayList<DataSourceDefinition>(); + if (StringUtils.isBlank(serializedDefinitions) == false) + { + String[] definitions = serializedDefinitions.split(DEFINITIONS_DELIM); + for (String definition : definitions) + { + result.add(fromString(definition)); + } + } + return result; + } + + /** + * Creates a string representation of specified definition. It can be used as an input of + * {@link #listFromString(String)}. + */ + public static String toString(List<DataSourceDefinition> definitions) + { + StringBuilder builder = new StringBuilder(); + for (DataSourceDefinition definition : definitions) + { + builder.append(definition).append(DEFINITIONS_DELIM); + } + return builder.toString(); + } + + /** + * Creates an instance from the specified string. The input could be the output of + * {@link #toString()}. + */ + public static DataSourceDefinition fromString(String serializedDefinition) + { + DataSourceDefinition result = new DataSourceDefinition(); + String[] split = serializedDefinition.split(ATTRIBUTE_DELIM); + for (String definition : split) + { + int indexOfEqualsSign = definition.indexOf('='); + if (indexOfEqualsSign < 0) + { + throw new IllegalArgumentException("Missing '=': " + definition); + } + String key = definition.substring(0, indexOfEqualsSign); + String value = definition.substring(indexOfEqualsSign + 1); + String setter = "set" + StringUtils.capitalize(key); + try + { + Method method = DataSourceDefinition.class.getMethod(setter, String.class); + method.invoke(result, value); + } catch (Exception ex) + { + throw new IllegalArgumentException("Invalid attribute '" + key + "'.", ex); + } + } + return result; + } + + private String code; + + private String driverClassName; + + private String hostPart; + + private String sid; // aka database name + + private String username; + + private String password; + + public String getCode() + { + return code; + } + + public void setCode(String code) + { + this.code = code; + } + + public String getDriverClassName() + { + return driverClassName; + } + + public void setDriverClassName(String driverClassName) + { + this.driverClassName = driverClassName; + } + + public String getHostPart() + { + return hostPart; + } + + public void setHostPart(String hostPart) + { + this.hostPart = hostPart; + } + + public String getSid() + { + return sid; + } + + public void setSid(String sid) + { + this.sid = sid; + } + + public String getUsername() + { + return username; + } + + public void setUsername(String username) + { + this.username = username; + } + + public String getPassword() + { + return password; + } + + public void setPassword(String password) + { + this.password = password; + } + + @Override + public DataSourceDefinition clone() + { + try + { + return (DataSourceDefinition) super.clone(); + } catch (CloneNotSupportedException ex) + { + throw CheckedExceptionTunnel.wrapIfNecessary(ex); + } + } + + @Override + public boolean equals(Object obj) + { + if (obj == this) + { + return true; + } + if (obj instanceof DataSourceDefinition == false) + { + return false; + } + DataSourceDefinition that = (DataSourceDefinition) obj; + return equals(this.code, that.code) && equals(this.driverClassName, that.driverClassName) + && equals(this.hostPart, that.hostPart) && equals(this.sid, that.sid) + && equals(this.username, that.username) && equals(this.password, that.password); + } + + private boolean equals(String thisStringOrNull, String thatStringOrNull) + { + return thisStringOrNull == null ? thisStringOrNull == thatStringOrNull : thisStringOrNull + .equals(thatStringOrNull); + } + + @Override + public int hashCode() + { + int sum = hashCode(0, code); + sum = hashCode(sum, driverClassName); + sum = hashCode(sum, hostPart); + sum = hashCode(sum, sid); + sum = hashCode(sum, username); + sum = hashCode(sum, password); + return sum; + } + + private int hashCode(int sum, String attribute) + { + return attribute == null ? 37 * sum : 37 * sum + attribute.hashCode(); + } + + /** + * Returns this instance as a string which allows reconstruction by applying + * {@link #fromString(String)}. + */ + @Override + public String toString() + { + StringBuilder builder = new StringBuilder(); + add(builder, "code"); + add(builder, "driverClassName"); + add(builder, "hostPart"); + add(builder, "sid"); + add(builder, "username"); + add(builder, "password"); + return builder.toString(); + } + + private void add(StringBuilder builder, String attributeName) + { + String getter = "get" + StringUtils.capitalize(attributeName); + try + { + Method method = DataSourceDefinition.class.getMethod(getter); + Object value = method.invoke(this); + if (value != null) + { + builder.append(attributeName).append('=').append(value).append(ATTRIBUTE_DELIM); + } + } catch (Exception ex) + { + throw new IllegalArgumentException("Invalid attribute '" + attributeName + "'.", ex); + } + + } + +} diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/DataSourceWithDefinition.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/DataSourceWithDefinition.java new file mode 100644 index 0000000000000000000000000000000000000000..5cc522d68978cff93a693df356eca0b1fa5d34f6 --- /dev/null +++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/DataSourceWithDefinition.java @@ -0,0 +1,52 @@ +/* + * Copyright 2012 ETH Zuerich, CISD + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.systemsx.cisd.openbis.generic.shared.dto; + +import javax.sql.DataSource; + + +/** + * Bean for a {@link DataSource} together with its {@link DataSourceDefinition}. + * + * @author Franz-Josef Elmer + */ +public class DataSourceWithDefinition +{ + private final DataSource dataSource; + private final DataSourceDefinition definition; + + public DataSourceWithDefinition(DataSource dataSource, DataSourceDefinition definitionOrNull) + { + if (dataSource == null) + { + throw new IllegalArgumentException("Unspecified data source."); + } + this.dataSource = dataSource; + this.definition = definitionOrNull; + } + + public DataSource getDataSource() + { + return dataSource; + } + + public DataSourceDefinition getDefinitionOrNull() + { + return definition; + } + +} diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/DataStoreServerInfo.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/DataStoreServerInfo.java index ed023659be616e1643a9bfbcf6831aced48ab59e..5f858101c4e5d0c589d1615f278d9397c87e3f07 100644 --- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/DataStoreServerInfo.java +++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/DataStoreServerInfo.java @@ -17,6 +17,7 @@ package ch.systemsx.cisd.openbis.generic.shared.dto; import java.io.Serializable; +import java.util.List; import javax.servlet.http.HttpServletRequest; @@ -54,6 +55,8 @@ public class DataStoreServerInfo implements Serializable private DatastoreServiceDescriptions servicesDescriptions; + private List<DataSourceDefinition> dataSourceDefinitions; + private boolean archiverConfigured; private int timeoutInMinutes; @@ -142,4 +145,14 @@ public class DataStoreServerInfo implements Serializable this.timeoutInMinutes = timeoutInMinutes; } + public List<DataSourceDefinition> getDataSourceDefinitions() + { + return dataSourceDefinitions; + } + + public void setDataSourceDefinitions(List<DataSourceDefinition> dataSourceDefinitions) + { + this.dataSourceDefinitions = dataSourceDefinitions; + } + } diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/IDataSourceFactory.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/util/IDataSourceFactory.java similarity index 55% rename from datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/IDataSourceFactory.java rename to openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/util/IDataSourceFactory.java index fe5ff95eb7b9ef8fe703ac1eae716dba07ca536f..10037320e3e34fb9228b83042c4874507a72284a 100644 --- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/IDataSourceFactory.java +++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/util/IDataSourceFactory.java @@ -13,13 +13,22 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.systemsx.cisd.openbis.dss.generic.shared; +package ch.systemsx.cisd.openbis.generic.shared.util; import java.util.Properties; -import javax.sql.DataSource; +import ch.systemsx.cisd.openbis.generic.shared.dto.DataSourceWithDefinition; -interface IDataSourceFactory +/** + * Factory of a {@link DataSourceWithDefinition}. + * + * @author Franz-Josef Elmer + */ +public interface IDataSourceFactory { - DataSource create(Properties dbProps); + /** + * Creates a data source optional definition from specified properties. Note, that if the + * definition is provided its code attribute might be undefined. + */ + public DataSourceWithDefinition create(Properties dbProps); } \ No newline at end of file diff --git a/openbis/source/java/genericApplicationContext.xml b/openbis/source/java/genericApplicationContext.xml index 2656059f3259b3aa4ead3cb1a879ef65758423ea..bf4e1fb663c24fe92c8b41a7787708dbea4f1b0c 100644 --- a/openbis/source/java/genericApplicationContext.xml +++ b/openbis/source/java/genericApplicationContext.xml @@ -117,7 +117,13 @@ class="ch.systemsx.cisd.openbis.generic.server.DataStoreServiceRegistrator"> <constructor-arg ref="dao-factory" /> </bean> - + + <bean id="dss-based-data-source-provider" + class="ch.systemsx.cisd.openbis.generic.server.dataaccess.DataStoreServerBasedDataSourceProvider"> + <constructor-arg ref="dao-factory" /> + <constructor-arg value="etc/dss-datasource-mapping" /> + </bean> + <!-- // Common --> @@ -198,6 +204,7 @@ <constructor-arg ref="trusted-origin-domain-provider" /> <constructor-arg ref="etl-entity-operation-checker" /> <constructor-arg ref="data-store-service-registrator" /> + <constructor-arg ref="dss-based-data-source-provider" /> <property name="conversationClient" ref="service-conversation-client-manager" /> <property name="conversationServer" ref="service-conversation-server-manager" /> </bean> diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/ETLServiceTest.java b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/ETLServiceTest.java index af48d604111ec832cc9b27cc788a55a99ce3a8ad..9a878b07e8bd904c5032b58767c43c1bdb2ad7f8 100644 --- a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/ETLServiceTest.java +++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/ETLServiceTest.java @@ -43,6 +43,7 @@ import ch.systemsx.cisd.openbis.generic.server.business.IDataStoreServiceFactory import ch.systemsx.cisd.openbis.generic.server.business.IServiceConversationClientManagerLocal; import ch.systemsx.cisd.openbis.generic.server.business.IServiceConversationServerManagerLocal; import ch.systemsx.cisd.openbis.generic.server.business.bo.ICommonBusinessObjectFactory; +import ch.systemsx.cisd.openbis.generic.server.dataaccess.IDataStoreDataSourceManager; import ch.systemsx.cisd.openbis.generic.server.dataaccess.PersistencyResources; import ch.systemsx.cisd.openbis.generic.shared.AbstractServerTestCase; import ch.systemsx.cisd.openbis.generic.shared.CommonTestUtils; @@ -95,6 +96,7 @@ import ch.systemsx.cisd.openbis.generic.shared.dto.DataStoreServicePE; import ch.systemsx.cisd.openbis.generic.shared.dto.DataTypePE; import ch.systemsx.cisd.openbis.generic.shared.dto.DatastoreServiceDescriptions; import ch.systemsx.cisd.openbis.generic.shared.dto.DeletionPE; +import ch.systemsx.cisd.openbis.generic.shared.dto.DataSourceDefinition; import ch.systemsx.cisd.openbis.generic.shared.dto.ExperimentPE; import ch.systemsx.cisd.openbis.generic.shared.dto.ExperimentUpdatesDTO; import ch.systemsx.cisd.openbis.generic.shared.dto.ExternalDataPE; @@ -167,6 +169,8 @@ public class ETLServiceTest extends AbstractServerTestCase private PersonPE sessionPerson; + private IDataStoreDataSourceManager dataSourceManager; + @Override @BeforeMethod @SuppressWarnings("unchecked") @@ -178,6 +182,7 @@ public class ETLServiceTest extends AbstractServerTestCase dataStoreService = context.mock(IDataStoreService.class); entityOperationChecker = context.mock(IETLEntityOperationChecker.class); dataStoreServiceRegistrator = context.mock(IDataStoreServiceRegistrator.class); + dataSourceManager = context.mock(IDataStoreDataSourceManager.class); conversationClient = context.mock(IServiceConversationClientManagerLocal.class); conversationServer = context.mock(IServiceConversationServerManagerLocal.class); sessionManagerForEntityOperations = @@ -307,6 +312,8 @@ public class ETLServiceTest extends AbstractServerTestCase will(returnValue(IDataStoreService.VERSION)); allowing(dataStoreDAO).createOrUpdateDataStore(with(dataStoreRecordingMatcher)); + + one(dataSourceManager).handle(DSS_CODE, info.getDataSourceDefinitions()); } }); @@ -374,6 +381,8 @@ public class ETLServiceTest extends AbstractServerTestCase will(returnValue(IDataStoreService.VERSION)); allowing(dataStoreDAO).createOrUpdateDataStore(with(dataStoreRecordingMatcher)); + + one(dataSourceManager).handle(DSS_CODE, info.getDataSourceDefinitions()); } }); @@ -1510,7 +1519,7 @@ public class ETLServiceTest extends AbstractServerTestCase ETLService etlService = new ETLService(authenticationService, sessionManager, daoFactory, propertiesBatchManager, boFactory, dssfactory, null, - entityOperationChecker, dataStoreServiceRegistrator, + entityOperationChecker, dataStoreServiceRegistrator, dataSourceManager, sessionManagerForEntityOperations); etlService.setConversationClient(conversationClient); etlService.setConversationServer(conversationServer); @@ -1534,6 +1543,10 @@ public class ETLServiceTest extends AbstractServerTestCase DatastoreServiceDescriptions services = new DatastoreServiceDescriptions(reporting, processing); info.setServicesDescriptions(services); + DataSourceDefinition dataSourceDefinition = new DataSourceDefinition(); + dataSourceDefinition.setCode("my_db"); + dataSourceDefinition.setDriverClassName("my.class"); + info.setDataSourceDefinitions(Arrays.asList(dataSourceDefinition)); return info; } diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/DataStoreServerBasedDataSourceProviderTest.java b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/DataStoreServerBasedDataSourceProviderTest.java index 1f6c4d9dbe513b9a6615151b2caae89145e815c4..581d2c7657d9e9a72dd78b84965d910104e31fcb 100644 --- a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/DataStoreServerBasedDataSourceProviderTest.java +++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/DataStoreServerBasedDataSourceProviderTest.java @@ -18,45 +18,96 @@ package ch.systemsx.cisd.openbis.generic.server.dataaccess; import static ch.systemsx.cisd.openbis.generic.server.dataaccess.DataStoreServerBasedDataSourceProvider.DATA_STORE_SERVERS_KEY; +import java.io.File; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Map.Entry; import java.util.Properties; +import java.util.Set; +import java.util.TreeSet; import javax.sql.DataSource; -import org.apache.commons.dbcp.BasicDataSource; +import org.jmock.Expectations; import org.jmock.Mockery; -import org.testng.AssertJUnit; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; -import ch.systemsx.cisd.common.exceptions.ConfigurationFailureException; +import ch.systemsx.cisd.base.tests.AbstractFileSystemTestCase; +import ch.systemsx.cisd.common.exceptions.EnvironmentFailureException; +import ch.systemsx.cisd.common.filesystem.FileUtilities; +import ch.systemsx.cisd.common.shared.basic.string.CommaSeparatedListBuilder; +import ch.systemsx.cisd.dbmigration.DatabaseEngine; +import ch.systemsx.cisd.dbmigration.MonitoringDataSource; +import ch.systemsx.cisd.dbmigration.SimpleDatabaseConfigurationContext; +import ch.systemsx.cisd.openbis.generic.shared.dto.DataSourceDefinition; +import ch.systemsx.cisd.openbis.generic.shared.dto.DataSourceWithDefinition; +import ch.systemsx.cisd.openbis.generic.shared.dto.DataStorePE; +import ch.systemsx.cisd.openbis.generic.shared.util.IDataSourceFactory; /** * @author Franz-Josef Elmer */ -public class DataStoreServerBasedDataSourceProviderTest extends AssertJUnit +public class DataStoreServerBasedDataSourceProviderTest extends AbstractFileSystemTestCase { + private static final String DRIVER_CLASS = DatabaseEngine.POSTGRESQL.getDriverClass(); + + private static class MockDataSource extends MonitoringDataSource + { + private final Properties properties; + + private boolean closed; + + MockDataSource(Properties properties) + { + this.properties = properties; + } + + @Override + public synchronized void close() throws SQLException + { + closed = true; + } + } + private Mockery context; - private IDAOFactory daoFactory; + private IDAOFactory daofactory; - private DataStoreServerBasedDataSourceProvider dataSourceProvider; + private File mappingFile; + private IDataStoreDAO dataStoreDAO; + + private Properties props; + + @Override @BeforeMethod public void setUp() { context = new Mockery(); - daoFactory = context.mock(IDAOFactory.class); - dataSourceProvider = new DataStoreServerBasedDataSourceProvider(daoFactory); - Properties props = new Properties(); - props.setProperty(DATA_STORE_SERVERS_KEY, "dss1, dss2[tech1], dss2[tech2]"); - props.setProperty("dss1.database-driver", "org.postgresql.Driver"); - props.setProperty("dss1.database-url", "jdbc:postgresql://localhost/db1"); - props.setProperty("dss2[tech1].database-driver", "org.postgresql.Driver"); - props.setProperty("dss2[tech1].database-url", "jdbc:postgresql://localhost/db21"); - props.setProperty("dss2[tech2].database-driver", "org.postgresql.Driver"); - props.setProperty("dss2[tech2].database-url", "jdbc:postgresql://localhost/db22"); - dataSourceProvider.init(props); + daofactory = context.mock(IDAOFactory.class); + dataStoreDAO = context.mock(IDataStoreDAO.class); + mappingFile = new File(workingDirectory, "mapping.txt"); + context.checking(new Expectations() + { + { + allowing(daofactory).getDataStoreDAO(); + will(returnValue(dataStoreDAO)); + } + }); + DataSourceConfigBuilder builder = new DataSourceConfigBuilder(); + builder.plugin("dss1", null).driver("org.postgresql.Driver") + .url("jdbc:postgresql://localhost/db1"); + builder.plugin("dss2", "tech1").driver("org.postgresql.Driver") + .url("jdbc:postgresql://localhost/db21"); + builder.plugin("dss2", "tech2").driver("org.postgresql.Driver") + .url("jdbc:postgresql://localhost/db22"); + props = builder.get(); } @AfterMethod @@ -68,40 +119,288 @@ public class DataStoreServerBasedDataSourceProviderTest extends AssertJUnit @Test public void testGetDataSourceByDataStoreServerCode() { - assertURL("jdbc:postgresql://localhost/db1", - dataSourceProvider.getDataSourceByDataStoreServerCode("dss1", "my-tech")); - assertURL("jdbc:postgresql://localhost/db21", - dataSourceProvider.getDataSourceByDataStoreServerCode("dss2", "tech1")); - assertURL("jdbc:postgresql://localhost/db22", - dataSourceProvider.getDataSourceByDataStoreServerCode("dss2", "tech2")); + prepareListDataStores(); + DataStoreServerBasedDataSourceProvider dataSourceProvider = createDataSourceProvider(); + DataSource dataSource = + dataSourceProvider.getDataSourceByDataStoreServerCode("dss1", "my-tech"); + + assertDataSourceProps("[database-driver=org.postgresql.Driver, " + + "database-url=jdbc:postgresql://localhost/db1]", dataSource); + context.assertIsSatisfied(); + } + + @Test + public void testGetDataSourceByDataStoreServerCodeAndModuleCode() + { + prepareListDataStores(); + DataStoreServerBasedDataSourceProvider dataSourceProvider = createDataSourceProvider(); + + DataSource dataSource = + dataSourceProvider.getDataSourceByDataStoreServerCode("dss2", "tech1"); + + assertDataSourceProps("[database-driver=org.postgresql.Driver, " + + "database-url=jdbc:postgresql://localhost/db21]", dataSource); context.assertIsSatisfied(); } @Test public void testGetDataSourceByDataStoreServerCodeNotFound() { + prepareListDataStores(); + DataStoreServerBasedDataSourceProvider dataSourceProvider = createDataSourceProvider(); + try { dataSourceProvider.getDataSourceByDataStoreServerCode("dss2", "my-tech"); - fail("ConfigurationFailureException expected"); - } catch (ConfigurationFailureException ex) + fail("EnvironmentFailureException expected"); + } catch (EnvironmentFailureException ex) { - assertEquals( - "No data source configured for Data Store Server 'dss2' and technology 'my-tech'.", + assertEquals("Couldn't find data source core plugin 'DSS2[MY-TECH]' nor 'DSS2'.", ex.getMessage()); } context.assertIsSatisfied(); } - void assertURL(String expectedURL, DataSource dataSource) + /** + * This use case simulates the following use case: + * <ol> + * <li>Three DSS instances all using imaging_db + * <li>DSS1 and DSS2 using a DB at AS. + * <li>DSS3 using a DB at DSS. + * </ol> + * The data source instances for DSS1 and DSS2 should be the same. + */ + @Test + public void testThreeDssDBOneAtDSSTwoOnAS() { - assertNotNull(dataSource); - if (dataSource instanceof BasicDataSource) + DataSourceConfigBuilder builder = new DataSourceConfigBuilder(); + builder.plugin("all", "screening").property("key", "all[screening]"); + props = builder.get(); + FileUtilities.writeToFile(mappingFile, "# example mapping file\n" + + "*.proteomics.config = all[proteomics]\n" + + "*.proteomics.data-source-code = proteomics_db\n" + + "*.screening.config = all[screening]\n" + + "*.screening.data-source-code = imaging_db\n" + + "*.screening.host-part = localhost:1234\n" + "*.screening.username = openbis\n" + + "*.screening.password = abcd\n" + "DSS3.screening.host-part = a.b.c\n"); + DataStorePE dss1 = + dataStore( + "DSS1", + new DataSourceDefinitionBuilder().code("imaging_db") + .driverClassName(DRIVER_CLASS).hostPart("abc").sid("imaging_dev") + .username("dss1").password("42").get()); + DataStorePE dss2 = + dataStore( + "DSS2", + new DataSourceDefinitionBuilder().code("imaging_db") + .driverClassName(DRIVER_CLASS).hostPart("123").sid("imaging_dev") + .username("dss2").password("42").get()); + DataStorePE dss3 = + dataStore( + "DSS3", + new DataSourceDefinitionBuilder().code("imaging_db") + .driverClassName(DRIVER_CLASS).hostPart("def").sid("imaging_dev") + .username("dss3").password("42").get()); + prepareListDataStores(dss1, dss2, dss3); + DataStoreServerBasedDataSourceProvider dataSourceProvider = createDataSourceProvider(); + + DataSource ds1 = dataSourceProvider.getDataSourceByDataStoreServerCode("dss1", "screening"); + DataSource ds2 = dataSourceProvider.getDataSourceByDataStoreServerCode("dss2", "screening"); + DataSource ds3 = dataSourceProvider.getDataSourceByDataStoreServerCode("dss3", "screening"); + + assertDataSourceProps("[database-driver=org.postgresql.Driver, " + + "database-password=abcd, " + + "database-url=jdbc:postgresql://localhost:1234/imaging_dev, " + + "database-username=openbis, " + "key=all[screening]]", ds1); + assertDataSourceProps("[database-driver=org.postgresql.Driver, " + + "database-password=abcd, " + + "database-url=jdbc:postgresql://localhost:1234/imaging_dev, " + + "database-username=openbis, " + "key=all[screening]]", ds2); + assertSame(ds1, ds2); + assertDataSourceProps("[database-driver=org.postgresql.Driver, " + + "database-password=abcd, " + "database-url=jdbc:postgresql://a.b.c/imaging_dev, " + + "database-username=openbis, " + "key=all[screening]]", ds3); + + context.assertIsSatisfied(); + } + + private void assertDataSourceProps(String expectedProps, DataSource dataSource) + { + Set<Entry<Object, Object>> entrySet = ((MockDataSource) dataSource).properties.entrySet(); + List<Entry<Object, Object>> sortedProps = new ArrayList<Entry<Object, Object>>(entrySet); + Collections.sort(sortedProps, new Comparator<Entry<Object, Object>>() + { + @Override + public int compare(Entry<Object, Object> o1, Entry<Object, Object> o2) + { + return o1.getKey().toString().compareTo(o2.getKey().toString()); + } + }); + assertEquals(expectedProps, sortedProps.toString()); + } + + private void prepareListDataStores(final DataStorePE... dataStores) + { + context.checking(new Expectations() + { + { + one(dataStoreDAO).listDataStores(); + will(returnValue(Arrays.asList(dataStores))); + } + }); + } + + private DataStorePE dataStore(String code, DataSourceDefinition... definitions) + { + DataStorePE dataStore = new DataStorePE(); + dataStore.setCode(code); + String serializedDefinitions = DataSourceDefinition.toString(Arrays.asList(definitions)); + dataStore.setSerializedDataSourceDefinitions(serializedDefinitions); + return dataStore; + } + + private DataStoreServerBasedDataSourceProvider createDataSourceProvider() + { + DataStoreServerBasedDataSourceProvider dataSourceProvider = + new DataStoreServerBasedDataSourceProvider(daofactory, mappingFile.getPath(), + new IDataSourceFactory() + { + @Override + public DataSourceWithDefinition create(Properties dbProps) + { + return new DataSourceWithDefinition( + new MockDataSource(dbProps), null); + } + }); + dataSourceProvider.init(props); + return dataSourceProvider; + } + + private static final class DataSourceDefinitionBuilder + { + private DataSourceDefinition definition = new DataSourceDefinition(); + + DataSourceDefinition get() { - BasicDataSource basicDataSource = (BasicDataSource) dataSource; - assertEquals(expectedURL, basicDataSource.getUrl()); + return definition; + } + + DataSourceDefinitionBuilder code(String code) + { + definition.setCode(code); + return this; + } + + DataSourceDefinitionBuilder driverClassName(String driverClassName) + { + definition.setDriverClassName(driverClassName); + return this; + } + + DataSourceDefinitionBuilder hostPart(String hostPart) + { + definition.setHostPart(hostPart); + return this; + } + + DataSourceDefinitionBuilder sid(String sid) + { + definition.setSid(sid); + return this; + } + + DataSourceDefinitionBuilder username(String username) + { + definition.setUsername(username); + return this; + } + + DataSourceDefinitionBuilder password(String password) + { + definition.setPassword(password); + return this; + } + } + + private static final class DataSourceConfigBuilder + { + private final Set<String> pluginKeys = new TreeSet<String>(); + + private final Properties properties = new Properties(); + + public Properties get() + { + CommaSeparatedListBuilder builder = new CommaSeparatedListBuilder(); + for (String pluginKey : pluginKeys) + { + builder.append(pluginKey); + } + properties.setProperty(DATA_STORE_SERVERS_KEY, builder.toString()); + return properties; + } + + public SubConfigBuilder plugin(String dataStoreCode, String moduleCodeOrNull) + { + return new SubConfigBuilder(this, pluginkey(dataStoreCode, moduleCodeOrNull)); + } + + public DataSourceConfigBuilder property(String pluginKey, String key, String value) + { + pluginKeys.add(pluginKey); + return property(pluginKey + "." + key, value); + } + + public DataSourceConfigBuilder property(String key, String value) + { + properties.setProperty(key, value); + return this; + } + + private String pluginkey(String dataStoreCode, String moduleCodeOrNull) + { + return dataStoreCode + (moduleCodeOrNull == null ? "" : "[" + moduleCodeOrNull + "]"); + } + } + + private static final class SubConfigBuilder + { + private final DataSourceConfigBuilder builder; + + private final String pluginKey; + + SubConfigBuilder(DataSourceConfigBuilder builder, String pluginKey) + { + this.builder = builder; + this.pluginKey = pluginKey; + } + + public SubConfigBuilder driver(String value) + { + return property(SimpleDatabaseConfigurationContext.DRIVER_KEY, value); + } + + public SubConfigBuilder url(String value) + { + return property(SimpleDatabaseConfigurationContext.URL_KEY, value); + } + + public SubConfigBuilder user(String value) + { + return property(SimpleDatabaseConfigurationContext.USER_KEY, value); + } + + public SubConfigBuilder password(String value) + { + return property(SimpleDatabaseConfigurationContext.PASSWORD_KEY, value); + } + + public SubConfigBuilder property(String key, String value) + { + builder.property(pluginKey, key, value); + return this; } } + } diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/dto/DataSourceDefinitionTest.java b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/dto/DataSourceDefinitionTest.java new file mode 100644 index 0000000000000000000000000000000000000000..346c4013c20654aca478b2dfe0e07fa22df4ce4a --- /dev/null +++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/dto/DataSourceDefinitionTest.java @@ -0,0 +1,93 @@ +/* + * Copyright 2012 ETH Zuerich, CISD + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.systemsx.cisd.openbis.generic.shared.dto; + +import java.util.List; + +import org.testng.AssertJUnit; +import org.testng.annotations.Test; + +import ch.systemsx.cisd.dbmigration.DatabaseConfigurationContext; + +/** + * @author Franz-Josef Elmer + */ +public class DataSourceDefinitionTest extends AssertJUnit +{ + @Test + public void testToString() + { + DataSourceDefinition definition = new DataSourceDefinition(); + definition.setCode("ABC"); + definition.setDriverClassName("my.driver"); + definition.setPassword("</[?a&\"'"); + + assertEquals("code=ABC\tdriverClassName=my.driver\tpassword=</[?a&\"'\t", + definition.toString()); + } + + @Test + public void testFromString() + { + String example = "code=Alpha\thostPart=abc:8889\tsid=my_db\tpassword=1=2^2&<7>?\"abc\"\t"; + + DataSourceDefinition definition = DataSourceDefinition.fromString(example); + + assertEquals("Alpha", definition.getCode()); + assertEquals("my_db", definition.getSid()); + assertEquals(null, definition.getDriverClassName()); + assertEquals("abc:8889", definition.getHostPart()); + assertEquals(null, definition.getUsername()); + assertEquals("1=2^2&<7>?\"abc\"", definition.getPassword()); + assertEquals(example, definition.toString()); + } + + @Test + public void testListFromString() + { + String examples = "code=a\tsid=my_db\t\ncode=b\tusername=einstein\t\n"; + + List<DataSourceDefinition> definitions = DataSourceDefinition.listFromString(examples); + + assertEquals("[code=a\tsid=my_db\t, code=b\tusername=einstein\t]", definitions.toString()); + assertEquals(examples, DataSourceDefinition.toString(definitions)); + } + + @Test + public void testListFromEmptyString() + { + List<DataSourceDefinition> definitions = DataSourceDefinition.listFromString(""); + + assertEquals("[]", definitions.toString()); + } + + @Test + public void testCreateFromContext() + { + DatabaseConfigurationContext context = new DatabaseConfigurationContext(); + context.setDatabaseEngineCode("postgresql"); + context.setUrlHostPart("my-db:4711"); + context.setBasicDatabaseName("openbis"); + context.setDatabaseKind("dev"); + context.setOwner("albert"); + context.setPassword("Einstein"); + DataSourceDefinition definition = DataSourceDefinition.createFromContext(context); + + assertEquals("driverClassName=org.postgresql.Driver\thostPart=my-db:4711\t" + + "sid=openbis_dev\tusername=albert\tpassword=Einstein\t", definition.toString()); + } +} diff --git a/rtd_phosphonetx/source/java/proteomics-plugin-applicationContext.xml b/rtd_phosphonetx/source/java/proteomics-plugin-applicationContext.xml index 59a6dcaf5320bc073c9eb81ddcc17d5e059d485e..9051fee4dd0cc826dd4abe93c00053717f1831ac 100644 --- a/rtd_phosphonetx/source/java/proteomics-plugin-applicationContext.xml +++ b/rtd_phosphonetx/source/java/proteomics-plugin-applicationContext.xml @@ -7,11 +7,6 @@ http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd"> - <bean id="dss-based-data-source-provider" class="ch.systemsx.cisd.openbis.generic.server.dataaccess.DataStoreServerBasedDataSourceProvider"> - <constructor-arg ref="dao-factory"/> - </bean> - - <bean id="proteomics-dao-factory" class="ch.systemsx.cisd.openbis.plugin.proteomics.server.dataaccess.db.PhosphoNetXDAOFactory"> <constructor-arg ref="dss-based-data-source-provider"/> diff --git a/screening/source/java/screening-plugin-applicationContext.xml b/screening/source/java/screening-plugin-applicationContext.xml index 0bf118996a2ac41364ffd1061ecf1ff6037ae6e5..55dac4fadbe848f2947e300c2e61e380229f575b 100644 --- a/screening/source/java/screening-plugin-applicationContext.xml +++ b/screening/source/java/screening-plugin-applicationContext.xml @@ -7,10 +7,6 @@ http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd"> - <bean id="dss-based-data-source-provider" class="ch.systemsx.cisd.openbis.generic.server.dataaccess.DataStoreServerBasedDataSourceProvider"> - <constructor-arg ref="dao-factory"/> - </bean> - <bean id="screening-dao-factory" class="ch.systemsx.cisd.openbis.plugin.screening.server.dataaccess.db.ScreeningDAOFactory"> <constructor-arg ref="dss-based-data-source-provider"/>