package pt.ist.fenixframework.pstm; import java.io.ObjectStreamException; import java.io.Serializable; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; import java.util.Collections; import java.util.IdentityHashMap; import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.apache.ojb.broker.PersistenceBroker; import org.apache.ojb.broker.PersistenceBrokerFactory; import dml.DomainClass; import dml.DomainModel; import pt.ist.fenixframework.pstm.repository.DbUtil; public class DomainClassInfo implements Serializable { private static final Logger logger = LoggerFactory.getLogger(DomainClassInfo.class); private volatile static Map<Class, DomainClassInfo> classInfoMap; private volatile static DomainClassInfo[] classInfoById; private volatile static long serverOidBase; static void initializeClassInfos(int serverId) { serverOidBase = (long)serverId << 48; // the server id provides de 16 most significant bits of the OID PersistenceBroker broker = null; ResultSet rs = null; Statement stmt = null; try { broker = PersistenceBrokerFactory.defaultPersistenceBroker(); // repeat until success while (true) { broker.beginTransaction(); Connection conn = broker.serviceConnectionManager().getConnection(); stmt = conn.createStatement(); rs = stmt.executeQuery("SELECT DOMAIN_CLASS_NAME,DOMAIN_CLASS_ID FROM FF$DOMAIN_CLASS_INFO"); Map<Class, DomainClassInfo> map = new IdentityHashMap<Class, DomainClassInfo>(); ArrayList<DomainClassInfo> array = new ArrayList<DomainClassInfo>(); int maxId = 0; // read all infos while (rs.next()) { String classname = rs.getString(1); int cid = rs.getInt(2); DomainClassInfo classInfo = new DomainClassInfo(classname, cid); maxId = Math.max(maxId, cid); addNewInfo(map, array, classInfo); } // create any missing records try { DomainModel model = MetadataManager.getDomainModel(); for (DomainClass domClass : model.getDomainClasses()) { Class javaClass = Class.forName(domClass.getFullName()); if (javaClass != PersistentRoot.class && !map.containsKey(javaClass)) { DomainClassInfo classInfo = new DomainClassInfo(javaClass, ++maxId); addNewInfo(map, array, classInfo); if (logger.isInfoEnabled()) { logger.info("Registering new domain class '" + javaClass.getName() + "' with id " + classInfo.classId); } stmt.executeUpdate("INSERT INTO FF$DOMAIN_CLASS_INFO VALUES ('" + javaClass.getName() + "', " + classInfo.classId + ")"); } } // try to commit broker.commitTransaction(); // the commit was ok, so finish the initialization by // assigning to the static variables classInfoMap = Collections.unmodifiableMap(map); classInfoById = new DomainClassInfo[maxId + 1]; array.toArray(classInfoById); return; } catch (SQLException e) { logger.info("The registration of new DomainClassInfos failed. Retrying..."); // the inserts into the database or the commit may fail if a // concurrent execution tries to create new records also // if that happens, abort the current transaction and retry // with a new one broker.abortTransaction(); } } } catch (Exception e) { // if an exception occurs, throw an error throw new Error(e); } finally { if (broker != null) { if (broker.isInTransaction()) { broker.abortTransaction(); } broker.close(); } if (rs != null) { try { rs.close(); } catch (SQLException e) { e.printStackTrace(); } } if (stmt != null) { try { stmt.close(); } catch (SQLException e) { e.printStackTrace(); } } } } private static Class findClass(String classname) { try { return Class.forName(classname); } catch (ClassNotFoundException cnfe) { // domain classes may disappear, but their id should not be reused // so, if the corresponding Java class does not exist, return null return null; } } private static void addNewInfo(Map<Class, DomainClassInfo> map, ArrayList<DomainClassInfo> array, DomainClassInfo info) { if (info.domainClass != null) { map.put(info.domainClass, info); } int index = info.classId; int size = array.size(); if (size <= index) { array.ensureCapacity(index + 1); while (size < index) { array.add(null); size++; } array.add(info); } else { array.set(info.classId, info); } } public static int mapClassToId(Class objClass) { if (objClass == PersistentRoot.class) { return 0; } else { DomainClassInfo domainClassInfo = classInfoMap.get(objClass); if (domainClassInfo == null) { throw new RuntimeException("Domain class not registered: " + objClass.getCanonicalName()); } return domainClassInfo.classId; } } private static Class mapIdToClass(int cid) { if (cid == 0) { return PersistentRoot.class; } else if ((cid < 1) || (cid >= classInfoById.length)) { return null; } else { return classInfoById[cid].domainClass; } } private static int mapOidToClassId(long oid) { if (oid == 1) { return 0; } else { return (((int) (oid >> 32)) & 0x0000FFFF); // shift class id to // rightmost position and // clear server id bits } } public static Class mapOidToClass(long oid) { return mapIdToClass(mapOidToClassId(oid)); } public static long getNextOidFor(Class objClass) throws Exception { int nextKey; DomainClassInfo info = classInfoMap.get(objClass); synchronized(info) { int lastKey = info.getLastKey(); if (lastKey == UNKNOWN_KEY) { // not yet initialized from the DB lastKey = initLastKeyFor(info); } nextKey = lastKey + 1; info.setLastKey(nextKey); } // System.out.print("Server(" + (int)(serverOidBase >> 48) + ")"); // System.out.print(", Class(" + info.classId + ")"); // System.out.print(", Object(" + nextKey + ")"); // System.out.println(": " + serverOidBase + " + " + ((long)info.classId << 32) + " + " // + nextKey + " = " + (serverOidBase + ((long)info.classId << 32) + nextKey)); // build and return OID if ((PersistentRoot.class == objClass) && (nextKey == 1)) { // this first PersistentRoot instance is special and always takes a known value return 1L; } else { return serverOidBase + ((long)info.classId << 32) + nextKey; } } /* Invocations to this method should be synchronized in the <code>info</code> argument */ private static int initLastKeyFor(DomainClassInfo info) throws Exception { long baseRange = serverOidBase + ((long)info.classId << 32); long maxId = getMaxIdForClass(info.domainClass.getName(), baseRange, baseRange + 0xFFFFFFFFL); return (int)maxId; // the lower 32 bit are the object's relative id. } public static long getMaxIdForClass(String className, long lowestId, long highestId) throws Exception { PersistenceBroker broker = null; Statement stmt = null; try { broker = PersistenceBrokerFactory.defaultPersistenceBroker(); broker.beginTransaction(); Connection conn = broker.serviceConnectionManager().getConnection(); stmt = conn.createStatement(); StringBuilder sqlStmtText = new StringBuilder(); sqlStmtText.append("SELECT MAX(OID) FROM "); DomainClass domainClass = MetadataManager.getDomainModel().findClass(className); sqlStmtText.append(OJBMetadataGenerator.getExpectedTableName(domainClass)); sqlStmtText.append(" WHERE OID > "); sqlStmtText.append(lowestId); sqlStmtText.append(" AND OID <= "); sqlStmtText.append(highestId); sqlStmtText.append(";"); ResultSet rs = stmt.executeQuery(sqlStmtText.toString()); broker.commitTransaction(); rs.first(); return rs.getLong(1); // getLong() will return 0 in case there is no line matching the query } finally { if (broker != null) { if (broker.isInTransaction()) { broker.abortTransaction(); } broker.close(); } if (stmt != null) { try { stmt.close(); } catch (SQLException e) { // nothing can be done now. } } } } private static final int UNKNOWN_KEY = 0; // the non-static part starts here public final String domainClassName; public final transient Class domainClass; public final int classId; /** The maximum object key used for objects of this class in this server */ private transient int lastKey = UNKNOWN_KEY; public DomainClassInfo(Class domainClass, int classId) { this(domainClass.getName(), domainClass, classId); } public DomainClassInfo(String domainClassName, int classId) { this(domainClassName, findClass(domainClassName), classId); } public DomainClassInfo(String domainClassName, Class domainClass, int classId) { this.domainClassName = domainClassName; this.domainClass = domainClass; this.classId = classId; } protected int getLastKey() { return this.lastKey; } protected void setLastKey(int lastKey) { this.lastKey = lastKey; } // serialization code protected Object writeReplace() throws ObjectStreamException { return new SerializedForm(this); } private static class SerializedForm implements Serializable { private static final long serialVersionUID = 1L; private String className; private int classId; SerializedForm(DomainClassInfo obj) { this.className = obj.domainClassName; this.classId = obj.classId; } Object readResolve() throws ObjectStreamException, ClassNotFoundException { return new DomainClassInfo(this.className, this.classId); } } }