package com.laytonsmith.persistence;
import com.laytonsmith.PureUtilities.ClassLoading.ClassDiscovery;
import com.laytonsmith.annotations.datasource;
import com.laytonsmith.core.CHLog;
import com.laytonsmith.core.LogLevel;
import com.laytonsmith.core.constructs.Target;
import com.laytonsmith.persistence.io.ConnectionMixinFactory;
import java.lang.reflect.InvocationTargetException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* This utility class provides the means to interact with given data sources.
*
*
*/
public class DataSourceFactory {
private static Map<URI, DataSource> dataSourcePool = new HashMap<>();
/**
* Given a connection uri and the connection options, creates and returns a
* new DataSource object, which can be used to do direct operations on the
* data source. Generally, you should go through the PersistenceNetwork
* class to perform operations on the network as a whole, however.
*
* @param uri The full connection uri
* @param options The connection mixin options
* @return A new DataSource object
* @throws DataSourceException If there is a problem connecting to the data
* source
* @throws URISyntaxException If the URI is invalid
*/
public static DataSource GetDataSource(String uri, ConnectionMixinFactory.ConnectionMixinOptions options) throws DataSourceException, URISyntaxException {
return GetDataSource(new URI(uri), options);
}
/**
* Internally, DataSourceFactory re-uses connections, for efficiency reasons. When
* the server is shutdown, a clean shutdown of all the cached connections is
* desired. This method will disconnect all persistently connecting connections,
* as well as delete them from the cache.
*/
public static void DisconnectAll(){
for(DataSource ds : dataSourcePool.values()){
try {
ds.disconnect();
} catch (DataSourceException ex) {
CHLog.GetLogger().Log(CHLog.Tags.PERSISTENCE, LogLevel.WARNING, ex.getMessage(), Target.UNKNOWN);
}
}
dataSourcePool.clear();
}
/**
* Given a connection uri and the connection options, creates and returns a
* new DataSource object, which can be used to do direct operations on the
* data source. Generally, you should go through the PersistenceNetwork
* class to perform operations on the network as a whole, however.
*
* @param uri The full connection uri
* @param options The connection mixin options
* @return A new DataSource object
* @throws DataSourceException If there is a problem connecting to the data
* source
*/
public static DataSource GetDataSource(URI uri, ConnectionMixinFactory.ConnectionMixinOptions options) throws DataSourceException {
init();
if(dataSourcePool.containsKey(uri)){
return dataSourcePool.get(uri);
}
List<DataSource.DataSourceModifier> modifiers = new ArrayList<DataSource.DataSourceModifier>();
while (DataSource.DataSourceModifier.isModifier(uri.getScheme())) {
modifiers.add(DataSource.DataSourceModifier.getModifier(uri.getScheme()));
try {
uri = new URI(uri.getSchemeSpecificPart());
} catch (URISyntaxException ex) {
throw new DataSourceException(null, ex);
}
}
Class c = protocolHandlers.get(uri.getScheme());
if (c == null) {
throw new DataSourceException("Invalid scheme: " + uri.getScheme());
}
try {
DataSource ds = (DataSource) c.getConstructor(URI.class, ConnectionMixinFactory.ConnectionMixinOptions.class).newInstance(uri, options);
for (DataSource.DataSourceModifier m : modifiers) {
ds.addModifier(m);
}
try {
if (ds instanceof AbstractDataSource) {
((AbstractDataSource) ds).checkModifiers();
}
} catch (DataSourceException e) {
//Warning, for invalid modifiers. This isn't an error, invalid modifiers will just be
//ignored, but the user probably meant something else if they're getting this warning,
//so we still alert them to the issue.
CHLog.GetLogger().Log(CHLog.Tags.PERSISTENCE, LogLevel.WARNING, e.getMessage(), Target.UNKNOWN);
}
//If the data source is transient, it will populate itself later, as needed.
//Otherwise, we can go ahead and populate it now.
if (!ds.getModifiers().contains(DataSource.DataSourceModifier.TRANSIENT)) {
ds.populate();
}
dataSourcePool.put(uri, ds);
return ds;
} catch (InvocationTargetException | NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException | DataSourceException ex) {
if(ex instanceof InvocationTargetException && ex.getCause() instanceof DataSourceException){
throw (DataSourceException)ex.getCause();
}
throw new DataSourceException("Could not instantiate a DataSource for " + c.getName() + ": " + ex.getMessage(), ex);
}
}
private static Map<String, Class> protocolHandlers;
private static void init() {
if (protocolHandlers == null) {
protocolHandlers = new HashMap<String, Class>();
Set<Class<?>> classes = ClassDiscovery.getDefaultInstance().loadClassesWithAnnotation(datasource.class);
for (Class<?> c : classes) {
if (DataSource.class.isAssignableFrom(c)) {
protocolHandlers.put((c.getAnnotation(datasource.class)).value(), c);
} else {
throw new Error(c.getName() + " does not implement DataSource!");
}
}
}
}
/**
* Returns a list of supported protocols.
*
* @return
*/
public static Set<String> GetSupportedProtocols() {
init();
return new HashSet<String>(protocolHandlers.keySet());
}
}