/* objectEventCustom.java This file is a management class for object event-class records in Ganymede. Created: 9 July 1998 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 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.rmi.RemoteException; import java.util.Vector; import arlut.csd.ganymede.common.Invid; import arlut.csd.ganymede.common.NotLoggedInException; import arlut.csd.ganymede.common.ObjectStatus; import arlut.csd.ganymede.common.QueryResult; import arlut.csd.ganymede.common.ReturnVal; import arlut.csd.ganymede.common.SchemaConstants; import arlut.csd.Util.TranslationService; /*------------------------------------------------------------------------------ class objectEventCustom ------------------------------------------------------------------------------*/ public class objectEventCustom extends DBEditObject implements SchemaConstants { /** * TranslationService object for handling string localization in the * Ganymede server. */ static final TranslationService ts = TranslationService.getTranslationService("arlut.csd.ganymede.server.objectEventCustom"); /** * We're going to present the user with a list of recommended event * names to choose from. */ static private QueryResult eventNames = null; static { eventNames = new QueryResult(); eventNames.addRow(null, "objectcreated", false); eventNames.addRow(null, "objectchanged", false); eventNames.addRow(null, "inactivateobject", false); eventNames.addRow(null, "deleteobject", false); eventNames.addRow(null, "reactivateobject", false); eventNames.addRow(null, "expirationwarn", false); eventNames.addRow(null, "expirenotify", false); eventNames.addRow(null, "removalwarn", false); eventNames.addRow(null, "removenotify", false); } // --- /** * Since object types can only be changed by the schema editor, we'll * cache the object type list for the duration of this object's being * edited. We could even make this a static, but then we'd have to * have the DBSchemaEdit code know to clear it when the schema was * edited, which would be a hassle. */ QueryResult objectTypeList = null; /* -- */ /** * Customization Constructor */ public objectEventCustom(DBObjectBase objectBase) throws RemoteException { super(objectBase); } /** * Create new object constructor */ public objectEventCustom(DBObjectBase objectBase, Invid invid, DBEditSet editset) throws RemoteException { super(objectBase, invid, editset); } /** * Check-out constructor, used by DBObject.createShadow() * to pull out an object for editing. */ public objectEventCustom(DBObject original, DBEditSet editset) throws RemoteException { super(original, editset); } /** * We'll use DBObject's identity-based equals */ @Override public boolean equals(Object param) { return super.equals(param); } /** * <p>Customization method to control whether a specified field is * required to be defined at commit time for a given object.</p> * * <p>To be overridden in DBEditObject subclasses.</p> */ @Override public boolean fieldRequired(DBObject object, short fieldid) { // both fields defined in event are required switch (fieldid) { case SchemaConstants.ObjectEventToken: case SchemaConstants.ObjectEventName: return true; } return false; } /** * This method provides a hook that a DBEditObject subclass * can use to indicate whether a given field can only * choose from a choice provided by obtainChoiceList() */ @Override public boolean mustChoose(DBField field) { // by default, we assume that InvidDBField's are always // must choose. if (field instanceof InvidDBField) { return true; } if (field.getID() == SchemaConstants.ObjectEventObjectName) { return true; } return false; } /** * <p>This method provides a hook that can be used to generate * choice lists for invid and string fields that provide * such. String and Invid DBFields will call their owner's * obtainChoiceList() method to get a list of valid choices.</p> * * <p>This method will provide a reasonable default for targetted * invid fields.</p> */ @Override public QueryResult obtainChoiceList(DBField field) throws NotLoggedInException { if (field.getID() == SchemaConstants.ObjectEventObjectName) { if (objectTypeList == null) { objectTypeList = new QueryResult(); for (String elem: Ganymede.db.getBaseNameList()) { objectTypeList.addRow(null, elem, false); } } return objectTypeList; } if (field.getID() == SchemaConstants.ObjectEventToken) { return eventNames.getCopy(); } return super.obtainChoiceList(field); } /** * <p>This method allows the DBEditObject to have executive approval * of any scalar set operation, and to take any special actions in * reaction to the set. When a scalar field has its value set, it * will call its owners finalizeSetValue() method, passing itself as * the <field> parameter, and passing the new value to be * approved as the <value> parameter. A Ganymede customizer * who creates custom subclasses of the DBEditObject class can * override the finalizeSetValue() method and write his own logic * to examine any change and either approve or reject the change.</p> * * <p>A custom finalizeSetValue() method will typically need to * examine the field parameter to see which field is being changed, * and then do the appropriate checking based on the value * parameter. The finalizeSetValue() method can call the normal * this.getFieldValueLocal() type calls to examine the current state * of the object, if such information is necessary to make * appropriate decisions.</p> * * <p>If finalizeSetValue() returns null or a ReturnVal object with * a positive success value, the DBField that called us is * guaranteed to proceed to make the change to its value. If this * method returns a non-success code in its ReturnVal, as with the * result of a call to Ganymede.createErrorDialog(), the DBField * that called us will not make the change, and the field will be * left unchanged. Any error dialog returned from finalizeSetValue() * will be passed to the user.</p> * * <p>The DBField that called us will take care of all standard * checks on the operation (including a call to our own * verifyNewValue() method before calling this method. Under normal * circumstances, we won't need to do anything here. * finalizeSetValue() is useful when you need to do unusually * involved checks, and for when you want a chance to trigger other * changes in response to a particular field's value being * changed.</p> * * @return A ReturnVal indicating success or failure. May * be simply 'null' to indicate success if no feedback need * be provided. */ @Override public ReturnVal finalizeSetValue(DBField field, Object value) { if (field.getID() == SchemaConstants.ObjectEventObjectName) { // let the field be cleared if this object is being deleted. if (value == null) { return null; } DBObjectBase base = Ganymede.db.getObjectBase((String) value); if (base == null) { // "Error, no object type matching "{0}" could be found. This is probably an error in the Ganymede code." return Ganymede.createErrorDialog(ts.l("finalizeSetValue.bad_base", (String) value)); } ReturnVal retVal = null; retVal = setFieldValueLocal(SchemaConstants.ObjectEventObjectType, Integer.valueOf(base.getTypeID())); if (!ReturnVal.didSucceed(retVal)) { return retVal; } // the change was accepted and made sense so far.. update our hidden label field return ReturnVal.merge(retVal, updateLabel((String) value, (String) field.getOwner().getFieldValueLocal(SchemaConstants.ObjectEventToken))); } if (field.getID() == SchemaConstants.ObjectEventToken) { return updateLabel((String) field.getOwner().getFieldValueLocal(SchemaConstants.ObjectEventObjectName), (String) value); } return null; } /** * <p>This method provides a pre-commit hook that runs after the * user has hit commit but before the system has established write * locks for the commit.</p> * * <p>The intended purpose of this hook is to allow objects that * dynamically maintain hidden label fields to update those fields * from the contents of the object's other fields at commit * time.</p> * * <p>This method runs in a checkpointed context. If this method * fails in any operation, you should return a ReturnVal with a * failure dialog encoded, and the transaction's commit will be * blocked and a dialog explaining the problem will be presented to * the user.</p> * * <p>To be overridden on necessity in DBEditObject subclasses.</p> * * @return A ReturnVal indicating success or failure. May * be simply 'null' to indicate success if no feedback need * be provided. */ @Override public ReturnVal preCommitHook() { if (this.getStatus() == ObjectStatus.DELETING || this.getStatus() == ObjectStatus.DROPPING) { return null; } return updateLabel((String) getFieldValueLocal(SchemaConstants.ObjectEventObjectName), (String) getFieldValueLocal(SchemaConstants.ObjectEventToken)); } /** * This local method updates the hidden, composite, label field for * us. */ ReturnVal updateLabel(String typeName, String token) { // we only set the label if we have both a typename and a // token.. otherwise, the setFieldValueLocal() call we do here // will allocate a partial label in the namespace, and once a // namespace value is associated with a transaction, it sticks // until the transaction is committed or cancelled. // // by only setting null or a complete label, we avoid having // partial names block other transactions who might be assembling // an object event themselves. if (typeName == null || token == null) { return setFieldValueLocal(SchemaConstants.ObjectEventLabel, null); } StringBuilder result = new StringBuilder(); result.append(typeName); result.append(":"); result.append(token); return setFieldValueLocal(SchemaConstants.ObjectEventLabel, result.toString()); } /** * <p>Customization method to verify whether the user should be able * to see a specific field in a given object. Instances of DBField * will wind up calling up to here to let us override the normal * visibility process.</p> * * <p>Note that it is permissible for session to be null, in which * case this method will always return the default visiblity for the * field in question.</p> * * <p>If field is not from an object of the same base as this * DBEditObject, an exception will be thrown.</p> * * <p>To be overridden in DBEditObject subclasses.</p> */ @Override public boolean canSeeField(DBSession session, DBField field) { // by default, return the field definition's visibility if (field.getObjTypeID() != getTypeID()) { throw new IllegalArgumentException("field/object mismatch"); } // We don't want the user to see the ObjectEventObjectType // field, since we use it as a scratch pad internally for // keeping the name correct. if (field.getID() == SchemaConstants.ObjectEventObjectType) { return false; } return field.getFieldDef().isVisible(); } }