org.datanucleus.store.db4o.ConnectionFactoryImpl Maven / Gradle / Ivy
/**********************************************************************
Copyright (c) 2007 Erik Bengtson and others. All rights reserved.
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.
Contributors:
2007 Andy Jefferson - removed logConfiguration which is for StoreManager
2007 Andy Jefferson - added javadocs
2007 Andy Jefferson - added relative filestore support
2008 Joe Batt - embedded server support
2009 Constantino Cerbo - unique field check code
...
**********************************************************************/
package org.datanucleus.store.db4o;
import java.io.File;
import java.io.PrintStream;
import java.net.URL;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import javax.transaction.xa.XAException;
import javax.transaction.xa.XAResource;
import javax.transaction.xa.Xid;
import org.datanucleus.ClassLoaderResolver;
import org.datanucleus.OMFContext;
import org.datanucleus.PersistenceConfiguration;
import org.datanucleus.exceptions.NucleusDataStoreException;
import org.datanucleus.exceptions.NucleusException;
import org.datanucleus.exceptions.NucleusUserException;
import org.datanucleus.metadata.AbstractClassMetaData;
import org.datanucleus.metadata.AbstractMemberMetaData;
import org.datanucleus.metadata.MetaDataManager;
import org.datanucleus.store.AbstractStoreManager;
import org.datanucleus.store.connection.AbstractConnectionFactory;
import org.datanucleus.store.connection.AbstractManagedConnection;
import org.datanucleus.store.connection.ManagedConnection;
import org.datanucleus.store.connection.ManagedConnectionResourceListener;
import org.datanucleus.util.Localiser;
import org.datanucleus.util.NucleusLogger;
import org.datanucleus.util.StringUtils;
import com.db4o.Db4o;
import com.db4o.ObjectContainer;
import com.db4o.ObjectServer;
import com.db4o.config.ConfigScope;
import com.db4o.config.Configuration;
import com.db4o.constraints.UniqueFieldValueConstraint;
import com.db4o.constraints.UniqueFieldValueConstraintViolationException;
import com.db4o.ext.DatabaseFileLockedException;
import com.db4o.io.CachedIoAdapter;
import com.db4o.io.NonFlushingIoAdapter;
import com.db4o.io.RandomAccessFileAdapter;
/**
* Implementation of a ConnectionFactory for DB4O.
* Obtains access to the DB4O ObjectContainer supporting use of
*
* - File-based DB4O using a URL like "db4o:file:{filename}"
* - Embedded server-based DB4O using a URL like "db4o:server:{filename}"
* - Server-based DB4O using a URL like "db4o:host:port"
*
*
* When an ObjectContainer is obtained for an ObjectManager, it is cached so all subsequent uses for that
* ObjectManager will have the same ObjectContainer. This means that the ObjectContainer is aligned with the
* transaction, and so we can use commit/rollback on the ObjectContainer. When the txn commits we still have
* the ObjectContainer in the cache so if a subsequent txn starts on the same ObjectManager it will then have
* the same ObjectContainer. When the ObjectManager is closed the ObjectContainer is finally closed.
*
*/
@SuppressWarnings("deprecation")
public class ConnectionFactoryImpl extends AbstractConnectionFactory
{
/** Localiser for messages. */
protected static final Localiser LOCALISER_DB4O = Localiser.getInstance(
"org.datanucleus.store.db4o.Localisation", DB4OStoreManager.class.getClassLoader());
/** Use embedded server mode. */
private boolean db4oUseEmbeddedServer = false;
/** Embedded file-based server (if used). */
private ObjectServer db4oEmbeddedServer = null;
/** Name of the DB4O database file when using a local database. */
private String db4oFilename = null;
/** Hostname where the DB4O server is located. */
private String db4oHostname = null;
/** Port on the DB4O server. */
private int db4oPort = 0;
/** Cache of the DB4O ObjectContainer keyed by ObjectManager. */
protected Map objectContainerByObjectManager = new HashMap();
/**
* Constructor
* @param omfContext The OMF context
* @param resourceType Type of resource (tx, nontx)
*/
public ConnectionFactoryImpl(OMFContext omfContext, String resourceType)
{
super(omfContext, resourceType);
String url = omfContext.getStoreManager().getConnectionURL();
if (!url.startsWith("db4o"))
{
throw new NucleusException(LOCALISER_DB4O.msg("DB4O.URLInvalid", url));
}
// Split the URL into filename, or "host:port"
String db4oStr = url.substring(5); // Omit the db4o prefix
if (db4oStr.indexOf("server:") < 0 && db4oStr.indexOf("file:") < 0)
{
// TCP/IP Server
db4oHostname = db4oStr.substring(0, db4oStr.indexOf(':'));
try
{
db4oPort = new Integer(db4oStr.substring(db4oStr.indexOf(':') + 1)).intValue();
}
catch (NumberFormatException nfe)
{
throw new NucleusUserException(LOCALISER_DB4O.msg("DB4O.URLInvalid", url));
}
}
else
{
// Local file, or Embedded Server
String filename = db4oStr.substring(db4oStr.indexOf(":") + 1);
try
{
// Utilise URL, encoding then decoding - caters for absolute or relative
String filenameUri = new URL("file:" + URLEncoder.encode(filename, "UTF-16")).getPath();
db4oFilename = new File(URLDecoder.decode(filenameUri, "UTF-16")).getAbsolutePath();
}
catch (Exception e)
{
throw new NucleusUserException(LOCALISER_DB4O.msg("DB4O.FilenameError",
db4oFilename, e.getMessage()), e);
}
db4oUseEmbeddedServer = db4oStr.startsWith("server:");
}
}
/**
* Convenience method to notify the factory that the specified ObjectManager is closing, so to close
* any resources for it.
* @param om ObjectManager
*/
public void closeObjectContainerForObjectManager(Object poolKey)
{
Object obj = objectContainerByObjectManager.get(poolKey);
if (obj != null)
{
ObjectContainer cont = (ObjectContainer)obj;
if (NucleusLogger.DATASTORE.isDebugEnabled())
{
NucleusLogger.DATASTORE.debug(LOCALISER_DB4O.msg("DB4O.ClosingConnection",
omfContext.getStoreManager().getConnectionURL(), StringUtils.toJVMIDString(cont)));
}
try
{
cont.close();
}
catch (UniqueFieldValueConstraintViolationException e)
{
// TODO improve the exception message. Currently UniqueFieldValueConstraintViolationException
// doesn't have getter for field and classname. Feature requested on db4o JIRA portal:
// http://tracker.db4o.com/browse/COR-1659
String msg = e.getLocalizedMessage();
throw new NucleusUserException(LOCALISER_DB4O.msg("DB4O.Insert.ObjectWithIdAlreadyExists", "", "") + " - " + msg);
}
catch (Exception e)
{
throw new NucleusDataStoreException(e.getMessage(), e);
}
objectContainerByObjectManager.remove(poolKey);
}
if (objectContainerByObjectManager.isEmpty() && db4oEmbeddedServer != null)
{
db4oEmbeddedServer.close();
db4oEmbeddedServer = null;
}
}
protected Configuration getNewConfiguration()
{
Configuration config = Db4o.newConfiguration();
PersistenceConfiguration conf = omfContext.getPersistenceConfiguration();
String filename = conf.getStringProperty("datanucleus.db4o.outputFile");
if (filename != null)
{
// DB4O Output, for debugging
try
{
PrintStream strm = new PrintStream(new File(filename));
config.setOut(strm);
config.messageLevel(3);
}
catch (Exception e)
{
e.printStackTrace();
}
}
// Global DB4O configurations - leave version decisions to particular classes (specified in MetaData)
config.generateVersionNumbers(ConfigScope.INDIVIDUALLY);
if (!conf.getBooleanProperty("datanucleus.db4o.flushFileBuffers"))
{
// db4o deprecated the config.flushFileBuffers() method and recommends this instead
RandomAccessFileAdapter randomAccessFileAdapter = new RandomAccessFileAdapter();
NonFlushingIoAdapter nonFlushingIoAdapter =
new NonFlushingIoAdapter(randomAccessFileAdapter);
CachedIoAdapter cachedIoAdapter = new CachedIoAdapter(nonFlushingIoAdapter);
config.io(cachedIoAdapter);
}
config.automaticShutDown(conf.getBooleanProperty("datanucleus.db4o.automaticShutdown"));
config.internStrings(conf.getBooleanProperty("datanucleus.db4o.internStrings"));
config.optimizeNativeQueries(conf.getBooleanProperty("datanucleus.db4o.optimizeNativeQueries"));
config.lockDatabaseFile(conf.getBooleanProperty("datanucleus.db4o.lockDatabaseFile"));
config.exceptionsOnNotStorable(conf.getBooleanProperty("datanucleus.db4o.exceptionsOnNotStorable"));
config.generateUUIDs(conf.getBooleanProperty("datanucleus.db4o.generateUUIDs") ?
ConfigScope.GLOBALLY : ConfigScope.INDIVIDUALLY);
applyUniqueFieldValueConstraints(config);
return config;
}
private void applyUniqueFieldValueConstraints(Configuration config)
{
AbstractStoreManager sm = (AbstractStoreManager) omfContext.getStoreManager();
MetaDataManager mdm = sm.getMetaDataManager();
Collection classesWithMetaData = mdm.getClassesWithMetaData();
for (String className : classesWithMetaData)
{
ClassLoaderResolver clr = omfContext.getClassLoaderResolver(getClass().getClassLoader());
AbstractClassMetaData cmd = mdm.getMetaDataForClass(className, clr);
String fullClassName = cmd.getFullClassName();
AbstractMemberMetaData[] fmds = cmd.getManagedMembers();
for (AbstractMemberMetaData memberMetaData : fmds)
{
if (memberMetaData.isPrimaryKey())
{
String fieldName = memberMetaData.getName();
config.objectClass(fullClassName).objectField(fieldName).indexed(true);
config.add(new UniqueFieldValueConstraint(fullClassName, fieldName));
}
}
}
}
/**
* Convenience method to create a new ObjectContainer for this datastore.
* @return The ObjectContainer
*/
protected ObjectContainer getNewObjectContainer()
{
ObjectContainer container = null;
PersistenceConfiguration conf = omfContext.getPersistenceConfiguration();
if (db4oFilename != null)
{
// DB4O using local file
try
{
if (db4oUseEmbeddedServer)
{
// Embedded Server
if (db4oEmbeddedServer == null)
{
Configuration config = getNewConfiguration();
db4oEmbeddedServer = Db4o.openServer(config, db4oFilename, 0);
}
container = db4oEmbeddedServer.openClient();
}
else
{
// Local file
Configuration config = getNewConfiguration();
container = Db4o.openFile(config, db4oFilename);
}
if (NucleusLogger.DATASTORE.isDebugEnabled())
{
NucleusLogger.DATASTORE.debug(LOCALISER_DB4O.msg("DB4O.OpeningConnection",
conf.getStringProperty("datanucleusConnectionURL"), StringUtils.toJVMIDString(container)));
}
}
catch (DatabaseFileLockedException e)
{
throw new NucleusDataStoreException(LOCALISER_DB4O.msg("DB4O.ConnectionError",
omfContext.getStoreManager().getConnectionURL()), e);
}
}
else
{
// DB4O using client connecting to TCP/IP Server at hostname:port
try
{
Configuration config = getNewConfiguration();
container = Db4o.openClient(config, db4oHostname, db4oPort,
omfContext.getStoreManager().getConnectionUserName(),
omfContext.getStoreManager().getConnectionPassword());
if (NucleusLogger.DATASTORE.isDebugEnabled())
{
NucleusLogger.DATASTORE.debug(LOCALISER_DB4O.msg("DB4O.OpeningConnection",
omfContext.getStoreManager().getConnectionURL(), StringUtils.toJVMIDString(container)));
}
}
catch (Exception e)
{
throw new NucleusDataStoreException(LOCALISER_DB4O.msg("DB4O.ConnectionError",
omfContext.getStoreManager().getConnectionURL()), e);
}
}
return container;
}
/**
* Obtain a connection from the Factory. The connection will be enlisted within the {@link org.datanucleus.Transaction}
* associated to the poolKey
if "enlist" is set to true.
* @param poolKey the pool that is bound the connection during its lifecycle (or null)
* @param options Any options for then creating the connection
* @return the {@link org.datanucleus.store.connection.ManagedConnection}
*/
public ManagedConnection createManagedConnection(Object poolKey, Map options)
{
return new ManagedConnectionImpl(omfContext, poolKey, options);
}
/**
* Implementation of a ManagedConnection for DB4O.
*/
class ManagedConnectionImpl extends AbstractManagedConnection
{
OMFContext omf;
Object poolKey;
/**
* Constructor.
* @param omf
* @param poolKey
* @param transactionOptions Any options
*/
ManagedConnectionImpl(OMFContext omf, Object poolKey, Map transactionOptions)
{
this.omf = omf;
this.poolKey = poolKey;
}
/**
* Obtain a XAResource which can be enlisted in a transaction
*/
public XAResource getXAResource()
{
ObjectContainer cont = (ObjectContainer)getConnection();
return new EmulatedXAResource(cont);
}
/**
* Create a connection to the resource
*/
public Object getConnection()
{
if (conn == null)
{
if (poolKey != null)
{
Object objCont = objectContainerByObjectManager.get(poolKey);
if (objCont != null)
{
conn = objCont;
return conn;
}
}
conn = getNewObjectContainer();
if (poolKey != null)
{
objectContainerByObjectManager.put(poolKey, conn);
// Register the ObjectContainer as active for this StoreManager
((DB4OStoreManager) omf.getStoreManager()).registerObjectContainer((ObjectContainer)conn);
}
}
return conn;
}
/**
* Close the connection
*/
public void close()
{
for (int i=0; i