/* dhcpEntryCustom.java This file is a management class for Automounter map entry objects in Ganymede. Created: 10 October 2007 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.gasharl; import java.util.Vector; import arlut.csd.Util.VectorUtils; import arlut.csd.ganymede.common.Invid; import arlut.csd.ganymede.common.NotLoggedInException; import arlut.csd.ganymede.common.ObjectStatus; import arlut.csd.ganymede.common.PermEntry; import arlut.csd.ganymede.common.QueryResult; import arlut.csd.ganymede.common.ReturnVal; import arlut.csd.ganymede.common.SchemaConstants; import arlut.csd.ganymede.server.DBEditObject; import arlut.csd.ganymede.server.DBEditSet; import arlut.csd.ganymede.server.DBField; import arlut.csd.ganymede.server.DBObject; import arlut.csd.ganymede.server.DBObjectBase; import arlut.csd.ganymede.server.DBSession; import arlut.csd.ganymede.server.Ganymede; import arlut.csd.ganymede.server.GanymedeSession; import arlut.csd.ganymede.server.InvidDBField; import arlut.csd.ganymede.server.StringDBField; /*------------------------------------------------------------------------------ class dhcpEntryCustom ------------------------------------------------------------------------------*/ /** * This class represents the option value objects that are embedded in * the options field in the DHCP Entry object. */ public class dhcpEntryCustom extends DBEditObject implements SchemaConstants, dhcpEntrySchema { private final static boolean debug = false; /** * * Customization Constructor * */ public dhcpEntryCustom(DBObjectBase objectBase) { super(objectBase); } /** * * Create new object constructor * */ public dhcpEntryCustom(DBObjectBase objectBase, Invid invid, DBEditSet editset) { super(objectBase, invid, editset); } /** * * Check-out constructor, used by DBObject.createShadow() * to pull out an object for editing. * */ public dhcpEntryCustom(DBObject original, DBEditSet editset) { super(original, editset); } /** * <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 on necessity in DBEditObject subclasses.</p> * * <p>Note that this method will not be called if the controlling * GanymedeSession's enableOversight is turned off, as in * bulk loading.</p> * * <p>Note as well that the designated label field for objects are * always required, whatever this method returns, and that this * requirement holds without regard to the GanymedeSession's * enableOversight value.</p> * * <p><b>*PSEUDOSTATIC*</b></p> */ @Override public boolean fieldRequired(DBObject object, short fieldid) { switch (fieldid) { case dhcpEntrySchema.LABEL: case dhcpEntrySchema.TYPE: case dhcpEntrySchema.VALUE: return true; } return false; } /** * <p>This method returns a key that can be used by the client to * cache the value returned by choices(). If the client already has * the key cached on the client side, it can provide the choice list * from its cache rather than calling choices() on this object * again.</p> * * <p>The default logic in this method is designed to cause the * client to cache choice lists for invid fields in the 'all objects * of invid target type' cache bucket. If your InvidDBField needs * to provide a restricted subset of objects of the targeted type as * the choice list, you'll need to override this method to either * return null (to turn off choice list caching), or generate some * kind of unique key that won't collide with the Short objects used * to represent the default object list caches.</p> * * <p>See also the {@link * arlut.csd.ganymede.server.DBEditObject#choiceListHasExceptions(arlut.csd.ganymede.server.DBField)} * hook, which controls whether or not the default logic will * encourage the client to cache a given InvidDBField's choice * list.</p> * * <p>If there is no caching key, this method will return null.</p> */ @Override public Object obtainChoicesKey(DBField field) { // We want to have the client always query for values in the type // field, since we are going to be dynamically filtering values // out in order to prevent multiple entries with identical type // selections. if (field.getID() == dhcpEntrySchema.TYPE) { return null; } return super.obtainChoicesKey(field); } /** * <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, filtered by the GanymedeSession's * visibilityFilterInvids list.</p> * * <p>NOTE: This method does not need to be synchronized. Making this * synchronized can lead to DBEditObject/DBSession nested monitor * deadlocks.</p> */ @Override public QueryResult obtainChoiceList(DBField field) throws NotLoggedInException { if (field.getID() != dhcpEntrySchema.TYPE) { return super.obtainChoiceList(field); } // Dynamically construct our custom filtered list of available // types for this entry InvidDBField invf = getInvidField(dhcpEntrySchema.TYPE); // ok, we are returning the list of choices for what type this // entry should belong to. We don't want it to include any types // that we already have an entry for. Vector<Invid> typesToSkip = new Vector(); Vector<Invid> siblings = getSiblingInvids(); Invid typeInvid; for (int i = 0; i < siblings.size(); i++) { DBObject entry = lookupInvid(siblings.get(i), false); typeInvid = (Invid) entry.getFieldValueLocal(dhcpEntrySchema.TYPE); typesToSkip.add(typeInvid); } // ok, typesToSkip has a list of invid's to skip in our choice // list. Vector<Invid> suggestedTypes = super.obtainChoiceList(field).getInvids(); Vector<Invid> acceptableTypes = VectorUtils.difference(suggestedTypes, typesToSkip); QueryResult result = new QueryResult(); for (Invid type: acceptableTypes) { result.addRow(type, lookupInvidLabel(type), false); } return result; } private boolean ownedBySystem() { return this.ownedBySystem(this); } private boolean ownedBySystem(DBObject object) { return object.getParentInvid().getType() == systemSchema.BASE; } private boolean ownedByDHCPGroup() { return this.ownedByDHCPGroup(this); } private boolean ownedByDHCPGroup(DBObject object) { return object.getParentInvid().getType() == dhcpGroupSchema.BASE; } private boolean ownedByDHCPNetwork() { return this.ownedByDHCPNetwork(this); } private boolean ownedByDHCPNetwork(DBObject object) { return object.getParentInvid().getType() == dhcpNetworkSchema.BASE; } private boolean ownedByDHCPSubnet() { return this.ownedByDHCPSubnet(this); } private boolean ownedByDHCPSubnet(DBObject object) { return object.getParentInvid().getType() == dhcpSubnetSchema.BASE; } private Vector<Invid> getSiblingInvids() { Vector<Invid> result = null; if (ownedByDHCPGroup()) { result = (Vector<Invid>) getParentObj().getFieldValuesLocal(dhcpGroupSchema.OPTIONS); } else if (ownedBySystem()) { result = (Vector<Invid>) getParentObj().getFieldValuesLocal(systemSchema.DHCPOPTIONS); } else if (ownedByDHCPNetwork()) { Vector<Invid> optionsVect = (Vector<Invid>) getParentObj().getFieldValuesLocal(dhcpNetworkSchema.OPTIONS); if (optionsVect.contains(getInvid())) { result = optionsVect; } if (result == null) { throw new RuntimeException("couldn't find our own invid in parent dhcp network fields."); } } else if (ownedByDHCPSubnet()) { Vector<Invid> optionsVect = (Vector<Invid>) getParentObj().getFieldValuesLocal(dhcpSubnetSchema.OPTIONS); if (optionsVect.contains(getInvid())) { result = optionsVect; } else if (getParentObj().isDefined(dhcpSubnetSchema.GUEST_OPTIONS)) { Vector<Invid> guestOptionsVect = (Vector<Invid>) getParentObj().getFieldValuesLocal(dhcpSubnetSchema.GUEST_OPTIONS); if (guestOptionsVect.contains(getInvid())) { result = guestOptionsVect; } } if (result == null) { throw new RuntimeException("couldn't find our own invid in parent dhcp subnet fields."); } } // we are not our own sibling. result.remove(getInvid()); return result; } /** * <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; } String parentName = lookupInvidLabel((Invid) getFieldValueLocal(SchemaConstants.ContainerField)); return setHiddenLabel(parentName); } /** * This method is used to update the hidden label field (which we * have to have for xml address-ability reasons) to match the name * of the parent object and the option type that we're pointing to. */ public ReturnVal setHiddenLabel(String parentName) { Invid typeInvid = (Invid) getFieldValueLocal(dhcpEntrySchema.TYPE); return setFieldValueLocal(dhcpEntrySchema.LABEL, parentName + ":" + String.valueOf(lookupInvidLabel(typeInvid))); } /** * <p>If this DBEditObject is managing an embedded object, the * getEmbeddedObjectLabel() can be overridden to display a synthetic * label in the context of viewing or editing the containing object, * and when doing queries on the containing type.</p> * * <p>The getLabel() method will not consult this hook, however, and * embedded objects will be represented with their unique label * field when processed in an XML context.</p> * * <b>*PSEUDOSTATIC*</b> */ @Override public final String getEmbeddedObjectDisplayLabelHook(DBObject object) { InvidDBField typeField; StringDBField valueField; StringBuilder buff = new StringBuilder(); /* -- */ if ((object == null) || (object.getTypeID() != getTypeID())) { return null; } typeField = object.getInvidField(TYPE); valueField = object.getStringField(VALUE); if (typeField != null) { if (typeField.getValueLocal() != null) { buff.append(typeField.getValueString() + " : "); } } if (valueField != null) { if (valueField.getValueLocal() != null) { buff.append(valueField.getValueString()); } } return buff.toString(); } /** * <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 synchronized ReturnVal finalizeSetValue(DBField field, Object value) { ReturnVal result = null; Invid parentInvid = getParentInvid(); if (isDeleting()) { return null; // we're being deleted } if (field.getID() == TYPE && value != null) { Invid oldInvid = (Invid) field.getValueLocal(); if (oldInvid == null) { // we set the type after the value. make sure the value // is compatible with the type. StringDBField valueField = this.getStringField(dhcpEntrySchema.VALUE); String valueString = (String) valueField.getValueLocal(); if (valueString != null) { DBObject verifyObject = lookupInvid((Invid) value); result = ReturnVal.merge(result, dhcpOptionCustom.verifyAcceptableValue(verifyObject, valueString)); } } else { DBObject oldOptionObject = lookupInvid(oldInvid); String oldOptionType = (String) oldOptionObject.getFieldValueLocal(dhcpOptionSchema.OPTIONTYPE); DBObject newOptionObject = lookupInvid((Invid) value); String newOptionType = (String) newOptionObject.getFieldValueLocal(dhcpOptionSchema.OPTIONTYPE); if (!newOptionType.equals(oldOptionType)) { // we've changed to an incompatible option, clear the // value field. StringDBField valueField = getStringField(dhcpEntrySchema.VALUE); result = valueField.setValueLocal(""); if (result != null && !result.didSucceed()) { return result; } if (result == null) { result = new ReturnVal(true, true); } result.addRescanField(getInvid(), dhcpEntrySchema.VALUE); } } } // make the client refresh the displayed label for this embedded // dhcpEntry if the TYPE or VALUE has been changed if (field.getID() == TYPE || field.getID() == VALUE) { if (result == null) { result = new ReturnVal(true, true); } if (ownedByDHCPGroup()) { result.addRescanField(parentInvid, dhcpGroupSchema.OPTIONS); } else if (ownedBySystem()) { result.addRescanField(parentInvid, systemSchema.DHCPOPTIONS); } if (field.getID() == TYPE) { // force the client to requery legal (and non-taken) types Vector<Invid> siblings = getSiblingInvids(); for (Invid sibling: siblings) { result.addRescanField(sibling, dhcpEntrySchema.TYPE); } } return result; } return result; // success by default } /** * <p>Provides a hook that can be used to approve, disapprove, * and/or transform any values to be set in any field in this * object.</p> * * <p>verifyNewValue can be used to canonicalize a * submitted value. The verifyNewValue method may call * {@link arlut.csd.ganymede.common.ReturnVal#setTransformedValueObject(java.lang.Object, arlut.csd.ganymede.common.Invid, short) setTransformedValue()} * on the ReturnVal returned in order to substitute a new value for * the provided value prior to any other processing on the server.</p> * * <p>This method is called before any NameSpace checking is done, before the * {@link arlut.csd.ganymede.server.DBEditObject#wizardHook(arlut.csd.ganymede.server.DBField,int,java.lang.Object,java.lang.Object) wizardHook()} * method, and before the appropriate * {@link arlut.csd.ganymede.server.DBEditObject#finalizeSetValue(arlut.csd.ganymede.server.DBField, Object) finalizeSetValue()}, * {@link arlut.csd.ganymede.server.DBEditObject#finalizeSetElement(arlut.csd.ganymede.server.DBField, int, Object) finalizeSetElement()}, * {@link arlut.csd.ganymede.server.DBEditObject#finalizeAddElement(arlut.csd.ganymede.server.DBField, java.lang.Object) finalizeAddElement()}, * or {@link arlut.csd.ganymede.server.DBEditObject#finalizeAddElements(arlut.csd.ganymede.server.DBField, java.util.Vector) finalizeAddElements()} * method is called.</p> * * @param field The DBField contained within this object whose value * is being changed * @param value The value that is being proposed to go into field. * * @return A ReturnVal indicating success or failure. May be simply * 'null' to indicate success if no feedback need be provided. If * {@link arlut.csd.ganymede.common.ReturnVal#hasTransformedValue() hasTransformedValue()} * returns true when callled on the returned ReturnVal, the value * returned by {@link arlut.csd.ganymede.common.ReturnVal#getTransformedValueObject() getTransformedValueObject()} * will be used for all further processing in the server, and will * be the value actually saved in the DBStore. */ @Override public ReturnVal verifyNewValue(DBField field, Object value) { if (field.getID() == dhcpEntrySchema.VALUE) { if (debug) { Ganymede.debug("attempting to verify: " + String.valueOf(value)); } String inString = (String) value; String transformedString; if ((inString == null) || (inString.equals(""))) { return null; // okay by us! } Invid dhcpType = (Invid) getFieldValueLocal(dhcpEntrySchema.TYPE); if (dhcpType == null) { // okay, we'll verify it later. return null; } DBObject verifyObject = lookupInvid(dhcpType); ReturnVal retVal = dhcpOptionCustom.verifyAcceptableValue(verifyObject, inString); if (retVal == null) { if (debug) { Ganymede.debug("verifying as is: " + String.valueOf(value)); } return super.verifyNewValue(field, value); // no change, so no problem } else if (retVal.didSucceed() && retVal.hasTransformedValue()) { retVal.requestRefresh(this.getInvid(), dhcpEntrySchema.VALUE); } return retVal; } return super.verifyNewValue(field, value); } /** * <p>Customization method to verify whether the user should be able to * see a specific field in a given object. Instances of * {@link arlut.csd.ganymede.server.DBField 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 on necessity in DBEditObject subclasses.</p> * * <p><b>*PSEUDOSTATIC*</b></p> */ @Override public boolean canSeeField(DBSession session, DBField field) { // don't show off our hidden label for direct editing or viewing if (field.getID() == dhcpEntrySchema.LABEL) { return false; } return super.canSeeField(session, field); } }