blob: 5cbb50907153763b3ef595937719a1a623329d2b [file] [log] [blame]
/***************************************************************************
* Copyright (c) 2004 - 2008 Eike Stepper, Germany.
* 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:
* Stefan Winkler - initial API and implementation
**************************************************************************/
package org.eclipse.emf.cdo.server.internal.db.jdbc;
import org.eclipse.emf.cdo.common.id.CDOID;
import org.eclipse.emf.cdo.common.id.CDOIDUtil;
import org.eclipse.emf.cdo.common.revision.CDORevision;
import org.eclipse.emf.cdo.server.IStoreChunkReader.Chunk;
import org.eclipse.emf.cdo.server.db.IAttributeMapping;
import org.eclipse.emf.cdo.server.db.IClassMapping;
import org.eclipse.emf.cdo.server.db.IDBStoreChunkReader;
import org.eclipse.emf.cdo.server.db.IJDBCDelegate;
import org.eclipse.emf.cdo.server.db.IReferenceMapping;
import org.eclipse.emf.cdo.server.internal.db.FeatureServerInfo;
import org.eclipse.emf.cdo.spi.common.InternalCDORevision;
import org.eclipse.net4j.db.DBException;
import org.eclipse.net4j.db.DBUtil;
import org.eclipse.net4j.db.IDBConnectionProvider;
import org.eclipse.net4j.util.collection.MoveableList;
import org.eclipse.net4j.util.lifecycle.Lifecycle;
import org.eclipse.net4j.util.om.monitor.OMMonitor;
import org.eclipse.net4j.util.om.monitor.OMMonitor.Async;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Collections;
import java.util.List;
/**
* This abstract implementation of the {@link IJDBCDelegate} interface is used to translate CDO-related objects to base
* types (e.g. CDOIDs to long) and then to delegate the database execution part to the doXYZ methods which should be
* implemented by extenders. The purpose of this is to provide several JDBC strategies (e.g. simple or prepared
* statement database access) depending on the requirements, but a single point of interpreting and transforming
* CDO-internal structures. The JDBCDelegate also keeps open one database connection and one statement which can be used
* to perform operations on the database. Extenders may, but don't have to, use the statement to perform operations.
*
* @author Stefan Winkler
* @since 2.0
*/
public abstract class AbstractJDBCDelegate extends Lifecycle implements IJDBCDelegate
{
private IDBConnectionProvider connectionProvider;
private boolean readOnly;
private Connection connection;
private Statement statement;
public AbstractJDBCDelegate()
{
}
public IDBConnectionProvider getConnectionProvider()
{
return connectionProvider;
}
public void setConnectionProvider(IDBConnectionProvider connectionProvider)
{
checkInactive();
this.connectionProvider = connectionProvider;
}
public boolean isReadOnly()
{
return readOnly;
}
public void setReadOnly(boolean readOnly)
{
checkInactive();
this.readOnly = readOnly;
}
public final Connection getConnection()
{
return connection;
}
public final Statement getStatement()
{
if (statement == null)
{
try
{
statement = getConnection().createStatement();
}
catch (SQLException ex)
{
throw new DBException(ex);
}
}
return statement;
}
public PreparedStatement getPreparedStatement(String sql)
{
try
{
return getConnection().prepareStatement(sql);
}
catch (SQLException ex)
{
throw new DBException(ex);
}
}
public final void commit(OMMonitor monitor)
{
monitor.begin();
Async async = monitor.forkAsync();
try
{
getConnection().commit();
}
catch (SQLException ex)
{
throw new DBException(ex);
}
finally
{
async.stop();
monitor.done();
}
}
public final void rollback()
{
try
{
getConnection().rollback();
}
catch (SQLException ex)
{
throw new DBException(ex);
}
}
public final void insertAttributes(CDORevision revision, IClassMapping classMapping)
{
doInsertAttributes(classMapping.getTable().getName(), revision, classMapping.getAttributeMappings(), classMapping
.hasFullRevisionInfo());
}
public final void insertReference(CDORevision sourceRevision, int index, CDOID targetId,
IReferenceMapping referenceMapping)
{
doInsertReference(referenceMapping.getTable().getName(), referenceMapping.isWithFeature() ? FeatureServerInfo
.getDBID(referenceMapping.getFeature()) : 0, CDOIDUtil.getLong(sourceRevision.getID()), sourceRevision
.getVersion(), index, CDOIDUtil.getLong(targetId));
}
public final void updateRevised(CDORevision revision, IClassMapping classMapping)
{
doUpdateRevised(classMapping.getTable().getName(), revision.getCreated() - 1, CDOIDUtil.getLong(revision.getID()),
revision.getVersion() - 1);
}
public final void updateRevised(CDOID id, long revised, IClassMapping classMapping)
{
doUpdateRevised(classMapping.getTable().getName(), revised, CDOIDUtil.getLong(id));
}
public final void selectRevisionAttributes(CDORevision revision, IClassMapping classMapping, String where)
{
List<IAttributeMapping> attributeMappings = classMapping.getAttributeMappings();
if (attributeMappings == null)
{
attributeMappings = Collections.emptyList();
}
boolean withFullRevisionInfo = classMapping.hasFullRevisionInfo();
ResultSet resultSet = null;
try
{
resultSet = doSelectRevisionAttributes(classMapping.getTable().getName(), CDOIDUtil.getLong(revision.getID()),
attributeMappings, withFullRevisionInfo, where);
if (!resultSet.next())
{
throw new IllegalStateException("Revision not found: " + CDOIDUtil.getLong(revision.getID()));
}
int i = 0;
if (withFullRevisionInfo)
{
InternalCDORevision rev = (InternalCDORevision)revision;
rev.setVersion(resultSet.getInt(++i));
rev.setCreated(resultSet.getLong(++i));
rev.setRevised(resultSet.getLong(++i));
rev.setResourceID(CDOIDUtil.createLong(resultSet.getLong(++i)));
rev.setContainerID(CDOIDUtil.createLong(resultSet.getLong(++i)));
rev.setContainingFeatureID(resultSet.getInt(++i));
}
if (attributeMappings != null)
{
for (IAttributeMapping attributeMapping : attributeMappings)
{
attributeMapping.extractValue(resultSet, ++i, revision);
}
}
}
catch (SQLException ex)
{
throw new DBException(ex);
}
finally
{
close(resultSet);
}
}
public void selectRevisionReferences(CDORevision revision, IReferenceMapping referenceMapping, int referenceChunk)
{
MoveableList<Object> list = ((InternalCDORevision)revision).getList(referenceMapping.getFeature());
CDOID source = revision.getID();
long sourceId = CDOIDUtil.getLong(source);
int version = revision.getVersion();
ResultSet resultSet = null;
try
{
resultSet = doSelectRevisionReferences(referenceMapping.getTable().getName(), sourceId, version, referenceMapping
.isWithFeature() ? FeatureServerInfo.getDBID(referenceMapping.getFeature()) : 0, "");
while (resultSet.next() && (referenceChunk == CDORevision.UNCHUNKED || --referenceChunk >= 0))
{
long target = resultSet.getLong(1);
list.add(CDOIDUtil.createLong(target));
}
// TODO Optimize this?
while (resultSet.next())
{
list.add(InternalCDORevision.UNINITIALIZED);
}
}
catch (SQLException ex)
{
throw new DBException(ex);
}
finally
{
close(resultSet);
}
}
public void selectRevisionReferenceChunks(IDBStoreChunkReader chunkReader, List<Chunk> chunks,
IReferenceMapping referenceMapping, String where)
{
CDOID source = chunkReader.getRevision().getID();
long sourceId = CDOIDUtil.getLong(source);
int version = chunkReader.getRevision().getVersion();
ResultSet resultSet = null;
try
{
resultSet = doSelectRevisionReferences(referenceMapping.getTable().getName(), sourceId, version, referenceMapping
.isWithFeature() ? FeatureServerInfo.getDBID(referenceMapping.getFeature()) : 0, where);
Chunk chunk = null;
int chunkSize = 0;
int chunkIndex = 0;
int indexInChunk = 0;
while (resultSet.next())
{
long target = resultSet.getLong(1);
if (chunk == null)
{
chunk = chunks.get(chunkIndex++);
chunkSize = chunk.size();
}
chunk.add(indexInChunk++, CDOIDUtil.createLong(target));
if (indexInChunk == chunkSize)
{
chunk = null;
indexInChunk = 0;
}
}
}
catch (SQLException ex)
{
throw new DBException(ex);
}
finally
{
close(resultSet);
}
}
@Override
protected void doActivate() throws Exception
{
super.doActivate();
connection = connectionProvider.getConnection();
connection.setAutoCommit(readOnly);
}
@Override
protected void doDeactivate() throws Exception
{
DBUtil.close(statement);
statement = null;
DBUtil.close(connection);
connection = null;
super.doDeactivate();
}
/**
* Release a statement which has been used by the doSelectXxx implementations to create the respective ResultSet. This
* must only be called with statements created by subclasses. Subclasses should override to handle special cases like
* cached statements which are kept open.
*
* @param stmt
* the statement to close
*/
protected void releaseStatement(Statement stmt)
{
DBUtil.close(stmt);
}
/**
* Insert an attribute row.
*/
protected abstract void doInsertAttributes(String tableName, CDORevision revision,
List<IAttributeMapping> attributeMappings, boolean withFullRevisionInfo);
/**
* Insert a reference row. Note: this is likely to be replaced by an implementation that supports storing multiple
* references in one batch.
*/
protected abstract void doInsertReference(String tableName, int dbId, long source, int version, int i, long target);
/**
* Set the revised date of all unrevised rows of cdoid
*/
protected abstract void doUpdateRevised(String tableName, long revised, long cdoid);
/**
* Set the revised date of a specific revision's previous version.
*/
protected abstract void doUpdateRevised(String tableName, long revisedStamp, long cdoid, int version);
/**
* Select a revision's attributes. The caller is resposible for closing resultSet and associated statement, if
* appropriate.
*/
protected abstract ResultSet doSelectRevisionAttributes(String tableName, long revisionId,
List<IAttributeMapping> attributeMappings, boolean hasFullRevisionInfo, String where) throws SQLException;
/**
* Select a revision's references (or a part thereof) The caller is resposible for closing resultSet and associated
* statement, if appropriate.
*/
protected abstract ResultSet doSelectRevisionReferences(String tableName, long sourceId, int version,
int dbFeatureID, String where) throws SQLException;
/**
* Close the given result set and the statement, if this is needed (which is the case, iff the resultSet's statement
* is not the one which is kept open by this instance).
*/
private void close(ResultSet resultSet)
{
Statement stmt = null;
try
{
stmt = resultSet.getStatement();
}
catch (Exception ex)
{
// Ignore
}
finally
{
DBUtil.close(resultSet);
}
// if the statement is one that has been created for the operation only
// release it.
if (stmt != statement)
{
releaseStatement(stmt);
}
}
}