/* GASH 2 DBObjectBase.java The GANYMEDE object storage system. Created: 2 July 1996 Module By: Jonathan Abbey, jonabbey@arlut.utexas.edu ----------------------------------------------------------------------- Ganymede Directory Management System Copyright (C) 1996-2014 The University of Texas at Austin Ganymede is a registered trademark of The University of Texas at Austin Contact information Web site: http://www.arlut.utexas.edu/gash2 Author Email: ganymede_author@arlut.utexas.edu Email mailing list: ganymede@arlut.utexas.edu US Mail: Computer Science Division Applied Research Laboratories The University of Texas at Austin PO Box 8029, Austin TX 78713-8029 Telephone: (512) 835-3200 This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ package arlut.csd.ganymede.server; import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; import java.io.PrintWriter; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.rmi.RemoteException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.Enumeration; import java.util.HashSet; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Vector; import org.python.core.PyInteger; import arlut.csd.JDialog.JDialogBuff; import arlut.csd.Util.JythonMap; import arlut.csd.Util.Queue; import arlut.csd.Util.StringUtils; import arlut.csd.Util.TranslationService; import arlut.csd.Util.VecQuickSort; import arlut.csd.Util.VectorUtils; import arlut.csd.Util.XMLItem; import arlut.csd.Util.XMLNameValidator; import arlut.csd.Util.XMLUtils; import arlut.csd.Util.booleanSemaphore; import arlut.csd.ganymede.common.BaseListTransport; import arlut.csd.ganymede.common.CategoryTransport; import arlut.csd.ganymede.common.FieldTemplate; import arlut.csd.ganymede.common.FieldType; import arlut.csd.ganymede.common.Invid; import arlut.csd.ganymede.common.ReturnVal; import arlut.csd.ganymede.common.SchemaConstants; import arlut.csd.ganymede.rmi.Base; import arlut.csd.ganymede.rmi.BaseField; import arlut.csd.ganymede.rmi.Category; import arlut.csd.ganymede.rmi.CategoryNode; import arlut.csd.ganymede.rmi.Session; /*------------------------------------------------------------------------------ class DBObjectBase ------------------------------------------------------------------------------*/ /** * <p>The data dictionary and object store for a particular kind of * object in the {@link arlut.csd.ganymede.server.DBStore DBStore} on * the Ganymede server.</p> * * <p>Each DBObjectBase object includes a set of {@link * arlut.csd.ganymede.server.DBObjectBaseField DBObjectBaseField} * objects, which define the types and constraints on fields that may * be present in objects of this type. These field definitions are * held in an {@link arlut.csd.ganymede.server.DBBaseFieldTable * DBBaseFieldTable}.</p> * * <p>The actual {@link arlut.csd.ganymede.server.DBObject DBObject}'s * themselves are contained in an optimized {@link * arlut.csd.ganymede.server.DBObjectTable DBObjectTable} contained * within this DBObjectBase.</p> * * <p>In addition to holding name, type id, and category information * for a given object type, the DBObjectBase class may also contain a * string classname for a Java class to be dynamically loaded to * manage the server's interactions with objects of this type. Such a * class name must refer to a subclass of the {@link * arlut.csd.ganymede.server.DBEditObject DBEditObject} class. If * such a custom class is defined for this object type, DBObjectBase * will contain an {@link * arlut.csd.ganymede.server.DBObjectBase#objectHook objectHook} * DBEditObject instance whose methods will be consulted to customize * a lot of the server's functioning.</p> * * <p>DBObjectBase also keeps track of {@link * arlut.csd.ganymede.server.DBReadLock DBReadLocks}, {@link * arlut.csd.ganymede.server.DBWriteLock DBWriteLocks}, and {@link * arlut.csd.ganymede.server.DBDumpLock DBDumpLocks}, to manage * changes to be made to objects contained in this DBObjectBase.</p> * * <p>DBObjectBase implements the {@link arlut.csd.ganymede.rmi.Base * Base} RMI remote interface, which is used by the client to * determine type information for objects of this type, as well as by * the schema editor when the schema is being edited.</p> */ public final class DBObjectBase implements Base, CategoryNode, JythonMap { static boolean debug = true; /** * <p>TranslationService object for handling string localization in * the Ganymede server.</p> */ static final TranslationService ts = TranslationService.getTranslationService("arlut.csd.ganymede.server.DBObjectBase"); /** * <p>More debugging.</p> */ final static boolean debug2 = false; final static boolean xmldebug = false; /** * The number of spaces we save in our hashtable before it will need * to grow again. */ static final int GROWTHSPACE = 250; private static HashMap<String, String> upgradeClassMap = null; public static void setDebug(boolean val) { System.err.println("DBObjectBase.setDebug(): " + val); debug = val; } private static Comparator comparator = new Comparator() { public int compare(Object a, Object b) { DBObjectBaseField aF, bF; aF = (DBObjectBaseField) a; bF = (DBObjectBaseField) b; if (aF.tmp_displayOrder < bF.tmp_displayOrder) { return -1; } else if (bF.tmp_displayOrder > aF.tmp_displayOrder) { return 1; } else { return 0; } } }; /** * <p>If we are processing an older ganymede.db file, our built-in * classes may be specified using the older packages. This method * initializes a static map upgradeClassMap from the old class names * to the new ones, for use by the receive() method in this * class.</p> */ private synchronized static void prepClassMap() { if (upgradeClassMap == null) { upgradeClassMap = new HashMap<String, String>(); upgradeClassMap.put("arlut.csd.ganymede.eventCustom", "arlut.csd.ganymede.server.eventCustom"); upgradeClassMap.put("arlut.csd.ganymede.objectEventCustom", "arlut.csd.ganymede.server.objectEventCustom"); upgradeClassMap.put("arlut.csd.ganymede.ownerCustom", "arlut.csd.ganymede.server.ownerCustom"); upgradeClassMap.put("arlut.csd.ganymede.adminPersonaCustom", "arlut.csd.ganymede.server.adminPersonaCustom"); upgradeClassMap.put("arlut.csd.ganymede.permCustom", "arlut.csd.ganymede.server.permCustom"); upgradeClassMap.put("arlut.csd.ganymede.taskCustom", "arlut.csd.ganymede.server.taskCustom"); } } /* - */ /** * Enum of possible editing states for a DBObjectBase to be in. */ public enum EditingMode { /** * The object base is locked and may not have any of its object or * field definition parameters changed in any way. */ LOCKED, /** * The object base is being constructed, and any and all object or * field definition parameters may be set. */ INITIALIZING, /** * The object base is being loaded, and any and all object or * field definition parameters may be set. */ LOADING, /** * The object base is being created ex nihilo by a DBSchemaEdit * context, and certain fields that might not be mutable in other * circumstances can have their configuration set. */ CREATING, /** * A pre-existing object base is being edited by a DBSchemaEdit * context. */ EDITING; } /* - */ /** * <p>The central Ganymede database object that this object base is contained * within.</p> */ final private DBStore store; /** * <p>Name of this object type</p> */ private String object_name; /** * <p>short type id code for this object type. This number is * used as the {@link arlut.csd.ganymede.common.Invid#type type} code * in {@link arlut.csd.ganymede.common.Invid Invid}s pointing to objects * of this type.</p> */ private short type_code; /** * <p>Fully qualified package and class name for a custom {@link * arlut.csd.ganymede.server.DBEditObject DBEditObject} subclass to * be dynamically loaded to manage operations on this * DBObjectBase.</p> * * <p>Note that this needs not to be private because {@link * arlut.csd.ganymede.server.DBStore#initializeSchema()} uses it to * initialize class definitions when bootstrapping. If we ever * revise the initializeSchema code (tricky, given that * setClassInfo() automatically sets the classdef and creates the * objectHook, which we aren't necessarily ready to do during * initializeSchema until the very end) , we can think about making * this private.</p> */ private String classname; /** * <p>Class definition for a {@link * arlut.csd.ganymede.server.DBEditObject DBEditObject} subclass * dynamically loaded to manage operations on this DBObjectBase.</p> */ private Class classdef; /** * <p>Option string to be available to custom classes. The purpose * of this field is to allow the use of Jython custom classes, and * to be able to define a single Jython class which can consult this * string to find the Jython program text for this object base.</p> */ private String classOptionString; /** * <p>Which field represents our label? This should always be a * field code corresponding to a namespace-constrained string, * i.p. address or numeric field.</p> * * <p>This member variable will be set to -1 if the label field has * not yet been set.</p> */ private short label_id; /** * what category is this object in? */ private Category category; /** * <p>If true, this type of object is used as a target for an * edit-in-place {@link arlut.csd.ganymede.server.InvidDBField * InvidDBField}.</p> */ private boolean embedded; /** * Controls whether changes to this object base (and its field * definitions) are permitted at the present. */ private EditingMode editingMode = EditingMode.INITIALIZING; // runtime data /** * Custom field dictionary sorted in display order. This List does * *not* include any built-in fields. Elements of this List are * {@link arlut.csd.ganymede.server.DBObjectBaseField * DBObjectBaseFields}. */ private List<DBObjectBaseField> customFields; /** * <p>Cached template vector</p> */ private Vector<FieldTemplate> templateVector; /** * field dictionary */ private DBBaseFieldTable fieldTable; /** * objects in our objectBase */ private DBObjectTable objectTable; /** * highest invid to date */ private int maxid; /** * <p>Used only during loading of pre-2.0 format ganymede.db * files</p> * * <p>Note that this is currently DBObjectBase's only package * private field, because we share the use of these tmp_displayOrder * fields with DBBaseCategory and DBObjectBaseField, and there's * just little upside in reworking the old compatibility code to use * setters and accessors for this purpose.</p> */ int tmp_displayOrder = -1; /** * <p>Timestamp for the last time this DBObjectBase was changed, * used by {@link arlut.csd.ganymede.server.GanymedeBuilderTask * GanymedeBuilderTasks} to determine whether a particular build * sequence is necessary.</p> * * <p>See also {@link * arlut.csd.ganymede.server.DBObjectBaseField#lastChange} for a * timestamp for the last time that a given field was changed in any * object in the containing object base. Both the per-DBObjectBase * and per-DBObjectBaseField lastChange variables only track the * last change since the Ganymede server was started.</p> */ private Date lastChange; /** * <p>If this DBObjectBase is locked with an exclusive lock (a * {@link arlut.csd.ganymede.server.DBWriteLock DBWriteLock}), this * field will point to it.</p> * * <p>This field is not currently used for anything in particular in * the lock logic, it is here strictly for informational/debugging * purposes.</p> */ private DBWriteLock currentLock; /** * <p>Set of {@link arlut.csd.ganymede.server.DBWriteLock * DBWriteLock}s pending on this DBObjectBase. DBWriteLocks will * add themselves to the writerList upon entering establish(). If * writerList is not empty, no new {@link * arlut.csd.ganymede.server.DBReadLock DBReadLock}s will be allowed * to add to add themselve to the readerList in this DBObjectBase. * {@link arlut.csd.ganymede.server.DBDumpLock DBDumpLock}s don't * check the writerList, and will add themselves to the dumperList * as needed, which will block any further writers from queuing up * in the list.</p> * * <p>When a DBWriteLock is locked onto this base, it is taken out * of writerList, writeInProgress is set to true, and currentLock is * set to point to the DBWriteLock that has exclusive access.</p> * * <p>Note that there is no guarantee that DBWriteLocks will be * granted access to any given DBObjectBase in the order that their * threads entered the establish() method, as different DBWriteLocks * may be attempting to establish() on differing sets of * DBObjectBases. There is not in fact any attempt in the * DBWriteLock establish() method to ensure that writers are given * the lock on a DBObjectBase in their writerList ordering. The * establish() methods may establish() any writer in any order, * depending on the server's threading behavior.</p> */ private Vector<DBWriteLock> writerList; /** * <p>Collection of {@link arlut.csd.ganymede.server.DBReadLock * DBReadLock}s that are locked on this DBObjectBase.</p> */ private Vector<DBReadLock> readerList; /** * <p>Set of {@link arlut.csd.ganymede.server.DBDumpLock * DBDumpLock}s pending on this DBObjectBase. DBDumpLocks will add * themselves to the dumperList upon entering establish(). If * dumperList is not empty, no new {@link * arlut.csd.ganymede.server.DBWriteLock DBWriteLock}s will be * allowed to add themselves to the {@link * arlut.csd.ganymede.server.DBObjectBase#writerList writerList} in * this DBObjectBase.</p> * * <p>Note that there is no guarantee that DBDumpLocks will be * granted access to any given DBObjectBase in the order that their * threads entered the establish() method, as different DBDumpLocks * may be attempting to establish() on differing sets of * DBObjectBases. There is not in fact any attempt in the * DBDumpLock establish() method to ensure that writers are given * the lock on a DBObjectBase in their dumperList ordering. The * establish() methods may establish() any dumper in any order, * depending on the server's threading behavior.</p> */ private Vector<DBDumpLock> dumperList; /** * <p>Collection of {@link arlut.csd.ganymede.server.DBDumpLock * DBDumpLock}s that are locked on this DBObjectBase.</p> */ private Vector<DBDumpLock> dumpLockList; /** * <p>Boolean semaphore monitoring whether or not this DBObjectBase * is currently locked for writing. We use a booleanSemaphore here * rather than a simple boolean so that we can force a memory * barrier for access on multiple CPU systems.</p> */ private booleanSemaphore writeInProgress = new booleanSemaphore(false); /** * Used to keep track of schema editing */ private DBSchemaEdit editor; /** * <p>This unmodifiable List holds the current collection of {@link * arlut.csd.ganymede.server.DBObject DBObject} objects in this * DBObjectBase, for enumeration access. The GanymedeSession query * logic iterates over this List so that querying on single bases * can proceed while commits are under way.</p> * * <p>This is practicable because assignment to this variable is an * inherently atomic event in the Java spec, so we just wait to * assign a new Vector here until we have a new one composed. We * just have to depend on all code that accesses this vector to grab * its own reference to this vector and then not modify it, and to * drop reference to it when the iteration is complete.</p> */ private List<DBObject> iterationList; // Customization Management Object /** * <p>Each DBObjectBase can have an instantiation of a custom {@link * arlut.csd.ganymede.server.DBEditObject DBEditObject} subclass to * respond to a number of 'pseudostatic' method calls which * customize the Ganymede server's behavior when dealing with * objects of this DBObjectBase's type. The DBObjectBase {@link * arlut.csd.ganymede.server.DBObjectBase#createHook() createHook()} * method is responsible for loading the custom DBEditObject * subclass ({@link arlut.csd.ganymede.server.DBObjectBase#classdef * classdef}) from the {@link * arlut.csd.ganymede.server.DBObjectBase#classname classname} * specified in the ganymede.db schema section.</p> * * <p>objectHook should never be null while the server is in * operation. If the Ganymede schema definition data in the * ganymede.db file does not specify a special class for this object * type's objectHook, DBObjectBase should have an instance of the * base DBEditObject class here.</p> * * <p>See the Ganymede DBEditObject subclassing/customization guide * for a lot more details on the use of DBEditObjects as * objectHooks.</p> */ private DBEditObject objectHook; /* -- */ /** * <p>Schema initialization constructor. Used by DBStore when * bootstrapping.</p> * * @param store The DBStore database this DBObjectBase is being created for. * @param embedded If true, objects of this DBObjectBase type will not * be top-level objects, but rather will be embedded using edit-in-place * {@link arlut.csd.ganymede.server.InvidDBField InvidDBFields}. */ public DBObjectBase(DBStore store, boolean embedded) throws RemoteException { this(store, embedded, true, null); // NB: editingMode is left in EditingMode.INITIALIZING for DBStore // initialization. } /** * <p>Creation constructor. Used when the schema editor interface * is used to create a new DBObjectBase.</p> * * @param store The DBStore database this DBObjectBase is being created for. * @param id The id code to be assigned to this new DBObjectBase. * @param embedded If true, objects of this DBObjectBase type will not * be top-level objects, but rather will be embedded using edit-in-place * @param editor The DBSchemaEdit object controlling the edit. */ public DBObjectBase(DBStore store, short id, boolean embedded, DBSchemaEdit editor) throws RemoteException { this(store, embedded, true, editor); setTypeID(id); editingMode = EditingMode.CREATING; } /** * <p>Receive constructor. Used to initialize this DBObjectBase * from disk and load the objects of this type in from the standing * store.</p> * * @param in Input stream to read this object base from. * @param store The Ganymede database object we are loading into. */ public DBObjectBase(DataInput in, DBStore store) throws IOException, RemoteException { // create an empty object base without creating the built in // fields.. we'll load fields and create the system standard // fields once we know whether the newly loaded object definition // is for an embedded object or not. this(store, false, false, null); editingMode = EditingMode.LOADING; receive(in); editingMode = EditingMode.LOCKED; // need to recreate objectHook now that we have loaded our classdef info // from disk. objectHook = this.createHook(); } /** * <p>Copy constructor. Used to create a copy that we can play with * for schema editing.</p> * * @param original The original DBObjectBase we're creating a copy * of for modification during a schema edit. * @param editor The DBSchemaEdit object controlling the edit. */ public DBObjectBase(DBObjectBase original, DBSchemaEdit editor) throws RemoteException { this(original.store, original.embedded, true, editor); synchronized (original) { object_name = original.object_name; classname = original.classname; classOptionString = original.classOptionString; classdef = original.classdef; type_code = original.type_code; label_id = original.label_id; category = original.category; embedded = original.embedded; // make copies of all the custom field definitions for this // object type, and save them into our own field hash. for (DBObjectBaseField fieldDef: original.customFields) { DBObjectBaseField bf = new DBObjectBaseField(fieldDef, this); addFieldToEnd(bf); } // remember the objects.. note that we don't at this point notify // the objects that this new DBObjectBase is their owner.. we'll // take care of that when and if the DBSchemaEdit base editing session // commits this copy objectTable = original.objectTable; iterationList = original.iterationList; // this is safe to do only in the schema editing context maxid = original.maxid; objectHook = original.objectHook; lastChange = new Date(); } editingMode = EditingMode.EDITING; } /** * <p>This constructor actually does all the work of initializing a * new DBObjectBase. All other constructors for DBObjectBase will * call this constructor before doing any other work.</p> * * @param store The DBStore database this DBObjectBase is being created for. * @param embedded If true, objects of this DBObjectBase type will not * be top-level objects, but rather will be embedded using edit-in-place * {@link arlut.csd.ganymede.server.InvidDBField InvidDBFields}. * @param createFields If true, the standard fields required by the * server for its own operations will be created as part of * DBObjectBase creation. This should be false if this DBObjectBase * is being created in the process of loading data from a * pre-existing database which will presumably already have all * essential fields defined. * @param editor If this DBObjectBase is being created in the * context of an interactive or xml based schema editor, this * parameter will point to the DBSchemaEdit object controlling the * edit. */ private DBObjectBase(DBStore store, boolean embedded, boolean createFields, DBSchemaEdit editor) throws RemoteException { if (editor == null && !store.isLoading()) { throw new IllegalStateException("Can't create a DBObjectBase unless loading or editing."); } editingMode = EditingMode.INITIALIZING; this.store = store; this.editor = editor; writerList = new Vector<DBWriteLock>(); readerList = new Vector<DBReadLock>(); dumperList = new Vector<DBDumpLock>(); dumpLockList = new Vector<DBDumpLock>(); iterationList = Collections.unmodifiableList(new ArrayList<DBObject>()); object_name = ""; classname = ""; classdef = null; type_code = -1; label_id = -1; category = null; customFields = new ArrayList<DBObjectBaseField>(); fieldTable = new DBBaseFieldTable(); objectTable = new DBObjectTable(4000, (float) 1.0); maxid = 0; lastChange = new Date(); editor = null; this.embedded = embedded; if (createFields) { createBuiltIns(embedded); } objectHook = this.createHook(); Ganymede.rmi.publishObject(this); } synchronized void emit(DataOutput out, boolean dumpObjects) throws IOException { out.writeUTF(object_name); if (classname == null) { out.writeUTF(""); } else { out.writeUTF(classname); } if (classOptionString == null) // added at file version 2.6 { out.writeUTF(""); } else { out.writeUTF(classOptionString); } out.writeShort(type_code); out.writeShort((short) customFields.size()); // should have no more than 32k fields // and write out the custom field definitions, in display order for (DBObjectBaseField fieldDef: customFields) { fieldDef.emit(out); } out.writeShort(label_id); // added at file version 1.1 out.writeBoolean(embedded); // added at file version 1.5 if (dumpObjects) { out.writeInt(maxid); // added at file version 1.12 out.writeInt(objectTable.size()); for (DBObject object: objectTable) { object.emit(out); } } else { out.writeInt(0); // maxid added at file version 1.12 out.writeInt(0); // table size } } synchronized void receive(DataInput in) throws IOException { int size; DBObject tempObject; int temp_val; int object_count; DBObjectBaseField field; /* -- */ setName(in.readUTF()); // we use setName to filter out any bad chars in transition to 1.0 classname = in.readUTF(); // if we're reading a ganymede.db file from before the DBStore 2.7 // rework, let's fix up our class names if (store.isLessThan(2,7)) { prepClassMap(); if (upgradeClassMap.containsKey(classname)) { String newclassname = upgradeClassMap.get(classname); if (debug) { // "DBObjectBase.receive(): loading {0}" System.err.println(ts.l("receive.basename", object_name)); // "DBObjectBase.receive(): Rewriting old system class name: {0} as {1}" System.err.println(ts.l("receive.rewritingClassname", classname, newclassname)); } classname = newclassname; } } if (store.isAtLeast(2,6)) { classOptionString = in.readUTF(); } else { classOptionString = null; } type_code = in.readShort(); // read our index for the DBStore's objectbase hash // how many field definitions? size = in.readShort(); // read in the custom field dictionary for this object, in display // order for (int i = 0; i < size; i++) { field = new DBObjectBaseField(in, this); // skip any system standard field definitions, which will // be created in the field table separately if (field.getID() <= SchemaConstants.FinalSystemField) { continue; // don't save the db's version of a system standard field } if (type_code == SchemaConstants.OwnerBase && field.getID() == SchemaConstants.OwnerObjectsOwned) { continue; // as of DBStore 2.7, we no longer symmetrically link owner groups to owned objects } addFieldToEnd(field); } // if we're reading an old ganymede.db file, sort the customFields // for 2.0 the customFields will have been read in sort order if (store.isLessThan(2,0)) { new VecQuickSort(customFields, comparator).sort(); } // at file version 1.1, we introduced label_id's. if (store.isAtLeast(1,1)) { label_id = in.readShort(); } else { label_id = -1; } // at file version 1.3, we introduced object base categories's. // at file version 2.0, we took the category specification out of // the DBObjectBase block in favor of having it defined by context // of the DBBaseCategory this DBObjectBase was read in. if (store.isBetweenRevs(1,3,2,0)) { String categoryName = in.readUTF(); category = (DBBaseCategory) store.getCategoryNode(categoryName); if (category != null) { category.addNodeAfter(this, null); // add to end of category } } // if we're reading an old ganymede.db file, read in the display // order for this base. if we're at 2.0 or later, the // DBObjectBase will be read in order within its category from the // file. if (store.isBetweenRevs(1,4,2,0)) { tmp_displayOrder = in.readInt(); } else { tmp_displayOrder = -1; } if (store.isAtLeast(1,5)) { embedded = in.readBoolean(); // added at file version 1.5 } // create the system standard fields for this object definition // now that we know whether the object is embedded or not createBuiltIns(embedded); if (store.isAtLeast(1,12)) { maxid = in.readInt(); // added at file version 1.12 } // read in the objects belonging to this ObjectBase object_count = in.readInt(); if (debug) { // "DBObjectBase.receive(): Loading {0,number,#} {1} objects" System.err.println(ts.l("receive.debug", Integer.valueOf(object_count), object_name)); } List<DBObject> tmpIterationList = new ArrayList<DBObject>(object_count); if (object_count > 0) { temp_val = gnu.trove.PrimeFinder.nextPrime((int) Math.ceil((object_count + GROWTHSPACE) / 0.75f)); } else { temp_val = gnu.trove.PrimeFinder.nextPrime((int) Math.ceil(500 / 0.75f)); } objectTable = new DBObjectTable(temp_val, 0.75f); for (int i = 0; i < object_count; i++) { tempObject = new DBObject(this, in, false); if (tempObject.getID() > maxid) { maxid = tempObject.getID(); } tmpIterationList.add(tempObject); objectTable.putNoSyncNoRemove(tempObject); tempObject.registerAsymmetricLinks(); // register anonymous invid fields } // lock and load this.iterationList = Collections.unmodifiableList(tmpIterationList); } /** * <p>This method is used to add a DBObjectBase's information to * this {@link arlut.csd.ganymede.common.CategoryTransport * CategoryTransport} object for serialization to the client.</p> */ public void addBaseToTransport(CategoryTransport transport, GanymedeSession session) { transport.addChunk("base"); transport.addChunk(this.getName()); transport.addChunk(this.getPath()); transport.addChunk(String.valueOf(this.getTypeID())); transport.addChunk(String.valueOf(this.getLabelField())); transport.addChunk(this.getLabelFieldName()); transport.addChunk(String.valueOf(this.canInactivate())); if (session != null) { transport.addChunk(String.valueOf(this.canCreate(session))); } else { transport.addChunk(String.valueOf(true)); } transport.addChunk(String.valueOf(this.isEmbedded())); } /** * <p>This method is used to add a DBObjectBase's information to * this {@link arlut.csd.ganymede.common.BaseListTransport * BaseListTransport} object for serialization to the client.</p> */ public void addBaseToTransport(BaseListTransport transport, GanymedeSession session) { transport.addChunk("base"); transport.addChunk(this.getName()); transport.addChunk(this.getPath()); transport.addChunk(String.valueOf(this.getTypeID())); transport.addChunk(String.valueOf(this.getLabelField())); transport.addChunk(this.getLabelFieldName()); transport.addChunk(String.valueOf(this.canInactivate())); if (session != null) { transport.addChunk(String.valueOf(this.canCreate(session))); } else { transport.addChunk(String.valueOf(true)); } transport.addChunk(String.valueOf(this.isEmbedded())); } /** * <p>This method is used to instantiate the system default fields * in a newly created or loaded DBObjectBase.</p> */ private synchronized void createBuiltIns(boolean embedded) { DBObjectBaseField bf; /* -- */ if (embedded) { /* Set up our 0 field, the containing object owning us */ bf = addSystemField("Containing Object", SchemaConstants.ContainerField, FieldType.INVID); bf.setTargetBase((short) -1); // we can point at anything, but there'll be a special bf.setTargetField((short) -1); // procedure for handling deletion and what not.. bf.setVisibility(false); // we don't want the client to show the owner link // note that we won't have an expiration date or removal date // for an embedded object } else { /* Set up our 0 field, the owner list. */ bf = addSystemField("Owner list", SchemaConstants.OwnerListField, FieldType.INVID); bf.setTargetBase(SchemaConstants.OwnerBase); bf.setTargetField((short) -1); bf.setArray(true); addSystemField("Expiration Date", SchemaConstants.ExpirationField, FieldType.DATE); addSystemField("Removal Date", SchemaConstants.RemovalField, FieldType.DATE); addSystemField("Notes", SchemaConstants.NotesField, FieldType.STRING); addSystemField("Creation Date", SchemaConstants.CreationDateField, FieldType.DATE); addSystemField("Creator Info", SchemaConstants.CreatorField, FieldType.STRING); addSystemField("Modification Date", SchemaConstants.ModificationDateField, FieldType.DATE); addSystemField("Modifier Info", SchemaConstants.ModifierField, FieldType.STRING); } } /** * <p>This method dumps schema information to an XML stream.</p> */ synchronized void emitXML(XMLDumpContext xmlOut) throws IOException { xmlOut.startElementIndent("objectdef"); xmlOut.attribute("name", XMLUtils.XMLEncode(object_name)); xmlOut.attribute("id", java.lang.Short.toString(type_code)); xmlOut.indentOut(); xmlOut.startElementIndent("label"); xmlOut.attribute("fieldid", java.lang.Integer.toString(label_id)); xmlOut.endElement("label"); if (classname != null && !classname.equals("")) { xmlOut.startElementIndent("classdef"); xmlOut.attribute("name", classname); if (classOptionString != null) { xmlOut.attribute("optionString", classOptionString); } xmlOut.endElement("classdef"); } if (embedded) { xmlOut.startElementIndent("embedded"); xmlOut.endElement("embedded"); } emitXML_fields(xmlOut); xmlOut.indentIn(); xmlOut.endElementIndent("objectdef"); } /** * <p>This helper method writes out all the field descriptions for * this DBObjectBase, wrapping fields in <tab> container * elements that identify the tab that the fields belong to.</p> * * <p>This logic is based on the assumption that all fields that are * in a common tab will be contiguous in the customFields ordering, * and that either all fields in the object type will have tabs * defined, or none of them will.</p> */ private void emitXML_fields(XMLDumpContext xmlOut) throws IOException { synchronized (customFields) { String lastTabName = null; boolean first = true; for (DBObjectBaseField fieldDef: customFields) { String currentTabName = fieldDef.getTabName(); if (currentTabName == null) { currentTabName = ts.l("global.default_tab"); // "General" } if (!StringUtils.stringEquals(currentTabName, lastTabName)) { if (lastTabName != null) { xmlOut.indentIn(); xmlOut.endElementIndent("tab"); } xmlOut.startElementIndent("tab"); xmlOut.attribute("name", currentTabName); xmlOut.indentOut(); } fieldDef.emitXML(xmlOut); lastTabName = currentTabName; } xmlOut.indentIn(); xmlOut.endElementIndent("tab"); } } /** * <p>This method is used to read the definition for this * DBObjectBase from an XMLItem <objectdef> tree and to make * the appropriate modifications to this DBObjectBase to make it fit * the specification provided in the XML, if at all possible.</p> * * <p>Called in the context of an XML-based schema edit.</p> */ synchronized ReturnVal setXML(XMLItem root, boolean resolveInvidLinks, PrintWriter err) { String _objectName = null; Integer _idInt; DBObjectBaseField newField; GHashtable nameTable = new GHashtable(true); // case insensitive HashMap<Integer, XMLItem> idTable = new HashMap<Integer, XMLItem>(); String _classStr = null; String _classOptionStr = null; Integer _labelInt = null; boolean _embedded = false; boolean classSet = false; boolean labelSet = false; List<Integer> fieldsInXML = new ArrayList<Integer>(); // ordered List of field id's List<Integer> fieldsInBase = new ArrayList<Integer>(); // field id's previously in schema ReturnVal retVal; /* -- */ if (root == null || !root.matches("objectdef")) { // "DBObjectBase.setXML(): root element != open objectdef: {0}" throw new IllegalArgumentException(ts.l("setXML.baddoc", root)); } if (xmldebug) { // "Setting XML for object Base..{0}" err.println(ts.l("setXML.debugroot", root)); } // GanymedeXMLSession.processSchema does a handleBaseRenaming up // front, but if we are a newly created base we might not have had // our name set yet.. go ahead and try to do it here _objectName = XMLUtils.XMLDecode(root.getAttrStr("name")); if (_objectName == null || _objectName.equals("")) { return Ganymede.createErrorDialog("xml", // "DBObjectBase.setXML(): objectdef missing name attribute:\n {0}" ts.l("setXML.missingname", root.getTreeString())); } // we call setName() at the bottom, after we know for sure what our // embedded status is going to be _idInt = root.getAttrInt("id"); if (_idInt == null) { return Ganymede.createErrorDialog("xml", // "DBObjectBase.setXML(): objectdef missing id attribute:\n {0}" ts.l("setXML.missingid", root.getTreeString())); } if (xmldebug) { // "Setting id" err.println(ts.l("setXML.debugid")); } retVal = setTypeID(_idInt.shortValue()); if (!ReturnVal.didSucceed(retVal)) { return retVal; } // first scan the children nodes, make sure all fields have unique // names and id's, build up a list of Integer field id's for us to // compare against the list of field id's currently defined if (xmldebug) { // "Scanning fields" err.println(ts.l("setXML.debugscanning")); } // loop over tab and field hierarchy, flatten everything out into // a Vector so that we don't have to worry about the difference // between a tab-container structure vs. a flat structure when it // comes time to actually process the fields, below. List<XMLItem> fieldDefV = new ArrayList<XMLItem>(); XMLItem children[] = root.getChildren(); for (XMLItem item: children) { if (item.matches("tab")) { fieldDefV.add(item); XMLItem tabChildren[] = item.getChildren(); for (XMLItem childItem: tabChildren) { if (childItem.matches("fielddef")) { fieldDefV.add(childItem); } } } else { fieldDefV.add(item); } } for (XMLItem item: fieldDefV) { if (item.matches("fielddef")) { String _fieldNameStr = XMLUtils.XMLDecode(item.getAttrStr("name")); Integer _fieldIDInt = item.getAttrInt("id"); if (_fieldNameStr == null || _fieldIDInt == null) { return Ganymede.createErrorDialog("xml", // "Field definition missing name and/or id: {0}" ts.l("setXML.noid", item.getTreeString())); } if (nameTable.containsKey(_fieldNameStr)) { return Ganymede.createErrorDialog("xml", // "More than one field in objectdef: {0}\ncontains field name {1}" ts.l("setXML.dupfieldname", root.getTreeString(), _fieldNameStr)); } DBObjectBaseField _field = getField(_fieldNameStr); if (_field != null && _field.isBuiltIn()) { return Ganymede.createErrorDialog("xml", // "Can't set a field:\n{0}\nwith the same name as a pre-existing built-in field in objectdef:\n{1}" ts.l("setXML.sysfield", item.getTreeString(), root.getTreeString())); } nameTable.put(_fieldNameStr, item); if (_fieldIDInt.shortValue() < 100) { return Ganymede.createErrorDialog("xml", // "Can't modify or set a field:\n{0}\nwith a field id in the global field range:\n{1}" ts.l("setXML.noglobals", item.getTreeString(), root.getTreeString())); } if (idTable.containsKey(_fieldIDInt)) { return Ganymede.createErrorDialog("xml", // "More than one field in objectdef: {0}\ncontains field id {1}" ts.l("setXML.dupfieldid", root.getTreeString(), _fieldIDInt)); } idTable.put(_fieldIDInt, item); fieldsInXML.add(_fieldIDInt); } } if (xmldebug) { // "Calculating fields to delete" err.println(ts.l("setXML.debugdels")); } // we've got a vector of Integers for the field id's we are going to be // setting.. we now need to find out field id's that are missing // in the xml, as we'll need to delete these for (DBObjectBaseField _field: customFields) { fieldsInBase.add(Integer.valueOf(_field.getID())); } for (Integer _fieldID: (Vector<Integer>) VectorUtils.difference(fieldsInBase, fieldsInXML)) { DBObjectBaseField _field = getField(_fieldID.shortValue()); // "\t\tDeleting field {0}" err.println(ts.l("setXML.deleting", _field.getName())); retVal = deleteField(_field.getName()); if (!ReturnVal.didSucceed(retVal)) { return retVal; } } // we've done our first data integrity test and we've figured out // what fields we're going to need to delete.. go ahead and scan // ahead and actually create and/or edit fields as needed // first, let's set the default tab name, in case we don't see // specific tab names specified in this object's xml definition String currentTabName = ts.l("global.default_tab"); // "General" for (XMLItem item: fieldDefV) { if (item.matches("classdef")) { if (classSet) { return Ganymede.createErrorDialog("xml", // "Objectdef contains more than one classdef element:\n{0}" ts.l("setXML.dupclassdef", root.getTreeString())); } _classStr = item.getAttrStr("name"); _classOptionStr = item.getAttrStr("optionString"); classSet = true; } else if (item.matches("embedded")) { _embedded = true; } else if (item.matches("label")) { if (labelSet) { return Ganymede.createErrorDialog("xml", // "Objectdef contains more than one label element:\n{0}" ts.l("setXML.duplabel", root.getTreeString())); } _labelInt = item.getAttrInt("fieldid"); labelSet = true; } else if (item.matches("tab")) { currentTabName = item.getAttrStr("name"); continue; } else if (item.matches("fielddef")) { newField = getField(item.getAttrInt("id").shortValue()); if (newField == null) { err.println("\t\tCreating field " + item.getAttrStr("name")); try { newField = new DBObjectBaseField(this); newField.setIsInUse(false); } catch (RemoteException ex) { Ganymede.logError(ex); throw new RuntimeException("Publishing error " + ex.getMessage()); } if (xmldebug) { // "Setting XML on new field {0}" err.println(ts.l("setXML.debugnew", item)); } retVal = newField.setXML(item, resolveInvidLinks, err); if (!ReturnVal.didSucceed(retVal)) { return retVal; } retVal = newField.setTabName(currentTabName); if (!ReturnVal.didSucceed(retVal)) { return retVal; } addFieldToEnd(newField); } else { // "\t\tEditing field {0}" err.println(ts.l("setXML.editing", item.getAttrStr("name"))); retVal = newField.setXML(item, resolveInvidLinks, err); if (!ReturnVal.didSucceed(retVal)) { return retVal; } retVal = newField.setTabName(currentTabName); if (!ReturnVal.didSucceed(retVal)) { return retVal; } } } else { return Ganymede.createErrorDialog("xml", // "Unrecognized XML item: {0} in objectdef:\n{1}" ts.l("setXML.unrecognized", item, root.getTreeString())); } } // and set or clear the label and class options if (xmldebug) { // "Setting label field" err.println(ts.l("setXML.debuglabel")); } if (_labelInt == null) { retVal = setLabelField(null); } else { retVal = setLabelField(_labelInt.shortValue()); } if (!ReturnVal.didSucceed(retVal)) { return retVal; } if (xmldebug) { // "Setting class name" err.println(ts.l("setXML.debugclass")); } retVal = setClassInfo(_classStr, _classOptionStr); if (!ReturnVal.didSucceed(retVal)) { return retVal; } // we have to set embedded before calling setName() so that // setName() can enforce the embedded object naming convention embedded = _embedded; // XXX need to work on this if (xmldebug) { // "Setting object name" err.println(ts.l("setXML.debugname")); } retVal = setName(_objectName); if (!ReturnVal.didSucceed(retVal)) { return retVal; } // and we need to order the fields in customFields in the same // order they appeared in the XML if (xmldebug) { // "Sorting fields" err.println(ts.l("setXML.debugsorting")); } List<DBObjectBaseField> _newCustom = new ArrayList<DBObjectBaseField>(); for (Integer _fieldID: fieldsInXML) { DBObjectBaseField _field = getField(_fieldID.shortValue()); if (_field == null) { return Ganymede.createErrorDialog("xml", // "Couldn't find field {0} while resorting customFields." ts.l("setXML.mysteryfield", _fieldID)); } _newCustom.add(_field); } // make sure we are consistent like we think we should be Vector _intersection = VectorUtils.intersection(customFields, _newCustom); if ((_intersection.size() != customFields.size()) || (_intersection.size() != _newCustom.size())) { // "Consistency error while resorting customFields in base {0}" err.println(ts.l("setXML.inconsistent", getName())); err.println("customFields.size() = " + customFields.size()); err.println("_newCustom.size() = " + _newCustom.size()); err.println("_intersection.size() = " + _intersection.size()); return Ganymede.createErrorDialog("xml", // "Consistency error while resorting customFields." ts.l("setXML.consistencyerror")); } customFields = _newCustom; // and we're done if (xmldebug) { // "Done processing object base {0}" err.println(ts.l("setXML.debugdone", root)); } return null; } /** * <p>This method returns true if this object base is for an * embedded object. Embedded objects do not have their own * expiration and removal dates, do not have history trails, and can * be only owned by a single object, not by a list of * administrators.</p> */ public boolean isEmbedded() { return embedded; } /** * <p>This method indicates whether this base may be removed in the * Schema Editor.</p> * * <p>We don't allow removal of built-in Bases that the server * depends on for its operation, such as permissions, notification, * and logging object types.</p> * * @see arlut.csd.ganymede.rmi.Base */ public boolean isRemovable() { return (getTypeID() > SchemaConstants.FinalBase); } /** * <p>Returns the editing mode that this object base is currently * in.</p> */ public EditingMode getEditingMode() { return editingMode; } /** * <p>Sets the editing mode that this object base is currently * in.</p> * * <p>Server-side only for security.</p> */ public void setEditingMode(EditingMode newMode) { editingMode = newMode; } /** * <p>This method is used to force a reload of the custom object * code for this object type.</p> */ public synchronized void reloadCustomClass() { this.classdef = null; try { this.objectHook = this.createHook(); } catch (RemoteException ex) { throw new RuntimeException("Unexpected remote exception.. RMI init prob? " + ex); } } /** * <p> This method will attempt to invoke an apppropriate * <i>factory</i> method on this object base's specified custom * class. This adds a layer of indirection to Ganymede's custom * class loading behaviour. Instead of trying to invoke a * constructor directly on the custom class, we instead look for a * method called "factory" in the custom class that takes the same * parameters.</p> * * <p>This allows programmers to define a custom class that can do * fancy tricks before constructing a new {@link * arlut.csd.ganymede.server DBEditObject DBEditObject} like, say, * loading the class from a Jython interpreter, or doing * code-generation from an external class descriptor file.</p> * * @param classParams the list of parameter types * @param methodParams the parameters to pass to the factory method * @return a new {@link arlut.csd.ganymede.server.DBEditObject DBEditObject} * @throws SecurityException * @throws InvocationTargetException * @throws IllegalAccessException */ protected DBEditObject invokeFactory(Class[] classParams, Object[] methodParams) throws SecurityException, InvocationTargetException, IllegalAccessException { /* * Deepak sez: This is sort of a hack. Sun, in its wisdom, decided to make * Class.searchMethods() private. So now if I want to see if a class has a * particular method, I have to do a getMethods() and iterate through the * results. Instead, I've decided to cheat and use the fact that when you * call Class.getMethod(), a NoSuchMethodException is thrown if that method * isn't defined. */ try { Method factory = classdef.getMethod("factory", classParams); return (DBEditObject) factory.invoke(null, methodParams); } catch (NoSuchMethodException ex) { /* * Don't panic...if there's no factory method defined, we'll just return * null in the end. */ } return null; } /** * <p>This method is used to create a DBEditObject subclass handle * ({@link arlut.csd.ganymede.server.DBObjectBase#objectHook * objectHook}), to allow various classes to make calls to * overridden methods for DBEditObject subclasses.</p> */ private DBEditObject createHook() throws RemoteException { if (classdef == null) { if (classname != null && !classname.equals("")) { try { classdef = Class.forName(classname); } catch (ClassNotFoundException ex) { // it might seem like we would emit this message a lot // if a class file wasn't in our custom.jar file, but // in fact createHook only gets called once // usually.. even if we can't find the class, we'll // pass back a default DBEditObject // "DBObjectBase.createHook(): class definition could not be found: {0}" System.err.println(ts.l("createHook.noclass", ex)); classdef = null; } } // if we don't have a custom object hook, use the default if (classdef == null) { return new DBEditObject(this); } } Constructor c; DBEditObject e_object = null; Class[] cParams = new Class[1]; cParams[0] = this.getClass(); Object[] params = new Object[1]; params[0] = this; try { /* First, try to use classdef's factory method to create the object. * If that doesn't work (if the classdef has no factory methods, for * example), then use a default constructor. */ e_object = invokeFactory(cParams, params); if (e_object == null) { c = classdef.getDeclaredConstructor(cParams); // DBObjectBase constructor e_object = (DBEditObject) c.newInstance(params); } } catch (NoSuchMethodException ex) { System.err.println("NoSuchMethodException " + ex); } catch (SecurityException ex) { System.err.println("SecurityException " + ex); } catch (IllegalAccessException ex) { System.err.println("IllegalAccessException " + ex); } catch (IllegalArgumentException ex) { System.err.println("IllegalArgumentException " + ex); } catch (InstantiationException ex) { System.err.println("InstantiationException " + ex); } catch (InvocationTargetException ex) { System.err.println("InvocationTargetException " + ex); } finally { if (e_object == null) { e_object = new DBEditObject(this); } } if (debug2) { System.err.println("Created objectHook: object of type " + e_object.getClass()); } return e_object; } /** * <p>Factory method to create a new DBEditObject of this type. The * created DBEditObject will be connected to the editset, and will * not be integrated into the DBStore until the editset is * committed.</p> * * <p><b>IMPORTANT NOTE</b>: This method *must not* be public! All * DBEditObject customization classes should go through * DBSession.createDBObject() to create new objects.</p> * * @param editset The transaction this object is to be created in */ DBEditObject createNewObject(DBEditSet editset) { return createNewObject(editset, null); } /** * <p>Factory method to create a new DBEditObject of this type. The * created DBEditObject will be connected to the editset, and will * not be integrated into the DBStore until the editset is * committed.</p> * * <p><b>IMPORTANT NOTE</b>: This method *must not* be public! All * DBEditObject customization classes should go through * DBSession.createDBObject() to create new objects.</p> * * @param editset The transaction this object is to be created in * @param chosenSlot If this is non-null, the object will be assigned * the given invid, if available */ synchronized DBEditObject createNewObject(DBEditSet editset, Invid chosenSlot) { DBEditObject e_object = null; Invid invid; /* -- */ if (editset == null) { // "null editset in createNewObject" throw new NullPointerException(ts.l("createNewObject.noeditset")); } if (chosenSlot == null) { invid = Invid.createInvid(getTypeID(), getNextID()); if (debug2) { if (objectTable.containsKey(invid.getNum())) { // "bad invid chosen in createNewObject: num already taken" throw new IllegalArgumentException(ts.l("createNewObject.badinvid")); } } } else { if (chosenSlot.getType() != type_code) { // "bad chosen_slot passed into createNewObject: bad type" throw new IllegalArgumentException(ts.l("createNewObject.badslottype")); } if (objectTable.containsKey(chosenSlot.getNum())) { // "bad chosen_slot passed into createNewObject: num already taken" throw new IllegalArgumentException(ts.l("createNewObject.badslotnum")); } invid = chosenSlot; if (maxid < invid.getNum()) { maxid = invid.getNum(); } } // it is crucial that we will have called createHook() before // getting here, or else we might return a non-differentiated // DBEditObject instead of a proper object of custom class type if (classdef == null) { e_object = new DBEditObject(this, invid, editset); } else { Constructor c; Class classArray[]; Object parameterArray[]; classArray = new Class[3]; classArray[0] = this.getClass(); classArray[1] = invid.getClass(); classArray[2] = editset.getClass(); parameterArray = new Object[3]; parameterArray[0] = this; parameterArray[1] = invid; parameterArray[2] = editset; String error_code = null; try { /* First, try to use classdef's factory method to create the object. If * that doesn't work (if the classdef has no factory methods, for * example), then use a default constructor. */ e_object = invokeFactory(classArray, parameterArray); if (e_object == null) { c = classdef.getDeclaredConstructor(classArray); e_object = (DBEditObject) c.newInstance(parameterArray); } } catch (NoSuchMethodException ex) { error_code = "NoSuchMethod Exception"; } catch (SecurityException ex) { error_code = "Security Exception"; } catch (IllegalAccessException ex) { error_code = "Illegal Access Exception"; } catch (IllegalArgumentException ex) { error_code = "Illegal Argument Exception"; } catch (InstantiationException ex) { error_code = "Instantiation Exception"; } catch (InvocationTargetException ex) { error_code = "Invocation Target Exception: " + ex.getTargetException() + "\n" + ex.getMessage() + "\n\n" + Ganymede.stackTrace(ex) + "\n"; } finally { if (e_object == null) { if (error_code != null) { // "createNewObject failure: {0} in trying to construct custom object" String errormsg = ts.l("createNewObject.failure1", error_code); Ganymede.debug(errormsg); throw new GanymedeManagementException(errormsg); } else { throw new GanymedeManagementException(); } } } } return e_object; } /** * <p>Check-out constructor, used by DBObject.createShadow() to pull * out an object for editing.</p> * * @param original the object to create the shadow of * @param editset the transaction this object is to be created in */ public DBEditObject createNewObject(DBObject original, DBEditSet editset) { // if we are a customized object type, dynamically invoke // the proper check-out constructor for the DBEditObject // subtype. if (classdef != null) { Constructor c; Class classArray[]; Object parameterArray[]; classArray = new Class[2]; classArray[0] = original.getClass(); classArray[1] = editset.getClass(); parameterArray = new Object[2]; parameterArray[0] = original; parameterArray[1] = editset; String error_code = null; DBEditObject shadowObject = null; try { /* * First, try to use classdef's factory method to create the object. * If that doesn't work (if the classdef has no factory methods, for * example), then use a default constructor. */ shadowObject = invokeFactory(classArray, parameterArray); if (shadowObject == null) { c = classdef.getDeclaredConstructor(classArray); shadowObject = (DBEditObject) c.newInstance(parameterArray); } } catch (NoSuchMethodException ex) { error_code = "NoSuchMethod Exception"; } catch (SecurityException ex) { error_code = "Security Exception"; } catch (IllegalAccessException ex) { error_code = "Illegal Access Exception"; } catch (IllegalArgumentException ex) { error_code = "Illegal Argument Exception"; } catch (InstantiationException ex) { error_code = "Instantiation Exception"; } catch (InvocationTargetException ex) { InvocationTargetException tex = (InvocationTargetException) ex; Ganymede.logError(tex.getTargetException()); error_code = "Invocation Target Exception " + tex.getTargetException(); } finally { if (shadowObject == null) { if (error_code != null) { // "createNewObject failure: {0} in trying to check out custom object" String errormsg = ts.l("createNewObject.failure2", error_code); Ganymede.debug(errormsg); throw new GanymedeManagementException(errormsg); } else { throw new GanymedeManagementException(); } } } return shadowObject; } else { return new DBEditObject(original, editset); } } /** * allocate a new object id */ synchronized int getNextID() { return ++maxid; } /** * <p>releases an id if an object initially created by * createDBObject is rejected due to its transaction being * aborted</p> * * <p>note that we aren't being real fancy here.. if this doesn't * work, it doesn't work.. we have 2 billion slots in this object * base after all..</p> */ synchronized void releaseId(int id) { if (id==maxid) { maxid--; } } /** * <p>Returns the DBStore containing this DBObjectBase.</p> */ public DBStore getStore() { return store; } /** * Returns the DBSchemaEdit managing changes to this DBObjectBase, * or null if this DBObjectBase is not being edited. */ public DBSchemaEdit getEditor() { return editor; } /** * <p>Returns the name of this object type. Guaranteed * to be unique in the Ganymede server. </p> * * @see arlut.csd.ganymede.rmi.Base */ public synchronized String getName() { return object_name; } /** * Returns the name and category path of this object type. * Guaranteed to be unique in the Ganymede server. */ public synchronized String getPath() { if (category == null) { return "./" + getName(); } try { return category.getPath() + "/" + object_name; } catch (RemoteException ex) { throw new RuntimeException(ex.getMessage()); } } /** * <p>Sets the name for this object type</p> * * <p>This method is only valid when the Base reference is obtained * from a {@link arlut.csd.ganymede.rmi.SchemaEdit SchemaEdit} * reference by the Ganymede schema editor.</p> * * @see arlut.csd.ganymede.rmi.Base */ public synchronized ReturnVal setName(String newName) { securityCheck(); // no change, no harm if (newName.equals(object_name)) { return null; } if (!XMLNameValidator.isValidGanymedeName(newName)) { // "Schema Editing Error" // ""{0}" is not an acceptable Ganymede type name.\n\nAll Ganymede type names must be acceptable XML element names, save that spaces are allowed and underscores are not." return Ganymede.createErrorDialog(ts.l("global.schema_editing_error"), ts.l("setName.invalid_ganymede_name", newName)); } // check to make sure another object type isn't using the proposed // new name if (isEditing()) { if (this.editor.getBase(newName) != null) { return Ganymede.createErrorDialog("Schema Editing Error", // "Can't rename base {0} to {1}, that name is already taken." ts.l("setName.norename", object_name, newName)); } } else { if (this.store.getObjectBase(newName) != null) { return Ganymede.createErrorDialog("Schema Editing Error", // "Can't rename base {0} to {1}, that name is already taken." ts.l("setName.norename", object_name, newName)); } } // ok, go for it object_name = newName; return null; } /** * <p>Returns the name of the class managing this object type</p> * * @see arlut.csd.ganymede.rmi.Base */ public synchronized String getClassName() { return classname; } /** * <p>Returns the option string for the class definition.. see * {@link arlut.csd.ganymede.server.DBObjectBase#classOptionString * classOptionString} for more details.</p> */ public synchronized String getClassOptionString() { return classOptionString; } /** * <p>This method is used to associate a management class with this * object base.</p> * * <p>The newClassName argument must be fully qualified, and must * refer to one of two kinds of classes. The first is a {@link * arlut.csd.ganymede.server.DBEditObject DBEditObject} subclass * that implements the requisite three constructors, a la the * traditional Ganymede customization hook. The second is a {@link * arlut.csd.ganymede.server.JythonEditObjectFactory} subclass, * which provides a set of factory methods which return DBEditObject * instances.</p> * * <p>If newClassName is a JythonEditObjectFactory, the * newOptionString argument will be available to the factory methods * so that the constructed objects can be dynamically customized. * This is intended to support the use of DBEditObject subclasses * written in Jython, with support for dynamic reloading during * server execution.</p> * * <p>This method is only valid when the Base reference is obtained * from a {@link arlut.csd.ganymede.rmi.SchemaEdit SchemaEdit} reference * by the Ganymede schema editor.</p> * * @see arlut.csd.ganymede.rmi.Base */ public synchronized ReturnVal setClassInfo(String newClassName, String newOptionString) { securityCheck(); /* * Remember the original values we've got, just in case the new * values don't take for whatever reason. */ String originalClassName = classname; Class originalClassDef = classdef; DBEditObject originalObjectHook = objectHook; /* FindBugs doesn't like me using == here, but it's a test for null or identity, and we do the equals() test as well. */ if ((newClassName == classname || (newClassName != null && newClassName.equals(classname))) && (newOptionString == classOptionString || (newOptionString != null && newOptionString.equals(classOptionString)))) { return null; // no change } if (newClassName == null || newClassName.equals("")) { classname = ""; classdef = null; objectHook = null; return null; } classname = newClassName; if (newOptionString != null && newOptionString.equals("")) { newOptionString = null; } classOptionString = newOptionString; // Reset the classdef so that createHook can load the newly specified // class via class.forName() classdef = null; // try to create an object using the proposed class // information.. if we can't, no big deal, it'll just have to be // done after the server is restarted. try { objectHook = this.createHook(); } catch (RemoteException ex) { // Restore our state back to the way it was originally classname = originalClassName; classdef = originalClassDef; objectHook = originalObjectHook; return Ganymede.createErrorDialog("setClassInfo Failure", // "Internal RemoteException in setClassInfo: {0}" ts.l("setClassInfo.internalError", Ganymede.stackTrace(ex))); } // if the objectHook returned the default DBEditObject class even // though we asked for something different, we'll need to complain if (objectHook.getClass().getName().equals("arlut.csd.ganymede.server.DBEditObject") && !classname.equals("arlut.csd.ganymede.server.DBEditObject")) { ReturnVal retVal = new ReturnVal(false); if (classOptionString == null) { retVal.setDialog(new JDialogBuff("Schema Editor Warning", // "Couldn't find class {0} in the server's CLASSPATH. This probably means // that you have not yet rebuilt the custom.jar file with this class added." ts.l("setClassInfo.noclass", classname), Ganymede.OK, null, "error.gif")); } else { retVal.setDialog(new JDialogBuff("Schema Editor Warning", // "Couldn't load custom management logic from class {0} using class option string '\ // {1}'.\n\nThis may mean that you have not yet rebuilt the custom jar file with the {0} class added, or that \ // the resource specified in the option string can not be found by {0}'s factory methods." ts.l("setClassInfo.noclassoption", classname, classOptionString), Ganymede.OK, null, "error.gif")); } // Restore our state back to the way it was originally classname = originalClassName; classdef = originalClassDef; objectHook = originalObjectHook; return retVal; } return null; } /** * <p>Returns the class definition for this object type</p> */ public synchronized Class getClassDef() { return classdef; } /** * <p>This method is used to adjust the ordering of a custom field * in this Base.</p> * * @param fieldName The name of the field to move * @param previousFieldName The name of the field that fieldName is going to * be put after, or null if fieldName is to be the first field displayed * in this object type. */ public synchronized ReturnVal moveFieldAfter(String fieldName, String previousFieldName) { securityCheck(); DBObjectBaseField oldField = getField(fieldName); if (oldField == null) { return Ganymede.createErrorDialog("Schema Editing Error", // "Error, can't move field {0}, no such field in object type." ts.l("moveFieldAfter.nomove", fieldName)); } if (previousFieldName == null || previousFieldName.equals("")) { customFields.remove(oldField); customFields.add(0, oldField); return null; } DBObjectBaseField prevField = getField(previousFieldName); if (prevField == null || !customFields.contains(prevField)) { return Ganymede.createErrorDialog("Schema Editing Error", // "Error, can't move field {0} after {1}, no such field in object type." ts.l("moveFieldAfter.nofield", fieldName, previousFieldName)); } customFields.remove(oldField); customFields.add(customFields.indexOf(prevField) + 1, oldField); return null; } /** * <p>This method is used to adjust the ordering of a custom field * in this Base.</p> * * @param fieldName The name of the field to move * @param nextFieldName The name of the field that fieldName is going to * be put before, or null if fieldName is to be the last field displayed * in this object type. */ public synchronized ReturnVal moveFieldBefore(String fieldName, String nextFieldName) { securityCheck(); DBObjectBaseField oldField = getField(fieldName); if (oldField == null) { return Ganymede.createErrorDialog("Schema Editing Error", // "Error, can't move field {0}, no such field in object type." ts.l("moveFieldBefore.nomove", fieldName)); } if (nextFieldName == null || nextFieldName.equals("")) { customFields.remove(oldField); customFields.add(oldField); return null; } DBObjectBaseField nextField = getField(nextFieldName); if (nextField == null || !customFields.contains(nextField)) { return Ganymede.createErrorDialog("Schema Editing Error", // "Error, can't move field {0} before {1}, no such field in object type." ts.l("moveFieldBefore.nofield", fieldName, nextFieldName)); } customFields.remove(oldField); customFields.add(customFields.indexOf(nextField), oldField); return null; } /** * <p>Returns true if the current session is permitted to create an * object of this type.</p> * * @see arlut.csd.ganymede.rmi.Base */ public boolean canCreate(Session session) { return getObjectHook().canCreate(session); } /** * <p>Returns true if this object type can be inactivated</p> * * @see arlut.csd.ganymede.rmi.Base */ public synchronized boolean canInactivate() { return getObjectHook().canBeInactivated(); } /** * <p>Returns the invid type id for this object definition</p> * * @see arlut.csd.ganymede.rmi.Base */ public synchronized short getTypeID() { return type_code; } /** * <p>Sets the object ID code for this object type</p> */ public synchronized ReturnVal setTypeID(short objectId) { securityCheck(); if (objectId == type_code) { return null; } if ((objectId != type_code) && (type_code != -1)) { return Ganymede.createErrorDialog("xml", // "Can't change the type_code for an existing object base" ts.l("setTypeID.notypemutation")); } if (store.getObjectBase(objectId) != null) { return Ganymede.createErrorDialog("xml", // "Can't set the type_code for object base {0} to that of an existing object base" ts.l("setTypeID.typeconflict", this.toString())); } type_code = objectId; return null; } /** * <p>Returns the short type id for the field designated as this * object's primary label field.</p> * * @see arlut.csd.ganymede.rmi.Base */ public short getLabelField() { return label_id; } /** * <p>Returns the field definition for the field designated as this * object's primary label field. null is returned if no label has * been designated.</p> * * @see arlut.csd.ganymede.rmi.Base */ public DBObjectBaseField getLabelFieldDef() { if (label_id == -1) { return null; } return getField(label_id); } /** * <p>Returns the field name for the field designated as this * object's primary label field. null is returned if no label has * been designated.</p> * * @see arlut.csd.ganymede.rmi.Base */ public String getLabelFieldName() { DBObjectBaseField fieldDef; /* -- */ if (label_id == -1) { return null; } fieldDef = getField(label_id); if (fieldDef == null) { return null; } return fieldDef.getName(); } /** * <p>Returns the invid type id for this object definition as a * Short, suitable for use in a hash.</p> */ public Short getKey() { return Short.valueOf(type_code); } /** * Returns the number of objects in this object base. */ public int getObjectCount() { return objectTable.size(); } /** * Returns the number of fields in this object base. */ public int getFieldCount() { return fieldTable.size(); } /** * Returns the number of custom fields in this object base. */ public int getCustomFieldCount() { return customFields.size(); } /** * <p>Returns all {@link arlut.csd.ganymede.server.DBObjectBaseField * DBObjectBaseField} base field definitions for objects of this * type, in random order.</p> * * @see arlut.csd.ganymede.rmi.Base */ public Vector<DBObjectBaseField> getFields() { return getFields(true); } /** * <p>Returns {@link arlut.csd.ganymede.server.DBObjectBaseField * DBObjectBaseField} base field definitions for objects of this * type. * * <p>If includeBuiltIns is false, the fields returned will be the * custom fields defined for this object type, and they will be * returned in display order. If includeBuiltIns is true, the * built-in fields will be appended to the Vector after the custom * types, in random order.</p> * * @see arlut.csd.ganymede.rmi.Base */ public synchronized Vector<DBObjectBaseField> getFields(boolean includeBuiltIns) { Vector<DBObjectBaseField> result = new Vector<DBObjectBaseField>(fieldTable.size()); /* -- */ // first we return the custom fields, pre-sorted display order for (DBObjectBaseField field: customFields) { result.add(field); } // now if we are to return the built-in fields, go ahead and add // them to the end in field id order if (includeBuiltIns) { for (DBObjectBaseField field: getStandardFields()) { result.add(field); } } return result; } /** * <p>Returns the field definition for the field matching id, or * null if no match found.</p> * * @see arlut.csd.ganymede.rmi.BaseField * @see arlut.csd.ganymede.rmi.Base */ public DBObjectBaseField getField(short id) { return fieldTable.get(id); } /** * <p>Returns the field definition for the field matching id, or * null if no match found.</p> */ public DBObjectBaseField getField(Short id) { return fieldTable.get(id.shortValue()); } /** * <p>Returns the field definition for the field matching name, * or null if no match found.</p> * * @see arlut.csd.ganymede.rmi.BaseField * @see arlut.csd.ganymede.rmi.Base */ public synchronized DBObjectBaseField getField(String name) { return fieldTable.get(name); } /** * <p>Choose what field will serve as this objectBase's label.</p> * * <p>This method is only valid when the Base reference is obtained * from a {@link arlut.csd.ganymede.rmi.SchemaEdit SchemaEdit} * reference by the Ganymede schema editor.</p> * * @see arlut.csd.ganymede.rmi.Base */ public ReturnVal setLabelField(String fieldName) { securityCheck(); DBObjectBaseField bF = getField(fieldName); if (bF == null) { return Ganymede.createErrorDialog("Schema Editing Error", // "setLabelField() called with an unrecognized field name." ts.l("setLabelField.badfieldname")); } if (bF.getNameSpace() == null) { // "Error, label fields must be namespace-constrained." return Ganymede.createErrorDialog(ts.l("setLabelField.nonamespace")); } label_id = bF.getID(); return null; } /** * <p>Choose what field will serve as this objectBase's label.</p> * * <p>This method is only valid when the Base reference is obtained * from a {@link arlut.csd.ganymede.rmi.SchemaEdit SchemaEdit} * reference by the Ganymede schema editor.</p> * * @see arlut.csd.ganymede.rmi.Base */ public ReturnVal setLabelField(short fieldID) { securityCheck(); DBObjectBaseField bF = getField(fieldID); if (bF == null) { return Ganymede.createErrorDialog("Schema Editing Error", // "setLabelField() called with an unrecognized field id." ts.l("setLabelField.badfieldid")); } if (bF.getNameSpace() == null) { // "Error, label fields must be namespace-constrained." return Ganymede.createErrorDialog(ts.l("setLabelField.nonamespace")); } label_id = fieldID; return null; } /** * <p>Get the parent Category for this object type. This is used by * the Ganymede client and schema editor to present object types in * a hierarchical tree.</p> * * @see arlut.csd.ganymede.rmi.Base * @see arlut.csd.ganymede.rmi.CategoryNode */ public Category getCategory() { return category; } /** * <p>Set the objectbase category. This operation only registers * the category in this base, it doesn't register the base in the * category. The proper way to add this base to a Category is to * call addNode(Base, nodeBefore) on the appropriate Category * object. That addNode() operation will call setCategory() * here.</p> * * <p>This method is only valid when the Base reference is obtained * from a {@link arlut.csd.ganymede.rmi.SchemaEdit SchemaEdit} * reference by the Ganymede schema editor.</p> * * @see arlut.csd.ganymede.rmi.CategoryNode */ public void setCategory(Category category) { securityCheck(); this.category = category; } /** * <p>Creates a new base field, inserts it into the DBObjectBase * field definitions hash, and returns a reference to it. </p> * * <p>This method is only valid when the Base reference is obtained * from a {@link arlut.csd.ganymede.rmi.SchemaEdit SchemaEdit} * reference by the Ganymede schema editor.</p> * * @see arlut.csd.ganymede.rmi.Base */ public synchronized BaseField createNewField() { short id; DBObjectBaseField field; /* -- */ securityCheck(); id = getNextFieldID(); try { field = new DBObjectBaseField(this); field.setIsInUse(false); } catch (RemoteException ex) { // "Couldn't create field due to initialization error: {0}" throw new RuntimeException(ts.l("createNewField.noluck", ex)); } // set its id field.setID(id); // give it an initial, unique name String newName = ts.l("createNewField.defaultname"); int i = 2; while (getField(newName) != null) { newName = ts.l("createNewField.defaultname") + i++; } field.setName(newName); // default it to boolean, until such time as a schema editor // changes it field.setType(FieldType.BOOLEAN); // and set it up in our field hash and add this to the sorted // fields vector addFieldToEnd(field); return field; } /** * <p>This method is used to remove a field definition from the * current schema.</p> * * <p>Of course, this removal will only take effect if the schema * editor commits.</p> * * @see arlut.csd.ganymede.rmi.Base */ public synchronized ReturnVal deleteField(String fieldName) { DBObjectBaseField field = null; short id = -1; /* -- */ securityCheck(); if (fieldInUse(fieldName)) { return Ganymede.createErrorDialog("Schema Editing Error", // "deleteField() called on object type {0} with a field name ({1}) that is in use in the database." ts.l("deleteField.fieldused", getName(), fieldName)); } field = getField(fieldName); if (field == null) { return Ganymede.createErrorDialog("Schema Editing Error", // "deleteField() called on object type {0} with an unrecognized field name ({1})." ts.l("deleteField.fieldunknown", getName(), fieldName)); } if (!field.isRemovable()) { return Ganymede.createErrorDialog("Schema Editing Error", // "deleteField() called on object type {0} with a system field name ({1}) that may not be deleted." ts.l("deleteField.sysfield", getName(), fieldName)); } removeField(field); if (debug2) { Ganymede.debug("field definition " + getName() + ":" + field.getName() + " removed"); } if (id == label_id) { label_id = -1; } return null; } /** * <p>This method is used by the SchemaEditor to detect whether any * objects are using a field definition.</p> * * <p>Server-side only.</p> */ public boolean fieldInUse(DBObjectBaseField bF) { synchronized (objectTable) { for (DBObject obj: objectTable) { if (obj.getField(bF.getID()) != null) { return true; } } } return false; } /** * <p>This method is used by the SchemaEditor to detect whether any * objects are using a field definition.</p> * * @see arlut.csd.ganymede.rmi.Base */ public boolean fieldInUse(String fieldName) { DBObjectBaseField fieldDef = getField(fieldName); if (fieldDef == null) { // "Can't check for non-existent field: {0}" throw new RuntimeException(ts.l("fieldInUse.nofield", fieldName)); } short id = fieldDef.getID(); synchronized (objectTable) { for (DBObject obj: objectTable) { if (obj.getField(id) != null) { return true; } } } return false; } /** * <p>Helper method for DBEditObject subclasses</p> */ public DBEditObject getObjectHook() { if (objectHook == null) { try { objectHook = createHook(); } catch (RemoteException ex) { // "Error, couldn't create hook in getObjectHook().\n{0}" throw new RuntimeException(ts.l("getObjectHook.error", Ganymede.stackTrace(ex))); } } return objectHook; } /** * <p>Get the next available field id for a new custom field.</p> */ synchronized short getNextFieldID() { short id = 256; // below 256 reserved for future server-mandatory fields for (DBObjectBaseField fieldDef: fieldTable) { if (fieldDef.getID() >= id) { id = (short) (fieldDef.getID() + 1); } } return id; } /** * <p>This server-side routine provides a convenient accessor to * retrieve a specific object stored in this DBObjectBase.</p> */ public final DBObject getObject(Invid invid) { if (invid.getType() != this.getTypeID()) { // "Wrong Invid type" throw new IllegalArgumentException(ts.l("getObject.badtype")); } return objectTable.get(invid.getNum()); } /** * <p>This server-side routine provides a convenient accessor to * retrieve a specific object stored in this DBObjectBase.</p> */ public final DBObject getObject(int objectID) { return objectTable.get(objectID); } public final boolean containsKey(int id) { return objectTable.containsKey(id); } public synchronized final void put(DBObject newObject) { objectTable.put(newObject); if (newObject.getID() > maxid) { maxid = newObject.getID(); } } public final void remove(int id) { objectTable.remove(id); } /** * <p>Returns an Enumeration that loops over DBObjects in this * DBObjectBase.</p> */ public final Enumeration<DBObject> getObjectsEnum() { return objectTable.elements(); } /** * <p>Returns an Iterable<DBObject> that loops over DBObjects in * this DBObjectBase.</p> */ public Iterable<DBObject> getObjects() { return new Iterable<DBObject>() { public Iterator<DBObject> iterator() { return objectTable.iterator(); } }; } /** * <p>Returns an Iterable<DBObjectBaseField> that loops over * all custom DBObjectBaseFields defined in this DBObjectBase, in * display order.</p> */ public Iterable<DBObjectBaseField> getCustomFields() { return new Iterable<DBObjectBaseField>() { public Iterator<DBObjectBaseField> iterator() { return Collections.unmodifiableList(customFields).iterator(); } }; } /** * <p>Returns an Iterable<DBObjectBaseField> that loops over * all built-in/standard DBObjectBaseFields defined in this * DBObjectBase, in ascending field id order.</p> */ public Iterable<DBObjectBaseField> getStandardFields() { return new Iterable<DBObjectBaseField>() { public Iterator<DBObjectBaseField> iterator() { return fieldTable.builtInIterator(); } }; } /** * <p>Returns an Iterable<DBObjectBaseField> that loops over * all DBObjectBaseFields defined in this DBObjectBase, in ascending * field id order.</p> */ public Iterable<DBObjectBaseField> getFieldsInFieldOrder() { return new Iterable<DBObjectBaseField>() { public Iterator<DBObjectBaseField> iterator() { return fieldTable.iterator(); } }; } /** * <p>Returns an Iterable<DBObjectBaseField> that loops over * all DBObjectBaseFields defined in this DBObjectBase, first the * custom fields in display order, then the standard fields in * ascending field id order.</p> */ public Iterable<DBObjectBaseField> getFieldsInDisplayOrder() { return new Iterable<DBObjectBaseField>() { private Iterator<DBObjectBaseField> it = customFields.iterator(); private boolean inStandard = false; public Iterator<DBObjectBaseField> iterator() { return new Iterator<DBObjectBaseField>() { public boolean hasNext() { if (inStandard) { return it.hasNext(); } else { if (it.hasNext()) { return true; } else { inStandard = true; it = getStandardFields().iterator(); return it.hasNext(); } } } public DBObjectBaseField next() { return it.next(); } public void remove() { throw new UnsupportedOperationException(); } }; } }; } /** * <p>This method is used by the {@link * arlut.csd.ganymede.server.DBSchemaEdit} class' {@link * arlut.csd.ganymede.server.DBSchemaEdit#checkCommitState()} * method. It is used to verify that this DBObjectBase is in a * state suitable to be committed.</p> * * <p>Returns null on success, or a ReturnVal encoding an error * dialog on failure.</p> */ synchronized ReturnVal checkSchemaState() { if (label_id == -1) { // "Error, object type {0} has no label field defined." return Ganymede.createErrorDialog(ts.l("checkSchemaState.nolabel", this.getName())); } DBObjectBaseField labelFieldDef = getField(label_id); if (labelFieldDef.getNameSpace() == null) { // Error, object type {0}''s label field ({1}) is not unique value-constrained. // You must set a namespace constraint for this field before committing this schema change. return Ganymede.createErrorDialog(ts.l("checkSchemaState.notunique", this.getName(), labelFieldDef.getName())); } return null; } /** * <p>Clear the editing flag. This disables the DBObjectBase set * methods on this ObjectBase and all dependent field definitions. * This method also updates the FieldTemplate for each field in this * object base.</p> */ synchronized void clearEditor() { if (DBSchemaEdit.debug) { // "DBObjectBase.clearEditor(): clearing editor for {0}" Ganymede.debug(ts.l("clearEditor.clearing", getName())); } if (!isEditing()) { throw new IllegalArgumentException(ts.l("global.notediting")); } this.editor = null; setEditingMode(EditingMode.LOCKED); this.templateVector = null; // we need to make sure any objectHook for this class knows that // we are now its objectBase and not the pre-edit DBObjectBase. this.reloadCustomClass(); // all objects stored in this object base need to be updated to // point to the edited object base, and to have deleted field // types removed from objects in the object base. this.updateBaseRefs(); synchronized (fieldTable) { for (DBObjectBaseField fieldDef: fieldTable) { fieldDef.clearEditor(); } } if (debug2) { if (customFields == null) { System.err.println("DBObjectBase.clearEditor(): customFields (" + this.toString() + "== null!!!"); } else { for (int i = 0; i <customFields.size(); i++) { System.err.println("DBObjectBase.clearEditor(): customFields[" + i + "(" + this.toString() + ")] = " + customFields.get(i)); } } } } /** * <p>This method is used by the DBEditSet commit logic to replace * this DBObjectBase's iterationSet with a new List<DBObject> * with the current objectTable's values. This method should only * be called within the context of a DBWriteLock being established * on this DBObjectBase, in the DBEditSet.commitTransaction() * logic.</p> */ void updateIterationSet() { List<DBObject> newIterationList; synchronized (objectTable) { newIterationList = new ArrayList<DBObject>(objectTable.values()); } this.iterationList = Collections.unmodifiableList(newIterationList); } /** * <p>This method returns an unmodifiable List containing references * to all objects in this DBObjectBase at the time the vector * reference is accessed.</p> */ public List<DBObject> getIterationSet() { return iterationList; } /** * <p>This method is used to update base references and remove * deleted field types in objects after this base has replaced an * old version via the SchemaEditor.</p> */ private void updateBaseRefs() { synchronized (objectTable) { for (DBObject obj: objectTable) { if (DBSchemaEdit.debug) { // "Updating base reference on {0}" System.err.println(ts.l("updateBaseRefs.updating", obj)); } objectTable.replaceNoSync(new DBObject(obj, this)); } } } /** * <p>This method is used to allow objects in this base to notify us * when their state changes. It is called from the {@link * arlut.csd.ganymede.server.DBEditSet DBEditSet} commit() * method.</p> * * <p>We use this method to be able to determine the last time * anything in this DBObjectBase changed when making decisions as to * what needs to be done in BuilderTasks.</p> */ void updateTimeStamp() { lastChange = new Date(); } /** * <p>Returns a Date object containing the time that any changes * were committed to this DBObjectBase.</p> */ public Date getTimeStamp() { return new Date(lastChange.getTime()); } /** * Returns true if any commits have been made to this DBObjectBase * more recently than the comparison date. */ public boolean changedSince(Date comparison) { return lastChange.after(comparison); } // // the following methods are used to manage locks on this base // All methods that modify writerList, readerList, or dumperList // must be synchronized on store.lockSync // /** * <p>Returns true if this DBObjectBase is currently locked for * reading, writing, or dumping.</p> */ boolean isLocked() { synchronized (store.lockSync) { return (!isReaderEmpty() || writeInProgress.isSet() || !isDumpLockListEmpty()); } } /** * <p>Returns true if we have a writer lock locking us.</p> */ boolean isWriteInProgress() { return writeInProgress.isSet(); } /** * <p>Used by {@link arlut.csd.ganymede.server.DBWriteLock * DBWriteLock} to establish or clear a lock.</p> */ void setWriteInProgress(boolean state) { if (writeInProgress.set(state) == state) { if (state) { // "double write lock in DBObjectBase" Ganymede.logAssert(ts.l("setWriteInProgress.doublelock")); } else { // "double write unlock in DBObjectBase" Ganymede.logAssert(ts.l("setWriteInProgress.doubleunlock")); } } } /** * <p>Used by {@link arlut.csd.ganymede.server.DBWriteLock * DBWriteLock} to set a possibly informative lock reference, so * that a debugger can show a reference to the DBWriteLock locking * us down.</p> */ void setWriteLock(DBWriteLock lock) { this.setWriteInProgress(true); this.currentLock = lock; } void clearWriteLock(DBWriteLock lock) { if (this.currentLock != lock) { throw new IllegalArgumentException("mismatched writelock"); } this.setWriteInProgress(false); this.currentLock = null; } /** * <p>Add a DBWriteLock to this base's writer wait set.</p> */ boolean addWaitingWriter(DBWriteLock writer) { synchronized (store.lockSync) { writerList.add(writer); } return true; } /** * <p>Remove a DBWriteLock from this base's writer wait set.</p> */ boolean removeWaitingWriter(DBWriteLock writer) { boolean result; synchronized (store.lockSync) { result = writerList.remove(writer); store.lockSync.notifyAll(); return result; } } /** * <p>Returns true if this base has a non-empty writer waiting list * or actually has a write lock established.</p> */ boolean hasWriter() { return !isWaitingWriterListEmpty() || isWriteInProgress(); } /** * <p>Returns true if this base's writer wait set is empty.</p> */ boolean isWaitingWriterListEmpty() { return writerList.isEmpty(); } /** * <p>Returns the size of the writer wait set</p> */ int getWaitingWriterListSize() { return writerList.size(); } /** * <p>Add a DBReadLock to this base's reader list.</p> */ boolean addReader(DBReadLock reader) { synchronized (store.lockSync) { readerList.add(reader); } return true; } /** * <p>Remove a DBReadLock from this base's reader list.</p> */ boolean removeReader(DBReadLock reader) { boolean result; synchronized (store.lockSync) { result = readerList.remove(reader); store.lockSync.notifyAll(); return result; } } /** * <p>Returns true if this base's reader list is empty.</p> */ boolean isReaderEmpty() { return readerList.isEmpty(); } /** * <p>Returns the size of the reader list</p> */ int getReaderSize() { return readerList.size(); } /** * <p>Add a DBDumpLock to this base's dumper waiting set.</p> */ boolean addWaitingDumper(DBDumpLock dumper) { synchronized (store.lockSync) { dumperList.add(dumper); } return true; } /** * <p>Remove a DBDumpLock from this base's dumper waiting set.</p> */ boolean removeWaitingDumper(DBDumpLock dumper) { boolean result; /* -- */ synchronized (store.lockSync) { result = dumperList.remove(dumper); store.lockSync.notifyAll(); return result; } } /** * <p>Returns true if this base's dumper wait set is empty.</p> */ boolean isWaitingDumperListEmpty() { return dumperList.isEmpty(); } /** * <p>Returns the size of the dumper wait set</p> */ int getWaitingDumperListSize() { return dumperList.size(); } /** * <p>Add a DBDumpLock to this base's dumper lock list.</p> */ boolean addDumpLock(DBDumpLock dumper) { synchronized (store.lockSync) { dumpLockList.add(dumper); } return true; } /** * <p>Remove a DBDumpLock from this base's dumper lock list.</p> */ boolean removeDumpLock(DBDumpLock dumper) { boolean result; synchronized (store.lockSync) { result = dumpLockList.remove(dumper); store.lockSync.notifyAll(); return result; } } /** * <p>Returns true if this base's dumper lock list is empty.</p> */ boolean isDumpLockListEmpty() { return dumpLockList.isEmpty(); } /** * <p>Returns the size of the dumper lock list</p> */ int getDumpLockListSize() { return dumpLockList.size(); } /** * <p>Returns a Vector of field definition templates, in display * order, with custom fields returned first, followed by the * standard built-in fields.</p> * * @see arlut.csd.ganymede.common.FieldTemplate * @see arlut.csd.ganymede.rmi.Session */ public synchronized Vector<FieldTemplate> getFieldTemplateVector() { if (templateVector == null) { templateVector = new Vector<FieldTemplate>(); // first load our custom fields, in display sorted order for (DBObjectBaseField fieldDef: getCustomFields()) { templateVector.add(fieldDef.getTemplate()); } // then load our system fields for (DBObjectBaseField fieldDef: getStandardFields()) { templateVector.add(fieldDef.getTemplate()); } } return templateVector; } /** * <p>This method is used to put a new user field into both the * hashed field table and the customFields vector.</p> */ synchronized void addFieldToStart(DBObjectBaseField field) { if (field.getID() <= SchemaConstants.FinalSystemField) { // "Error, attempted to add a system field using addFieldToStart()." throw new IllegalArgumentException(ts.l("addFieldToStart.sysfield")); } fieldTable.put(field); customFields.add(0,field); } /** * <p>This method is used to put a new user field into both the * hashed field table and the customFields vector, with the new * field definition to be added in the display list after the field * definition with the previousField code.</p> */ synchronized void addFieldAfter(DBObjectBaseField field, short previousField) { if (field.getID() <= SchemaConstants.FinalSystemField) { // "Error, attempted to add a system field using addFieldToStart()." throw new IllegalArgumentException(ts.l("addFieldAfter.sysfield")); } synchronized (customFields) { for (int i = 0; i < customFields.size(); i++) { DBObjectBaseField myDef = customFields.get(i); if (myDef.getID() == previousField) { customFields.add(i+1, field); fieldTable.put(field); return; } } } // "Error, couldn''t add field def {0} after field {1}, field {1} was not found in object base." throw new IllegalArgumentException(ts.l("addFieldAfter.noSuchPrevious", field, Integer.valueOf(previousField))); } /** * <p>This method is used to put a new user field into both the * field id ordered field table and the display sorted customFields * vector.</p> */ synchronized void addFieldToEnd(DBObjectBaseField field) { if (field.getID() <= SchemaConstants.FinalSystemField) { // "Error, attempted to add a system field using addFieldToEnd()." throw new IllegalArgumentException(ts.l("addFieldToEnd.sysfield")); } fieldTable.put(field); customFields.add(field); } /** * <p>This method is used to instantiate a mandatory system field in * this object.</p> */ synchronized DBObjectBaseField addSystemField(String name, short id, short type) { DBObjectBaseField bf; try { bf = new DBObjectBaseField(this); } catch (RemoteException ex) { throw new RuntimeException(ex.getMessage()); } // we use direct assignment for these fields to avoid schema // editing checks bf.setName(name); bf.setID(id); bf.setType(type); addSystemField(bf); return bf; } /** * <p>This method is used to store a system field.</p> */ synchronized void addSystemField(DBObjectBaseField field) { if (field.getID() > SchemaConstants.FinalSystemField) { // "Error, attempted to add a non-system field using addSystemField()." throw new IllegalArgumentException(ts.l("addSystemField.nonsysfield")); } fieldTable.put(field); } /** * <p>This method is used to remove a field from this base's field * database.</p> * * <p>This method is not intended for access from outside this * class.. use deleteField() for that.</p> */ private synchronized void removeField(DBObjectBaseField field) { fieldTable.remove(field.getID()); if (field.getID() > SchemaConstants.FinalSystemField) { customFields.remove(field); } } // general convenience methods /** * <p>Indicates whether methods on this DBObjectBase are being * called in the context of the server being loaded.</p> * * <p>If this method returns false, many of the data setter methods * in this class will only be permitted if this DBObjectBase is * connected with a non-null DBSchemaEdit object.</p> * * <p>In general, isLoading() and isEditing() should never be true * at the same time.</p> */ public boolean isLoading() { return this.store.isLoading(); } /** * <p>Indicates whether methods on this DBObjectBase are being * called in the context of the schema being edited.</p> * * <p>If this method returns false, many of the data setter methods * in this class will only be permitted if the DBStore is in loading * mode.</p> * * <p>In general, isEditing() and isLoading() should never be true * at the same time.</p> */ public boolean isEditing() { return this.editor != null; } /** * <p>Checks to see if we are in either a loading or editing * context.</p> * * <p>If we are in neither, we throw an exception up, so that a * modified client can't screw with our schema without appropriate * authorization.</p> * * <p>This is necessary because we make RMI references to * DBObjectBase objects available to all Ganymede RMI clients, most * of which have not been granted permission to modify our * schema.</p> */ private void securityCheck() { if (!isLoading() && !isEditing()) { // "not in a schema editing context" throw new IllegalStateException(ts.l("global.notediting")); } } /* ************************************************************************* * * The following methods are for Jython/Map support * * For this object, the Map interface allows for indexing based on either * the name or the numeric ID of a DBObject. Indexing by numeric id, however, * is only supported for "direct" access to the Map; the numeric id numbers * won't appear in the list of keys for the Map. * * EXAMPLE: * MyDBObjectBaseVariable.get("foo") will return the DBObject with the label * of "foo". */ /** * <p>Returns true if this DBObjectBase contains an object with key * as the label.</p> * * <p>Used for Jython support.</p> */ public boolean has_key(Object key) { return keys().contains(key); } /** * <p>Returns a List of labels for objects contained in this * DBObjectBase.</p> * * <p>Used for Jython support.</p> */ public List items() { List list = new ArrayList(); for (DBObject obj: getIterationSet()) { Object[] tuple = new Object[2]; tuple[0] = obj.getLabel(); tuple[1] = obj; list.add(tuple); } return list; } /** * <p>Returns a Set of Strings containing the labels for all objects * in htis DBObjectBase.</p> * * <p>Used for Jython support.</p> */ public Set<String> keys() { Set<String> keys = new HashSet<String>(objectTable.size()); for (DBObject obj: getIterationSet()) { keys.add(obj.getLabel()); } return keys; } /** * <p>Returns true if this DBObjectBase contains an object with key * as the label.</p> * * <p>An alternate name for {@link #has_key(java.lang.Object)}.</p> * * <p>Used for Jython support.</p> */ public boolean containsKey(Object key) { return has_key(key); } /** * <p>Returns true if this DBObjectBase contains an object that * equals() value.</p> * * <p>Used for Jython support.</p> */ public boolean containsValue(Object value) { return getIterationSet().contains(value); } /** * <p>Returns a Set of <label, DBObject> entries, one for each * DBObject contained in this DBObjectBase.</p> * * <p>Used for Jython support.</p> */ public Set<Entry> entrySet() { Set<Entry> entrySet = new HashSet<Entry>(objectTable.size()); for (DBObject obj: getIterationSet()) { entrySet.add(new Entry(obj)); } return entrySet; } /** * <p>Returns the DBObject in this DBObjectBase that matches key, or * null if no such object can be found.</p> * * <p>key can be a PyInteger or Integer in order to fetch the * DBObject by the object id number, a String in order to fetch by * label.</p> * * <p>Used for Jython support.</p> */ public Object get(Object key) { if (key instanceof PyInteger) { PyInteger pi = (PyInteger) key; return objectTable.get(pi.getValue()); } else if (key instanceof Integer) { return objectTable.get(((Integer) key).intValue()); } else if (key instanceof String) { String labelFieldName = getLabelFieldName(); if (labelFieldName == null) { return null; } DBNameSpace namespace = getField(labelFieldName).getNameSpace(); if (namespace == null) { return null; } DBField field = namespace.lookupPersistent(key); if (field.getObjTypeID() == getTypeID()) { return field.getOwner(); } else { return null; } } return null; } /** * <p>Returns true if there are no objects in this DBObjectBase.</p> * * <p>Used for Jython support.</p> */ public boolean isEmpty() { return objectTable.isEmpty(); } /** * <p>Returns a Set of Strings containing the labels for all objects * in htis DBObjectBase.</p> * * <p>Used for Jython support.</p> */ public Set keySet() { return keys(); } /** * <p>Returns the number of objects in this DBObjectBase.</p> */ public int size() { return objectTable.size(); } /** * <p>Returns a Collection of DBObjects contained in this * DBObjectBase.</p> */ public Collection<DBObject> values() { return getIterationSet(); } /** * <p>Returns a String corresponding to the list of labels of * objects in this DBObjectBase.</p> */ public String toString() { return keys().toString(); } /** * <p>Static nested class to represent a label, DBObject * pairing.</p> */ static class Entry implements Map.Entry { Object key, value; public Entry( DBObject obj ) { key = obj.getLabel(); value = obj; } public Object getKey() { return key; } public Object getValue() { return value; } public Object setValue(Object value) { return null; } } /** * <p>Unsupported operation that we have to support the Map * interface.</p> */ public void clear() { throw new UnsupportedOperationException(); } /** * <p>Unsupported operation that we have to support the Map * interface.</p> */ public Object put(Object key, Object value) { throw new UnsupportedOperationException(); } /** * <p>Unsupported operation that we have to support the Map * interface.</p> */ public void putAll(Map t) { throw new UnsupportedOperationException(); } /** * <p>Unsupported operation that we have to support the Map * interface.</p> */ public Object remove(Object key) { throw new UnsupportedOperationException(); } }