Skip to content
Snippets Groups Projects
Commit 9ddbd534 authored by felmer's avatar felmer
Browse files

SP-332, BIS-225: feature implemented and partially tested

SVN: 27937
parent 7b34452b
No related branches found
No related tags found
No related merge requests found
Showing
with 1406 additions and 91 deletions
......@@ -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);
}
......
......@@ -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.
*/
......
......@@ -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));
}
}
......@@ -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();
}
......@@ -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);
}
}
......@@ -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}"/>
......
......@@ -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;
}
......
......@@ -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;
}
}
......@@ -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";
......
......@@ -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,
......
......@@ -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.
*
......
/*
* 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);
}
/*
* 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);
}
}
}
/*
* 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;
}
}
......@@ -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;
}
}
......@@ -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
......@@ -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>
......
......@@ -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;
}
......
......@@ -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;
}
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment