blob: 3917333db89b1e599f82d9a3e0385873a056cfb8 [file] [log] [blame]
/**
* Copyright (c) 2004 - 2011 Eike Stepper (Berlin, Germany) and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Eike Stepper - initial API and implementation
* Stefan Winkler - Bug 259402
* Stefan Winkler - Bug 271444: [DB] Multiple refactorings bug 271444
* Stefan Winkler - Bug 249610: [DB] Support external references (Implementation)
* Stefan Winkler - Bug 289056: [DB] Exception "ERROR: relation "cdo_external_refs" does not exist" while executing test-suite for PostgreSQL
*/
package org.eclipse.emf.cdo.server.internal.db;
import org.eclipse.emf.cdo.common.CDOCommonRepository.IDGenerationLocation;
import org.eclipse.emf.cdo.common.branch.CDOBranch;
import org.eclipse.emf.cdo.common.branch.CDOBranchPoint;
import org.eclipse.emf.cdo.common.id.CDOID;
import org.eclipse.emf.cdo.common.revision.CDOAllRevisionsProvider;
import org.eclipse.emf.cdo.common.revision.CDORevision;
import org.eclipse.emf.cdo.common.revision.CDORevisionHandler;
import org.eclipse.emf.cdo.server.ISession;
import org.eclipse.emf.cdo.server.ITransaction;
import org.eclipse.emf.cdo.server.IView;
import org.eclipse.emf.cdo.server.StoreThreadLocal;
import org.eclipse.emf.cdo.server.db.IDBStore;
import org.eclipse.emf.cdo.server.db.IDBStoreAccessor;
import org.eclipse.emf.cdo.server.db.IIDHandler;
import org.eclipse.emf.cdo.server.db.IMetaDataManager;
import org.eclipse.emf.cdo.server.db.mapping.IMappingStrategy;
import org.eclipse.emf.cdo.server.internal.db.bundle.OM;
import org.eclipse.emf.cdo.server.internal.db.messages.Messages;
import org.eclipse.emf.cdo.spi.server.InternalRepository;
import org.eclipse.emf.cdo.spi.server.LongIDStoreAccessor;
import org.eclipse.emf.cdo.spi.server.Store;
import org.eclipse.emf.cdo.spi.server.StoreAccessorPool;
import org.eclipse.net4j.db.DBException;
import org.eclipse.net4j.db.DBUtil;
import org.eclipse.net4j.db.IDBAdapter;
import org.eclipse.net4j.db.IDBConnectionProvider;
import org.eclipse.net4j.db.ddl.IDBSchema;
import org.eclipse.net4j.db.ddl.IDBTable;
import org.eclipse.net4j.spi.db.DBSchema;
import org.eclipse.net4j.util.ReflectUtil.ExcludeFromDump;
import org.eclipse.net4j.util.lifecycle.LifecycleUtil;
import org.eclipse.net4j.util.om.monitor.ProgressDistributor;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.Timer;
/**
* @author Eike Stepper
*/
public class DBStore extends Store implements IDBStore, CDOAllRevisionsProvider
{
public static final String TYPE = "db"; //$NON-NLS-1$
private static final String PROP_REPOSITORY_CREATED = "org.eclipse.emf.cdo.server.db.repositoryCreated"; //$NON-NLS-1$
private static final String PROP_REPOSITORY_STOPPED = "org.eclipse.emf.cdo.server.db.repositoryStopped"; //$NON-NLS-1$
private static final String PROP_NEXT_LOCAL_CDOID = "org.eclipse.emf.cdo.server.db.nextLocalCDOID"; //$NON-NLS-1$
private static final String PROP_LAST_CDOID = "org.eclipse.emf.cdo.server.db.lastCDOID"; //$NON-NLS-1$
private static final String PROP_LAST_BRANCHID = "org.eclipse.emf.cdo.server.db.lastBranchID"; //$NON-NLS-1$
private static final String PROP_LAST_LOCAL_BRANCHID = "org.eclipse.emf.cdo.server.db.lastLocalBranchID"; //$NON-NLS-1$
private static final String PROP_LAST_COMMITTIME = "org.eclipse.emf.cdo.server.db.lastCommitTime"; //$NON-NLS-1$
private static final String PROP_LAST_NONLOCAL_COMMITTIME = "org.eclipse.emf.cdo.server.db.lastNonLocalCommitTime"; //$NON-NLS-1$
private static final String PROP_GRACEFULLY_SHUT_DOWN = "org.eclipse.emf.cdo.server.db.gracefullyShutDown"; //$NON-NLS-1$
private long creationTime;
private boolean firstTime;
private Map<String, String> properties;
private IIDHandler idHandler;
private IMetaDataManager metaDataManager = new MetaDataManager(this);
private DurableLockingManager durableLockingManager = new DurableLockingManager(this);
private IMappingStrategy mappingStrategy;
private IDBSchema dbSchema;
private IDBAdapter dbAdapter;
private IDBConnectionProvider dbConnectionProvider;
@ExcludeFromDump
private transient ProgressDistributor accessorWriteDistributor = new ProgressDistributor.Geometric()
{
@Override
public String toString()
{
String result = "accessorWriteDistributor"; //$NON-NLS-1$
if (getRepository() != null)
{
result += ": " + getRepository().getName(); //$NON-NLS-1$
}
return result;
}
};
@ExcludeFromDump
private transient StoreAccessorPool readerPool = new StoreAccessorPool(this, null);
@ExcludeFromDump
private transient StoreAccessorPool writerPool = new StoreAccessorPool(this, null);
@ExcludeFromDump
private transient Timer connectionKeepAliveTimer;
public DBStore()
{
super(TYPE, null, set(ChangeFormat.REVISION, ChangeFormat.DELTA), //
set(RevisionTemporality.AUDITING, RevisionTemporality.NONE), //
set(RevisionParallelism.NONE, RevisionParallelism.BRANCHING));
}
public IMappingStrategy getMappingStrategy()
{
return mappingStrategy;
}
public void setMappingStrategy(IMappingStrategy mappingStrategy)
{
this.mappingStrategy = mappingStrategy;
mappingStrategy.setStore(this);
}
public IDBAdapter getDBAdapter()
{
return dbAdapter;
}
public void setDBAdapter(IDBAdapter dbAdapter)
{
this.dbAdapter = dbAdapter;
}
public void setProperties(Map<String, String> properties)
{
this.properties = properties;
}
public Map<String, String> getProperties()
{
return properties;
}
public IIDHandler getIDHandler()
{
return idHandler;
}
public Connection getConnection()
{
Connection connection = dbConnectionProvider.getConnection();
if (connection == null)
{
throw new DBException("No connection from connection provider: " + dbConnectionProvider); //$NON-NLS-1$
}
try
{
connection.setAutoCommit(false);
}
catch (SQLException ex)
{
throw new DBException(ex, "SET AUTO COMMIT = false");
}
return connection;
}
public void setDbConnectionProvider(IDBConnectionProvider dbConnectionProvider)
{
this.dbConnectionProvider = dbConnectionProvider;
}
public void setDataSource(DataSource dataSource)
{
dbConnectionProvider = DBUtil.createConnectionProvider(dataSource);
}
public IMetaDataManager getMetaDataManager()
{
return metaDataManager;
}
public DurableLockingManager getDurableLockingManager()
{
return durableLockingManager;
}
public Timer getConnectionKeepAliveTimer()
{
return connectionKeepAliveTimer;
}
@Override
public Set<ChangeFormat> getSupportedChangeFormats()
{
if (mappingStrategy.hasDeltaSupport())
{
return set(ChangeFormat.DELTA);
}
return set(ChangeFormat.REVISION);
}
public ProgressDistributor getAccessorWriteDistributor()
{
return accessorWriteDistributor;
}
public IDBSchema getDBSchema()
{
return dbSchema;
}
public Map<String, String> getPersistentProperties(Set<String> names)
{
Connection connection = null;
PreparedStatement selectStmt = null;
String sql = null;
try
{
connection = getConnection();
Map<String, String> result = new HashMap<String, String>();
boolean allProperties = names == null || names.isEmpty();
if (allProperties)
{
sql = CDODBSchema.SQL_SELECT_ALL_PROPERTIES;
selectStmt = connection.prepareStatement(sql);
ResultSet resultSet = null;
try
{
resultSet = selectStmt.executeQuery();
while (resultSet.next())
{
String key = resultSet.getString(1);
String value = resultSet.getString(2);
result.put(key, value);
}
}
finally
{
DBUtil.close(resultSet);
}
}
else
{
sql = CDODBSchema.SQL_SELECT_PROPERTIES;
selectStmt = connection.prepareStatement(sql);
for (String name : names)
{
selectStmt.setString(1, name);
ResultSet resultSet = null;
try
{
resultSet = selectStmt.executeQuery();
if (resultSet.next())
{
String value = resultSet.getString(1);
result.put(name, value);
}
}
finally
{
DBUtil.close(resultSet);
}
}
}
return result;
}
catch (SQLException ex)
{
throw new DBException(ex, sql);
}
finally
{
DBUtil.close(selectStmt);
DBUtil.close(connection);
}
}
public void setPersistentProperties(Map<String, String> properties)
{
Connection connection = null;
PreparedStatement deleteStmt = null;
PreparedStatement insertStmt = null;
String sql = null;
try
{
connection = getConnection();
deleteStmt = connection.prepareStatement(CDODBSchema.SQL_DELETE_PROPERTIES);
insertStmt = connection.prepareStatement(CDODBSchema.SQL_INSERT_PROPERTIES);
for (Entry<String, String> entry : properties.entrySet())
{
String name = entry.getKey();
String value = entry.getValue();
sql = CDODBSchema.SQL_DELETE_PROPERTIES;
deleteStmt.setString(1, name);
deleteStmt.executeUpdate();
sql = CDODBSchema.SQL_INSERT_PROPERTIES;
insertStmt.setString(1, name);
insertStmt.setString(2, value);
insertStmt.executeUpdate();
}
sql = null;
connection.commit();
}
catch (SQLException ex)
{
throw new DBException(ex, sql);
}
finally
{
DBUtil.close(insertStmt);
DBUtil.close(deleteStmt);
DBUtil.close(connection);
}
}
public void removePersistentProperties(Set<String> names)
{
Connection connection = null;
PreparedStatement deleteStmt = null;
try
{
connection = getConnection();
deleteStmt = connection.prepareStatement(CDODBSchema.SQL_DELETE_PROPERTIES);
for (String name : names)
{
deleteStmt.setString(1, name);
deleteStmt.executeUpdate();
}
connection.commit();
}
catch (SQLException ex)
{
throw new DBException(ex, CDODBSchema.SQL_DELETE_PROPERTIES);
}
finally
{
DBUtil.close(deleteStmt);
DBUtil.close(connection);
}
}
@Override
public DBStoreAccessor getReader(ISession session)
{
return (DBStoreAccessor)super.getReader(session);
}
@Override
public DBStoreAccessor getWriter(ITransaction transaction)
{
return (DBStoreAccessor)super.getWriter(transaction);
}
@Override
protected StoreAccessorPool getReaderPool(ISession session, boolean forReleasing)
{
return readerPool;
}
@Override
protected StoreAccessorPool getWriterPool(IView view, boolean forReleasing)
{
return writerPool;
}
@Override
protected DBStoreAccessor createReader(ISession session) throws DBException
{
return new DBStoreAccessor(this, session);
}
@Override
protected DBStoreAccessor createWriter(ITransaction transaction) throws DBException
{
return new DBStoreAccessor(this, transaction);
}
public Map<CDOBranch, List<CDORevision>> getAllRevisions()
{
final Map<CDOBranch, List<CDORevision>> result = new HashMap<CDOBranch, List<CDORevision>>();
IDBStoreAccessor accessor = getReader(null);
StoreThreadLocal.setAccessor(accessor);
try
{
accessor.handleRevisions(null, null, CDOBranchPoint.UNSPECIFIED_DATE, true,
new CDORevisionHandler.Filtered.Undetached(new CDORevisionHandler()
{
public boolean handleRevision(CDORevision revision)
{
CDOBranch branch = revision.getBranch();
List<CDORevision> list = result.get(branch);
if (list == null)
{
list = new ArrayList<CDORevision>();
result.put(branch, list);
}
list.add(revision);
return true;
}
}));
}
finally
{
StoreThreadLocal.release();
}
return result;
}
public CDOID createObjectID(String val)
{
return idHandler.createCDOID(val);
}
public boolean isLocal(CDOID id)
{
return idHandler.isLocalCDOID(id);
}
public CDOID getNextCDOID(LongIDStoreAccessor accessor, CDORevision revision)
{
return idHandler.getNextCDOID(revision);
}
public long getCreationTime()
{
return creationTime;
}
public void setCreationTime(long creationTime)
{
this.creationTime = creationTime;
Map<String, String> map = new HashMap<String, String>();
map.put(PROP_REPOSITORY_CREATED, Long.toString(creationTime));
setPersistentProperties(map);
}
public boolean isFirstStart()
{
return firstTime;
}
@Override
protected void doBeforeActivate() throws Exception
{
super.doBeforeActivate();
checkNull(mappingStrategy, Messages.getString("DBStore.2")); //$NON-NLS-1$
checkNull(dbAdapter, Messages.getString("DBStore.1")); //$NON-NLS-1$
checkNull(dbConnectionProvider, Messages.getString("DBStore.0")); //$NON-NLS-1$
}
@Override
protected void doActivate() throws Exception
{
super.doActivate();
if (getRepository().getIDGenerationLocation() == IDGenerationLocation.CLIENT)
{
idHandler = new UUIDHandler(this);
}
else
{
idHandler = new LongIDHandler(this);
}
setObjectIDTypes(idHandler.getObjectIDTypes());
connectionKeepAliveTimer = new Timer("Connection-Keep-Alive-" + this); //$NON-NLS-1$
Set<IDBTable> createdTables = null;
Connection connection = getConnection();
try
{
if (isDropAllDataOnActivate())
{
OM.LOG.info("Dropping all tables from repository " + getRepository().getName() + "...");
DBUtil.dropAllTables(connection, null);
connection.commit();
}
createdTables = CDODBSchema.INSTANCE.create(dbAdapter, connection);
connection.commit();
}
finally
{
DBUtil.close(connection);
}
dbSchema = createSchema();
LifecycleUtil.activate(idHandler);
LifecycleUtil.activate(metaDataManager);
LifecycleUtil.activate(durableLockingManager);
LifecycleUtil.activate(mappingStrategy);
setRevisionTemporality(mappingStrategy.hasAuditSupport() ? RevisionTemporality.AUDITING : RevisionTemporality.NONE);
setRevisionParallelism(mappingStrategy.hasBranchingSupport() ? RevisionParallelism.BRANCHING
: RevisionParallelism.NONE);
if (isFirstStart(createdTables))
{
firstStart();
}
else
{
reStart();
}
}
@Override
protected void doDeactivate() throws Exception
{
LifecycleUtil.deactivate(mappingStrategy);
LifecycleUtil.deactivate(durableLockingManager);
LifecycleUtil.deactivate(metaDataManager);
LifecycleUtil.deactivate(idHandler);
Map<String, String> map = new HashMap<String, String>();
map.put(PROP_GRACEFULLY_SHUT_DOWN, Boolean.TRUE.toString());
map.put(PROP_REPOSITORY_STOPPED, Long.toString(getRepository().getTimeStamp()));
if (getRepository().getIDGenerationLocation() == IDGenerationLocation.STORE)
{
map.put(PROP_NEXT_LOCAL_CDOID, Store.idToString(idHandler.getNextLocalObjectID()));
map.put(PROP_LAST_CDOID, Store.idToString(idHandler.getLastObjectID()));
}
map.put(PROP_LAST_BRANCHID, Integer.toString(getLastBranchID()));
map.put(PROP_LAST_LOCAL_BRANCHID, Integer.toString(getLastLocalBranchID()));
map.put(PROP_LAST_COMMITTIME, Long.toString(getLastCommitTime()));
map.put(PROP_LAST_NONLOCAL_COMMITTIME, Long.toString(getLastNonLocalCommitTime()));
setPersistentProperties(map);
if (readerPool != null)
{
readerPool.dispose();
}
if (writerPool != null)
{
writerPool.dispose();
}
connectionKeepAliveTimer.cancel();
connectionKeepAliveTimer = null;
super.doDeactivate();
}
protected boolean isFirstStart(Set<IDBTable> createdTables)
{
if (createdTables.contains(CDODBSchema.PROPERTIES))
{
return true;
}
Set<String> names = new HashSet<String>();
names.add(PROP_REPOSITORY_CREATED);
Map<String, String> map = getPersistentProperties(names);
return map.get(PROP_REPOSITORY_CREATED) == null;
}
protected void firstStart()
{
InternalRepository repository = getRepository();
setCreationTime(repository.getTimeStamp());
firstTime = true;
}
protected void reStart()
{
Set<String> names = new HashSet<String>();
names.add(PROP_REPOSITORY_CREATED);
names.add(PROP_GRACEFULLY_SHUT_DOWN);
Map<String, String> map = getPersistentProperties(names);
creationTime = Long.valueOf(map.get(PROP_REPOSITORY_CREATED));
if (map.containsKey(PROP_GRACEFULLY_SHUT_DOWN))
{
names.clear();
InternalRepository repository = getRepository();
boolean generatingIDs = repository.getIDGenerationLocation() == IDGenerationLocation.STORE;
if (generatingIDs)
{
names.add(PROP_NEXT_LOCAL_CDOID);
names.add(PROP_LAST_CDOID);
}
names.add(PROP_LAST_BRANCHID);
names.add(PROP_LAST_LOCAL_BRANCHID);
names.add(PROP_LAST_COMMITTIME);
names.add(PROP_LAST_NONLOCAL_COMMITTIME);
map = getPersistentProperties(names);
if (generatingIDs)
{
idHandler.setNextLocalObjectID(Store.stringToID(map.get(PROP_NEXT_LOCAL_CDOID)));
idHandler.setLastObjectID(Store.stringToID(map.get(PROP_LAST_CDOID)));
}
setLastBranchID(Integer.valueOf(map.get(PROP_LAST_BRANCHID)));
setLastLocalBranchID(Integer.valueOf(map.get(PROP_LAST_LOCAL_BRANCHID)));
setLastCommitTime(Long.valueOf(map.get(PROP_LAST_COMMITTIME)));
setLastNonLocalCommitTime(Long.valueOf(map.get(PROP_LAST_NONLOCAL_COMMITTIME)));
}
else
{
repairAfterCrash();
}
removePersistentProperties(Collections.singleton(PROP_GRACEFULLY_SHUT_DOWN));
}
protected void repairAfterCrash()
{
String name = getRepository().getName();
OM.LOG.warn(MessageFormat.format(Messages.getString("DBStore.9"), name)); //$NON-NLS-1$
Connection connection = getConnection();
try
{
connection.setAutoCommit(false);
connection.setReadOnly(true);
mappingStrategy.repairAfterCrash(dbAdapter, connection); // Must update the idHandler
boolean storeIDs = getRepository().getIDGenerationLocation() == IDGenerationLocation.STORE;
CDOID lastObjectID = storeIDs ? idHandler.getLastObjectID() : CDOID.NULL;
CDOID nextLocalObjectID = storeIDs ? idHandler.getNextLocalObjectID() : CDOID.NULL;
int branchID = DBUtil.selectMaximumInt(connection, CDODBSchema.BRANCHES_ID);
setLastBranchID(branchID > 0 ? branchID : 0);
int localBranchID = DBUtil.selectMinimumInt(connection, CDODBSchema.BRANCHES_ID);
setLastLocalBranchID(localBranchID < 0 ? localBranchID : 0);
long lastCommitTime = DBUtil.selectMaximumLong(connection, CDODBSchema.COMMIT_INFOS_TIMESTAMP);
setLastCommitTime(lastCommitTime);
long lastNonLocalCommitTime = DBUtil.selectMaximumLong(connection, CDODBSchema.COMMIT_INFOS_TIMESTAMP,
CDOBranch.MAIN_BRANCH_ID + "<=" + CDODBSchema.COMMIT_INFOS_BRANCH);
setLastNonLocalCommitTime(lastNonLocalCommitTime);
if (storeIDs)
{
OM.LOG
.info(MessageFormat.format(
Messages.getString("DBStore.10"), name, lastObjectID, nextLocalObjectID, getLastBranchID(), getLastCommitTime(), getLastNonLocalCommitTime())); //$NON-NLS-1$
}
else
{
OM.LOG
.info(MessageFormat.format(
Messages.getString("DBStore.10b"), name, getLastBranchID(), getLastCommitTime(), getLastNonLocalCommitTime())); //$NON-NLS-1$
}
}
catch (SQLException e)
{
OM.LOG.error(MessageFormat.format(Messages.getString("DBStore.11"), name), e); //$NON-NLS-1$
throw new DBException(e);
}
finally
{
DBUtil.close(connection);
}
}
protected IDBSchema createSchema()
{
String name = getRepository().getName();
return new DBSchema(name);
}
}