/* GASH 2 DBObject.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.PrintStream; import java.io.PrintWriter; import java.rmi.Remote; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Vector; import org.python.core.PyInteger; import arlut.csd.Util.JythonMap; import arlut.csd.Util.TranslationService; import arlut.csd.Util.XMLUtils; import arlut.csd.ganymede.common.GanyPermissionsException; import arlut.csd.ganymede.common.FieldInfo; import arlut.csd.ganymede.common.FieldTemplate; import arlut.csd.ganymede.common.FieldType; import arlut.csd.ganymede.common.Invid; import arlut.csd.ganymede.common.NotLoggedInException; import arlut.csd.ganymede.common.ObjectStatus; import arlut.csd.ganymede.common.PermEntry; import arlut.csd.ganymede.common.ReturnVal; import arlut.csd.ganymede.common.SchemaConstants; import arlut.csd.ganymede.rmi.db_field; import arlut.csd.ganymede.rmi.db_object; /*------------------------------------------------------------------------------ class DBObject ------------------------------------------------------------------------------*/ /** * <p>Class to hold a typed, read-only database object as represented * in the Ganymede {@link arlut.csd.ganymede.server.DBStore DBStore} * database. DBObjects can be exported via RMI for remote access by * remote clients. Clients directly access instances of DBObject for * viewing or editing in the form of a {@link * arlut.csd.ganymede.rmi.db_object db_object} RMI interface type * passed as return value in calls made on the {@link * arlut.csd.ganymede.rmi.Session Session} remote interface.</p> * * <p>A DBObject is identified by a unique identifier called an {@link * arlut.csd.ganymede.common.Invid Invid} and contains a set of {@link * arlut.csd.ganymede.server.DBField DBField} objects which hold the * actual data values held in the object. The client typically * interacts with the fields held in this object directly using the * {@link arlut.csd.ganymede.rmi.db_field db_field} remote interface * which is returned by the DBObject getField methods. DBObject is * not directly involved in the client's interaction with the * DBFields, although the DBFields will call methods on the owning * DBObject to consult about permissions and the like. Clients that * call the GanymedeSession's {@link * arlut.csd.ganymede.server.GanymedeSession#view_db_object(arlut.csd.ganymede.common.Invid) * view_db_object()} method to view a DBObject actually interact with * a copy of the DBObject created by the view_db_object() method to * enforce appropriate read permissions.</p> * * <p>A plain DBObject is not editable; all value-changing calls to * DBFields contained in a plain DBObject will reject any change * requests. In order to edit a DBObject, a client must get access to * a {@link arlut.csd.ganymede.server.DBEditObject DBEditObject} * object derived from the DBObject. This is typically done by * calling {@link * arlut.csd.ganymede.rmi.Session#edit_db_object(arlut.csd.ganymede.common.Invid) * edit_db_object} on the server's {@link * arlut.csd.ganymede.rmi.Session Session} remote interface.</p> * * <p>The DBStore contains a single read-only DBObject in its database * for each Invid. In order to change a DBObject, that DBObject must * have its {@link * arlut.csd.ganymede.server.DBObject#createShadow(arlut.csd.ganymede.server.DBEditSet) * createShadow} method called. This is a synchronized method which * attaches a new DBEditObject to the DBObject. Only one DBEditObject * can be created from a single DBObject at a time, and it must be * created in the context of a {@link * arlut.csd.ganymede.server.DBEditSet DBEditSet} transaction object. * Once the DBEditObject is created, that transaction has exclusive * right to make changes to the DBEditObject. When the transaction is * committed, a new DBObject is created from the values held in the * DBEditObject. That DBObject is then placed back into the DBStore, * replacing the original DBObject. If instead the transaction is * aborted, the DBObject forgets about the DBEditObject that had been * attached to it and the DBObject is once again available for other * transactions to edit.</p> * * <p>Actually, the above picture is a bit too simple. The server's * DBStore object does not directly contain DBObjects, but instead * contains {@link arlut.csd.ganymede.server.DBObjectBase * DBObjectBase} objects, which define a type of DBObject, and contain * all DBObjects of that type in turn. The DBObjectBase is * responsible for making sure that each DBObject has its own unique * Invid based on the DBObjectBase's type id and a unique number for * the individual DBObject.</p> * * <p>In terms of type definition, the DBObjectBase object acts as a * template for objects of the type. Each DBObjectBase contains a set * of {@link arlut.csd.ganymede.server.DBObjectBaseField * DBObjectBaseField} objects which define the names and types of * DBFields that a DBObject of that type is meant to store.</p> * * <p>In addition, each DBObjectBase can be linked to a custom * DBEditObject subclass that oversees all kinds of operations on * DBObjects of this kind. Custom DBEditObject subclasses can define * special logic for object creation, viewing, and editing, including * custom object linking logic, acceptable value constraints, and even * step-by-step wizard dialog sequences to oversee certain kinds of * operations.</p> * * <p>All DBObjects have a certain number of DBFields pre-defined, * including an {@link arlut.csd.ganymede.server.InvidDBField * InvidDBField} listing the owner groups that this DBObject belongs * to, a number of {@link arlut.csd.ganymede.server.StringDBField * StringDBField}s that contain information about the last admin to * modify this DBObject, {@link arlut.csd.ganymede.server.DateDBField * DateDBField}s recording the creation and last modification dates of * this object, and so on. See {@link * arlut.csd.ganymede.common.SchemaConstants SchemaConstants} for * details on the built-in field types.</p> * * <p>DBObject has had its synchronization revised so that only the * createShadow, clearShadow, getFieldPerm, receive, and emitXML * methods are sync'ed on the DBObject itself. Everything else syncs * on the field table held within the DBObject. createShadow() and * clearShadow() in particular must remain sync'ed on the same * monitor, but for most things we want to sync on the interior * fieldAry.</p> * * <p>Is all this clear? Good!</p> * * @author Jonathan Abbey, jonabbey@arlut.utexas.edu, ARL:UT */ public class DBObject implements db_object, FieldType, Remote, JythonMap { static boolean debug = false; final static boolean debugEmit = false; final static boolean debugReceive = false; /** * <p>TranslationService object for handling string localization in * the Ganymede server.</p> */ static final TranslationService ts = TranslationService.getTranslationService("arlut.csd.ganymede.server.DBObject"); /** * Counter field we use to display loading statistics at server start time. */ static public int objectCount = 0; // --- public static void setDebug(boolean val) { debug = val; } /* - */ /** * The type definition for this object. */ protected final DBObjectBase objectBase; /** * <p>Our fields, ordered by ascending field id.</p> * * <p>This reference will be null in the case where we are * constructed as a DBEditObject subclass for use as a pseudo-static * objectHook.</p> * * @see arlut.csd.ganymede.server.DBField */ private final DBField[] fieldAry; /** * <p>Permission cache for our fields, in ascending field id order * using the same indexing as fieldAry.</p> * * <p>This reference will be null unless this instance was checked * out for editing or viewing by a specific GanymedeSession.</p> */ private final PermEntry[] permCacheAry; /** * <p>If this object is being edited or removed, this points to the * DBEditObject copy that is being edited. If this object is not * being edited, this field will be null, and we are available for * someone to edit.</p> */ private DBEditObject shadowObject = null; /** * <p>If this object is being viewed by a particular Ganymede * Session, we record that here.</p> */ protected final GanymedeSession gSession; /** * <p>A fixed copy of our Invid, so that we don't have to create new * ones all the time when people call getInvid() on us.</p> */ private final Invid myInvid; /** * <p>Used by the DBObjectTable logic for hash bucket chaining.</p> */ DBObject next = null; /* -- */ /** * <p>No param constructor, here to allow DBEditObject to * super()-chain to us with a no-param constructor for the * pseudo-static objectHook case.</p> */ public DBObject(DBObjectBase base) { this.objectBase = base; this.gSession = null; this.permCacheAry = null; this.fieldAry = null; this.myInvid = null; } /** * <p>Constructor to create an object of type objectBase with the * specified object number.</p> * * <p>This is used through super() chaining by the DBEditObject * check-out object constructor.</p> */ DBObject(DBObjectBase objectBase, int id, GanymedeSession gSession) { this.gSession = gSession; this.objectBase = objectBase; this.myInvid = Invid.createInvid(objectBase.getTypeID(), id); this.fieldAry = new DBField[objectBase.getFieldCount()]; this.permCacheAry = new PermEntry[objectBase.getFieldCount()]; } /** * <p>Read constructor. Constructs an objectBase from a DataInput * stream.</p> */ DBObject(DBObjectBase objectBase, DataInput in, boolean journalProcessing) throws IOException { if (objectBase == null) { throw new RuntimeException("Error, null object base"); } this.gSession = null; this.permCacheAry = null; this.objectBase = objectBase; this.myInvid = Invid.createInvid(objectBase.getTypeID(), in.readInt()); // number of fields int tmp_count = in.readShort(); if (debug && tmp_count == 0) { // "DBObject.receive(): No fields reading object {0}" System.err.println(ts.l("receive.nofields", Integer.valueOf(getID()))); } this.fieldAry = new DBField[tmp_count]; this.receive(in, journalProcessing); DBObject.objectCount++; } /** * <p>This check-in constructor is used to create a non-editable * DBObject from a DBEditObject that we have finished editing. * Whenever a transaction checks a created or edited shadow back * into the DBStore, it actually does so by creating a new DBObject * to replace any previous version of the object in the DBStore.</p> * * @param eObj The shadow object to copy into the new DBObject * * @see arlut.csd.ganymede.server.DBEditSet#commit(java.lang.String) * @see arlut.csd.ganymede.server.DBEditSet#release() */ DBObject(DBEditObject eObj) { List<DBField> copyFields = eObj.getFieldVect(); if (copyFields == null) { // "Error, tried to call the DBObject check-in constructor with a pseudo-static DBEditObject" throw new NullPointerException(ts.l("global.pseudostatic_constructor")); } this.gSession = null; this.permCacheAry = null; this.objectBase = eObj.objectBase; this.myInvid = eObj.getInvid(); synchronized (eObj) { short count = 0; for (DBField field: copyFields) { if (field != null && field.isDefined()) { count++; } } // put any defined fields into the object we're going // to commit back into our DBStore this.fieldAry = new DBField[count]; int j = 0; for (DBField field: copyFields) { if (field != null && field.isDefined()) { // clean up any cached data the field was holding during // editing try { field.cleanup(); } catch (Exception ex) { // we don't want to throw an uncaught exception in // the check-in constructor, because we'll break // the transaction commit in a really bad way if // that happens. // // field cleanup should be a // fail-without-consequence kind of thing, so // we'll just carry on if it happens ex.printStackTrace(); } // Create a new copy and save it in the new DBObject. We // *must not* directly save the field from the DBEditObject, // because that field has likely been RMI exported to a // remote client, and if we keep the exported field in // local use, all of the extra bulk of the RMI mechanism // will also be retained, as the DBField's Stub and Skel // are associated with the field through a weak hash ref. By // letting the old field from the DBEditObject get locally // garbage collected, we make it possible for all the RMI // stuff to get garbage collected as well. // Making a copy here rather than saving a ref to the // exported field makes a *huge* difference in overall // memory usage on the Ganymede server. fieldAry[j++] = field.getCopy(this); // safe since we started with an empty fieldAry } } } } /** * <p>This constructor is used to make a copy of a DBObject with an * updated DBObjectBase type definition, in order to replace an * object in a DBObjectBase table with a new version referencing an * updated DBObjectBase type definition.</p> * * <p>Any fields that are no longer present in the typeDefinition * are excluded from the copy made.</p> */ public DBObject(DBObject original, DBObjectBase typeDefinition) { this.objectBase = typeDefinition; this.myInvid = original.myInvid; this.gSession = null; this.permCacheAry = null; DBField oldAry[] = original.fieldAry; int count = 0; synchronized (oldAry) { for (DBField field: oldAry) { if (field == null) { continue; } if (typeDefinition.getField(field.getID()) != null && field.isDefined()) { count++; } } this.fieldAry = new DBField[count]; int i = 0; for (DBField field: oldAry) { if (field == null) { continue; } if (typeDefinition.getField(field.getID()) != null && field.isDefined()) { // we have to make a new copy of each field to make // sure the final owner reference points back to us. this.fieldAry[i++] = field.getCopy(this); } } } } /** * <p>This is a view-copy constructor, designed to make a view-only * duplicate of an object from the database. This view-only object * knows who is looking at it through its GanymedeSession reference, * and so can properly enforce field access permissions.</p> * * <p><gSession> may be null, in which case the returned DBObject * will be simply an un-linked fresh copy of <original>.</p> */ public DBObject(DBObject original, GanymedeSession gSession) { if (original == null || original.fieldAry == null) { throw new NullPointerException(ts.l("global.pseudostatic_constructor")); } this.gSession = gSession; this.myInvid = original.myInvid; this.objectBase = original.objectBase; this.permCacheAry = new PermEntry[original.fieldAry.length]; synchronized (original.fieldAry) { this.fieldAry = new DBField[original.fieldAry.length]; for (int i = 0; i < original.fieldAry.length; i++) { fieldAry[i] = original.fieldAry[i].getCopy(this); } } } /** * <p>Creation constructor, is responsible for creating a new * editable object with all fields listed in the {@link * arlut.csd.ganymede.server.DBObjectBaseField DBObjectBaseField} * instantiated but undefined.</p> * * <p>This constructor is not really intended to be overridden in subclasses. * Creation time field value initialization is to be handled by * initializeNewObject().</p> * * @see arlut.csd.ganymede.server.DBField */ DBObject(DBObjectBase objectBase, Invid invid, DBEditSet editset) { if (editset == null) { // "Null DBEditSet" throw new NullPointerException(ts.l("init.notrans")); } this.objectBase = objectBase; this.gSession = editset.getDBSession().getGSession(); this.myInvid = invid; /* -- */ synchronized (objectBase) { this.fieldAry = new DBField[objectBase.getFieldCount()]; this.permCacheAry = new PermEntry[objectBase.getFieldCount()]; int i = 0; // the iterator on DBBaseFieldTable gives us the field // defintion objects in field id order, which we need to order // the fieldAry elements properly. for (DBObjectBaseField fieldDef: objectBase.getFieldsInFieldOrder()) { DBField newField = DBField.createTypedField(this, fieldDef); if (newField == null) { throw new NullPointerException("Error creating typed field when creating object"); } fieldAry[i++] = newField; } } } /** * <p>Copy constructor that takes a DBObject and a DBObjectDeltaRec * and creates a new DBObject with the changes in delta applied to * the original.</p> * * <p>The original object is not modified.</p> */ DBObject(DBObject original, DBObjectDeltaRec delta) { this.objectBase = original.objectBase; this.myInvid = original.myInvid; this.gSession = null; Map<Short, DBField> fieldMap = new HashMap<Short, DBField>(); DBField[] originals = original.listDBFields(); for (DBField field: originals) { fieldMap.put(field.getID(), DBField.copyField(this, field)); } for (fieldDeltaRec fieldRec: delta) { if (!fieldRec.vector) { if (fieldRec.scalarValue == null) { fieldMap.remove(fieldRec.fieldcode); continue; } else { fieldMap.put(fieldRec.fieldcode, DBField.copyField(this, fieldRec.scalarValue)); } } else { DBField fieldCopy = DBField.copyField(this, original.getField(fieldRec.fieldcode)); if (fieldRec.addValues != null) { for (Object value: fieldRec.addValues) { fieldCopy.getVectVal().add(value); } } if (fieldRec.delValues != null) { for (Object value: fieldRec.delValues) { fieldCopy.getVectVal().remove(value); } } if (fieldCopy.isDefined()) { fieldMap.put(fieldRec.fieldcode, fieldCopy); } } } this.permCacheAry = null; this.fieldAry = new DBField[fieldMap.size()]; int i = 0; for (DBObjectBaseField fieldDef: objectBase.getFieldsInFieldOrder()) { DBField field = fieldMap.get(fieldDef.getID()); fieldAry[i++] = field; } } /** * Returns the non-editing DBEditObject singleton that provides * management oversight to Objects of this type. */ public final DBEditObject getHook() { return objectBase.getObjectHook(); } /** * <p>This method makes the fields in this object remotely accessible. * Used by GanymedeSession when it provides a DBObject to the * client.</p> */ public final void exportFields() { if (fieldAry == null) { throw new NullPointerException(ts.l("global.pseudostatic")); } synchronized (fieldAry) { for (DBField field: fieldAry) { // export can fail if the object has already // been exported.. don't worry about it if // it happens.. the client will know about it // if we try to pass a non-exported object // back to it, anyway. Ganymede.rmi.publishObject(field); } } } /** * <p>This method pulls the fields in this object from remote * accessibility through RMI, possibly improving our security * posture and reducing the memory loading on the RMI system.</p> */ public final void unexportFields() { if (fieldAry == null) { throw new NullPointerException(ts.l("global.pseudostatic")); } synchronized (fieldAry) { for (DBField field: fieldAry) { // unexport can fail (return false) if the object has // already been unexported, or if it was never exported, // but we don't care as long as it's not exported after // this point. Ganymede.rmi.unpublishObject(field, true); } } } public final int hashCode() { return myInvid.getNum(); } /** * <p>Returns the numeric id of the object in the objectBase</p> * * @see arlut.csd.ganymede.rmi.db_object */ public final int getID() { return myInvid.getNum(); } /** * <p>Returns the invid of this object for the db_object remote * interface</p> * * @see arlut.csd.ganymede.rmi.db_object */ public final Invid getInvid() { return myInvid; } /** * <p>Returns the numeric id of the object's objectBase</p> * * @see arlut.csd.ganymede.rmi.db_object */ public final short getTypeID() { return objectBase.getTypeID(); } /** * <p>Returns the name of the object's objectBase</p> * * @see arlut.csd.ganymede.rmi.db_object */ public final String getTypeName() { return objectBase.getName(); } /** * <p>Returns the data dictionary for this object</p> */ public final DBObjectBase getBase() { return objectBase; } /** * Returns the original version of the object that we were created * to edit. If we are a newly created object, this method will * return null. */ public DBObject getOriginal() { return this; } /** * <p>Returns the field definition for the given field code, or null * if that field code is not registered with this object type.</p> */ public final DBObjectBaseField getFieldDef(String fieldName) { return objectBase.getField(fieldName); } /** * <p>Returns the field definition for the given field code, or null * if that field code is not registered with this object type.</p> */ public final DBObjectBaseField getFieldDef(short fieldcode) { return objectBase.getField(fieldcode); } /** * <p>Returns the permission that apply to the given fieldName in this * object.</p> * * <p>If this object was not made in the context of a specific * GanymedeSession, full permissions will be given for access to the * field.</p> */ public final PermEntry getFieldPerm(String fieldName) { DBField f = getField(fieldName); if (f == null) { // "Can''t find permissions for non-existent field "{0}"" throw new IllegalArgumentException(ts.l("getFieldPerm.nofield", fieldName)); } return this.getFieldPerm(f.getID()); } /** * <p>Returns the permission that apply to the field with the given * fieldcode in this object.</p> * * <p>If this object was not made in the context of a specific * GanymedeSession, full permissions will be given for access to the * field.</p> */ public final synchronized PermEntry getFieldPerm(short fieldcode) { PermEntry result = null; DBPermissionManager permManager = null; /* -- */ if (fieldAry == null) { throw new NullPointerException(ts.l("global.pseudostatic")); } if (this.gSession == null) { return PermEntry.fullPerms; // assume supergash if we have no session } else { permManager = this.gSession.getPermManager(); } short index = findField(fieldcode); if (index == -1) { // "Unrecognized fieldcode: {0}" throw new IllegalArgumentException(ts.l("getFieldPerm.nofield", Integer.valueOf(fieldcode))); } if (permCacheAry != null) { result = permCacheAry[index]; if (result == null) { result = permManager.getPerm(this, fieldcode); if (result == null) { result = permManager.getPerm(this); } permCacheAry[index] = result; } } return result; } /** * <p>Returns the GanymedeSession that this object is checked out in * care of.</p> */ public final GanymedeSession getGSession() { return gSession; } /** * <p>Returns the DBSession that this object is checked out in care * of, or null if it is checked out from the persistent store.</p> * * @deprecated Use {@link #getDBSession()} instead. */ @Deprecated public final DBSession getSession() { return this.getDBSession(); } /** * <p>Returns the DBSession that this object is checked out in care * of, or null if it is checked out from the persistent store.</p> */ public final DBSession getDBSession() { try { return gSession.getDBSession(); } catch (NullPointerException ex) { return null; } } /** * <p>Provide easy server-side access to this object's name in a * String context for debug and non-critical output.</p> */ public String toString() { return getLabel() + " (" + getInvid() + ")"; } /** * <p>Simple equals test.. doesn't really test to see if things are * value-equals, but rather identity equals.</p> */ public boolean equals(Object param) { if (!(param instanceof DBObject)) { return false; } try { return (getInvid().equals(((DBObject) param).getInvid())); } catch (NullPointerException ex) { return false; } } /** * <p>Returns the primary label of this object.</p> * * <p>We don't synchronize getLabel(), as it is very, very * frequently called from all over, and we don't want to chance * deadlock. getField() and getValueString() are both synchronized * on subcomponents of DBObject, so this method should be adequately * safe as written.</p> * * @see arlut.csd.ganymede.rmi.db_object */ public String getLabel() { DBField f = getField(objectBase.getLabelField()); if (f != null && f.isDefined()) { String result = f.getValueString(); // the label must be unique, but if we're a newly created // object, we won't have any label value yet. Go ahead and // synthesize one for the time being. if (result == null || result.length() == 0) { // "New {0}: {1,number,#}" result = ts.l("getLabel.null_label", getTypeName(), Integer.valueOf(getID())); } return result; } else { // we should never get here.. objects shouldn't be in the // database without their label field. // "New {0}: {1,number,#}" return ts.l("getLabel.null_label", getTypeName(), Integer.valueOf(getID())); } } /** * <p>If this object is not embedded, returns the label of this * object in the same way that getLabel() does.</p> * * <p>If this object is embedded, returns a /-separated label * containing the name of all containing objects followed by this * object's label.</p> * * @see arlut.csd.ganymede.rmi.db_object */ public final String getPathLabel() { if (!isEmbedded()) { return this.getLabel(); } else { return getParentObj().getPathLabel() + "/" + this.getLabel(); } } /** * <p>If this object type is embedded, this method will return the * desired display label for the embedded object.</p> * * <p>This label may not be the same as returned by getLabel(), * which is guaranteed to be derived from a namespace constrained * label field, suitable for use in the XML context.</p> * * @see arlut.csd.ganymede.rmi.db_object */ public String getEmbeddedObjectDisplayLabel() { return getHook().getEmbeddedObjectDisplayLabelHook(this); } /** * <p>Get access to the field that serves as this object's * label.</p> * * @see arlut.csd.ganymede.rmi.db_object */ public final DBField getLabelField() { return getField(objectBase.getLabelField()); } /** * <p>Get access to the field id for the field that serves as this * object's label.</p> * * @see arlut.csd.ganymede.rmi.db_object */ public final short getLabelFieldID() { return objectBase.getLabelField(); } /** * <p>Returns true if this object is an embedded type.</p> * * @see arlut.csd.ganymede.rmi.db_object */ public final boolean isEmbedded() { return objectBase.isEmbedded(); } /** * <p>The emit() method is part of the process of dumping the DBStore * to disk. emit() dumps an object in its entirety to the * given out stream.</p> * * @param out A {@link arlut.csd.ganymede.server.DBJournal DBJournal} or * {@link arlut.csd.ganymede.server.DBStore DBStore} writing stream. */ final void emit(DataOutput out) throws IOException { // System.err.println("Emitting " + objectBase.getName() + " <" + id + ">"); if (fieldAry == null) { throw new NullPointerException(ts.l("global.pseudostatic")); } out.writeInt(getID()); // write out our object id synchronized (fieldAry) { short count = 0; for (DBField field: fieldAry) { if (field != null && field.isDefined()) { count++; } } if (count == 0) { // "**** Error: writing object with no fields: {0}" Ganymede.debug(ts.l("emit.nofields", objectBase.getName() + " <" + getID() + ">")); } out.writeShort(count); for (DBField field: fieldAry) { if (field != null && field.isDefined()) { out.writeShort(field.getID()); field.emit(out); } } } } /** * <p>The receive() method is part of the process of loading the * {@link arlut.csd.ganymede.server.DBStore DBStore} * from disk. receive() reads an object from the given in stream and * instantiates it into the DBStore.</p> * * <p>This method is synchronized, but there are a lot of other methods * in DBObject which are not synchronized and which could cause problems * if they are run concurrently with receive. All the ones that * play in the fieldAry array. This is only workable because receive * is not called on an object after it has been loaded into the * database.</p> */ private synchronized void receive(DataInput in, boolean journalProcessing) throws IOException { int upgradeSkipCount = 0; /* -- */ for (int i = 0; i < this.fieldAry.length; i++) { // read our field code, look it up in our // DBObjectBase short fieldcode = in.readShort(); DBObjectBaseField definition = this.getFieldDef(fieldcode); // we used to have a couple of Invid vector fields that we // have gotten rid of, for the sake of improving Ganymede's // concurrency and reducing inter-object lock contention. The // BackLinksField we got rid of a long time ago, the // OwnerBase's OwnerObjectsOwned field we got rid of at // DBStore 2.7. if ((fieldcode == SchemaConstants.BackLinksField) || (getTypeID() == SchemaConstants.OwnerBase && fieldcode == SchemaConstants.OwnerObjectsOwned)) { // the backlinks field was always a vector of invids, so // now that we are no longer explicitly recording // asymmetric relationships with the backlinks field, we // can just skip forward in the database file and skip the // backlinks info // Ditto, starting at DBStore version 2.7, with the // OwnerBase's OwnerObjectsOwned if (Ganymede.db.isLessThan(2,3)) { upgradeSkipCount = in.readShort(); } else { upgradeSkipCount = in.readInt(); } int count = upgradeSkipCount; // our vector count while (count-- > 0) { in.readShort(); in.readInt(); } continue; } else if (definition == null) { // "What the heck? Null definition for {0}, fieldcode = {1}, {2}th field in object" throw new RuntimeException(ts.l("receive.nulldef", this.getTypeName(), Integer.valueOf(fieldcode), Integer.valueOf(i))); } if (debugReceive) { System.err.println("Reading field " + definition); } DBField tmp = DBField.readField(this, in, definition); if (tmp == null) { // "Don't recognize field type in datastore." throw new Error(ts.l("receive.badfieldtype")); } if (!journalProcessing && (definition.getNameSpace() != null)) { if (tmp.isVector()) { // mark the elements in the vector in the namespace // note that we don't use the namespace mark method here, // because we are just setting up the namespace, not // manipulating it in the context of an editset for (int j = 0; j < tmp.size(); j++) { if (definition.getNameSpace().containsKey(tmp.key(j))) { try { // "Non-unique value {0} detected in vector field {1} which is constrained by namespace {2}" throw new RuntimeException(ts.l("receive.vectornamespace", GHashtable.keyString(tmp.key(j)), definition, definition.getNameSpace())); } catch (RuntimeException ex) { ex.printStackTrace(); } } definition.getNameSpace().receiveValue(tmp.key(j), tmp); } } else { // mark the scalar value in the namespace if (definition.getNameSpace().containsKey(tmp.key())) { // "Non-unique value {0} detected in scalar field {1} which is constrained by namespace {2}" try { throw new RuntimeException(ts.l("receive.scalarnamespace", GHashtable.keyString(tmp.key()), definition, definition.getNameSpace())); } catch (RuntimeException ex) { ex.printStackTrace(); } } definition.getNameSpace().receiveValue(tmp.key(), tmp); } } // now add the field to our fields table if (Ganymede.db.isAtLeast(2, 15)) { // starting at DBStore 2.15, we know that the fields are // coming from the db file ordered by ascending field code fieldAry[i] = tmp; } else { // we have to be more conservative when loading an older // db or journal block. if (tmp.isDefined()) { saveField(tmp); } else { // "%%% Loader skipping empty field {0}" System.err.println(ts.l("receive.skipping", definition.getName())); } } } if (getTypeID() == SchemaConstants.OwnerBase && upgradeSkipCount != 0) { // "Skipped over {0} objects in deprecated OwnerObjectsOwned field while reading owner group {1}" System.err.println(ts.l("receive.upgradeSkippingOwned", Integer.valueOf(upgradeSkipCount), this.getLabel())); } } /** * <p>This method is used when this object is being dumped.</p> */ public final synchronized void emitXML(XMLDumpContext xmlOut) throws IOException { xmlOut.startElementIndent("object"); xmlOut.attribute("type", XMLUtils.XMLEncode(getTypeName())); xmlOut.attribute("id", getLabel()); if (xmlOut.isDumpingOid()) { xmlOut.attribute("oid", this.getInvid().toString()); } xmlOut.indentOut(); // by using getFieldVector(), we get the fields in display // order Vector<DBField> fieldVec = getFieldVector(false); for (DBField field: fieldVec) { if (field.isDefined()) { if (xmlOut.mayInclude(field)) { field.emitXML(xmlOut); } } } xmlOut.indentIn(); xmlOut.endElementIndent("object"); } /** * <p>Check this object out from the datastore for editing. This * method is intended to be called by the editDBObject method in * DBSession.. createShadow should not be called on an arbitrary * viewed object in other contexts.. probably should do something to * guarantee this?</p> * * <p>If this object is being edited, we say that it has a shadow * object; a session gets a copy of this object.. the copy is * actually a DBEditObject, which has the intelligence to allow the * client to modify the (copies of the) data fields.</p> * * <p>note: this is only used for editing pre-existing objects.. * the code for creating new objects is in DBSession.. this method * might be better incorporated into DBSession as well.</p> * * @param editset The transaction to own this shadow. */ final synchronized DBEditObject createShadow(DBEditSet editset) { if (this.shadowObject != null) { // this object has already been checked out // for editing / deleting return null; } this.shadowObject = objectBase.createNewObject(this, editset); // if this object currently points to an object that // is being deleted by way of an asymmetric InvidDBField, // addObject() may fail. In this case, we have to deny // the edit if (!editset.addObject(this.shadowObject)) { this.shadowObject = null; return null; } // update the session's checkout count first, then // update the database's overall checkout, which // will trigger a console update if (editset.session.GSession != null) { editset.session.GSession.checkOut(); // update session checked out count } this.objectBase.getStore().checkOut(); // update checked out count return this.shadowObject; } /** * <p>This method is the complement to createShadow, and * is used during editset release.</p> * * @param editset The transaction owning this object's shadow. * * @see arlut.csd.ganymede.server.DBEditSet#release() */ final synchronized boolean clearShadow(DBEditSet editset) { if (editset != this.shadowObject.editset) { // couldn't clear the shadow.. this editSet // wasn't the one to create the shadow // "DBObject.clearShadow(): couldn't clear, editset mismatch" Ganymede.debug(ts.l("clearShadow.mismatch")); return false; } this.shadowObject = null; if (editset.session.GSession != null) { editset.session.GSession.checkIn(); } this.objectBase.getStore().checkIn(); // update checked out count return true; } /** * <p>If this object is currently being edited by an active * GanymedeSession, this method will return a pointer to the * DBEditObject that is handling the edits.</p> * * <p>Otherwise, getShadow() returns null.</p> */ final public DBEditObject getShadow() { return this.shadowObject; } /** * <p>Get read-only Vector of DBFieldInfo objects for the custom * DBFields contained in this object, in display order.</p> * * <p>This method is called by the client so as to download all of * the field values in an object in a single remote method call.</p> * * <p>If the client does not have permission to view a field, that * field will be left out of the resulting Vector.</p> * * @see arlut.csd.ganymede.rmi.db_object */ public final Vector<FieldInfo> getFieldInfoVector() { Vector<FieldInfo> results = new Vector<FieldInfo>(); DBField field; /* -- */ if (fieldAry == null) { throw new NullPointerException(ts.l("global.pseudostatic")); } synchronized (fieldAry) { for (DBObjectBaseField fieldDef: objectBase.getCustomFields()) { field = getField(fieldDef.getID()); if (field != null) { try { results.add(new FieldInfo(field)); } catch (GanyPermissionsException ex) { // swallow the exception without comment, we'll // just leave the field out of the vector } } } } return results; } /** * <p>This method provides a Vector copy of the DBFields contained * in this object in a fashion that does not contribute to fieldAry * threadlock.</p> * * <p>Server-side only.</p> */ public final Vector<DBField> getFieldVect() { if (this.fieldAry == null) { return null; } Vector<DBField> fieldVect = new Vector<DBField>(fieldAry.length); synchronized (fieldAry) { for (DBField field: fieldAry) { fieldVect.add(field); } } return fieldVect; } /** * <p>Used by the DBEditObject check-out constructor to place an * array of fields in field order into the parent object's * pre-created fieldAry.</p> */ void setAllFields(DBField[] newFields) { for (int i = 0; i < newFields.length; i++) { this.fieldAry[i] = newFields[i]; } } /** * <p>This method places a DBField into a slot in this object's * fieldAry DBField array. As a (probably reckless) speed * optimization, this method makes no checks to ensure that another * DBField with the same field id has not previously been stored, so * it should only be used when the DBObject's fieldAry is in a known * state.</p> * * <p>saveField() saves fields in field id order to try and speed up * field retrieving, by allowing us to do boolean search to find * elements.</p> */ private void saveField(DBField field) { if (fieldAry == null) { throw new NullPointerException(ts.l("global.pseudostatic")); } if (field == null) { // "null value passed to saveField" throw new IllegalArgumentException(ts.l("saveField.null")); } synchronized (fieldAry) { int i = 0; while (i < fieldAry.length) { if (fieldAry[i] == null) { fieldAry[i] = field; return; } if (fieldAry[i].getID() > field.getID()) { break; } i++; } if (i == fieldAry.length) { throw new ArrayIndexOutOfBoundsException("saveField overran field array length"); } DBField currentField = null; DBField bubbleField = field; while (i < fieldAry.length) { currentField = fieldAry[i]; fieldAry[i] = bubbleField; bubbleField = currentField; i++; } } } /** * <p>Returns true if this object contains the given field id.</p> * * <p>Not permission checked, server-side only.</p> */ public final boolean hasField(short id) { return (getField(id) != null); } /** * <p>Returns true if this object contains the given field id.</p> * * <p>Not permission checked, server-side only.</p> */ public final boolean hasField(String fieldName) { return (getField(fieldName) != null); } /** * <p>Get read-only access to a field from this object, by name.</p> * * <p>This method retrieves a DBField from this object's fieldAry * DBField array. getField() uses a hashing algorithm to try * and speed up field retrieving, but we are optimizing for low * memory usage rather than O(1) operations.</p> * * @param fieldname The fieldname for the desired field of this object * * @see arlut.csd.ganymede.rmi.db_object */ public final DBField getField(short id) { if (fieldAry == null) { throw new NullPointerException(ts.l("global.pseudostatic")); } synchronized (fieldAry) { int index = java.util.Arrays.binarySearch(fieldAry, id); if (index < 0) { return null; } return fieldAry[index]; } } /** * <p>Get access to a field from this object. This method * is exported to clients over RMI.</p> * * <p>This method retrieves a DBField from this object's fieldAry * DBField array by field name. This access is done slowly, using a * simple iteration over the values in our packed hash * structure.</p> * * @param id The field code for the desired field of this object. * * @see arlut.csd.ganymede.rmi.db_object */ public final DBField getField(String fieldName) { if (fieldAry == null) { throw new NullPointerException(ts.l("global.pseudostatic")); } synchronized (fieldAry) { for (DBField field: fieldAry) { if (field != null && field.getName().equalsIgnoreCase(fieldName)) { return field; } } } return null; } /** * <p>This method finds the index for the given field id in this object's * fieldAry and permCacheAry tables.</p> * * @return -1 if we couldn't find a field with the given id */ private final short findField(short id) { if (fieldAry == null) { throw new NullPointerException(ts.l("global.pseudostatic")); } synchronized (fieldAry) { return (short) java.util.Arrays.binarySearch(fieldAry, id); } } /** * <p>This method clears any cached PermEntry value for the * given field id.</p>. * * <p>It is intended for use by custom DBEditObject subclasses which * oversee some of their own permissions. By calling this method, a * subclass can remove a cached field permission and cause the * permissions system to consult with the controlling custom * DBEditObject subclass afresh.</p> */ public final void clearFieldPerm(short id) { if (fieldAry == null) { throw new NullPointerException(ts.l("global.pseudostatic")); } synchronized (fieldAry) { int index = java.util.Arrays.binarySearch(fieldAry, id); if (index < 0) { return; } if (permCacheAry != null) { permCacheAry[index] = null; } } } /** * <p>Returns the name of a field from this object.</p> * * @param id The field code for the desired field of this object. * * @see arlut.csd.ganymede.rmi.db_object */ public final String getFieldName(short id) { DBField field = getField(id); if (field != null) { return field.toString(); } return "<<" + id + ">>"; } /** * <p>This method returns the short field id code for the named * field, if the field is present in this object, or -1 if the * field could not be found.</p> * * @see arlut.csd.ganymede.rmi.db_object */ public final short getFieldId(String fieldname) { DBField field; /* -- */ if (fieldAry == null) { throw new NullPointerException(ts.l("global.pseudostatic")); } synchronized (fieldAry) { for (int i = 0; i < fieldAry.length; i++) { field = fieldAry[i]; if (field != null && field.getName().equalsIgnoreCase(fieldname)) { return field.getID(); } } } return -1; } /** * <p>Get complete list of db_fields contained in this object. The * list returned will appear in field id order.</p> * * @see arlut.csd.ganymede.rmi.db_object */ public final db_field[] listFields() { return (db_field[]) listDBFields(); } /** * <p>Get complete list of DBFields contained in this object. The * list returned will appear in field id order.</p> */ public final DBField[] listDBFields() { DBField result[]; short count = 0; if (fieldAry == null) { throw new NullPointerException(ts.l("global.pseudostatic")); } synchronized (fieldAry) { for (DBField field: fieldAry) { if (field != null) { count++; } } result = new DBField[count]; count = 0; for (DBField field: fieldAry) { if (field != null) { result[count++] = field; } } } return result; } /** * <p>Returns true if inactivate() is a valid operation on * checked-out objects of this type.</p> * * @see arlut.csd.ganymede.rmi.db_object */ public final boolean canInactivate() { return objectBase.canInactivate(); } /** * <p>Returns true if this object has been inactivated and is * pending deletion.</p> * * @see arlut.csd.ganymede.rmi.db_object */ public boolean isInactivated() { return (objectBase.canInactivate() && (getFieldValueLocal(SchemaConstants.RemovalField) != null)); } /** * <p>This method examines all fields in the object and verifies * that they satisfy the elementary value constraints specified in * the Ganymede schema.</p> * * <p>If any fields do not meet the field constraints, a ReturnVal will * be returned with a free-form dialog describing the violations.</p> * * <p>If there are no field constraint violations, null will be * returned.</p> */ public final ReturnVal validateFieldIntegrity() { StringBuilder resultBuffer = new StringBuilder(); /* -- */ if (fieldAry == null) { throw new NullPointerException(ts.l("global.pseudostatic")); } // sync on fieldAry since we are looping over our fields and since // getField itself sync's on fieldAry. if we sync up front we may // reduce our lock acquisition time marginally synchronized (fieldAry) { // loop over the fields in display order (rather than the hash // order in the fieldAry) Vector<FieldTemplate> fieldTemplates = objectBase.getFieldTemplateVector(); for (FieldTemplate template: fieldTemplates) { DBField field = getField(template.getID()); if (field != null && field.isDefined()) { ReturnVal retVal = field.validateContents(); if (!ReturnVal.didSucceed(retVal)) { if (resultBuffer.length() > 0) { resultBuffer.append("\n"); } resultBuffer.append(retVal.getDialogText()); } } } } if (resultBuffer.length() > 0) { // we're using the non-logging ReturnVal.setErrorText() rather // than using Ganymede.createErrorDialog() because the // DBField.validateContents() call itself uses // Ganymede.createErrorDialog(). ReturnVal retVal = new ReturnVal(false); retVal.setErrorText(resultBuffer.toString()); return retVal; } return null; } /** * <p>Returns true if this object has all its required fields defined</p> * * <p>This method can be overridden in DBEditObject subclasses to do a * more refined validity check if desired.</p> * * @see arlut.csd.ganymede.rmi.db_object */ public boolean isValid() { return (checkRequiredFields() == null); } /** * <p>This method scans through all custom fields defined in the * {@link arlut.csd.ganymede.server.DBObjectBase DBObjectBase} for * this object type and determines if all required fields have been * filled in. If everything is ok, this method will return null. * If any required fields are found not to have been filled out, * this method returns a vector of field names that need to be * filled out.</p> * * <p>This method is used by the transaction commit logic to ensure a * consistent transaction. If server-local code has called * {@link arlut.csd.ganymede.server.GanymedeSession#enableOversight(boolean) GanymedeSession.enableOversight(false)} * this method will not be called at transaction commit time.</p> */ public final Vector<String> checkRequiredFields() { Vector<String> localFields = new Vector<String>(); /* -- */ // sync on fieldAry since we are looping over our fields and since // getField itself sync's on fieldAry if (fieldAry == null) { throw new NullPointerException(ts.l("global.pseudostatic")); } synchronized (fieldAry) { // assume that the object type's fields will not be changed at a // time when this method is called. A reasonable assumption, // as the objectbase field table is only altered when the // schema is being edited. for (DBObjectBaseField fieldDef: objectBase.getCustomFields()) { try { // nota bene: calling fieldRequired here could // potentially leave us open for threadlock, depending // on how the fieldRequired method is written. I // think this is a low-level risk, but not zero. if (getHook().fieldRequired(this, fieldDef.getID())) { DBField field = getField(fieldDef.getID()); if (field == null || !field.isDefined()) { localFields.add(fieldDef.getName()); } } } catch (NullPointerException ex) { Ganymede.logError(ex, "Null pointer exception in checkRequiredFields().\n" + "My type is " + getTypeName() + "\nMy invid is " + getInvid()); } } } // if all required fields checked out, return null to signify success if (localFields.size() == 0) { return null; } else { return localFields; } } /** * <p>Returns the date that this object is to go through final removal * if it has been inactivated.</p> * * @see arlut.csd.ganymede.rmi.db_object */ public final Date getRemovalDate() { DateDBField dbf = getDateField(SchemaConstants.RemovalField); if (dbf == null) { return null; } return dbf.value(); } /** * <p>Returns true if this object has an expiration date set.</p> * * @see arlut.csd.ganymede.rmi.db_object */ public final boolean willExpire() { return (getFieldValueLocal(SchemaConstants.ExpirationField) != null); } /** * <p>Returns true if this object has a removal date set.</p> * * @see arlut.csd.ganymede.rmi.db_object */ public final boolean willBeRemoved() { return (getFieldValueLocal(SchemaConstants.RemovalField) != null); } /** * <p>Returns the date that this object is to be automatically * inactivated if it has an expiration date set.</p> * * @see arlut.csd.ganymede.rmi.db_object */ public final Date getExpirationDate() { DateDBField dbf = getDateField(SchemaConstants.ExpirationField); if (dbf == null) { return null; } return dbf.value(); } /** * <p>Returns true if this object has an 'in-care-of' email address * that should be notified when this object is changed.</p> */ public final boolean hasEmailTarget() { return getHook().hasEmailTarget(this); } /** * <p>Returns a vector of email addresses that can be used to send * 'in-care-of' email for this object.</p> */ public final List<String> getEmailTargets() { return (List<String>) getHook().getEmailTargets(this); } /** * <p>Returns a String containing a URL that can be used by the * client to retrieve a picture representating this object.</p> * * <p>Intended to be used for users, primarily.</p> * * @see arlut.csd.ganymede.rmi.db_object */ public final String getImageURL() { return getHook().getImageURLForObject(this); } /** * <p>Shortcut method to set a field's value. Using this * method saves a roundtrip to the server, which is * particularly useful in database loading.</p> * * @see arlut.csd.ganymede.rmi.db_object */ public final ReturnVal setFieldValue(String fieldName, Object value) throws GanyPermissionsException { DBField field = getField(fieldName); if (field == null) { // "Error, object {0} does not contain a field named "{1}"." return Ganymede.createErrorDialog(this.getGSession(), null, ts.l("global.bad_field_name", this.getTypeName(), fieldName)); } // NB: we would go ahead and do like we did for getFieldValue(), // and define a third setFieldValue() function that would take a // DBField to implement the common logic, but we've had a history // of letting folks subclass setFieldValue(short id, Object value) // specifically to override setFieldValue behavior. We'll keep // bridging into the short-using setFieldValue() to preserve this // behavior. return this.setFieldValue(field.getID(), value); } /** * <p>Shortcut method to set a field's value. Using this * method saves a roundtrip to the server, which is * particularly useful in database loading.</p> * * @see arlut.csd.ganymede.rmi.db_object */ public ReturnVal setFieldValue(short fieldID, Object value) throws GanyPermissionsException { // we override in DBEditObject // "Server: Error in DBObject.setFieldValue()" // "setFieldValue called on a non-editable object" return Ganymede.createErrorDialog(this.getGSession(), ts.l("setFieldValue.noneditable"), ts.l("setFieldValue.noneditabletext")); } /** * <p>Shortcut method to get a field's value. Using this * method saves a roundtrip to the server, which is * particularly useful in database loading.</p> * * @see arlut.csd.ganymede.rmi.db_object */ public final Object getFieldValue(String fieldName) throws GanyPermissionsException { return this.getFieldValue(getField(fieldName)); } /** * <p>Shortcut method to get a field's value. Using this * method saves a roundtrip to the server, which is * particularly useful in database loading.</p> * * @see arlut.csd.ganymede.rmi.db_object */ public final Object getFieldValue(short fieldID) throws GanyPermissionsException { return this.getFieldValue(getField(fieldID)); } private Object getFieldValue(DBField f) throws GanyPermissionsException { if (f == null) { return null; } if (f.isVector()) { // "Couldn't get scalar value on vector field {0}" throw new IllegalArgumentException(ts.l("getFieldValue.badtype", f.getName())); } return f.getValue(); } /** * <p>Shortcut method to get a field's value. Used only * on the server, as permissions are not checked.</p> */ public final Object getFieldValueLocal(String fieldName) { return this.getFieldValueLocal(getField(fieldName)); } /** * <p>Shortcut method to get a field's value. Used only * on the server, as permissions are not checked.</p> */ public final Object getFieldValueLocal(short fieldID) { return this.getFieldValueLocal(getField(fieldID)); } private Object getFieldValueLocal(DBField f) { if (f == null) { return null; } if (f.isVector()) { // "Couldn't get scalar value on vector field {0}" throw new IllegalArgumentException(ts.l("getFieldValue.badtype", f.getName())); } return f.getValueLocal(); } /** * <p>This helper method is for use on the server, so that custom * code subclasses can call a simple method to look up an Invid and * get the appropriate DBObject, taking into account whether the * lookup is being done within a transaction or no.</p> * * <p>Note that unless the object has been checked out by the current session, * this method will return access to the object as it is stored directly * in the main datastore hashes. This means that the object will be * read-only and will grant all accesses, as it will have no notion of * what session or transaction owns it. If you need to have access to the * object's fields be protected, use {@link arlut.csd.ganymede.server.GanymedeSession GanymedeSession}'s * {@link arlut.csd.ganymede.server.GanymedeSession#view_db_object(arlut.csd.ganymede.common.Invid) * view_db_object()} method to get the object.</p> * * <p>This method will return null if the Invid provided does not * exist in the session or the persistent store.</p> * * @param target The Invid to retrieve. */ public final DBObject lookupInvid(Invid target) { return this.lookupInvid(target, false); } /** * <p>This helper method is for use on the server, so that custom * code subclasses can call a simple method to look up an Invid and * get the appropriate DBObject, taking into account whether the * lookup is being done within a transaction or no.</p> * * <p>Note that unless the object has been checked out by the current session, * this method will return access to the object as it is stored directly * in the main datastore hashes. This means that the object will be * read-only and will grant all accesses, as it will have no notion of * what session or transaction owns it. If you need to have access to the * object's fields be protected, use {@link arlut.csd.ganymede.server.GanymedeSession GanymedeSession}'s * {@link arlut.csd.ganymede.server.GanymedeSession#view_db_object(arlut.csd.ganymede.common.Invid) * view_db_object()} method to get the object.</p> * * <p>This method will return null if the Invid provided does not * exist in the session or the persistent store.</p> * * @param target The Invid to retrieve. * @param forceOriginal If true and the lookup is being done in the * middle of an editing session, we'll return a reference to the * original read-only DBObject from the persistent datastore, rather * than the checked out DBEditObject version being edited in the * transaction. */ public final DBObject lookupInvid(Invid target, boolean forceOriginal) { return lookupInvid(target, forceOriginal, null); } /** * <p>This helper method is for use on the server, so that custom * code subclasses can call a simple method to look up an Invid and * get the appropriate DBObject, taking into account whether the * lookup is being done within a transaction or no.</p> * * <p>Note that unless the object has been checked out by the current session, * this method will return access to the object as it is stored directly * in the main datastore hashes. This means that the object will be * read-only and will grant all accesses, as it will have no notion of * what session or transaction owns it. If you need to have access to the * object's fields be protected, use {@link arlut.csd.ganymede.server.GanymedeSession GanymedeSession}'s * {@link arlut.csd.ganymede.server.GanymedeSession#view_db_object(arlut.csd.ganymede.common.Invid) * view_db_object()} method to get the object.</p> * * <p>This method will return null if the Invid provided does not * exist in the session or the persistent store.</p> * * @param target The Invid to retrieve. * @param forceOriginal If true and the lookup is being done in the * middle of an editing session, we'll return a reference to the * original read-only DBObject from the persistent datastore, rather * than the checked out DBEditObject version being edited in the * transaction. * @param session Should be set if the invid needs to be looked up * in a session context even when this DBObject has not been checked * out for viewing in a GanymedeSession. As in InvidDBField.emitInvidXML(). */ public final DBObject lookupInvid(Invid target, boolean forceOriginal, DBSession session) { if (target == null) { return null; } if (session == null) { if (this.gSession != null) { session = this.gSession.getDBSession(); } } if (session != null) { DBObject retObj = session.viewDBObject(target); if (retObj == null) { return null; } if (retObj instanceof DBEditObject && forceOriginal) { return ((DBEditObject) retObj).getOriginal(); } else { return retObj; } } else { // we're not being viewed in a session context.. go ahead and // look it up in the DBStore directly return Ganymede.db.getObject(target); } } /** * <p>This helper method is for use on the server, so that custom * code subclasses can call a simple method to look up an Invid and * get the appropriate label, taking into account whether the lookup * is being done within a transaction or no.</p> * * <p>This method will return null if the Invid provided does not * exist in the session or the persistent store.</p> * * @param target The Invid whose label we want to retrieve. */ public final String lookupInvidLabel(Invid target) { return lookupInvidLabel(target, false); } /** * <p>This helper method is for use on the server, so that custom * code subclasses can call a simple method to look up an Invid and * get the appropriate label, taking into account whether the lookup * is being done within a transaction or no.</p> * * <p>This method will return null if the Invid provided does not * exist in the session or the persistent store.</p> * * <p>This method returns the canonical label for the Invid, rather * than using the possibly overridden lookupLabel() method to get * the label.</p> * * @param target The Invid whose label we want to retrieve. * @param forceOriginal If true and the lookup is being done in the * middle of an editing session, we'll return the label of the * original read-only DBObject from the persistent datastore, rather * than the checked out DBEditObject version being edited in the * transaction. */ public final String lookupInvidLabel(Invid target, boolean forceOriginal) { if (target == null) { return null; } DBObject temp = lookupInvid(target, forceOriginal); if (temp == null) { return null; } return temp.getLabel(); } /** * <p>For an embedded object, returns the Invid of the parent object * which contains contains this embedded object.</p> * * <p>Otherwise, returns null.</p> */ public final Invid getParentInvid() { if (!this.isEmbedded()) { return null; } return (Invid) this.getFieldValueLocal(SchemaConstants.ContainerField); } /** * <p>For an embedded object, returns a reference to the object * which contains this embedded object.</p> * * <p>Otherwise, returns null.</p> */ public final DBObject getParentObj() { if (!this.isEmbedded()) { return null; } return lookupInvid(getParentInvid(), false); } /** * <p>Returns true if this object has a field named fieldName and if * that object has a defined (i.e., non-empty) value set.</p> */ public final boolean isDefined(String fieldName) { return this.isDefined(getField(fieldName)); } /** * <p>Returns true if this object has a field with a field id of * fieldID and if that object has a defined (i.e., non-empty) value * set.</p> */ public final boolean isDefined(short fieldID) { return this.isDefined(getField(fieldID)); } /** * <p>Returns true if the given field is defined.</p> */ public final boolean isDefined(DBField f) { return (f != null) && f.isDefined(); } /** * <p>This method is for use on the server, so that custom code can * call a simple method to test to see if a boolean field is defined * and has a true value.</p> * * <p>An exception will be thrown if the field is not a boolean.</p> */ public final boolean isSet(String fieldName) { return this.isSet(getField(fieldName)); } /** * <p>This method is for use on the server, so that custom code can * call a simple method to test to see if a boolean field is defined * and has a true value.</p> * * <p>An exception will be thrown if the field is not a boolean.</p> */ public final boolean isSet(short fieldID) { return this.isSet(getField(fieldID)); } /** * <p>This method is for use on the server, so that custom code can * call a simple method to test to see if a boolean field is defined * and has a true value.</p> * * <p>An exception will be thrown if the field is not a boolean.</p> */ private boolean isSet(DBField f) { if (f == null) { return false; } if (f.isVector()) { // "Can't call isSet on a vector field." throw new RuntimeException(ts.l("isSet.vector")); } Boolean bool = (Boolean) f.getValueLocal(); return (bool != null && bool.booleanValue()); } /** * <p>Shortcut method to set a field's value. Using this method * saves a roundtrip to the server, which is particularly useful in * database loading.</p> * * <p>The Vector returned by getFieldValues() is a cloned copy of * the vector held in the DBField.</p> * * <p>If no such Vector field is defined on this object type, an * IllegalArgumentException will be thrown. If the field is defined * on this object type but is undefined in this individual object, * an empty Vector, detached from the field's internal state will be * returned.</p> * * <p>Will never return null.</p> * * @see arlut.csd.ganymede.rmi.db_object */ public final Vector getFieldValues(String fieldName) throws GanyPermissionsException { DBField field = this.getField(fieldName); if (field == null) { // Okay, this field doesn't have a copy of the desired field. // Let's see if we can go ahead and return an empty // synthesized Vector to the caller. DBObjectBaseField fieldDef = this.getFieldDef(fieldName); if (fieldDef == null) { throw new RuntimeException("No field named " + fieldName + " defined."); } if (this.gSession != null) { PermEntry perm = this.gSession.getPermManager().getPerm(this, fieldDef.getID()); if (!perm.isVisible()) { // "Don''t have permission to read field {0} in object {1}" throw new GanyPermissionsException(ts.l("global.no_read_perms", fieldName, this.getLabel())); } } if (!fieldDef.isArray()) { // "Couldn't get vector values on scalar field {0}" throw new IllegalArgumentException(ts.l("getFieldValues.badtype", fieldName)); } return new Vector(); } if (!field.isVector()) { // "Couldn't get vector values on scalar field {0}" throw new IllegalArgumentException(ts.l("getFieldValues.badtype", fieldName)); } return field.getValues(); } /** * <p>Shortcut method to set a field's value. Using this * method saves a roundtrip to the server, which is * particularly useful in database loading.</p> * * <p>The Vector returned by getFieldValues() is a cloned copy of * the vector held in the DBField.</p> * * <p>If no such Vector field is defined on this object type, an * IllegalArgumentException will be thrown. If the field is defined * on this object type but is undefined in this individual object, * an empty Vector, detached from the field's internal state will be * returned.</p> * * <p>Will never return null.</p> * * @see arlut.csd.ganymede.rmi.db_object */ public final Vector getFieldValues(short fieldID) throws GanyPermissionsException { DBField field = this.getField(fieldID); String fieldName = null; if (field == null) { // Okay, this field doesn't have a copy of the desired field. // Let's see if we can go ahead and return a new, empty Vector // to the caller. DBObjectBaseField fieldDef = this.getFieldDef(fieldID); if (fieldDef == null) { throw new RuntimeException("No field numbered " + fieldID + " is defined."); } fieldName = fieldDef.getName(); if (this.gSession != null) { PermEntry perm = this.gSession.getPermManager().getPerm(this, fieldID); if (!perm.isVisible()) { // "Don''t have permission to read field {0} in object {1}" throw new GanyPermissionsException(ts.l("global.no_read_perms", fieldName, this.getLabel())); } } if (!fieldDef.isArray()) { // "Couldn't get vector values on scalar field {0}" throw new IllegalArgumentException(ts.l("getFieldValues.badtype", fieldName)); } return new Vector(); } fieldName = field.getName(); if (!field.isVector()) { // "Couldn't get vector values on scalar field {0}" throw new IllegalArgumentException(ts.l("getFieldValues.badtype", fieldName)); } return field.getValues(); } /** * <p>Shortcut method to set a field's value. This is a server-side * method, but it can be a quick way to get a vector of * elements.</p> * * <p>The Vector returned by getFieldValuesLocal() is a cloned copy * of the vector held in the DBField.</p> * * <p>If no such Vector field is defined on this object type, an * IllegalArgumentException will be thrown. If the field is defined * on this object type but is undefined in this individual object, * an empty Vector, detached from the field's internal state will be * returned.</p> * * <p>Will never return null.</p> */ public final Vector getFieldValuesLocal(String fieldName) { DBField field = this.getField(fieldName); if (field == null) { DBObjectBaseField fieldDef = this.getFieldDef(fieldName); if (fieldDef == null) { throw new RuntimeException("No field named " + fieldName + " defined."); } if (!fieldDef.isArray()) { // "Couldn't get vector values on scalar field {0}" throw new IllegalArgumentException(ts.l("getFieldValues.badtype", fieldName)); } return new Vector(); } if (!field.isVector()) { // "Couldn't get vector values on scalar field {0}" throw new IllegalArgumentException(ts.l("getFieldValues.badtype", fieldName)); } return field.getValuesLocal(); } /** * <p>Shortcut method to set a field's value. This * is a server-side method, but it can be a quick * way to get a vector of elements.</p> * * <p>The Vector returned by getFieldValuesLocal() is a cloned copy * of the vector held in the DBField.</p> * * <p>If no such Vector field is defined on this object type, an * IllegalArgumentException will be thrown. If the field is defined * on this object type but is undefined in this individual object, * an empty Vector, detached from the field's internal state will be * returned.</p> * * <p>Will never return null.</p> */ public final Vector getFieldValuesLocal(short fieldID) { DBField field = this.getField(fieldID); String fieldName = null; if (field == null) { DBObjectBaseField fieldDef = this.getFieldDef(fieldID); if (fieldDef == null) { throw new RuntimeException("No field numbered " + fieldID + " is defined."); } fieldName = fieldDef.getName(); if (!fieldDef.isArray()) { // "Couldn't get vector values on scalar field {0}" throw new IllegalArgumentException(ts.l("getFieldValues.badtype", fieldName)); } return new Vector(); } fieldName = field.getName(); if (!field.isVector()) { // "Couldn't get vector values on scalar field {0}" throw new IllegalArgumentException(ts.l("getFieldValues.badtype", fieldName)); } return field.getValuesLocal(); } /** * <p>Get a display-order sorted list of DBFields contained in this * object.</p> * * <p>This is a server-side only operation.. permissions are not * checked.</p> */ public final Vector<DBField> getFieldVector(boolean customOnly) { if (fieldAry == null) { throw new NullPointerException(ts.l("global.pseudostatic")); } Vector<DBField> results = new Vector<DBField>(); synchronized (fieldAry) { // use objectBase.getCustomFields so that we're in display // order for (DBObjectBaseField fieldDef: objectBase.getCustomFields()) { DBField field = getField(fieldDef.getID()); if (field != null) { results.add(field); } } if (!customOnly) { for (DBField field: fieldAry) { if (field != null && field.isBuiltIn()) { results.add(field); } } } } return results; } /** * <p>Shortcut method to test to see if a vector field contains a * given value.</p> * * <p>If no such Vector field is defined on this object type, an * IllegalArgumentException will be thrown. If such a Vector field * is defined on this object type but is not present in this * instance, false will be returned.</p> * * @see arlut.csd.ganymede.rmi.db_object */ public final boolean containsFieldValue(short fieldID, Object val) throws GanyPermissionsException { DBField field = this.getField(fieldID); String fieldName = null; if (field == null) { return false; } fieldName = field.getName(); if (!field.isVector()) { // "Couldn't get vector values on scalar field {0}" throw new IllegalArgumentException(ts.l("getFieldValues.badtype", fieldName)); } return field.containsElement(val); } /** * <p>Shortcut method to test to see if a vector field contains a * given value.</p> * * <p>If no such Vector field is defined on this object type, an * IllegalArgumentException will be thrown. If such a Vector field * is defined on this object type but is not present in this * instance, false will be returned.</p> */ public final boolean containsFieldValueLocal(short fieldID, Object val) { DBField field = this.getField(fieldID); String fieldName = null; if (field == null) { return false; } fieldName = field.getName(); if (!field.isVector()) { // "Couldn't get vector values on scalar field {0}" throw new IllegalArgumentException(ts.l("getFieldValues.badtype", fieldName)); } return field.containsElementLocal(val); } /** * <p>Shortcut method to retrieve a indexed value from a named * vector field in this object.</p> * * <p>Will throw IllegalArgumentException if called on a scalar * field.</p> * * <p>This method checks access permissions, and will throw * GanyPermissionsException on an access violation.</p> */ public final Object getFieldElement(String fieldName, int index) throws GanyPermissionsException { return getFieldElement(getField(fieldName), index); } /** * <p>Shortcut method to retrieve a indexed value from a vector * field in this object.</p> * * <p>Will throw IllegalArgumentException if called on a scalar field.</p> * * <p>This method checks access permissions, and will throw * GanyPermissionsException on an access violation.</p> */ public final Object getFieldElement(short fieldID, int index) throws GanyPermissionsException { return getFieldElement(getField(fieldID), index); } /** * <p>Shortcut method to retrieve a indexed value from a named * vector field in this object.</p> * * <p>Will throw IllegalArgumentException if called on a scalar field.</p> * * <p>This method checks access permissions, and will throw * GanyPermissionsException on an access violation.</p> */ public final Object getFieldElement(DBField f, int index) throws GanyPermissionsException { if (f == null) { return null; } return f.getElement(index); } /** * <p>Shortcut method to retrieve a indexed value from a named * vector field in this object.</p> * * <p>Will throw IllegalArgumentException if called on a scalar * field.</p> * * <p>This method does not check access permissions.</p> */ public final Object getFieldElementLocal(String fieldName, int index) { return getFieldElementLocal(getField(fieldName), index); } /** * <p>Shortcut method to retrieve a indexed value from a vector * field in this object.</p> * * <p>Will throw IllegalArgumentException if called on a scalar * field.</p> * * <p>This method does not check access permissions.</p> */ public final Object getFieldElementLocal(short fieldID, int index) { return getFieldElementLocal(getField(fieldID), index); } /** * <p>Shortcut method to retrieve a indexed value from a vector * field in this object.</p> * * <p>Will throw IllegalArgumentException if called on a scalar * field.</p> * * <p>This method does not check access permissions.</p> */ public final Object getFieldElementLocal(DBField f, int index) { if (f == null) { return null; } return f.getElementLocal(index); } /** * <p>This method is used to provide a hook to allow different * objects to generate different labels for a given object based on * their perspective. This is used to sort of hackishly simulate a * relational-type capability for the purposes of viewing * context-sensitive labels of objects that are linked from Invid * fields in this object.</p> * * <p>This method primarily affects the results returned by {@link * arlut.csd.ganymede.server.InvidDBField#encodedValues()}, but it * can also affect the results shown by {@link * arlut.csd.ganymede.server.DBQueryEngine#query(arlut.csd.ganymede.common.Query, * arlut.csd.ganymede.server.DBEditObject)} when the * perspectiveObject parameter is non-null.</p> * * <p>See the automounter map and NFS volume DBEditObject * subclasses for how this is to be used, if you have * them.</p> */ public String lookupLabel(DBObject object) { return object.getLabel(); // default } /** * <p>This method is a convenience for server-side code. If * this object is an embedded object, this method will * return the label of the containing object. If this * object is not embedded, or the containing object's * label cannot be determined, null will be returned.</p> */ public String getContainingLabel() { if (!isEmbedded()) { return null; } InvidDBField field = getInvidField(SchemaConstants.ContainerField); if (field == null) { return null; } return field.getValueString(); } /** * Server-side type casting field accessor for BooleanDBFields. * * @param fieldID The field id to retrieve. * * @return a BooleanDBField if field fieldID is present and of the * proper type, or null if it does not exist. * * @throw ClassCastException if the field doesn't have the * appropriate type. * * @see arlut.csd.ganymede.rmi.db_object */ public final BooleanDBField getBooleanField(short fieldID) { return (BooleanDBField) this.getField(fieldID); } /** * Server-side type casting field accessor for BooleanDBFields. * * @param fieldname The name of the field to retrieve. * * @return a BooleanDBField if field fieldname is present and of the * proper type, or null if it does not exist. * * @throw ClassCastException if the field doesn't have the * appropriate type. * * @see arlut.csd.ganymede.rmi.db_object */ public final BooleanDBField getBooleanField(String fieldname) { return (BooleanDBField) this.getField(fieldname); } /** * Server-side type casting field accessor for DateDBField * * @param fieldID The field id to retrieve. * * @return a DateDBField if field fieldID is present and of the * proper type, or null if it does not exist. * * @throw ClassCastException if the field doesn't have the * appropriate type. * * @see arlut.csd.ganymede.rmi.db_object */ public final DateDBField getDateField(short fieldID) { return (DateDBField) this.getField(fieldID); } /** * Server-side type casting field accessor for DateDBField * * @param fieldname The name of the field to retrieve. * * @return a DateDBField if field fieldname is present and of the * proper type, or null if it does not exist. * * @throw ClassCastException if the field doesn't have the * appropriate type. * * @see arlut.csd.ganymede.rmi.db_object */ public final DateDBField getDateField(String fieldname) { return (DateDBField) this.getField(fieldname); } /** * Server-side type casting field accessor for FieldOptionDBField * * @param fieldID The field id to retrieve. * * @return a FieldOptionDBField if field fieldID is present and of * the proper type, or null if it does not exist. * * @throw ClassCastException if the field doesn't have the * appropriate type. * * @see arlut.csd.ganymede.rmi.db_object */ public final FieldOptionDBField getFieldOptionsField(short fieldID) { return (FieldOptionDBField) this.getField(fieldID); } /** * Server-side type casting field accessor for FieldOptionDBField * * @param fieldname The name of the field to retrieve. * * @return a FieldOptionDBField if field fieldname is present and of * the proper type, or null if it does not exist. * * @throw ClassCastException if the field doesn't have the * appropriate type. * * @see arlut.csd.ganymede.rmi.db_object */ public final FieldOptionDBField getFieldOptionsField(String fieldname) { return (FieldOptionDBField) this.getField(fieldname); } /** * Server-side type casting field accessor for FloatDBField * * @param fieldID The field id to retrieve. * * @return a FloatDBField if field fieldID is present and of the * proper type, or null if it does not exist. * * @throw ClassCastException if the field doesn't have the * appropriate type. * * @see arlut.csd.ganymede.rmi.db_object */ public final FloatDBField getFloatField(short fieldID) { return (FloatDBField) this.getField(fieldID); } /** * Server-side type casting field accessor for FloatDBField * * @param fieldname The name of the field to retrieve. * * @return a FloatDBField if field fieldname is present and of the * proper type, or null if it does not exist. * * @throw ClassCastException if the field doesn't have the * appropriate type. * * @see arlut.csd.ganymede.rmi.db_object */ public final FloatDBField getFloatField(String fieldname) { return (FloatDBField) this.getField(fieldname); } /** * Server-side type casting field accessor for InvidDBField * * @param fieldID The field id to retrieve. * * @return an InvidDBField if field fieldID is present and of the * proper type, or null if it does not exist. * * @throw ClassCastException if the field doesn't have the * appropriate type. * * @see arlut.csd.ganymede.rmi.db_object */ public final InvidDBField getInvidField(short fieldID) { return (InvidDBField) this.getField(fieldID); } /** * Server-side type casting field accessor for InvidDBField * * @param fieldname The name of the field to retrieve. * * @return an InvidDBField if field fieldname is present and of the * proper type, or null if it does not exist. * * @throw ClassCastException if the field doesn't have the * appropriate type. * * @see arlut.csd.ganymede.rmi.db_object */ public final InvidDBField getInvidField(String fieldname) { return (InvidDBField) this.getField(fieldname); } /** * Server-side type casting field accessor for IPDBField * * @param fieldID The field id to retrieve. * * @return an IPDBField if field fieldID is present and of the * proper type, or null if it does not exist. * * @throw ClassCastException if the field doesn't have the * appropriate type. * * @see arlut.csd.ganymede.rmi.db_object */ public final IPDBField getIPField(short fieldID) { return (IPDBField) this.getField(fieldID); } /** * Server-side type casting field accessor for IPDBField * * @param fieldname The name of the field to retrieve * * @return an IPDBField if field fieldname is present and of the * proper type, or null if it does not exist. * * @throw ClassCastException if the field doesn't have the * appropriate type. * * @see arlut.csd.ganymede.rmi.db_object */ public final IPDBField getIPField(String fieldname) { return (IPDBField) this.getField(fieldname); } /** * Server-side type casting field accessor for NumericDBField * * @param fieldID The field id to retrieve. * * @return a NumericDBField if field fieldID is present and of the * proper type, or null if it does not exist. * * @throw ClassCastException if the field doesn't have the * appropriate type. * * @see arlut.csd.ganymede.rmi.db_object */ public final NumericDBField getNumericField(short fieldID) { return (NumericDBField) this.getField(fieldID); } /** * Server-side type casting field accessor for NumericDBField * * @param fieldname The name of the field to retrieve. * * @return a NumericDBField if field fieldname is present and of the * proper type, or null if it does not exist. * * @throw ClassCastException if the field doesn't have the * appropriate type. * * @see arlut.csd.ganymede.rmi.db_object */ public final NumericDBField getNumericField(String fieldname) { return (NumericDBField) this.getField(fieldname); } /** * Server-side type casting field accessor for PasswordDBField * * @param fieldID The field id to retrieve. * * @return a PasswordDBField if field fieldID is present and of the * proper type, or null if it does not exist. * * @throw ClassCastException if the field doesn't have the * appropriate type. * * @see arlut.csd.ganymede.rmi.db_object */ public final PasswordDBField getPassField(short fieldID) { return (PasswordDBField) this.getField(fieldID); } /** * Server-side type casting field accessor for PasswordDBField * * @param fieldname The name of the field to retrieve. * * @return a PasswordDBField if field fieldname is present and of * the proper type, or null if it does not exist. * * @throw ClassCastException if the field doesn't have the * appropriate type. * * @see arlut.csd.ganymede.rmi.db_object */ public final PasswordDBField getPassField(String fieldname) { return (PasswordDBField) this.getField(fieldname); } /** * Server-side type casting field accessor for * PermissionMatrixDBField * * @param fieldID The field id to retrieve. * * @return a PermissionMatrixDBField if field fieldID is present and * of the proper type, or null if it does not exist. * * @throw ClassCastException if the field doesn't have the * appropriate type. * * @see arlut.csd.ganymede.rmi.db_object */ public final PermissionMatrixDBField getPermField(short fieldID) { return (PermissionMatrixDBField) this.getField(fieldID); } /** * Server-side type casting field accessor for * PermissionMatrixDBField * * @param fieldname The name of the field to retrieve. * * @return a PermissionMatrixDBField if field fieldname is present * and of the proper type, or null if it does not exist. * * @throw ClassCastException if the field doesn't have the * appropriate type. * * @see arlut.csd.ganymede.rmi.db_object */ public final PermissionMatrixDBField getPermField(String fieldname) { return (PermissionMatrixDBField) this.getField(fieldname); } /** * Server-side type casting field accessor for StringDBField * * @param fieldID The field id to retrieve. * * @return a StringDBField if field fieldID is present and of the * proper type, or null if it does not exist. * * @throw ClassCastException if the field doesn't have the * appropriate type. * * @see arlut.csd.ganymede.rmi.db_object */ public final StringDBField getStringField(short fieldID) { return (StringDBField) this.getField(fieldID); } /** * Server-side type casting field accessor for StringDBField * * @param fieldname The name of the field to retrieve. * * @return a StringDBField if field fieldname is present and of the * proper type, or null if it does not exist. * * @throw ClassCastException if the field doesn't have the * appropriate type. * * @see arlut.csd.ganymede.rmi.db_object */ public final StringDBField getStringField(String fieldname) { return (StringDBField) this.getField(fieldname); } /** * <p>This method returns a Vector of Invids for objects that are * pointed to from this object by way of non-symmetric links. These * are Invids that may need to be marked as non-deletable if this * object is checked out by a DBEditSet.</p> */ public final Set<Invid> getASymmetricTargets() { if (fieldAry == null) { throw new NullPointerException(ts.l("global.pseudostatic")); } HashSet<Invid> results = new HashSet<Invid>(); synchronized (fieldAry) { for (DBField field: fieldAry) { if (field == null) { continue; } if (field instanceof InvidDBField) { InvidDBField invField = (InvidDBField) field; if (!invField.isDefined() || invField.getFieldDef().isSymmetric()) { continue; } if (invField.isVector()) { results.addAll((Vector<Invid>) invField.getValuesLocal()); } else { results.add((Invid) invField.getValueLocal()); } } } } return results; } /** * <p>This method returns a Vector of Invids that point to this * object via forward asymmetric link fields.</p> */ public final Vector<Invid> getBackLinks() { return new Vector(Ganymede.db.aSymLinkTracker.getForwardLinkSources(getDBSession(), getInvid())); } /** * <p>This method is called to register all asymmetric pointers in * this object with the DBStore's aSymLinkTracker hash * structure.</p> * * <p>Typically this will be done when an object is first loaded * from the database, at a time when the DBStore aSymLinkTracker * hash structure has no entries for this object at all.</p> * * <p>During the commit process of a normal transaction, the {@link * arlut.csd.ganymede.server.DBLinkTracker#commit(arlut.csd.ganymede.server.DBSession) * commit()} method in the {@link * arlut.csd.ganymede.server.DBLinkTracker DBLinkTracker} class handles * these updates instead.</p> */ final void registerAsymmetricLinks() { Ganymede.db.aSymLinkTracker.registerObject(null, getASymmetricTargets(), getInvid()); } /** * <p>This method is called to unregister all asymmetric pointers in * this object from the DBStore's aSymLinkTracker hash structure.</p> * * <p>Typically this will be done when an object is being deleted from * the database in response to a journal entry, or if the object is * being replaced with an updated version from the journal.</p> * * <p>During the commit process of a normal transaction, the {@link * arlut.csd.ganymede.server.DBEditSet DBEditSet} class handles * these updates instead.</p> */ final void unregisterAsymmetricLinks() { Ganymede.db.aSymLinkTracker.unregisterObject(null, getASymmetricTargets(), getInvid()); } /** * <p>Generate a complete printed representation of the object, * suitable for printing to a debug or log stream.</p> */ public final void print(PrintStream out) { out.print(getPrintString()); } /** * <p>Generate a complete printed representation of the object, * suitable for printing to a debug or log stream.</p> */ public final void print(PrintWriter out) { out.print(getPrintString()); } /** * <p>This server-side method returns a summary description of * this object, including a listing of all non-null fields and * their contents.</p> * * <p>This method calls * {@link arlut.csd.ganymede.server.DBObject#appendObjectInfo(java.lang.StringBuffer, * java.lang.String, boolean) appendObjectInfo} to do most of its work.</p> */ public String getPrintString() { StringBuffer result = new StringBuffer(); this.appendObjectInfo(result, null, true); return result.toString(); } /** * <p>This method is used to provide a summary description of * this object, including a listing of all non-null fields and * their contents. This method is remotely callable by the client, * and so will only reveal fields that the user has permission * to view. This method returns a StringBuffer to work around * problems with serializing large strings in early versions of the * JDK.</p> * * <p>This method calls * {@link arlut.csd.ganymede.server.DBObject#appendObjectInfo(java.lang.StringBuffer, * java.lang.String, boolean) appendObjectInfo} to do most of its work.</p> * * @see arlut.csd.ganymede.rmi.db_object */ public StringBuffer getSummaryDescription() { StringBuffer result = new StringBuffer(); if (this.gSession != null && !this.gSession.getPermManager().getPerm(this).isVisible()) { return result; } this.appendObjectInfo(result, null, false); return result; } /** * <p>This method is used to concatenate a textual description of this * object to the passed-in StringBuffer. This description is relatively * free-form, and is intended to be used for human consumption and not for * programmatic operations.</p> * * <p>This method is called by * {@link arlut.csd.ganymede.server.DBObject#getSummaryDescription() getSummaryDescription}.</p> * * @param buffer The StringBuffer to append this object's description to * @param prefix Used for recursive calls on embedded objects, this prefix will * be inserted at the beginning of each line of text concatenated to buffer * by this method. * @param local If false, read permissions will be checked for each field before * adding it to the buffer. */ private void appendObjectInfo(StringBuffer buffer, String prefix, boolean local) { for (DBObjectBaseField fieldDef: objectBase.getCustomFields()) { DBField field = getField(fieldDef.getID()); if (field != null && field.isDefined() && (local || field.isVisible())) { if (!field.isEditInPlace()) { if (prefix != null) { buffer.append(prefix); } buffer.append(field.getName()); buffer.append(" : "); buffer.append(field.getValueString()); buffer.append("\n"); } else { InvidDBField invField = (InvidDBField) field; for (int j = 0; j < invField.size(); j++) { if (prefix != null) { buffer.append(prefix); } buffer.append(field.getName()); buffer.append("["); buffer.append(j); buffer.append("]"); buffer.append("\n"); Invid x = invField.value(j); DBObject remObj = null; if (gSession != null) { // if this object has been checked out for // viewing by a session, we'll use // view_db_object() so that we don't // reveal fields that should not be seen. try { ReturnVal retVal = gSession.view_db_object(x); remObj = (DBObject) retVal.getObject(); } catch (NotLoggedInException ex) { } } if (remObj == null) { // we use DBStore's static viewDBObject // method so that we can call this even // before the GanymedeServer object is // initialized remObj = DBStore.viewDBObject(x); } if (remObj instanceof DBEditObject) { DBEditObject eO = (DBEditObject) remObj; if (eO.getStatus() == ObjectStatus.DELETING) { remObj = eO.getOriginal(); } } if (remObj != null) { if (prefix != null) { remObj.appendObjectInfo(buffer, prefix + "\t", local); } else { remObj.appendObjectInfo(buffer, "\t", local); } } else { // remObj shouldn't be null during normal // operations, but it might be if we're doing // debug logging during loading, or something. if (prefix != null) { buffer.append(prefix + "\t" + x); } else { buffer.append("\t" + x); } } } } } } if (fieldAry == null) { throw new NullPointerException(ts.l("global.pseudostatic")); } synchronized (fieldAry) { // okay, got all the custom fields.. now we need to summarize all the // built-in fields that were not listed in customFields. for (DBField field: fieldAry) { if (field == null || !field.isBuiltIn() || !field.isDefined()) { continue; } if (local || field.isVisible()) { if (prefix != null) { buffer.append(prefix); } buffer.append(field.getName()); buffer.append(" : "); buffer.append(field.getValueString()); buffer.append("\n"); } } } } /************************************************************************** * * 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 DBField. 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: * MyDBObject.get("field_x") will return the DBField with the label * of "field_x". */ /** * Part of the JythonMap interface. */ public boolean has_key(Object key) { return (getField((String) key) != null); } /** * Part of the JythonMap interface. */ public List items() { List<Object[]> list = new ArrayList<Object[]>(); Object[] tuple; for (DBField field: getFieldVect()) { tuple = new Object[2]; tuple[0] = field.getName(); tuple[1] = field; list.add(tuple); } return list; } /** * Part of the JythonMap interface. */ public Set keys() { Set<String> keys = new HashSet<String>(); for (DBField field: getFieldVect()) { keys.add(field.getName()); } return keys; } /** * Part of the JythonMap interface. */ public boolean containsKey(Object key) { return has_key(key); } /** * <p>This method only returns true if a DBField is passed in which * is contained in this object, by object identity.</p> * * <p>Part of the JythonMap interface.</p> */ public boolean containsValue(Object value) { return getFieldVect().contains(value); } /** * <p>Part of the JythonMap interface.</p> */ public Set entrySet() { Set<Entry> entrySet = new HashSet<Entry>(); for (DBField field: getFieldVect()) { entrySet.add(new Entry(field)); } return entrySet; } /** * <p>Part of the JythonMap interface.</p> */ public Object get(Object key) { if (key instanceof PyInteger) { PyInteger pi = (PyInteger) key; return getField(Integer.valueOf(pi.getValue()).shortValue()); } else if (key instanceof Integer) { return getField(((Integer) key).shortValue()); } else if (key instanceof Short) { return getField(((Short) key).shortValue()); } else if (key instanceof String) { return getField((String) key); } return null; } /** * <p>Part of the JythonMap interface.</p> */ public boolean isEmpty() { return getFieldVect().isEmpty(); } /** * <p>Part of the JythonMap interface.</p> */ public Set keySet() { return keys(); } /** * <p>Part of the JythonMap interface.</p> */ public int size() { return getFieldVect().size(); } /** * <p>Part of the JythonMap interface.</p> */ public Collection values() { return getFieldVect(); } /** * <p>This is an embedded inner class within the * arlut.csd.ganymede.server.DBObject class. It is used in the * context of the Jython/Map support that Deepak added to * DBObject.</p> * * <p>Part of the JythonMap interface.</p> */ static class Entry implements Map.Entry { Object key, value; public Entry( DBField obj ) { key = obj.getName(); value = obj; } public Object getKey() { return key; } public Object getValue() { return value; } public Object setValue(Object value) { return null; } } /* * These methods are are no-ops since we don't want this object * messed with via the Map interface. */ /** * <p>Part of the JythonMap interface.</p> */ public void clear() { throw new UnsupportedOperationException(); } /** * <p>Part of the JythonMap interface.</p> */ public Object put(Object key, Object value) { throw new UnsupportedOperationException(); } /** * <p>Part of the JythonMap interface.</p> */ public void putAll(Map t) { throw new UnsupportedOperationException(); } /** * <p>Part of the JythonMap interface.</p> */ public Object remove(Object key) { throw new UnsupportedOperationException(); } }