blob: 34b5f895d3023a69b702d01533867fc7551d4622 [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:
* Stefan Winkler - initial API and implementation
* Stefan Winkler - bug 249610: [DB] Support external references (Implementation)
* Eike Stepper - maintenance
*/
package org.eclipse.emf.cdo.server.internal.db;
import org.eclipse.emf.cdo.common.id.CDOIDExternal;
import org.eclipse.emf.cdo.common.id.CDOIDUtil;
import org.eclipse.emf.cdo.common.protocol.CDODataInput;
import org.eclipse.emf.cdo.common.protocol.CDODataOutput;
import org.eclipse.emf.cdo.server.IStoreAccessor;
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.IPreparedStatementCache;
import org.eclipse.emf.cdo.server.db.IPreparedStatementCache.ReuseProbability;
import org.eclipse.emf.cdo.server.internal.db.bundle.OM;
import org.eclipse.net4j.db.DBException;
import org.eclipse.net4j.db.DBType;
import org.eclipse.net4j.db.DBUtil;
import org.eclipse.net4j.db.ddl.IDBField;
import org.eclipse.net4j.db.ddl.IDBIndex;
import org.eclipse.net4j.db.ddl.IDBTable;
import org.eclipse.net4j.util.ReflectUtil.ExcludeFromDump;
import org.eclipse.net4j.util.lifecycle.Lifecycle;
import org.eclipse.net4j.util.om.monitor.OMMonitor;
import java.io.IOException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.concurrent.atomic.AtomicLong;
/**
* @author Stefan Winkler
*/
public class ExternalReferenceManager extends Lifecycle
{
private static final int NULL = 0;
private IDBTable table;
private IDBField idField;
private IDBField uriField;
private IDBField timestampField;
private final IIDHandler idHandler;
private AtomicLong lastMappedID = new AtomicLong(NULL);
@ExcludeFromDump
private transient String sqlSelectByLongID;
@ExcludeFromDump
private transient String sqlSelectByURI;
@ExcludeFromDump
private transient String sqlInsert;
public ExternalReferenceManager(IIDHandler idHandler)
{
this.idHandler = idHandler;
}
public IIDHandler getIDHandler()
{
return idHandler;
}
public long mapExternalReference(CDOIDExternal id, long commitTime)
{
IDBStoreAccessor accessor = getAccessor();
return mapURI(accessor, id.getURI(), commitTime);
}
public CDOIDExternal unmapExternalReference(long mappedId)
{
IDBStoreAccessor accessor = getAccessor();
return CDOIDUtil.createExternal(unmapURI(accessor, mappedId));
}
public long mapURI(IDBStoreAccessor accessor, String uri, long commitTime)
{
long result = lookupByURI(accessor, uri);
if (result < NULL)
{
// mapping found
return result;
}
return insertNew(accessor, uri, commitTime);
}
public String unmapURI(IDBStoreAccessor accessor, long mappedId)
{
IPreparedStatementCache statementCache = accessor.getStatementCache();
PreparedStatement stmt = null;
ResultSet resultSet = null;
try
{
stmt = statementCache.getPreparedStatement(sqlSelectByLongID, ReuseProbability.HIGH);
stmt.setLong(1, mappedId);
resultSet = stmt.executeQuery();
if (!resultSet.next())
{
OM.LOG.error("External ID " + mappedId + " not found. Database inconsistent!");
throw new IllegalStateException("External ID " + mappedId + " not found. Database inconsistent!");
}
return resultSet.getString(1);
}
catch (SQLException e)
{
throw new DBException(e);
}
finally
{
DBUtil.close(resultSet);
statementCache.releasePreparedStatement(stmt);
}
}
public void rawExport(Connection connection, CDODataOutput out, long fromCommitTime, long toCommitTime)
throws IOException
{
String where = " WHERE " + timestampField + " BETWEEN " + fromCommitTime + " AND " + toCommitTime;
DBUtil.serializeTable(out, connection, table, null, where);
}
public void rawImport(Connection connection, CDODataInput in, long fromCommitTime, long toCommitTime,
OMMonitor monitor) throws IOException
{
DBUtil.deserializeTable(in, connection, table, monitor);
}
@Override
protected void doActivate() throws Exception
{
super.doActivate();
IDBStore store = idHandler.getStore();
table = store.getDBSchema().addTable("cdo_external_refs"); //$NON-NLS-1$
idField = table.addField("id", idHandler.getDBType()); //$NON-NLS-1$
uriField = table.addField("uri", DBType.VARCHAR, 1024); //$NON-NLS-1$
timestampField = table.addField("committime", DBType.BIGINT); //$NON-NLS-1$
table.addIndex(IDBIndex.Type.PRIMARY_KEY, idField);
table.addIndex(IDBIndex.Type.NON_UNIQUE, uriField);
IDBStoreAccessor writer = store.getWriter(null);
Connection connection = writer.getConnection();
Statement statement = null;
ResultSet resultSet = null;
try
{
statement = connection.createStatement();
store.getDBAdapter().createTable(table, statement);
connection.commit();
String sql = "SELECT MIN(" + idField + ") FROM " + table;
resultSet = statement.executeQuery(sql);
if (resultSet.next())
{
lastMappedID.set(resultSet.getLong(1));
}
// else: resultSet is empty => table is empty
// and lastMappedId stays 0 - as initialized.
}
catch (SQLException ex)
{
connection.rollback();
throw new DBException(ex);
}
finally
{
DBUtil.close(resultSet);
DBUtil.close(statement);
writer.release();
}
StringBuilder builder = new StringBuilder();
builder.append("INSERT INTO ");
builder.append(table);
builder.append("(");
builder.append(idField);
builder.append(",");
builder.append(uriField);
builder.append(",");
builder.append(timestampField);
builder.append(") VALUES (?, ?, ?)");
sqlInsert = builder.toString();
builder = new StringBuilder();
builder.append("SELECT "); //$NON-NLS-1$
builder.append(idField);
builder.append(" FROM "); //$NON-NLS-1$
builder.append(table);
builder.append(" WHERE "); //$NON-NLS-1$
builder.append(uriField);
builder.append("=?"); //$NON-NLS-1$
sqlSelectByURI = builder.toString();
builder = new StringBuilder();
builder.append("SELECT "); //$NON-NLS-1$
builder.append(uriField);
builder.append(" FROM "); //$NON-NLS-1$
builder.append(table);
builder.append(" WHERE "); //$NON-NLS-1$
builder.append(idField);
builder.append("=?"); //$NON-NLS-1$
sqlSelectByLongID = builder.toString();
}
private long insertNew(IDBStoreAccessor accessor, String uri, long commitTime)
{
long newMappedID = lastMappedID.decrementAndGet();
IPreparedStatementCache statementCache = accessor.getStatementCache();
PreparedStatement stmt = null;
try
{
stmt = statementCache.getPreparedStatement(sqlInsert, ReuseProbability.MEDIUM);
stmt.setLong(1, newMappedID);
stmt.setString(2, uri);
stmt.setLong(3, commitTime);
DBUtil.update(stmt, true);
return newMappedID;
}
catch (SQLException e)
{
throw new DBException(e);
}
finally
{
statementCache.releasePreparedStatement(stmt);
}
}
private long lookupByURI(IDBStoreAccessor accessor, String uri)
{
IPreparedStatementCache statementCache = accessor.getStatementCache();
PreparedStatement stmt = null;
ResultSet resultSet = null;
try
{
stmt = statementCache.getPreparedStatement(sqlSelectByURI, ReuseProbability.HIGH);
stmt.setString(1, uri);
resultSet = stmt.executeQuery();
if (resultSet.next())
{
return resultSet.getLong(1);
}
// Not found ...
return NULL;
}
catch (SQLException e)
{
throw new DBException(e);
}
finally
{
DBUtil.close(resultSet);
statementCache.releasePreparedStatement(stmt);
}
}
private static IDBStoreAccessor getAccessor()
{
IStoreAccessor accessor = StoreThreadLocal.getAccessor();
if (accessor == null)
{
throw new IllegalStateException("Can only be called from within a valid IDBStoreAccessor context");
}
return (IDBStoreAccessor)accessor;
}
}