/* GASH 2 DBField.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.rmi.Remote; import java.util.Date; import java.util.Vector; import arlut.csd.JDialog.JDialogBuff; import arlut.csd.Util.TranslationService; import arlut.csd.Util.VectorUtils; import arlut.csd.Util.WordWrap; import arlut.csd.ganymede.common.FieldInfo; import arlut.csd.ganymede.common.FieldTemplate; import arlut.csd.ganymede.common.FieldType; import arlut.csd.ganymede.common.GanyPermissionsException; import arlut.csd.ganymede.common.Invid; import arlut.csd.ganymede.common.NotLoggedInException; import arlut.csd.ganymede.common.PermEntry; import arlut.csd.ganymede.common.ReturnVal; import arlut.csd.ganymede.rmi.db_field; /*------------------------------------------------------------------------------ abstract class DBField ------------------------------------------------------------------------------*/ /** * <p>This abstract base class encapsulates the basic logic for fields in the * Ganymede {@link arlut.csd.ganymede.server.DBStore DBStore}, * including permissions and unique value handling.</p> * * <p>DBFields are the actual carriers of field value in the Ganymede * server. Each {@link arlut.csd.ganymede.server.DBObject DBObject} holds a * set of DBFields in an array. Each DBField is associated with a {@link * arlut.csd.ganymede.server.DBObjectBaseField DBObjectBaseField} field * definition (see {@link arlut.csd.ganymede.server.DBField#getFieldDef() * getFieldDef()}) by way of its owner's type and it's own field code, * which defines the type of the field as well as various generic and * type-specific attributes for the field. The DBObjectBaseField * information is created and edited with the Ganymede schema * editor.</p> * * <p>DBField is an abstract class. There is a different subclass of DBField * for each kind of data that can be held in the Ganymede server, as follows:</p> * * <ul> * <li>{@link arlut.csd.ganymede.server.StringDBField StringDBField}</li> * <li>{@link arlut.csd.ganymede.server.BooleanDBField BooleanDBField}</li> * <li>{@link arlut.csd.ganymede.server.NumericDBField NumericDBField}</li> * <li>{@link arlut.csd.ganymede.server.FieldOptionDBField FieldOptionDBField}</li> * <li>{@link arlut.csd.ganymede.server.FloatDBField FloatDBField}</li> * <li>{@link arlut.csd.ganymede.server.DateDBField DateDBField}</li> * <li>{@link arlut.csd.ganymede.server.InvidDBField InvidDBField}</li> * <li>{@link arlut.csd.ganymede.server.IPDBField IPDBField}</li> * <li>{@link arlut.csd.ganymede.server.PasswordDBField PasswordDBField}</li> * <li>{@link arlut.csd.ganymede.server.PermissionMatrixDBField PermissionMatrixDBField}</li> * </ul> * * <p>Each DBField subclass is responsible for writing itself to disk * on command with the {@link * arlut.csd.ganymede.server.DBField#emit(java.io.DataOutput) emit()} * method, and reading its state in with the {@link * arlut.csd.ganymede.server.DBField#receive(java.io.DataInput, * arlut.csd.ganymede.server.DBObjectBaseField) receive()} method. * Each DBField subclass may also have extensive special logic to * handle special operations on fields of the appropriate type. For * instance, the InvidDBField class has lots and lots of logic for * handling the bi-directional object linking that the server depends * on for its object handling. Mostly the DBField subclasses provide * customization that modifies how things like {@link * arlut.csd.ganymede.server.DBField#setValue(java.lang.Object) * setValue()} and {@link arlut.csd.ganymede.server.DBField#getValue() * getValue()} work, but FieldOptionDBField, PasswordDBField and * PermissionMatrixDBField don't fit with the standard generic * value-container model, and contain their own methods for * manipulating and accessing data held in the Ganymede database. Most * DBField subclasses only allow a single value to be held, but * StringDBField, InvidDBField, and IPDBField support vectors of * values.</p> * * <p>The Ganymede client can directly access fields in RMI-published * objects using the {@link arlut.csd.ganymede.rmi.db_field db_field} RMI * interface. Each concrete subclass of DBField has its own special * RMI interface which provides special methods for the client. * Adding a new data type to the Ganymede server will involve creating * a new DBField subclass, as well as a new RMI interface for any * special field methods. All client code would also need to be * modified to be aware of the new field type. DBObjectBaseField, * DBEditObject and DBObject would also need to be modified to be * aware of the new field type for schema editing, customization, and object loading. * The schema editor would have to be modified as well.</p> * * <p>But you can do it if you absolutely have to. Just be careful and take a good * look around at the code.</p> * * <p>Note that while DBField was designed to be subclassed, it should only be * necessary for adding a new data type to the server. All other likely * customizations you'd want to do are handled by * {@link arlut.csd.ganymede.server.DBEditObject DBEditObject} customization methods. Most * DBField methods at some point call methods on the DBObject/DBEditObject * that contains it. All methods that cause changes to fields call out to * finalizeXYZ() and/or wizardHook() methods in DBEditObject. Consult the * DBEditObject customization guide for details on the field/object interactions.</p> * * <p>An important note about synchronization: it is possible to encounter a * condition called a <b>nested monitor deadlock</b>, where a synchronized * method on a field can block trying to enter a synchronized method on * a {@link arlut.csd.ganymede.server.DBSession DBSession}, * {@link arlut.csd.ganymede.server.GanymedeSession GanymedeSession}, or * {@link arlut.csd.ganymede.server.DBEditObject DBEditObject} object that is itself blocked * on another thread trying to call a synchronized method on the same field.</p> * * <p>To avoid this condition, no field methods that call synchronized methods on * other objects should themselves be synchronized in any fashion.</p> */ public abstract class DBField implements Remote, db_field, FieldType, Comparable { /** * TranslationService object for handling string localization in * the Ganymede server. */ static final TranslationService ts = TranslationService.getTranslationService("arlut.csd.ganymede.server.DBField"); /** * Counter field we use to display loading statistics at server start time. */ static public int fieldCount = 0; /** * <p>This method acts as a factory class to create a typed DBField * subclass and attach it to a DBObject.</p> * * <p>Used by the DBEditObject's create object and check-out constructors.</p> */ static DBField createTypedField(DBObject object, DBObjectBaseField fieldDef) { switch (fieldDef.getType()) { case BOOLEAN: return new BooleanDBField(object, fieldDef); case NUMERIC: return new NumericDBField(object, fieldDef); case FLOAT: return new FloatDBField(object, fieldDef); case FIELDOPTIONS: return new FieldOptionDBField(object, fieldDef); case DATE: return new DateDBField(object, fieldDef); case STRING: return new StringDBField(object, fieldDef); case INVID: return new InvidDBField(object, fieldDef); case PERMISSIONMATRIX: return new PermissionMatrixDBField(object, fieldDef); case PASSWORD: return new PasswordDBField(object, fieldDef); case IP: return new IPDBField(object, fieldDef); default: throw new IllegalArgumentException("Bad field def type in DBField.createTypedField:" + fieldDef.getType()); } } /** * <p>This method acts as a factory class to copy a DBField subclass * and attach it to a DBObject, using the appropriate DBField * subclass' copy constructor.</p> * * <p>Used by the DBEditObject's check-out and check-in constructor, * but not by object cloning, which is creating a new object whose * fields are being copied from the old object as if a user was * doing it manually. This copyField method, by contrast, will only * do a dumb data copy, and will not fix up and InvidDBField * bindings, etc.</p> * * <p>Note that it is essential that this method never throw an * uncaught exception, because that will break commits in a very * ugly way.</p> */ static DBField copyField(DBObject object, DBField orig) { switch (orig.getType()) { case BOOLEAN: return new BooleanDBField(object, (BooleanDBField) orig); case NUMERIC: return new NumericDBField(object, (NumericDBField) orig); case FLOAT: return new FloatDBField(object, (FloatDBField) orig); case FIELDOPTIONS: return new FieldOptionDBField(object, (FieldOptionDBField) orig); case DATE: return new DateDBField(object, (DateDBField) orig); case STRING: return new StringDBField(object, (StringDBField) orig); case INVID: return new InvidDBField(object, (InvidDBField) orig); case PERMISSIONMATRIX: return new PermissionMatrixDBField(object, (PermissionMatrixDBField) orig); case PASSWORD: return new PasswordDBField(object, (PasswordDBField) orig); case IP: return new IPDBField(object, (IPDBField) orig); default: throw new IllegalArgumentException("Bad field def type in DBField.copyField:" + orig.getType()); } } /** * <p>This method is used to handle creating new objects from the * ganymede.db input stream when we are loading the database from * disk.</p> * * <p>Again, effectively used to map type constants to classes.</p> */ static DBField readField(DBObject object, DataInput in, DBObjectBaseField definition) throws IOException { DBField.fieldCount++; switch (definition.getType()) { case BOOLEAN: return new BooleanDBField(object, in, definition); case NUMERIC: return new NumericDBField(object, in, definition); case FLOAT: return new FloatDBField(object, in, definition); case FIELDOPTIONS: return new FieldOptionDBField(object, in, definition); case DATE: return new DateDBField(object, in, definition); case STRING: return new StringDBField(object, in, definition); case INVID: return new InvidDBField(object, in, definition); case PERMISSIONMATRIX: return new PermissionMatrixDBField(object, in, definition); case PASSWORD: return new PasswordDBField(object, in, definition); case IP: return new IPDBField(object, in, definition); default: throw new IllegalArgumentException("Bad field def type in DBField.readField:" + definition.getType()); } } /** * <p>Returns true if both field1 and field2 are non-null and belong * to the same Invid and share the same field id.</p> */ public static boolean matches(DBField field1, DBField field2) { if (field1 == null || field2 == null) { return false; } return field1.matches(field2); } // --- /** * <p>The object's current value. May be a Vector for vector * fields, in which case getVectVal() may be used to perform the * cast.</p> * * <p>package private</p> */ Object value = null; /** * The object this field is contained within. */ final DBObject owner; /** * The identifying field number for this field within the owning * object. This number is an index into the owning object type's * field dictionary. */ final short fieldcode; /* -- */ public DBField(DBObject owner, short fieldcode) { this.owner = owner; this.fieldcode = fieldcode; } /** * <p>This method is used to return a copy of this field, with the * field's owner set to newOwner.</p> */ public final DBField getCopy(DBObject newOwner) { return DBField.copyField(newOwner, this); } /** * <p>Returns the DBObject that this field is contained within.</p> * * <p>This method is duplicative of getOwner(), but we are keeping * both around for backwards compatibility.</p> */ public final DBObject getObject() { return owner; } /** * <p>This method is designed to handle casting this field's value into * a vector as needed. We don't bother to check whether value is a Vector * here, as the code which would have used the old values field should * do that for us themselves.</p> * * <p>This method does no permissions checking at all, and should only * be used from within DBField and subclass code. For other purposes, * use getValuesLocal().</p> * * <p>This method should always return a valid Vector if this field * is truly a vector field, as we don't keep empty vector fields in * non-editable objects, and if this is an editable object we'll * have created a vector when this field was initialized for * editing.</p> * * <p>Note that this method gives direct access to the value vector. * Any modifications made to the returned vector will affect the * value held in this field.</p> */ protected final Vector getVectVal() { return (Vector) value; } /** * <p>This method implements the Comparable interface.</p> * * <p>We are comparable in terms of the field id number for this field.</p> * * <p>The o parameter can be a Short, a short (using Java 5 * autoboxing), or another DBField.</p> */ public int compareTo(Object o) { if (o instanceof Number) { return fieldcode - ((Number) o).shortValue(); } else { DBField otherField = (DBField) o; return fieldcode - otherField.fieldcode; } } /** * <p>Object value of DBField. Used to represent value in value * hashes. Subclasses need to override this method in subclass.</p> */ public Object key() { if (isVector()) { throw new IllegalArgumentException(ts.l("global.oops_vector", getName(), owner.getLabel())); } return value; } /** * <p>Object value of a vector DBField. Used to represent value in * value hashes. Subclasses need to override this method in * subclass.</p> */ public Object key(int index) { if (!isVector()) { // "Vector method called on a scalar field: {0} in object {1}" throw new IllegalArgumentException(ts.l("global.oops_scalar", getName(), owner.getLabel())); } return getVectVal().get(index); } /** * <p>Returns number of elements in vector if this is a vector * field. If this is not a vector field, will return 1. (Should * throw exception?)</p> */ public int size() { if (!isVector()) { return 1; } else { return getVectVal().size(); } } /** * <p>Returns the maximum length of an array in this field type</p> */ public int getMaxArraySize() { if (!isVector()) { return 1; // should throw exception? } else { return getFieldDef().getMaxArraySize(); } } /** * <p>This method is responsible for writing out the contents of * this field to an binary output stream. It is used in writing * fields to the ganymede.db file and to the journal file.</p> * * <p>This method only writes out the value contents of this field. * The {@link arlut.csd.ganymede.server.DBObject DBObject} * {@link arlut.csd.ganymede.server.DBObject#emit(java.io.DataOutput) emit()} * method is responsible for writing out the field identifier information * ahead of the field's contents.</p> */ abstract void emit(DataOutput out) throws IOException; /** * <p>This method is responsible for reading in the contents of * this field from an binary input stream. It is used in reading * fields from the ganymede.db file and from the journal file.</p> * * <p>The code that calls receive() on this field is responsible for * having read enough of the binary input stream's context to * place the read cursor at the point in the file immediately after * the field's id and type information has been read.</p> */ abstract void receive(DataInput in, DBObjectBaseField definition) throws IOException; /** * This method is used when the database is being dumped, to write * out this field to disk. */ abstract void emitXML(XMLDumpContext dump) throws IOException; /** * <p>We don't expect these fields to ever be stored in a hash.</p> * * @throws UnsupportedOperationException */ public int hashCode() { throw new UnsupportedOperationException(); } /** * <p>Returns true if obj is a field with the same value(s) as * this one.</p> * * <p>This method is ok to be synchronized because it does not call * synchronized methods on any other object that is likely to have * another thread trying to call another synchronized method on * us.</p> */ public synchronized boolean equals(Object obj) { if (obj == null) { return false; } if (!(obj.getClass().equals(this.getClass()))) { return false; } DBField f = (DBField) obj; if (!isVector()) { return f.key().equals(this.key()); } else { if (f.size() != this.size()) { return false; } for (int i = 0; i < size(); i++) { if (!f.key(i).equals(this.key(i))) { return false; } } return true; } } /** * <p>Returns true if otherField has the same invid and field id as * this field.</p> */ public boolean matches(DBField otherField) { return otherField != null && otherField.getOwner().getInvid().equals(getOwner().getInvid()) && otherField.getID() == getID(); } /** * <p>This method copies the current value of this DBField to * target. The target DBField must be contained within a * checked-out DBEditObject in order to be updated. Any actions * that would normally occur from a user manually setting a value * into the field will occur.</p> * * <p>This includes most particularly the InvidDBField bind * logic.</p> * * @param target The DBField to copy this field's contents to. * @param local If true, permissions checking is skipped. * * @return A ReturnVal indicating success or failure. May be simply * 'null' to indicate success if no feedback need be provided. */ public synchronized ReturnVal copyFieldTo(DBField target, boolean local) { if (!local) { if (!verifyReadPermission()) { // "Copy field error" // "Can''t copy from field {0} in object {1}, due to a lack of read privileges." return Ganymede.createErrorDialog(this.getGSession(), ts.l("copyFieldTo.copy_error_sub"), ts.l("copyFieldTo.no_read", getName(), owner.getLabel())); } } if (!target.isEditable(local)) { // "Copy field error" // "Can''t copy to field {0} in object {1}, due to a lack of write privileges." return Ganymede.createErrorDialog(this.getGSession(), ts.l("copyFieldTo.copy_error_sub"), ts.l("copyFieldTo.no_write", target.getName(), target.owner.getLabel())); } if (!isVector()) { return target.setValueLocal(getValueLocal(), true); // inhibit wizards.. } else { Vector valuesToCopy = getVectVal(); if (valuesToCopy.size() == 0) { return null; } // We want to inhibit wizards and allow partial failure. // // We'll use addElementsLocal() here because we've already // verified read permission and write permission, above. // This could fail if we don't have write privileges for the // target field, so we'll return an error code back to the // cloneFromObject() method, which will pass it in an over-all // advisory (non-fatal) warning back to the client return target.addElementsLocal(valuesToCopy, true, true); } } /** * <p>This method is intended to run a consistency check on the * contents of this field against the constraints specified in the * {@link arlut.csd.ganymede.server.DBObjectBaseField} controlling * this field.</p> * * <p>Returns a {@link arlut.csd.ganymede.common.ReturnVal} * describing the error if the field's contents does not meet its * constraints, or null if the field is in compliance with its * constraints.</p> */ public ReturnVal validateContents() { if (!isVector()) { return this.verifyBasicConstraints(this.value); } else { if (isVector()) { Vector values = getVectVal(); synchronized (values) { for (Object item: values) { ReturnVal retVal = this.verifyBasicConstraints(item); if (!ReturnVal.didSucceed(retVal)) { return retVal; } } } if (size() > getMaxArraySize()) { // "Field {0} in object {1} contains more elements ({2,number,#}) than is allowed ({3,number,#})." return Ganymede.createErrorDialog(this.getGSession(), null, ts.l("validateContents.too_big_array", this.getName(), owner.getLabel(), Integer.valueOf(size()), Integer.valueOf(getMaxArraySize()))); } } } return null; } /** * <p>This method is intended to be called when this field is being * checked into the database. Subclasses of DBField will override * this method to clean up data that is cached for speed during * editing.</p> */ public void cleanup() { } /** * <p>Returns the DBSession that this field is associated with or null * if it is being viewed from the persistent store.</p> * * @deprecated Use {@link #getDBSession()} instead. */ @Deprecated public final DBSession getSession() { return this.getDBSession(); } /** * <p>Returns the DBSession that this field is associated with or null * if it is being viewed from the persistent store.</p> */ public final DBSession getDBSession() { try { return owner.getDBSession(); } catch (NullPointerException ex) { return null; } } /** * Returns the GanymedeSession that this field is associated with, * or null if it is being viewed from a naked DBSession or directly * from the persistent store. */ public final GanymedeSession getGSession() { try { return getDBSession().getGSession(); } catch (NullPointerException ex) { return null; } } // **** // // db_field methods // // **** /** * <p>Returns a handy field description packet for this field, * containing the static field elements for this field..</p> * * @see arlut.csd.ganymede.rmi.db_field */ public final FieldTemplate getFieldTemplate() { return getFieldDef().getTemplate(); } /** * <p>Returns a handy field description packet for this field.</p> * * @throws arlut.csd.ganymede.common.GanyPermissionsException If * permission is denied to read from this field. * * @see arlut.csd.ganymede.rmi.db_field */ public final FieldInfo getFieldInfo() throws GanyPermissionsException { return new FieldInfo(this); } /** * <p>Returns the schema name for this field.</p> * * @see arlut.csd.ganymede.rmi.db_field */ public final String getName() { return getFieldDef().getName(); } /** * <p>Returns the name for this field, encoded in a form suitable * for use as an XML element name.</p> */ public final String getXMLName() { return arlut.csd.Util.XMLUtils.XMLEncode(getFieldDef().getName()); } /** * <p>Returns the field # for this field.</p> * * @see arlut.csd.ganymede.rmi.db_field */ public final short getID() { return fieldcode; } /** * <p>Returns the object this field is part of.</p> * * <p>This method is duplicative of getObject(), but we are keeping * both around for backwards compatibility.</p> */ public final DBObject getOwner() { return owner; } /** * <p>Returns the description of this field from the schema.</p> * * @see arlut.csd.ganymede.rmi.db_field */ public final String getComment() { return getFieldDef().getComment(); } /** * <p>Returns the description of this field's type from the * schema.</p> * * @see arlut.csd.ganymede.rmi.db_field */ public final String getTypeDesc() { return getFieldDef().getTypeDesc(); } /** * <p>Returns the type code for this field from the schema as * defined in {@link arlut.csd.ganymede.common.FieldType}.</p> * * @see arlut.csd.ganymede.rmi.db_field */ public final short getType() { return getFieldDef().getType(); } /** * <p>This method returns a text encoded value for this DBField * without checking permissions.</p> * * <p>This method avoids checking permissions because it is used on * the server side only and because it is involved in the {@link * arlut.csd.ganymede.server.DBObject#getLabel() getLabel()} logic * for {@link arlut.csd.ganymede.server.DBObject DBObject}.</p> * * <p>If this method checked permissions and the getPerm() method * failed for some reason and tried to report the failure using * object.getLabel(), as it does at present, the server could get * into an infinite loop.</p> */ abstract public String getValueString(); /** * <p>Returns a String representing a reversible encoding of the * value of this field. Each field type will have its own encoding, * suitable for embedding in a {@link * arlut.csd.ganymede.common.DumpResult DumpResult}.</p> */ abstract public String getEncodingString(); /** * <p>Returns a String representing the change in value between this * field and orig. This String is intended for logging and email, * not for any sort of programmatic activity. The format of the * generated string is not defined, but is intended to be suitable * for inclusion in a log entry and in an email message.</p> * * <p>If there is no change in the field, null will be returned.</p> */ abstract public String getDiffString(DBField orig); /** * <p>This method returns true if this field is owned by an editable * object and its contents differ from the same field in the * DBEditObject's original object. If this field belongs to a newly * created DBEditObject, hasChanged() will always return true.</p> */ public boolean hasChanged() { if (!(getOwner() instanceof DBEditObject)) { return false; } DBObject orig = ((DBEditObject) getOwner()).getOriginal(); if (orig == null) { return true; } return hasChanged(orig.getField(getID())); } /** * <p>This method returns true if this field differs from the orig. * It is intended to do a quick before/after comparison when we are * handling a transaction commit.</p> */ public boolean hasChanged(DBField orig) { if (orig == null) { return true; } if (!(orig.getClass().equals(this.getClass()))) { throw new IllegalArgumentException("bad field comparison"); } return (!this.equals(orig)); } /** * <p>Returns true if this field has a value associated with it, or * false if it is an unfilled 'placeholder'</p>. * * @see arlut.csd.ganymede.rmi.db_field */ public synchronized boolean isDefined() { if (isVector()) { Vector values = getVectVal(); if (values.size() > 0) { return true; } else { return false; } } else { if (value != null) { return true; } else { return false; } } } /** * <p>This method is used to mark a field as undefined when it is * checked out for editing. Different subclasses of {@link * arlut.csd.ganymede.server.DBField DBField} may implement this in * different ways, if simply setting the field's value member to * null is not appropriate. Any namespace values claimed by the * field will be released, and when the transaction is committed, * this field will be released.</p> * * <p>Note that this method is really only intended for those fields * which have some significant internal structure to them, such as * permission matrix, field option matrix, and password fields.</p> * * <p>NOTE: There is, at present, no defined DBEditObject callback * method that tracks generic field nullification. This means that * if your code uses setUndefined on a PermissionMatrixDBField, * FieldOptionDBField, or PasswordDBField, the plugin code is not * currently given the opportunity to review and refuse that * operation. Caveat Coder.</p> * * @return A ReturnVal indicating success or failure. May * be simply 'null' to indicate success if no feedback need * be provided. * * @throws arlut.csd.ganymede.common.GanyPermissionsException If * local is false and permission is denied to write to this field. */ public synchronized ReturnVal setUndefined(boolean local) throws GanyPermissionsException { if (isVector()) { if (!isEditable(local)) // *sync* GanymedeSession possible { // "DBField.setUndefined(): couldn''t clear vector elements from field {0} in object {1}, due to a lack of write permissions." throw new GanyPermissionsException(ts.l("setUndefined.no_perm_vect", getName(), owner.getLabel())); } // we have to clone our values Vector in order to use // deleteElements(). Vector currentValues = new Vector(getVectVal()); if (currentValues.size() != 0) { return deleteElementsLocal(currentValues); } else { return null; // success } } else { return setValue(null, local, false); } } /** * <p>Returns true if this field is a vector, false otherwise.</p> * * @see arlut.csd.ganymede.rmi.db_field */ public final boolean isVector() { return getFieldDef().isArray(); } /** * <p>Returns true if this field is editable, false * otherwise.</p> * * <p>Note that DBField are only editable if they are * contained in a subclass of * {@link arlut.csd.ganymede.server.DBEditObject DBEditObject}.</p> * * @see arlut.csd.ganymede.rmi.db_field */ public final boolean isEditable() { return isEditable(false); } /** * <p>Returns true if this field is editable, false otherwise.</p> * * <p>Note that DBField are only editable if they are contained in a * subclass of {@link arlut.csd.ganymede.server.DBEditObject * DBEditObject}.</p> * * <p>Server-side method only</p> * * <p><b>*Deadlock Hazard.*</b></p> * * @param local If true, skip permissions checking */ public final boolean isEditable(boolean local) { DBEditObject eObj; /* -- */ if (!(owner instanceof DBEditObject)) { return false; } eObj = (DBEditObject) owner; // if our owner has already started the commit process, we can't // allow any changes, local access or no if (eObj.isCommitting()) { return false; } if (!local && !verifyWritePermission()) // *sync* possible on GanymedeSession { return false; } return true; } /** * <p>This method returns true if this field is one of the system * fields present in all objects.</p> */ public final boolean isBuiltIn() { return getFieldDef().isBuiltIn(); } /** * <p>Returns true if this field should be displayed in the current * client context.</p> * * @see arlut.csd.ganymede.rmi.db_field */ public final boolean isVisible() { return verifyReadPermission() && owner.getHook().canSeeField(null, this); } /** * <p>Returns true if this field is edit in place.</p> */ public final boolean isEditInPlace() { return getFieldDef().isEditInPlace(); } /** * <p>Returns the object type id for the object containing this * field.</p> */ public final int getObjTypeID() { return getFieldDef().base().getTypeID(); } /** * <p>Returns the DBNameSpace that this field is associated with, or * null if no NameSpace field is associated with this field.</p> */ public final DBNameSpace getNameSpace() { return getFieldDef().getNameSpace(); } /** * <p>Returns the DBObjectBaseField for this field.</p> */ public final DBObjectBaseField getFieldDef() { return owner.getFieldDef(fieldcode); } /** * <p>This version of getFieldDef() is intended for use by code * sections that need to interrogate a field's type definition * before it is linked to an owner object.</p> */ public final DBObjectBaseField getFieldDef(short objectType) { DBObjectBase base = Ganymede.db.getObjectBase(objectType); if (base == null) { return null; } return base.getField(fieldcode); } /** * <p>This version of getFieldDef() is intended for use by code * sections that need to interrogate a field's type definition * before it is linked to an owner object.</p> */ public final DBObjectBaseField getFieldDef(DBObjectBase base) { if (base == null) { return null; } return base.getField(fieldcode); } /** * <p>Returns the value of this field, if a scalar. An * IllegalArgumentException will be thrown if this field is a * vector.</p> * * <p>This method will throw a GanyPermissionsException if this * DBObject is being viewed by a GanymedeSession, and that * GanymedeSession lacks appropriate permission to see the * value.</p> * * @throws arlut.csd.ganymede.common.GanyPermissionsException If * permission is denied to read from this field. * * @see arlut.csd.ganymede.rmi.db_field */ public synchronized Object getValue() throws GanyPermissionsException { if (!verifyReadPermission()) { // "Don''t have permission to read field {0} in object {1}" throw new GanyPermissionsException(ts.l("global.no_read_perms", getName(), owner.getLabel())); } if (isVector()) { // "Scalar method called on a vector field: {0} in object {1}" throw new IllegalArgumentException(ts.l("global.oops_vector", getName(), owner.getLabel())); } return value; } /** * <p>Sets the value of this field, if a scalar.</p> * * <p>The ReturnVal object returned encodes success or failure, and * may optionally pass back a dialog.</p> * * <p>This method is intended to be called by code that needs to go * through the permission checking regime, and that needs to have * rescan information passed back. This includes most wizard * setValue calls.</p> * * @return A ReturnVal indicating success or failure. May be simply * 'null' to indicate success if no feedback need be provided. * * @throws arlut.csd.ganymede.common.GanyPermissionsException If * permission is denied to write to this field. * * @see arlut.csd.ganymede.rmi.db_field */ public final ReturnVal setValue(Object value) throws GanyPermissionsException { ReturnVal result; /* -- */ // do the thing, calling into our subclass result = setValue(value, false, false); if (ReturnVal.hasTransformedValue(result)) { result = rescanThisField(result); } return result; } /** * <p>Sets the value of this field, if a scalar.</p> * * <p><b>This method is server-side only, and bypasses permissions * checking.</b></p> * * <p>The ReturnVal object returned encodes success or failure, and * may optionally pass back a dialog.</p> * * @return A ReturnVal indicating success or failure. May be simply * 'null' to indicate success if no feedback need be provided. */ public final ReturnVal setValueLocal(Object value) { try { return setValue(value, true, false); } catch (GanyPermissionsException ex) { throw new RuntimeException(ex); } } /** * <p>Sets the value of this field, if a scalar.</p> * * <p><b>This method is server-side only, and bypasses permissions * checking.</b></p> * * <p>The ReturnVal object returned encodes success or failure, and * may optionally pass back a dialog.</p> * * @param value Value to set this field to * @param noWizards If true, wizards will be skipped * * @return A ReturnVal indicating success or failure. May be simply * 'null' to indicate success if no feedback need be provided. */ public final ReturnVal setValueLocal(Object value, boolean noWizards) { try { return setValue(value, true, noWizards); } catch (GanyPermissionsException ex) { throw new RuntimeException(ex); } } /** * <p>Sets the value of this field, if a scalar.</p> * * <p><b>This method is server-side only.</b></p> * * <p>The ReturnVal object returned encodes success or failure, and * may optionally pass back a dialog.</p> * * @param submittedValue Value to set this field to * @param local If true, permissions checking will be skipped * * @return A ReturnVal indicating success or failure. May be simply * 'null' to indicate success if no feedback need be provided. * * @throws arlut.csd.ganymede.common.GanyPermissionsException If * local is false and permission is denied to write to this field. */ public final ReturnVal setValue(Object submittedValue, boolean local) throws GanyPermissionsException { return setValue(submittedValue, local, false); } /** * <p>Sets the value of this field, if a scalar.</p> * * <p><b>This method is server-side only.</b></p> * * <p>The ReturnVal object returned encodes success or failure, and * may optionally pass back a dialog.</p> * * <p>This method will be overridden by DBField subclasses with * special needs.</p> * * @param submittedValue Value to set this field to * @param local If true, permissions checking will be skipped * @param noWizards If true, wizards will be skipped * * @return A ReturnVal indicating success or failure. May be simply * 'null' to indicate success if no feedback need be provided. * * @throws arlut.csd.ganymede.common.GanyPermissionsException If * local is false and permission is denied to write to this field. */ public synchronized ReturnVal setValue(Object submittedValue, boolean local, boolean noWizards) throws GanyPermissionsException { ReturnVal retVal = null; DBNameSpace ns; DBEditObject eObj; /* -- */ if (!isEditable(local)) // *sync* possible { // "Can''t change field {0} in object {1}, due to a lack of permissions or the object being in a non-editable state." throw new GanyPermissionsException(ts.l("global.no_write_perms", getName(), owner.getLabel())); } if (isVector()) { // "Scalar method called on a vector field: {0} in object {1}" throw new IllegalArgumentException(ts.l("global.oops_vector", getName(), owner.getLabel())); } if (this.value == submittedValue || (this.value != null && this.value.equals(submittedValue))) { return null; // no change (useful for null and for xmlclient) } if (submittedValue instanceof String) { submittedValue = ((String) submittedValue).intern(); } else if (submittedValue instanceof Invid) { submittedValue = ((Invid) submittedValue).intern(); } else if (submittedValue instanceof Date) { submittedValue = new Date(((Date) submittedValue).getTime()); // defensive copy } retVal = verifyNewValue(submittedValue); if (!ReturnVal.didSucceed(retVal)) { return retVal; } /* check to see if verifyNewValue canonicalized the submittedValue */ if (ReturnVal.hasTransformedValue(retVal)) { submittedValue = retVal.getTransformedValueObject(); } eObj = (DBEditObject) owner; if (!noWizards && !local && eObj.getGSession().enableOversight) { // Wizard check retVal = ReturnVal.merge(retVal, eObj.wizardHook(this, DBEditObject.SETVAL, submittedValue, null)); // if a wizard intercedes, we are going to let it take the // ball. we'll lose any transformation/rescan from the // verifyNewValue() call above, but the fact that the wizard // is taking over means that we're not directly accepting // whatever the user gave us, anyway. if (ReturnVal.wizardHandled(retVal)) { return retVal; } } // check to see if we can do the namespace manipulations implied by this // operation ns = getNameSpace(); if (ns != null) { unmark(this.value); // if we're not being told to clear this field, try to mark the // new value if (submittedValue != null) { if (!mark(submittedValue)) { if (this.value != null) { mark(this.value); // we aren't clearing the old value after all } return getConflictDialog("DBField.setValue()", submittedValue); } } } // check our owner, do it. Checking our owner should // be the last thing we do.. if it returns true, nothing // should stop us from running the change to completion boolean setValue = false; try { retVal = ReturnVal.merge(retVal, eObj.finalizeSetValue(this, submittedValue)); if (ReturnVal.didSucceed(retVal)) { this.value = submittedValue; setValue = true; } } finally { if (!setValue) { if (ns != null) { unmark(submittedValue); mark(this.value); } } } // go ahead and return the dialog that was set by finalizeSetValue(). return retVal; } /** * <p>Returns a Vector of the values of the elements in this field, * if a vector.</p> * * <p>This is only valid for vectors. If the field is a scalar, use * getValue().</p> * * <p>This method checks for read permissions.</p> * * <p>This method returns a safe copy of the contained Vector.</p> * * @throws arlut.csd.ganymede.common.GanyPermissionsException If * permission is denied to read from this field. * * @see arlut.csd.ganymede.rmi.db_field */ public synchronized Vector getValues() throws GanyPermissionsException { if (!verifyReadPermission()) { // "Don''t have permission to read field {0} in object {1}" throw new GanyPermissionsException(ts.l("global.no_read_perms", getName(), owner.getLabel())); } if (!isVector()) { // "Vector method called on a scalar field: {0} in object {1}" throw new IllegalArgumentException(ts.l("global.oops_scalar", getName(), owner.getLabel())); } return new Vector(getVectVal()); // defensive copy } /** * <p>Returns the value of an element of this field, if a * vector.</p> * * @throws arlut.csd.ganymede.common.GanyPermissionsException If * permission is denied to read from this field. * * @see arlut.csd.ganymede.rmi.db_field */ public synchronized Object getElement(int index) throws GanyPermissionsException { if (!verifyReadPermission()) { // "Don''t have permission to read field {0} in object {1}" throw new GanyPermissionsException(ts.l("global.no_read_perms", getName(), owner.getLabel())); } if (!isVector()) { // "Vector method called on a scalar field: {0} in object {1}" throw new IllegalArgumentException(ts.l("global.oops_scalar", getName(), owner.getLabel())); } if (index < 0) { // "Invalid index {0,num,#} for array access on field {0} in object {1}." throw new ArrayIndexOutOfBoundsException(ts.l("global.out_of_range", Integer.valueOf(index), getName(), owner.getLabel())); } return getVectVal().get(index); } /** * <p>Returns the value of an element of this field, if a * vector.</p> * * <p>For server-side use only, permissions are not checked.</p> */ public synchronized Object getElementLocal(int index) { if (!isVector()) { // "Vector method called on a scalar field: {0} in object {1}" throw new IllegalArgumentException(ts.l("global.oops_scalar", getName(), owner.getLabel())); } if (index < 0) { // "Invalid index {0,num,#} for array access on field {0} in object {1}." throw new ArrayIndexOutOfBoundsException(ts.l("global.out_of_range", Integer.valueOf(index), getName(), owner.getLabel())); } return getVectVal().get(index); } /** * <p>Sets the value of an element of this field, if a vector.</p> * * <P>The ReturnVal object returned encodes success or failure, and * may optionally pass back a dialog.</p> * * <p>The ReturnVal resulting from a successful setElement will * encode an order to rescan this field.</p> * * <p>Note that vector fields in Ganymede are not allowed to contain * duplicate values.</p> * * @return A ReturnVal indicating success or failure. May be simply * 'null' to indicate success if no feedback need be provided. * * @throws arlut.csd.ganymede.common.GanyPermissionsException If * permission is denied to to this field. * * @see arlut.csd.ganymede.rmi.db_field */ public final ReturnVal setElement(int index, Object value) throws GanyPermissionsException { if (!isVector()) { throw new IllegalArgumentException("vector accessor called on scalar field " + getName()); } if (value == null) { return Ganymede.createErrorDialog(this.getGSession(), "Field Error", "Null value passed to " + owner.getLabel() + ":" + getName() + ".setElement()"); } if ((index < 0) || (index > getVectVal().size())) { // "Invalid index {0,num,#} for array access on field {0} in object {1}." throw new ArrayIndexOutOfBoundsException(ts.l("global.out_of_range", Integer.valueOf(index), getName(), owner.getLabel())); } ReturnVal result = setElement(index, value, false, false); if (ReturnVal.hasTransformedValue(result)) { result = rescanThisField(result); } return result; } /** * <p>Sets the value of an element of this field, if a vector.</p> * * <p>Server-side method only</p> * * <p>The ReturnVal object returned encodes * success or failure, and may optionally * pass back a dialog.</p> * * <p>Note that vector fields in Ganymede are not allowed to contain * duplicate values.</p> * * @return A ReturnVal indicating success or failure. May * be simply 'null' to indicate success if no feedback need * be provided. */ public final ReturnVal setElementLocal(int index, Object value) { if (!isVector()) { // "Vector method called on a scalar field: {0} in object {1}" throw new IllegalArgumentException(ts.l("global.oops_scalar", getName(), owner.getLabel())); } if (value == null) { // "Null value passed to setElement() on field {0} in object {1}." return Ganymede.createErrorDialog(this.getGSession(), null, ts.l("setElementLocal.bad_null", getName(), owner.getLabel())); } if ((index < 0) || (index > getVectVal().size())) { // "Invalid index {0,num,#} for array access on field {0} in object {1}." throw new ArrayIndexOutOfBoundsException(ts.l("global.out_of_range", Integer.valueOf(index), getName(), owner.getLabel())); } try { return setElement(index, value, true, false); } catch (GanyPermissionsException ex) { throw new RuntimeException(ex); // should not happen } } /** * <p>Sets the value of an element of this field, if a vector.</p> * * <p>Server-side method only</p> * * <p>The ReturnVal object returned encodes success or failure, and * may optionally pass back a dialog. A null result means the * operation was carried out successfully and no information * needed to be passed back about side-effects.</p> * * <p>Note that vector fields in Ganymede are not allowed to contain * duplicate values.</p> * * @throws arlut.csd.ganymede.common.GanyPermissionsException If * local is false and permission is denied to write to this field. */ public final ReturnVal setElement(int index, Object submittedValue, boolean local) throws GanyPermissionsException { return setElement(index, submittedValue, local, false); } /** * <p>Sets the value of an element of this field, if a vector.</p> * * <p>Server-side method only</p> * * <p>The ReturnVal object returned encodes success or failure, and * may optionally pass back a dialog. A null result means the * operation was carried out successfully and no information * needed to be passed back about side-effects.</p> * * <p>Note that vector fields in Ganymede are not allowed to contain * duplicate values.</p> * * @throws arlut.csd.ganymede.common.GanyPermissionsException If * local is false and permission is denied to write to this field. */ public synchronized ReturnVal setElement(int index, Object submittedValue, boolean local, boolean noWizards) throws GanyPermissionsException { ReturnVal retVal = null; DBNameSpace ns; DBEditObject eObj; /* -- */ if (!isVector()) { // "Vector method called on a scalar field: {0} in object {1}" throw new IllegalArgumentException(ts.l("global.oops_scalar", getName(), owner.getLabel())); } if (!isEditable(local)) // *sync* on GanymedeSession possible. { // "Can''t change field {0} in object {1}, due to a lack of permissions or the object being in a non-editable state." throw new GanyPermissionsException(ts.l("global.no_write_perms", getName(), owner.getLabel())); } Vector values = getVectVal(); // make sure we're not duplicating an item int oldIndex = values.indexOf(submittedValue); if (oldIndex == index) { return null; // no-op } else if (oldIndex != -1) { return getDuplicateValueDialog("setElement", submittedValue); // duplicate } // make sure that the constraints on this field don't rule out, prima facie, the proposed value retVal = verifyNewValue(submittedValue); if (!ReturnVal.didSucceed(retVal)) { return retVal; } /* check to see if verifyNewValue canonicalized the submittedValue */ if (ReturnVal.hasTransformedValue(retVal)) { submittedValue = retVal.getTransformedValueObject(); } // allow the plugin class to review the operation eObj = (DBEditObject) owner; if (!noWizards && !local && eObj.getGSession().enableOversight) { // Wizard check retVal = ReturnVal.merge(retVal, eObj.wizardHook(this, DBEditObject.SETELEMENT, Integer.valueOf(index), submittedValue)); // if a wizard intercedes, we are going to let it take the // ball. we'll lose any transformation/rescan from the // verifyNewValue() call above, but the fact that the wizard // is taking over means that we're not directly accepting // whatever the user gave us, anyway. if (ReturnVal.wizardHandled(retVal)) { return retVal; } } // okay, we're going to proceed.. unless there's a namespace // violation ns = this.getNameSpace(); if (ns != null) { unmark(values.get(index)); if (!mark(submittedValue)) { mark(values.get(index)); // we aren't clearing the old value after all return getConflictDialog("DBField.setElement()", submittedValue); } } boolean setValue = false; // check our owner, do it. Checking our owner should be the last // thing we do.. if it returns true, nothing should stop us from // running the change to completion try { retVal = ReturnVal.merge(retVal, eObj.finalizeSetElement(this, index, submittedValue)); if (ReturnVal.didSucceed(retVal)) { values.set(index, submittedValue); setValue = true; } } finally { if (!setValue) { if (ns != null) { // values is in its final state.. if the submittedValue // isn't in it anywhere, unmark it in the namespace if (!values.contains(submittedValue)) { unmark(submittedValue); } // mark the old value.. we can always do this safely, even // if the value was already marked mark(values.get(index)); } } } return retVal; } /** * <p>Adds an element to the end of this field, if a vector.</p> * * <p>The ReturnVal object returned encodes success or failure, and * may optionally pass back a dialog.</p> * * <p>The ReturnVal resulting from a successful addElement will * encode an order to rescan this field.</p> * * <p>Note that vector fields in Ganymede are not allowed to contain * duplicate values.</p> * * @return A ReturnVal indicating success or failure. May * be simply 'null' to indicate success if no feedback need * be provided. * * @throws arlut.csd.ganymede.common.GanyPermissionsException If * permission is denied to to this field. * * @see arlut.csd.ganymede.rmi.db_field */ public final ReturnVal addElement(Object value) throws GanyPermissionsException { ReturnVal result = addElement(value, false, false); if (ReturnVal.hasTransformedValue(result)) { result = rescanThisField(result); } return result; } /** * <p>Adds an element to the end of this field, if a vector.</p> * * <p>Server-side method only</p> * * <p>The ReturnVal object returned encodes * success or failure, and may optionally * pass back a dialog.</p> * * <p>Note that vector fields in Ganymede are not allowed to contain * duplicate values.</p> * * @return A ReturnVal indicating success or failure. May * be simply 'null' to indicate success if no feedback need * be provided. */ public final ReturnVal addElementLocal(Object value) { try { return addElement(value, true, false); } catch (GanyPermissionsException ex) { throw new RuntimeException(ex); // should never happen } } /** * <p>Adds an element to the end of this field, if a vector.</p> * * <p>Server-side method only</p> * * <p>The ReturnVal object returned encodes * success or failure, and may optionally * pass back a dialog.</p> * * <p>Note that vector fields in Ganymede are not allowed to contain * duplicate values.</p> * * @param submittedValue Value to be added * @param local If true, permissions checking will be skipped * * @return A ReturnVal indicating success or failure. May * be simply 'null' to indicate success if no feedback need * be provided. * * @throws arlut.csd.ganymede.common.GanyPermissionsException If * local is false and permission is denied to write to this field. */ public final ReturnVal addElement(Object submittedValue, boolean local) throws GanyPermissionsException { return addElement(submittedValue, local, false); } /** * <p>Adds an element to the end of this field, if a vector.</p> * * <p>Server-side method only</p> * * <p>The ReturnVal object returned encodes * success or failure, and may optionally * pass back a dialog.</p> * * <p>Note that vector fields in Ganymede are not allowed to contain * duplicate values.</p> * * @param submittedValue Value to be added * @param local If true, permissions checking will be skipped * @param noWizards If true, wizards will be skipped * * @return A ReturnVal indicating success or failure. May * be simply 'null' to indicate success if no feedback need * be provided. * * @throws arlut.csd.ganymede.common.GanyPermissionsException If * local is false and permission is denied to write to this field. */ public synchronized ReturnVal addElement(Object submittedValue, boolean local, boolean noWizards) throws GanyPermissionsException { ReturnVal retVal = null; DBNameSpace ns; DBEditObject eObj; /* -- */ if (!isEditable(local)) // *sync* on GanymedeSession possible { // "Can''t change field {0} in object {1}, due to a lack of permissions or the object being in a non-editable state." throw new GanyPermissionsException(ts.l("global.no_write_perms", getName(), owner.getLabel())); } if (!isVector()) { // "Vector method called on a scalar field: {0} in object {1}" throw new IllegalArgumentException(ts.l("global.oops_scalar", getName(), owner.getLabel())); } if (submittedValue == null) { // "Null value passed to addElement() on field {0} in object {1}." throw new IllegalArgumentException(ts.l("addElement.bad_null", getName(), owner.getLabel())); } if (submittedValue instanceof String) { submittedValue = ((String) submittedValue).intern(); } else if (submittedValue instanceof Invid) { submittedValue = ((Invid) submittedValue).intern(); } // make sure we're not duplicating an item if (getVectVal().contains(submittedValue)) { return getDuplicateValueDialog("addElement", submittedValue); // duplicate } // verifyNewValue should setLastError for us. retVal = verifyNewValue(submittedValue); if (!ReturnVal.didSucceed(retVal)) { return retVal; } /* check to see if verifyNewValue canonicalized the submittedValue */ if (ReturnVal.hasTransformedValue(retVal)) { submittedValue = retVal.getTransformedValueObject(); } if (size() >= getMaxArraySize()) { // "addElement() Error: Field {0} in object {1} is already at or beyond its maximum allowed size." return Ganymede.createErrorDialog(this.getGSession(), null, ts.l("addElement.overflow", getName(), owner.getLabel())); } eObj = (DBEditObject) owner; if (!noWizards && !local && eObj.getGSession().enableOversight) { // Wizard check retVal = ReturnVal.merge(retVal, eObj.wizardHook(this, DBEditObject.ADDELEMENT, submittedValue, null)); // if a wizard intercedes, we are going to let it take the // ball. we'll lose any transformation/rescan from the // verifyNewValue() call above, but the fact that the wizard // is taking over means that we're not directly accepting // whatever the user gave us, anyway. if (ReturnVal.wizardHandled(retVal)) { return retVal; } } ns = getNameSpace(); if (ns != null) { if (!mark(submittedValue)) // *sync* DBNameSpace { return getConflictDialog("DBField.addElement()", submittedValue); } } boolean addedValue = false; try { retVal = ReturnVal.merge(retVal, eObj.finalizeAddElement(this, submittedValue)); if (ReturnVal.didSucceed(retVal)) { getVectVal().add(submittedValue); addedValue = true; } } finally { if (!addedValue) { if (ns != null) { // if the value that we were going to add is not // left in our vector, unmark the to-be-added // value if (!getVectVal().contains(submittedValue)) { unmark(submittedValue); // *sync* DBNameSpace } } } } return retVal; } /** * <p>Adds a set of elements to the end of this field, if a * vector. Using addElements() to add a sequence of items * to a field may be many times more efficient than calling * addElement() repeatedly, as addElements() can do a single * server checkpoint before attempting to add all the values.</p> * * <p>The ReturnVal object returned encodes success or failure, and * may optionally pass back a dialog. If a success code is returned, * all values were added. If failure is returned, no values * were added.</p> * * <p>The ReturnVal resulting from a successful addElements will * encode an order to rescan this field.</p> * * <p>Note that vector fields in Ganymede are not allowed to contain * duplicate values.</p> * * @return A ReturnVal indicating success or failure. May * be simply 'null' to indicate success if no feedback need * be provided. * * @throws arlut.csd.ganymede.common.GanyPermissionsException If * permission is denied to write to this field. * * @see arlut.csd.ganymede.rmi.db_field */ public final ReturnVal addElements(Vector values) throws GanyPermissionsException { // the interior addElements call will automatically call for a // rescanThisField() for us if it encountered any transformed // values while processing the objects in values return addElements(values, false, false); } /** * <p>Adds a set of elements to the end of this field, if a * vector. Using addElements() to add a sequence of items * to a field may be many times more efficient than calling * addElement() repeatedly, as addElements() can do a single * server checkpoint before attempting to add all the values.</p> * * <p>Server-side method only</p> * * <p>The ReturnVal object returned encodes success or failure, and * may optionally pass back a dialog. If a success code is returned, * all values were added. If failure is returned, no values * were added.</p> * * <p>Note that vector fields in Ganymede are not allowed to contain * duplicate values.</p> * * @return A ReturnVal indicating success or failure. May * be simply 'null' to indicate success if no feedback need * be provided. */ public final ReturnVal addElementsLocal(Vector values) { try { return addElements(values, true, false); } catch (GanyPermissionsException ex) { throw new RuntimeException(ex); // should never happen } } /** * <p>Adds a set of elements to the end of this field, if a * vector. Using addElements() to add a sequence of items * to a field may be many times more efficient than calling * addElement() repeatedly, as addElements() can do a single * server checkpoint before attempting to add all the values.</p> * * <p>Server-side method only</p> * * <p>The ReturnVal object returned encodes success or failure, and * may optionally pass back a dialog. If a success code is returned, * all values were added. If failure is returned, no values * were added.</p> * * <p>Note that vector fields in Ganymede are not allowed to contain * duplicate values.</p> * * @param submittedValues Values to be added * @param noWizards If true, wizards will be skipped * @param copyFieldMode If true, addElements will add any values * that it can, even if some values are refused by the server logic. * Any values that are skipped will be reported in a dialog passed * back in the returned ReturnVal. This is intended to support * vector field cloning, in which we add what values may be cloned, * and skip the rest. * * @return A ReturnVal indicating success or failure. May * be simply 'null' to indicate success if no feedback need * be provided. */ public final ReturnVal addElementsLocal(Vector submittedValues, boolean noWizards, boolean copyFieldMode) { try { return addElements(submittedValues, true, noWizards, copyFieldMode); } catch (GanyPermissionsException ex) { throw new RuntimeException(ex); // should never happen } } /** * <p>Adds a set of elements to the end of this field, if a * vector. Using addElements() to add a sequence of items * to a field may be many times more efficient than calling * addElement() repeatedly, as addElements() can do a single * server checkpoint before attempting to add all the values.</p> * * <p>Server-side method only</p> * * <p>The ReturnVal object returned encodes success or failure, and * may optionally pass back a dialog. If a success code is returned, * all values were added. If failure is returned, no values * were added.</p> * * <p>Note that vector fields in Ganymede are not allowed to contain * duplicate values.</p> * * @param submittedValues Values to be added * @param local If true, permissions checking will be skipped * * @return A ReturnVal indicating success or failure. May * be simply 'null' to indicate success if no feedback need * be provided. * * @throws arlut.csd.ganymede.common.GanyPermissionsException If * local is false and permission is denied to write to this field. */ public final ReturnVal addElements(Vector submittedValues, boolean local) throws GanyPermissionsException { return addElements(submittedValues, local, false); } /** * <p>Adds a set of elements to the end of this field, if a * vector. Using addElements() to add a sequence of items * to a field may be many times more efficient than calling * addElement() repeatedly, as addElements() can do a single * server checkpoint before attempting to add all the values.</p> * * <p>Server-side method only</p> * * <p>The ReturnVal object returned encodes success or failure, and * may optionally pass back a dialog. If a success code is returned, * all values were added. If failure is returned, no values * were added.</p> * * <p>Note that vector fields in Ganymede are not allowed to contain * duplicate values.</p> * * @param submittedValues Values to be added * @param local If true, permissions checking will be skipped * @param noWizards If true, wizards will be skipped * * @return A ReturnVal indicating success or failure. May * be simply 'null' to indicate success if no feedback need * be provided. * * @throws arlut.csd.ganymede.common.GanyPermissionsException If * local is false and permission is denied to write to this field. */ public final ReturnVal addElements(Vector submittedValues, boolean local, boolean noWizards) throws GanyPermissionsException { return addElements(submittedValues, local, noWizards, false); } /** * <p>Adds a set of elements to the end of this field, if a * vector. Using addElements() to add a sequence of items * to a field may be many times more efficient than calling * addElement() repeatedly, as addElements() can do a single * server checkpoint before attempting to add all the values.</p> * * <p>Server-side method only</p> * * <p>The ReturnVal object returned encodes success or failure, and * may optionally pass back a dialog. If a success code is returned, * all values were added. If failure is returned, no values * were added.</p> * * <p>Note that vector fields in Ganymede are not allowed to contain * duplicate values.</p> * * @param submittedValues Values to be added * @param local If true, permissions checking will be skipped * @param noWizards If true, wizards will be skipped * @param copyFieldMode If true, addElements will add any values that * it can, even if some values are refused by the server logic. Any * values that are skipped will be reported in a dialog passed back * in the returned ReturnVal * * @return A ReturnVal indicating success or failure. May * be simply 'null' to indicate success if no feedback need * be provided. * * @throws arlut.csd.ganymede.common.GanyPermissionsException If * local is false and permission is denied to write to this field. */ public synchronized ReturnVal addElements(Vector submittedValues, boolean local, boolean noWizards, boolean copyFieldMode) throws GanyPermissionsException { ReturnVal retVal = null; DBNameSpace ns; DBEditObject eObj; DBEditSet editset; Vector approvedValues = new Vector(); boolean transformed = false; /* -- */ if (!isEditable(local)) // *sync* on GanymedeSession possible { // "Can''t change field {0} in object {1}, due to a lack of permissions or the object being in a non-editable state." throw new GanyPermissionsException(ts.l("global.no_write_perms", getName(), owner.getLabel())); } if (!isVector()) { // "Vector method called on a scalar field: {0} in object {1}" throw new IllegalArgumentException(ts.l("global.oops_scalar", getName(), owner.getLabel())); } if (submittedValues == null || submittedValues.size() == 0) { // "Null or empty Vector passed to addElements() on field {0} in object {1}." return Ganymede.createErrorDialog(this.getGSession(), null, ts.l("addElements.bad_null", getName(), owner.getLabel())); } if (submittedValues == getVectVal()) { // "Error, attempt to add self elements to field {0} in object {1}." throw new IllegalArgumentException(ts.l("addElements.self_add", getName(), owner.getLabel())); } Vector duplicateValues = VectorUtils.intersection(getVectVal(), submittedValues); if (duplicateValues.size() > 0) { if (!copyFieldMode) { return getDuplicateValuesDialog("addElements", VectorUtils.vectorString(duplicateValues)); } else { submittedValues = VectorUtils.difference(submittedValues, getVectVal()); } } // can we add this many values? if (size() + submittedValues.size() > getMaxArraySize()) { // "addElements() Error: Field {0} in object {1} can''t take {2,number,#} new values..\n // It already has {3,number,#} elements, and may not have more than {4,number,#} total." return Ganymede.createErrorDialog(this.getGSession(), null, ts.l("addElements.overflow", getName(), owner.getLabel(), Integer.valueOf(submittedValues.size()), Integer.valueOf(size()), Integer.valueOf(getMaxArraySize()))); } // check to see if all of the submitted values are acceptable in // type and in identity. if copyFieldMode, we won't complain // unless none of the submitted values are acceptable StringBuilder errorBuf = new StringBuilder(); for (int i = 0; i < submittedValues.size(); i++) { Object submittedValue = submittedValues.get(i); // intern our strings and invids if (submittedValue instanceof String) { submittedValues.set(i, ((String) submittedValue).intern()); } else if (submittedValue instanceof Invid) { submittedValues.set(i, ((Invid) submittedValue).intern()); } retVal = verifyNewValue(submittedValue); if (ReturnVal.hasTransformedValue(retVal)) { submittedValue = retVal.getTransformedValueObject(); transformed = true; } if (!ReturnVal.didSucceed(retVal)) { if (!copyFieldMode) { return retVal; } else { if (retVal.getDialog() != null) { if (errorBuf.length() != 0) { errorBuf.append("\n\n"); } errorBuf.append(retVal.getDialog().getText()); } } } else { approvedValues.add(submittedValue); } } if (approvedValues.size() == 0) { // "addElements() Error" return Ganymede.createErrorDialog(this.getGSession(), ts.l("addElements.unapproved_title"), errorBuf.toString()); } // see if our container wants to intercede in the adding operation eObj = (DBEditObject) owner; editset = eObj.getEditSet(); if (!noWizards && !local && eObj.getGSession().enableOversight) { // Wizard check retVal = eObj.wizardHook(this, DBEditObject.ADDELEMENTS, approvedValues, null); // if a wizard intercedes, we are going to let it take the ball. if (ReturnVal.wizardHandled(retVal)) { return retVal; } } // check to see if all of the values being added are acceptable to // a namespace constraint ns = getNameSpace(); if (ns != null) { synchronized (ns) { for (Object item: approvedValues) { if (!ns.testmark(editset, item)) { return getConflictDialog("DBField.addElements()", item); } } for (Object item: approvedValues) { if (!ns.mark(editset, item, this)) { throw new RuntimeException("error: testmark / mark inconsistency"); } } } } // okay, see if the DBEditObject is willing to allow all of these // elements to be added boolean addedValues = false; try { retVal = ReturnVal.merge(retVal, eObj.finalizeAddElements(this, approvedValues)); if (ReturnVal.didSucceed(retVal)) { // okay, we're allowed to do it, so we add them all getVectVal().addAll(approvedValues); addedValues = true; if (retVal == null) { retVal = ReturnVal.success(); } // if we were not able to copy some of the values (and we // had copyFieldMode set), encode a description of what // happened along with the success code if (errorBuf.length() != 0) { // "Warning" retVal.setDialog(new JDialogBuff(ts.l("addElements.warning"), errorBuf.toString(), Ganymede.OK, // localized null, "ok.gif")); } if (transformed) { // one or more of the values we were given to add was // canonicalized or otherwise transformed. let the client // know it will need to ask us for the final state of the // field. retVal.requestRefresh(owner.getInvid(), this.getID()); } } } finally { if (!addedValues) { if (ns != null) { // for each value that we were going to add (and which we // marked in our namespace above), we need to unmark it if // it is not contained in our vector at this point. for (Object item: VectorUtils.difference(approvedValues, getVectVal())) { if (!ns.unmark(editset, item, this)) { // "Error encountered attempting to dissociate // reserved value {0} from field {1}. This // may be due to a server error, or it may be // due to a non-interactive transaction // currently at work trying to shuffle // namespace values between multiple objects. // In the latter case, you may be able to // succeed at this operation after the // non-interactive transaction gives up." throw new RuntimeException(ts.l("global.bad_unmark", item, this)); } } } } } return retVal; } /** * <p>Deletes an element of this field, if a vector.</p> * * <p>The ReturnVal object returned encodes success or failure, * and may optionally pass back a dialog.</p> * * <p>The ReturnVal resulting from a successful deleteElement will * encode an order to rescan this field.</p> * * @return A ReturnVal indicating success or failure. May * be simply 'null' to indicate success if no feedback need * be provided. * * @throws arlut.csd.ganymede.common.GanyPermissionsException If * permission is denied to write to this field. * * @see arlut.csd.ganymede.rmi.db_field */ public final ReturnVal deleteElement(int index) throws GanyPermissionsException { return deleteElement(index, false, false); } /** * <p>Deletes an element of this field, if a vector.</p> * * <p>Server-side method only</p> * * <p>The ReturnVal resulting from a successful deleteElement will * encode an order to rescan this field.</p> * * @return A ReturnVal indicating success or failure. May * be simply 'null' to indicate success if no feedback need * be provided. */ public final ReturnVal deleteElementLocal(int index) { try { return deleteElement(index, true, false); } catch (GanyPermissionsException ex) { throw new RuntimeException(ex); // should never happen } } /** * <p>Deletes an element of this field, if a vector.</p> * * <p>Server-side method only</p> * * <p>The ReturnVal resulting from a successful deleteElement will * encode an order to rescan this field.</p> * * @return A ReturnVal indicating success or failure. May * be simply 'null' to indicate success if no feedback need * be provided. * * @throws arlut.csd.ganymede.common.GanyPermissionsException If * local is false and permission is denied to write to this field. */ public final ReturnVal deleteElement(int index, boolean local) throws GanyPermissionsException { return deleteElement(index, local, false); } /** * <p>Deletes an element of this field, if a vector.</p> * * <p>Server-side method only</p> * * <p>The ReturnVal resulting from a successful deleteElement will * encode an order to rescan this field.</p> * * @return A ReturnVal indicating success or failure. May * be simply 'null' to indicate success if no feedback need * be provided. * * @throws arlut.csd.ganymede.common.GanyPermissionsException If * local is false and permission is denied to write to this field. */ public synchronized ReturnVal deleteElement(int index, boolean local, boolean noWizards) throws GanyPermissionsException { ReturnVal retVal = null; DBEditObject eObj; /* -- */ if (!isEditable(local)) // *sync* GanymedeSession possible { // "Can''t change field {0} in object {1}, due to a lack of permissions or the object being in a non-editable state." throw new GanyPermissionsException(ts.l("global.no_write_perms", getName(), owner.getLabel())); } if (!isVector()) { // "Vector method called on a scalar field: {0} in object {1}" throw new IllegalArgumentException(ts.l("global.oops_scalar", getName(), owner.getLabel())); } Vector values = getVectVal(); if ((index < 0) || (index >= values.size())) { // "Invalid index {0,number,#} for array access on field {0} in object {1}." throw new ArrayIndexOutOfBoundsException(ts.l("global.out_of_range", Integer.valueOf(index), getName(), owner.getLabel())); } eObj = (DBEditObject) owner; if (!noWizards && !local && eObj.getGSession().enableOversight) { // Wizard check retVal = eObj.wizardHook(this, DBEditObject.DELELEMENT, Integer.valueOf(index), null); // if a wizard intercedes, we are going to let it take the ball. if (ReturnVal.wizardHandled(retVal)) { return retVal; } } retVal = ReturnVal.merge(retVal, eObj.finalizeDeleteElement(this, index)); if (ReturnVal.didSucceed(retVal)) { Object valueToDelete = values.get(index); values.remove(index); // if this field no longer contains the element that // we are deleting, we're going to unmark that value // in our namespace if (!values.contains(valueToDelete)) { unmark(valueToDelete); } } return retVal; } /** * <p>Deletes an element of this field, if a vector.</p> * * <p>The ReturnVal object returned encodes success or failure, * and may optionally pass back a dialog.</p> * * <p>The ReturnVal resulting from a successful deleteElement will * encode an order to rescan this field.</p> * * @return A ReturnVal indicating success or failure. May * be simply 'null' to indicate success if no feedback need * be provided. * * @throws arlut.csd.ganymede.common.GanyPermissionsException If * permission is denied to write to this field. * * @see arlut.csd.ganymede.rmi.db_field */ public final ReturnVal deleteElement(Object value) throws GanyPermissionsException { return deleteElement(value, false, false); } /** * <p>Deletes an element of this field, if a vector.</p> * * <p>Server-side method only</p> * * <p>The ReturnVal resulting from a successful deleteElement will * encode an order to rescan this field.</p> * * @return A ReturnVal indicating success or failure. May * be simply 'null' to indicate success if no feedback need * be provided. */ public final ReturnVal deleteElementLocal(Object value) { try { return deleteElement(value, true, false); } catch (GanyPermissionsException ex) { throw new RuntimeException(ex); // should never happen } } /** * <p>Deletes an element of this field, if a vector.</p> * * <p>Server-side method only</p> * * <p>The ReturnVal resulting from a successful deleteElement will * encode an order to rescan this field.</p> * * @return A ReturnVal indicating success or failure. May * be simply 'null' to indicate success if no feedback need * be provided. * * @throws arlut.csd.ganymede.common.GanyPermissionsException If * local is false and permission is denied to write to this field. */ public final ReturnVal deleteElement(Object value, boolean local) throws GanyPermissionsException { return deleteElement(value, local, false); } /** * <p>Deletes an element of this field, if a vector.</p> * * <p>Server-side method only</p> * * <p>The ReturnVal resulting from a successful deleteElement will * encode an order to rescan this field.</p> * * @return A ReturnVal indicating success or failure. May * be simply 'null' to indicate success if no feedback need * be provided. * * @throws arlut.csd.ganymede.common.GanyPermissionsException If * local is false and permission is denied to write to this field. */ public final synchronized ReturnVal deleteElement(Object value, boolean local, boolean noWizards) throws GanyPermissionsException { if (!isEditable(local)) // *sync* GanymedeSession possible { // "Can''t change field {0} in object {1}, due to a lack of permissions or the object being in a non-editable state." throw new GanyPermissionsException(ts.l("global.no_write_perms", getName(), owner.getLabel())); } if (!isVector()) { // "Vector method called on a scalar field: {0} in object {1}" throw new IllegalArgumentException(ts.l("global.oops_scalar", getName(), owner.getLabel())); } if (value == null) { // "deleteElement() Error: Can''t delete null value from field {0} in object {1}." return Ganymede.createErrorDialog(this.getGSession(), null, ts.l("deleteElement.bad_null", getName(), owner.getLabel())); } int index = indexOfValue(value); if (index == -1) { // "deleteElement() Error: Value ''{0}'' not present to be deleted from field {1} in object {2}." return Ganymede.createErrorDialog(this.getGSession(), null, ts.l("deleteElement.missing_element", value, getName(), owner.getLabel())); } return deleteElement(index, local, noWizards); // *sync* DBNameSpace possible } /** * <p>Removes all elements from this field, if a * vector.</p> * * <p>The ReturnVal object returned encodes success or failure, and * may optionally pass back a dialog. If a success code is returned, * all elements in values was removed from this field. If a * failure code is returned, no elements in values were removed.</p> * * <p>The ReturnVal resulting from a successful deleteAllElements will * encode an order to rescan this field.</p> * * @return A ReturnVal indicating success or failure. May * be simply 'null' to indicate success if no feedback need * be provided. * * @throws arlut.csd.ganymede.common.GanyPermissionsException If * permission is denied to write to this field. * * @see arlut.csd.ganymede.rmi.db_field */ public final ReturnVal deleteAllElements() throws GanyPermissionsException { return this.deleteElements(this.getValues()); } /** * <p>Removes a set of elements from this field, if a * vector. Using deleteElements() to remove a sequence of items * from a field may be many times more efficient than calling * deleteElement() repeatedly, as removeElements() can do a single * server checkpoint before attempting to remove all the values.</p> * * <p>The ReturnVal object returned encodes success or failure, and * may optionally pass back a dialog. If a success code is returned, * all elements in values was removed from this field. If a * failure code is returned, no elements in values were removed.</p> * * <p>The ReturnVal resulting from a successful deleteElements will * encode an order to rescan this field.</p> * * @return A ReturnVal indicating success or failure. May * be simply 'null' to indicate success if no feedback need * be provided. * * @throws arlut.csd.ganymede.common.GanyPermissionsException If * permission is denied to write to this field. * * @see arlut.csd.ganymede.rmi.db_field */ public final ReturnVal deleteElements(Vector values) throws GanyPermissionsException { return deleteElements(values, false, false); } /** * <p>Removes a set of elements from this field, if a * vector. Using deleteElements() to remove a sequence of items * from a field may be many times more efficient than calling * deleteElement() repeatedly, as removeElements() can do a single * server checkpoint before attempting to remove all the values.</p> * * <p>The ReturnVal object returned encodes success or failure, and * may optionally pass back a dialog. If a success code is returned, * all elements in values was removed from this field. If a * failure code is returned, no elements in values were removed.</p> * * <p>Server-side method only</p> * * @return A ReturnVal indicating success or failure. May * be simply 'null' to indicate success if no feedback need * be provided. */ public final ReturnVal deleteElementsLocal(Vector values) { try { return deleteElements(values, true, false); } catch (GanyPermissionsException ex) { throw new RuntimeException(ex); // should never happen } } /** * <p>Removes a set of elements from this field, if a * vector. Using deleteElements() to remove a sequence of items * from a field may be many times more efficient than calling * deleteElement() repeatedly, as removeElements() can do a single * server checkpoint before attempting to remove all the values.</p> * * <p>The ReturnVal object returned encodes success or failure, and * may optionally pass back a dialog. If a success code is returned, * all elements in values was removed from this field. If a * failure code is returned, no elements in values were removed.</p> * * <p>Server-side method only</p> * * @return A ReturnVal indicating success or failure. May * be simply 'null' to indicate success if no feedback need * be provided. * * @throws arlut.csd.ganymede.common.GanyPermissionsException If * local is false and permission is denied to write to this field. */ public final ReturnVal deleteElements(Vector valuesToDelete, boolean local) throws GanyPermissionsException { return deleteElements(valuesToDelete, local, false); } /** * <p>Removes a set of elements from this field, if a * vector. Using deleteElements() to remove a sequence of items * from a field may be many times more efficient than calling * deleteElement() repeatedly, as removeElements() can do a single * server checkpoint before attempting to remove all the values.</p> * * <p>The ReturnVal object returned encodes success or failure, and * may optionally pass back a dialog. If a success code is returned, * all elements in values was removed from this field. If a * failure code is returned, no elements in values were removed.</p> * * <p>Server-side method only</p> * * @return A ReturnVal indicating success or failure. May * be simply 'null' to indicate success if no feedback need * be provided. * * @throws arlut.csd.ganymede.common.GanyPermissionsException If * local is false and permission is denied to write to this field. */ public synchronized ReturnVal deleteElements(Vector valuesToDelete, boolean local, boolean noWizards) throws GanyPermissionsException { ReturnVal retVal = null; DBNameSpace ns; DBEditObject eObj; DBEditSet editset; Vector currentValues; /* -- */ if (!isEditable(local)) // *sync* on GanymedeSession possible { // "Can''t change field {0} in object {1}, due to a lack of permissions or the object being in a non-editable state." throw new GanyPermissionsException(ts.l("global.no_write_perms", getName(), owner.getLabel())); } if (!isVector()) { // "Vector method called on a scalar field: {0} in object {1}" throw new IllegalArgumentException(ts.l("global.oops_scalar", getName(), owner.getLabel())); } if (valuesToDelete == null || valuesToDelete.size() == 0) { // "Null or empty Vector passed to deleteElements() on field {0} in object {1}." return Ganymede.createErrorDialog(this.getGSession(), null, ts.l("deleteElements.bad_null", getName(), owner.getLabel())); } // get access to our value vector. currentValues = getVectVal(); // make sure the two vectors we're going to be manipulating aren't // actually the same vector if (valuesToDelete == currentValues) { // "Error, attempt to delete self elements from field {0} in object {1}." throw new IllegalArgumentException(ts.l("deleteElements.self_delete", getName(), owner.getLabel())); } // see if we are being asked to remove items not in our vector Vector notPresent = VectorUtils.minus(valuesToDelete, currentValues); if (notPresent.size() != 0) { // "deleteElements() Error: Values ''{0}'' not present to be deleted from field {1} in object {2}." return Ganymede.createErrorDialog(this.getGSession(), null, ts.l("deleteElements.missing_elements", VectorUtils.vectorString(notPresent), getName(), owner.getLabel())); } // see if our container wants to intercede in the removing operation eObj = (DBEditObject) owner; editset = eObj.getEditSet(); if (!noWizards && !local && eObj.getGSession().enableOversight) { // Wizard check retVal = eObj.wizardHook(this, DBEditObject.DELELEMENTS, valuesToDelete, null); // if a wizard intercedes, we are going to let it take the ball. if (ReturnVal.wizardHandled(retVal)) { return retVal; } } // okay, see if the DBEditObject is willing to allow all of these // elements to be removed retVal = ReturnVal.merge(retVal, eObj.finalizeDeleteElements(this, valuesToDelete)); if (ReturnVal.didSucceed(retVal)) { // okay, we're allowed to remove, so take the items out currentValues.removeAll(valuesToDelete); // if this vector is connected to a namespace, clear out what // we've left out from the namespace ns = getNameSpace(); if (ns != null) { for (Object item: valuesToDelete) { if (!ns.unmark(editset, item, this)) { // "Error encountered attempting to dissociate // reserved value {0} from field {1}. This // may be due to a server error, or it may be // due to a non-interactive transaction // currently at work trying to shuffle // namespace values between multiple objects. // In the latter case, you may be able to // succeed at this operation after the // non-interactive transaction gives up." throw new RuntimeException(ts.l("global.bad_unmark", item, this)); } } } } return retVal; } /** * <p>Returns true if this field is a vector field and value is contained * in this field.</p> * * <p>This method always checks for read privileges.</p> * * @param value The value to look for in this field * * @throws arlut.csd.ganymede.common.GanyPermissionsException If * permission is denied to read from this field. * * @see arlut.csd.ganymede.rmi.db_field */ public final boolean containsElement(Object value) throws GanyPermissionsException { return containsElement(value, false); } /** * <p>This method returns true if this field is a vector * field and value is contained in this field.</p> * * <p>This method is server-side only, and never checks for read * privileges.</p> * * @param value The value to look for in this fieldu */ public final boolean containsElementLocal(Object value) { try { return containsElement(value, true); } catch (GanyPermissionsException ex) { throw new RuntimeException(ex); // should never happen } } /** * <p>This method returns true if this field is a vector * field and value is contained in this field.</p> * * <p>This method is server-side only.</p> * * @param value The value to look for in this field * @param local If false, read permissin is checked for this field * * @throws arlut.csd.ganymede.common.GanyPermissionsException If * local is false and permission is denied to read from this field. */ public final synchronized boolean containsElement(Object value, boolean local) throws GanyPermissionsException { if (!local && !verifyReadPermission()) { // "Don''t have permission to read field {0} in object {1}" throw new GanyPermissionsException(ts.l("global.no_read_perms", getName(), owner.getLabel())); } if (!isVector()) { // "Vector method called on a scalar field: {0} in object {1}" throw new IllegalArgumentException(ts.l("global.oops_scalar", getName(), owner.getLabel())); } return (indexOfValue(value) != -1); } /** * <p>Returns a {@link arlut.csd.ganymede.server.fieldDeltaRec * fieldDeltaRec} object listing the changes between this field's * state and that of the prior oldField state.</p> */ public synchronized fieldDeltaRec getVectorDiff(DBField oldField) { if (!isVector()) { // "Vector method called on a scalar field: {0} in object {1}" throw new IllegalArgumentException(ts.l("global.oops_scalar", getName(), owner.getLabel())); } if (oldField == null) { // "Bad call to getVectorDiff() on field {0} in object {1}. oldField is null." throw new IllegalArgumentException(ts.l("getVectorDiff.null_old", getName(), owner.getLabel())); } if ((oldField.getID() != getID()) || (oldField.getObjTypeID() != getObjTypeID())) { // "Bad call to getVectorDiff() on field {0} in object {1}. Incompatible fields." throw new IllegalArgumentException(ts.l("getVectorDiff.bad_type", getName(), owner.getLabel())); } /* - */ fieldDeltaRec deltaRec = new fieldDeltaRec(getID()); Vector oldValues = oldField.getVectVal(); Vector newValues = getVectVal(); Vector addedValues = VectorUtils.difference(newValues, oldValues); Vector deletedValues = VectorUtils.difference(oldValues, newValues); deltaRec.addValues(addedValues); deltaRec.delValues(deletedValues); return deltaRec; } // **** // // Server-side namespace management functions // // **** /** * <p>unmark() is used to make any and all namespace values in this * field as available for use by other objects in the same editset. * When the editset is committed, any unmarked values will be * flushed from the namespace.</p> * * <p><b>*Calls synchronized methods on DBNameSpace*</b></p> */ final void unmark() { DBNameSpace namespace; DBEditSet editset; /* -- */ namespace = getFieldDef().getNameSpace(); editset = ((DBEditObject) owner).getEditSet(); if (namespace == null) { return; } if (!isVector()) { if (!namespace.unmark(editset, this.key(), this)) { throw new RuntimeException(ts.l("global.bad_unmark", this.key(), this)); } } else { synchronized (namespace) { for (int i = 0; i < size(); i++) { if (!namespace.testunmark(editset, key(i), this)) { throw new RuntimeException(ts.l("global.bad_unmark", this.key(), this)); } } for (int i = 0; i < size(); i++) { if (!namespace.unmark(editset, key(i), this)) { // "Error: testunmark() / unmark() inconsistency" throw new RuntimeException(ts.l("unmark.testunmark_problem")); } } return; } } } /** * <p>Unmark a specific value associated with this field, rather * than unmark all values associated with this field. Note * that this method does not check to see if the value is * currently associated with this field, it just goes ahead * and unmarks it. This is to be used by the vector * modifiers (setElement, addElement, deleteElement, etc.) * to keep track of namespace modifications as we go along.</p> * * <p>If there is no namespace associated with this field, this * method will always return true, as a no-op.</p> * * <p><b>*Calls synchronized methods on DBNameSpace*</b></p> */ final boolean unmark(Object value) { DBNameSpace namespace; DBEditSet editset; /* -- */ namespace = getFieldDef().getNameSpace(); editset = ((DBEditObject) owner).getEditSet(); if (namespace == null) { return true; // do a no-op } if (value == null) { return true; // no previous value } return namespace.unmark(editset, value, this); } /** * <p>mark() is used to mark any and all values in this field as taken * in the namespace. When the editset is committed, marked values * will be permanently reserved in the namespace. If the editset is * instead aborted, the namespace values will be returned to their * pre-editset status.</p> * * <p>If there is no namespace associated with this field, this * method will always return true, as a no-op.</p> * * <p><b>*Calls synchronized methods on DBNameSpace*</b></p> */ final boolean mark() { DBNameSpace namespace; DBEditSet editset; /* -- */ namespace = getFieldDef().getNameSpace(); if (namespace == null) { return true; // do a no-op } editset = ((DBEditObject) owner).getEditSet(); if (!isVector()) { return namespace.mark(editset, this.key(), this); } else { synchronized (namespace) { for (int i = 0; i < size(); i++) { if (!namespace.testmark(editset, key(i))) { return false; } } for (int i = 0; i < size(); i++) { if (!namespace.mark(editset, key(i), this)) { throw new RuntimeException("error: testmark / mark inconsistency"); } } return true; } } } /** * <p>Mark a specific value associated with this field, rather than * mark all values associated with this field. Note that this * method does not in any way associate this value with this field * (add it, set it, etc.), it just marks it. This is to be used by * the vector modifiers (setElement, addElement, etc.) to keep * track of namespace modifications as we go along.</p> * * <p><b>*Calls synchronized methods on DBNameSpace*</b></p> */ final boolean mark(Object value) { DBNameSpace namespace; DBEditSet editset; /* -- */ namespace = getFieldDef().getNameSpace(); editset = ((DBEditObject) owner).getEditSet(); if (namespace == null) { return false; // should we throw an exception? } if (value == null) { return false; // throw new NullPointerException("null value in mark()"); } if (editset == null) { throw new NullPointerException("null editset in mark()"); } return namespace.mark(editset, value, this); } // **** // // Methods for subclasses to override to implement the // behavior for this field. // // **** /** * Overridable method to determine whether an * Object submitted to this field is of an appropriate * type. */ abstract public boolean verifyTypeMatch(Object o); /** * <p>Overridable method to verify that an object submitted to this * field has an appropriate value.</p> * * <p>This check is more limited than that of verifyNewValue().. all it * does is make sure that the object parameter passes the simple * value constraints of the field. verifyNewValue() does that plus * a bunch more, including calling to the DBEditObject hook for the * containing object type to see whether it happens to feel like * accepting the new value or not.</p> * * <p>verifyBasicConstraints() is used to double check for values that * are already in fields, in addition to being used as a likely * component of verifyNewValue() to verify new values.</p> * * @return A ReturnVal indicating success or failure. May * be simply 'null' to indicate success if no feedback need * be provided. */ public ReturnVal verifyBasicConstraints(Object o) { return null; } /** * <p>Overridable method to verify that an object submitted to this * field has an appropriate value.</p> * * <p>This method is intended to make the final go/no go decision about * whether a given value is appropriate to be placed in this field, * by whatever means (vector add, vector replacement, scalar * replacement).</p> * * <p>This method is expected to call the * {@link arlut.csd.ganymede.server.DBEditObject#verifyNewValue(arlut.csd.ganymede.server.DBField,java.lang.Object)} * method on {@link arlut.csd.ganymede.server.DBEditObject} in order to allow custom * plugin classes to deny any given value that the plugin might not * care for, for whatever reason. Otherwise, the go/no-go decision * will be made based on the checks performed by * {@link arlut.csd.ganymede.server.DBField#verifyBasicConstraints(java.lang.Object) verifyBasicConstraints}.</p> * * <p>The ReturnVal that is returned may have transformedValue set, in * which case the code that calls this verifyNewValue() method * should consider transformedValue as replacing the 'o' parameter * as the value that verifyNewValue wants to be put into this field. * This usage of transformedValue is for canonicalizing input data.</p> * * @param o The object to be tested and/or transformed * * @return A ReturnVal indicating success or failure. May * be simply 'null' to indicate success if no feedback need * be provided. */ abstract public ReturnVal verifyNewValue(Object o); /** * <p>Overridable method to verify that the current {@link * arlut.csd.ganymede.server.DBSession DBSession} / {@link * arlut.csd.ganymede.server.DBEditSet DBEditSet} has permission to read * values from this field.</p> */ public boolean verifyReadPermission() { if (owner.getGSession() == null) { return true; // we don't know who is looking at us, assume it's a server-local access } PermEntry pe = owner.getFieldPerm(getID()); if (pe == null) { return false; } return pe.isVisible(); } /** * <p>Overridable method to verify that the current {@link * arlut.csd.ganymede.server.DBSession DBSession} / {@link * arlut.csd.ganymede.server.DBEditSet DBEditSet} has permission to read * values from this field.</p> * * <p>This version of verifyReadPermission() is intended to be used * in a context in which it would be too expensive to make a * read-only duplicate copy of a DBObject from the DBObjectBase's * object table, strictly for the purpose of associating a * GanymedeSession with the DBObject for permissions * verification.</p> */ public synchronized boolean verifyReadPermission(GanymedeSession gSession) { if (gSession == null) { return true; // we don't know who is looking at us, assume it's a server-local access } PermEntry pe = gSession.getPermManager().getPerm(owner, getID()); // if there is no permission explicitly recorded for the field, // inherit from the object as a whole if (pe == null) { pe = gSession.getPermManager().getPerm(owner); } if (pe == null) { return false; } return pe.isVisible(); } /** * <p>Overridable method to verify that the current DBSession / * DBEditSet has permission to write values into this field.</p> */ public synchronized boolean verifyWritePermission() { if (!(owner instanceof DBEditObject)) { return false; // if we're not in a transaction, we certainly can't be edited. } if (owner.getGSession() != null) { try { owner.getGSession().checklogin(); // mostly for the lastaction update side-effect } catch (NotLoggedInException ex) { return false; } } PermEntry pe = owner.getFieldPerm(getID()); if (pe == null) { return false; } return pe.isEditable(); } /** * <p>Sub-class hook to support elements for which the default * equals() test is inadequate, such as IP addresses (represented * as arrays of Byte[] objects.</p> * * <p>Returns -1 if the value was not found in this field.</p> * * <p>This method assumes that the calling method has already verified * that this is a vector field.</p> */ public int indexOfValue(Object value) { return getVectVal().indexOf(value); } /** * <p>Returns a Vector of the values of the elements in this field, if * a vector.</p> * * <p>This is intended to be used within the Ganymede server, it * bypasses the permissions checking that getValues() does.</p> * * <p>This method returns a safe copy of the contained Vector.</p> */ public Vector getValuesLocal() { if (!isVector()) { // "Vector method called on a scalar field: {0} in object {1}" throw new IllegalArgumentException(ts.l("global.oops_scalar", getName(), owner.getLabel())); } return new Vector(getVectVal()); // defensive copy } /** * <p>Returns an Object carrying the value held in this field.</p> * * <p>This is intended to be used within the Ganymede server, it bypasses * the permissions checking that getValues() does.</p> */ public synchronized Object getValueLocal() { if (isVector()) { // "Scalar method called on a vector field: {0} in object {1}" throw new IllegalArgumentException(ts.l("global.oops_vector", getName(), owner.getLabel())); } return value; } // *** // // The following two methods implement checkpoint and rollback facilities for // DBField. These methods save the field's internal state and restore it // on demand at a later time. The intent is to allow checkpoint/restore // without changing the object identity (memory address) of the DBField so // that the DBEditSet checkpoint/restore logic can work. // // *** /** * <p>This method is used to basically dump state out of this field * so that the {@link arlut.csd.ganymede.server.DBEditSet DBEditSet} * {@link arlut.csd.ganymede.server.DBEditSet#checkpoint(java.lang.String) checkpoint()} * code can restore it later if need be.</p> * * <p>This method is not synchronized because all operations performed * by this method are either synchronized at a lower level or are * atomic.</p> * * <p>Called by {@link arlut.csd.ganymede.server.DBEditObject DBEditObject}'s * {@link arlut.csd.ganymede.server.DBEditObject#checkpoint() checkpoint()} * method.</p> */ public synchronized Object checkpoint() { if (isVector()) { return new Vector(getVectVal()); } else { return value; } } /** * <p>This method is used to basically force state into this field.</p> * * <p>It is used to place a value or set of values that were known to * be good during the current transaction back into this field, * without creating or changing this DBField's object identity, and * without doing any of the checking or side effects that calling * setValue() will typically do.</p> * * <p>In particular, it is not necessary to subclass this method for * use with {@link arlut.csd.ganymede.server.InvidDBField InvidDBField}, since * the {@link arlut.csd.ganymede.server.DBEditSet#rollback(java.lang.String) rollback()} * method will always rollback all objects in the transaction at the same * time. It is not necessary to have the InvidDBField subclass handle * binding/unbinding during rollback, since all objects which could conceivably * be involved in a link will also have their own states rolled back.</p> * * <p>Called by {@link arlut.csd.ganymede.server.DBEditObject DBEditObject}'s * {@link arlut.csd.ganymede.server.DBEditObject#rollback(java.util.Hashtable) rollback()} * method.</p> */ public synchronized void rollback(Object oldval) { if (!(owner instanceof DBEditObject)) { throw new RuntimeException("Invalid rollback on field " + getName() + ", not in an editable context"); } if (isVector()) { if (!(oldval instanceof Vector)) { throw new RuntimeException("Invalid vector rollback on field " + getName()); } else { // in theory we perhaps should iterate through the oldval // Vector to make sure that each element is of the right // type.. in practice, that would be a lot of overhead to // guard against something that should never happen, // anyway. // // i'm just saying this to cover my ass in case it does, // so i'll know that i was deliberately rather than // accidentally stupid. this.value = oldval; } } else { if (!verifyTypeMatch(oldval)) { throw new RuntimeException("Invalid scalar rollback on field " + getName()); } else { this.value = oldval; } } } /** * <p>This method takes the result of an operation on this field * and wraps it with a {@link arlut.csd.ganymede.common.ReturnVal ReturnVal} * that encodes an instruction to the client to rescan * this field. This isn't normally necessary for most client * operations, but it is necessary for the case in which wizards * call DBField.setValue() on behalf of the client, because in those * cases, the client otherwise won't know that the wizard modified * the field.</p> * * <p>This makes for a significant bit of overhead on client calls * to the field modifier methods, but this is avoided if code * on the server uses setValueLocal(), setElementLocal(), addElementLocal(), * or deleteElementLocal() to make changes to a field.</p> * * <p>If you are ever in a situation where you want to use the local * variants of the modifier methods (to avoid permissions checking * overhead), but you <b>do</b> want to have the field's rescan * information returned, you can do something like:</p> * * <pre> * * return field.rescanThisField(field.setValueLocal(null)); * * </pre> */ public final ReturnVal rescanThisField(ReturnVal original) { if (!ReturnVal.didSucceed(original)) { return original; } if (original == null) { original = ReturnVal.success(); } if (this.getID() == owner.getLabelFieldID()) { original.setObjectLabelChanged(owner.getInvid(), this.getValueString()); } original.addRescanField(getOwner().getInvid(), getID()); return original; } /** * For debugging */ public String toString() { return "[" + owner.toString() + ":" + getName() + "]"; } /** * <p>Handy utility method for reporting namespace conflict. This * method will work to identify the object and field which is in * conflict, and will return an appropriate {@link * arlut.csd.ganymede.common.ReturnVal ReturnVal} with an * appropriate error dialog.</p> */ public ReturnVal getConflictDialog(String methodName, Object conflictValue) { DBNameSpace ns = getNameSpace(); try { DBField conflictField = ns.lookupPersistent(conflictValue); if (conflictField != null) { DBObject conflictObject = conflictField.getOwner(); String conflictLabel = conflictObject.getPathLabel(); String conflictClassName = conflictObject.getTypeName(); // This action could not be completed because "{0}" is already being used. // // {1} "{2}" contains this value in its {3} field. // // You can choose a different value here, or you can try to edit or delete the "{2}" object to remove the conflict. return Ganymede.createErrorDialog(this.getGSession(), ts.l("getConflictDialog.errorTitle", methodName), WordWrap.wrap(ts.l("getConflictDialog.persistentError", conflictValue, conflictClassName, conflictLabel, conflictField.getName()), 80)); } else { conflictField = ns.lookupShadow(conflictValue); DBObject conflictObject = conflictField.getOwner(); String conflictLabel = conflictObject.getPathLabel(); String conflictClassName = conflictObject.getTypeName(); // This action could not be completed because "{0}" is currently being manipulated in a concurrent transaction. // // {1} "{2}" contains this value in its {3} field. // // You can choose a different value here, or you can try to edit or delete the "{2}" object to remove the conflict. return Ganymede.createErrorDialog(this.getGSession(), ts.l("getConflictDialog.errorTitle", methodName), WordWrap.wrap(ts.l("getConflictDialog.transactionError", conflictValue, conflictClassName, conflictLabel, conflictField.getName()), 80)); } } catch (NullPointerException ex) { Ganymede.logError(ex); return Ganymede.createErrorDialog(this.getGSession(), ts.l("getConflictDialog.errorTitle", methodName), ts.l("getConflictDialog.simpleError", conflictValue)); } } /** * <p>Handy utility method for reporting an attempted duplicate * submission to a vector field.</p> */ public ReturnVal getDuplicateValueDialog(String methodName, Object conflictValue) { // "Server: Error in {0}" // "This action could not be performed because "{0}" is already contained in field {1} in object {2}." return Ganymede.createErrorDialog(this.getGSession(), ts.l("getDuplicateValueDialog.error_in_method_title", methodName), WordWrap.wrap(ts.l("getDuplicateValueDialog.error_body", String.valueOf(conflictValue), getName(), owner.getLabel()), 80)); } /** * <p>Handy utility method for reporting an attempted duplicate * submission to a vector field.</p> */ public ReturnVal getDuplicateValuesDialog(String methodName, String conflictValues) { // "Server: Error in {0}" // "This action could not be performed because "{0}" are already contained in field {1} in object {2}." return Ganymede.createErrorDialog(this.getGSession(), ts.l("getDuplicateValueDialog.error_in_method_title", methodName), WordWrap.wrap(ts.l("getDuplicateValuesDialog.error_body", conflictValues, getName(), owner.getLabel()), 80)); } }