/* * Copyright (c) 2009-2013, 2016 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.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.IDBDatabase; import org.eclipse.net4j.db.IDBDatabase.RunnableWithSchema; import org.eclipse.net4j.db.IDBPreparedStatement; import org.eclipse.net4j.db.IDBPreparedStatement.ReuseProbability; import org.eclipse.net4j.db.ddl.IDBIndex; import org.eclipse.net4j.db.ddl.IDBSchema; 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.ResultSet; import java.sql.SQLException; import java.util.concurrent.atomic.AtomicLong; /** * @author Stefan Winkler */ public class ExternalReferenceManager extends Lifecycle { private static final String EXTERNAL_REFS = "cdo_external_refs"; private static final String EXTERNAL_REFS_ID = "ID"; private static final String EXTERNAL_REFS_URI = "URI"; private static final String EXTERNAL_REFS_COMMITTIME = "COMMITTIME"; private static final int NULL = 0; private IDBTable table; 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) { IDBPreparedStatement stmt = accessor.getDBConnection().prepareStatement(sqlSelectByLongID, ReuseProbability.HIGH); ResultSet resultSet = null; try { 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); DBUtil.close(stmt); } } public long lookupByURI(IDBStoreAccessor accessor, String uri) { IDBPreparedStatement stmt = accessor.getDBConnection().prepareStatement(sqlSelectByURI, ReuseProbability.HIGH); ResultSet resultSet = null; try { 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); DBUtil.close(stmt); } } public void rawExport(Connection connection, CDODataOutput out, long fromCommitTime, long toCommitTime) throws IOException { String where = " WHERE " + EXTERNAL_REFS_COMMITTIME + " 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(); final IDBStore store = idHandler.getStore(); IDBDatabase database = store.getDatabase(); table = database.getSchema().getTable(EXTERNAL_REFS); if (table == null) { database.updateSchema(new RunnableWithSchema() { public void run(IDBSchema schema) { table = schema.addTable(EXTERNAL_REFS); table.addField(EXTERNAL_REFS_ID, idHandler.getDBType(), store.getIDColumnLength(), true); table.addField(EXTERNAL_REFS_URI, DBType.VARCHAR, 1024); table.addField(EXTERNAL_REFS_COMMITTIME, DBType.BIGINT); table.addIndex(IDBIndex.Type.PRIMARY_KEY, EXTERNAL_REFS_ID); table.addIndex(IDBIndex.Type.NON_UNIQUE, EXTERNAL_REFS_URI); } }); } else { String sql = "SELECT MIN(" + EXTERNAL_REFS_ID + ") FROM " + table; IDBStoreAccessor writer = store.getWriter(null); IDBPreparedStatement stmt = writer.getDBConnection().prepareStatement(sql, ReuseProbability.LOW); ResultSet resultSet = null; try { resultSet = stmt.executeQuery(); if (resultSet.next()) { lastMappedID.set(resultSet.getLong(1)); } // else: resultSet is empty => table is empty // and lastMappedId stays 0 - as initialized. } catch (SQLException ex) { writer.getDBConnection().rollback(); throw new DBException(ex); } finally { DBUtil.close(resultSet); DBUtil.close(stmt); writer.release(); } } sqlInsert = "INSERT INTO " + table + "(" + EXTERNAL_REFS_ID + "," + EXTERNAL_REFS_URI + "," + EXTERNAL_REFS_COMMITTIME + ") VALUES (?, ?, ?)"; sqlSelectByURI = "SELECT " + EXTERNAL_REFS_ID + " FROM " + table + " WHERE " + EXTERNAL_REFS_URI + "=?"; sqlSelectByLongID = "SELECT " + EXTERNAL_REFS_URI + " FROM " + table + " WHERE " + EXTERNAL_REFS_ID + "=?"; } private long insertNew(IDBStoreAccessor accessor, String uri, long commitTime) { long newMappedID = lastMappedID.decrementAndGet(); IDBPreparedStatement stmt = accessor.getDBConnection().prepareStatement(sqlInsert, ReuseProbability.MEDIUM); try { 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 { DBUtil.close(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; } }