/*
interfaceCustom.java
This file is a management class for interface objects in Ganymede.
Created: 15 October 1997
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.ganymede.common.GanyPermissionsException;
import arlut.csd.ganymede.common.Invid;
import arlut.csd.ganymede.common.IPAddress;
import arlut.csd.ganymede.common.NotLoggedInException;
import arlut.csd.ganymede.common.ObjectHandle;
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.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.IPDBField;
/*------------------------------------------------------------------------------
class
interfaceCustom
------------------------------------------------------------------------------*/
public class interfaceCustom extends DBEditObject implements SchemaConstants {
static final boolean debug = false;
// ---
systemCustom sysObj = null;
boolean inFinalizeAddrChange = false;
boolean inFinalizeNetChange = false;
/* -- */
/**
* Customization Constructor
*/
public interfaceCustom(DBObjectBase objectBase)
{
super(objectBase);
}
/**
* Create new object constructor
*/
public interfaceCustom(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 interfaceCustom(DBObject original, DBEditSet editset)
{
super(original, editset);
}
/**
* <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;
}
// if we changed networks so as not to require a MAC address for
// this interface, go ahead and null it out as part of our
// pre-commit activities.
if (this.isDefined(interfaceSchema.IPNET) && !fieldRequired(this, interfaceSchema.ETHERNETINFO))
{
return this.setFieldValueLocal(interfaceSchema.ETHERNETINFO, null);
}
return null;
}
/**
* Update the hidden label with a proposed new interface name.
*
* @param interfaceName The new name being given to this interface.
* May safely be null.
*
* @return A ReturnVal indicating success or failure in setting the
* label. If the label was changed successfully, a directive is
* encoded into the ReturnVal to cause the system containing this
* interface to refresh the label of this interface.
*/
private ReturnVal updateHiddenLabelNAME(String interfaceName)
{
IPDBField ipfield = this.getIPField(interfaceSchema.ADDRESS);
String IPAddress = null;
if (ipfield != null)
{
IPAddress = ipfield.getValueString();
}
// we'll only include our MAC address if the network we're
// associated with requires it.
String MACAddress = null;
Invid netInvid = (Invid) this.getFieldValueLocal(interfaceSchema.IPNET);
DBObject networkObj = this.lookupInvid(netInvid);
if (networkObj == null || networkObj.isSet(networkSchema.MACREQUIRED))
{
MACAddress = (String) this.getFieldValueLocal(interfaceSchema.ETHERNETINFO);
}
ReturnVal retVal = this.setFieldValueLocal(interfaceSchema.HIDDENLABEL,
genLabel(interfaceName,
IPAddress,
MACAddress));
if (ReturnVal.didSucceed(retVal))
{
return ReturnVal.success().requestRefresh(getParentSysObj().getInvid(), systemSchema.INTERFACES);
}
return retVal;
}
/**
* Update the hidden label with a proposed new IP Address.
*
* @param addr The new IP Address being given to this interface.
* May safely be null.
*
* @return A ReturnVal indicating success or failure in setting the
* label. If the label was changed successfully, a directive is
* encoded into the ReturnVal to cause the system containing this
* interface to refresh the label of this interface.
*/
private ReturnVal updateHiddenLabelIPADDR(IPAddress addr)
{
String interfaceName = (String) this.getFieldValueLocal(interfaceSchema.NAME);
String IPAddress = null;
if (addr != null)
{
IPAddress = addr.toString();
}
String MACAddress = null;
Invid netInvid = getParentSysObj().findMatchingNet(addr);
DBObject networkObj = this.lookupInvid(netInvid);
if (networkObj == null || networkObj.isSet(networkSchema.MACREQUIRED))
{
MACAddress = (String) this.getFieldValueLocal(interfaceSchema.ETHERNETINFO);
}
ReturnVal retVal = this.setFieldValueLocal(interfaceSchema.HIDDENLABEL,
genLabel(interfaceName,
IPAddress,
MACAddress));
if (ReturnVal.didSucceed(retVal))
{
return ReturnVal.success().requestRefresh(getParentSysObj().getInvid(), systemSchema.INTERFACES);
}
return retVal;
}
/**
* Update the hidden label with a proposed new MAC Address
*
* @param MACAddress The new MAC Address being given to this
* interface. May safely be null.
*
* @return A ReturnVal indicating success or failure in setting the
* label. If the label was changed successfully, a directive is
* encoded into the ReturnVal to cause the system containing this
* interface to refresh the label of this interface.
*/
private ReturnVal updateHiddenLabelMACADDR(String MACAddress)
{
String interfaceName = (String) this.getFieldValueLocal(interfaceSchema.NAME);
IPDBField ipfield = this.getIPField(interfaceSchema.ADDRESS);
String IPAddress = null;
if (ipfield != null)
{
IPAddress = ipfield.getValueString();
}
// we'll only include our MAC address if the network we're
// associated with requires it.
Invid netInvid = (Invid) this.getFieldValueLocal(interfaceSchema.IPNET);
DBObject networkObj = this.lookupInvid(netInvid);
if (networkObj != null && !networkObj.isSet(networkSchema.MACREQUIRED))
{
MACAddress = null;
}
ReturnVal retVal = this.setFieldValueLocal(interfaceSchema.HIDDENLABEL,
genLabel(interfaceName,
IPAddress,
MACAddress));
if (ReturnVal.didSucceed(retVal))
{
return ReturnVal.success().requestRefresh(getParentSysObj().getInvid(), systemSchema.INTERFACES);
}
return retVal;
}
private final String genLabel(String interfaceName, String ipString, String macAddress)
{
StringBuilder result = new StringBuilder();
boolean openIP = false;
boolean openMAC = false;
/* -- */
if (interfaceName != null)
{
result.append(interfaceName);
}
if (ipString != null && !ipString.equals(""))
{
if (result.length() != 0)
{
result.append(" ");
}
result.append("[");
result.append(ipString);
openIP = true;
}
if (macAddress != null && !macAddress.trim().equals(""))
{
if (result.length() != 0)
{
result.append(" ");
}
if (!openIP)
{
result.append("[");
}
else
{
result.append("- ");
}
result.append(macAddress);
openMAC = true;
}
if (openIP || openMAC)
{
result.append("]");
}
return result.toString();
}
/**
* <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)
{
if (field.getID() == interfaceSchema.IPNET)
{
return null; // no caching net choices, thankyouverymuch
}
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() != interfaceSchema.IPNET)
{
return super.obtainChoiceList(field);
}
QueryResult result = new QueryResult();
// get the vector of currently available nets from our containing
// System
Vector<ObjectHandle> ipNetVec = getParentSysObj().getAvailableNets();
if (ipNetVec != null)
{
for (ObjectHandle handle: ipNetVec)
{
result.addRow(handle);
}
}
return result;
}
/**
* <p>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()</p>
*
* <p>To be overridden on necessity in DBEditObject subclasses,
* particularly if you have a StringDBField that you want to force
* to pick from the list of choices provided by your DBEditObject
* subclass' obtainChoiceList() method.</p>
*/
@Override public boolean mustChoose(DBField field)
{
// Don't force the IPNET field to be chosen from the choices()
// list, since the custom finalizeSetValue logic in this class
// takes care of that for us, and because the custom code in
// this class modifies the choices in finalizeSetValue before
// InvidDBField.setValue() calls verifyNewValue(), which would
// normally check out the value selected against the results
// of choices().
if (field.getID() == interfaceSchema.IPNET)
{
return false;
}
return super.mustChoose(field);
}
/**
* <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 String getEmbeddedObjectDisplayLabelHook(DBObject object)
{
DBObject parent = object.getParentObj();
try
{
DBObject typeObj = object.lookupInvid((Invid)parent.getFieldValueLocal(systemSchema.SYSTEMTYPE), false);
if (typeObj.getFieldValueLocal(systemTypeSchema.SYSTEMTYPE).equals("IP Telephone"))
{
DBObject userObj = object.lookupInvid((Invid)parent.getFieldValueLocal(systemSchema.PRIMARYUSER), false);
return String.valueOf(object.getLabel()) + " (" + String.valueOf(parent.getLabel()) + " - " + String.valueOf(userObj.getLabel()) + ")";
}
}
catch (NullPointerException ex)
{
}
return String.valueOf(object.getLabel()) + " (" + String.valueOf(parent.getLabel()) + ")";
}
/**
* <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() == interfaceSchema.HIDDENLABEL)
{
return false;
}
// For the rest of our fields that we are concerned with, the
// session will be null if we are being checked outside of an
// editable context. If we are not being edited, we don't
// care.. if the fields are there, they can see them.
if (!(field.getOwner() instanceof interfaceCustom) && (session == null))
{
return true;
}
// if we only have a single interface in this system, we don't
// want the name field to be visible
if ((field.getID() == interfaceSchema.NAME) ||
(field.getID() == interfaceSchema.ALIASES))
{
interfaceCustom iObj;
DBObject owner = field.getOwner();
if (owner instanceof interfaceCustom)
{
iObj = (interfaceCustom) owner;
}
else
{
iObj = (interfaceCustom) session.editDBObject(owner.getInvid());
}
Vector<Invid> siblings = iObj.getSiblingInvids();
if (siblings.size() == 0)
{
return false;
}
else
{
return true;
}
}
if (field.getID() == interfaceSchema.ETHERNETINFO)
{
DBObject owner = field.getOwner();
DBObject networkObj = owner.lookupInvid((Invid)owner.getFieldValueLocal(interfaceSchema.IPNET));
if (networkObj != null && !networkObj.isSet(networkSchema.MACREQUIRED))
{
return false; // we don't need to show the ethernet field if no MAC address is required
}
}
return super.canSeeField(session, field);
}
/**
* <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 interfaceSchema.NAME:
// the name is required if and only if the parent
// object has more than one interface
Vector<Invid> siblings = getSiblingInvids(object);
if (siblings.size() == 0)
{
return false;
}
else
{
return true;
}
case interfaceSchema.ADDRESS:
case interfaceSchema.IPNET:
return true;
case interfaceSchema.ETHERNETINFO:
DBObject networkObj = object.lookupInvid((Invid)object.getFieldValueLocal(interfaceSchema.IPNET));
// If networkObj is null, the DBEditSet will trigger on the
// missing/unset IPNET field, so we don't need to worry about
// that here. We don't want to throw an exception, though.
if (networkObj != null)
{
return networkObj.isSet(networkSchema.MACREQUIRED);
}
}
return false;
}
/**
* <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 this embedded interface is being removed, we won't try to get
// fancy with the address/ipnet stuff.
if (isDeleting())
{
return null;
}
if (field.getID() == interfaceSchema.IPNET)
{
// if this net change was initiated by an approved ADDRESS change,
// we're not going to try to second-guess their address choice.
if (inFinalizeAddrChange)
{
ReturnVal retVal = ReturnVal.success();
retVal.addRescanField(this.getInvid(), interfaceSchema.ETHERNETINFO);
return retVal;
}
// if the net is being set to a net that matches what's already
// in the address field for some reason, we'll go ahead and ok it
IPAddress address = (IPAddress) getFieldValueLocal(interfaceSchema.ADDRESS);
if (address != null && systemCustom.checkMatchingNet(getDBSession(), (Invid) value, address))
{
if (debug)
{
System.err.println("interfaceCustom.finalizeSetValue(): approving ipnet change");
}
// some IPNETs don't require MAC addresses
ReturnVal retVal = ReturnVal.success();
retVal.addRescanField(this.getInvid(), interfaceSchema.ETHERNETINFO);
return retVal;
}
// okay, we didn't match, tell the system object to remember the
// address that was formerly associated with the old network value
if (field.getValueLocal() != null)
{
getParentSysObj().saveAddress(address);
}
if (value == null)
{
IPDBField ipfield = getIPField(interfaceSchema.ADDRESS);
inFinalizeNetChange = true;
ipfield.setValueLocal(null);
inFinalizeNetChange = false;
ReturnVal retVal = ReturnVal.success();
retVal.addRescanField(this.getInvid(), interfaceSchema.ADDRESS);
retVal.addRescanField(this.getInvid(), interfaceSchema.ETHERNETINFO);
return retVal.merge(updateHiddenLabelIPADDR(null));
}
// now find a new address for this object based on the network we
// are being asked to change to.
address = getParentSysObj().getAddress((Invid) value);
if (address == null)
{
return Ganymede.createErrorDialog(this.getGSession(),
"Network Full",
"There are no more addresses available in the " +
getDBSession().getObjectLabel((Invid) value) +
" network.");
}
// we've got a new IP address, go ahead and set it
IPDBField ipfield = getIPField(interfaceSchema.ADDRESS);
// set the inFinalizeNetChange variable around the call to
// setValueLocal() so that the recursive call to finalizeSetValue()
// doesn't waste time trying to find a network to match the
// new address before we complete the network change
inFinalizeNetChange = true;
ipfield.setValueLocal(address);
inFinalizeNetChange = false;
// and tell the client to rescan the address field to update
// the display
ReturnVal retVal = ReturnVal.success();
retVal.addRescanField(this.getInvid(), interfaceSchema.ADDRESS);
// and tell the client to rescan the ethernet info field as
// well, in case we don't need it any more
retVal.addRescanField(this.getInvid(), interfaceSchema.ETHERNETINFO);
return retVal.merge(updateHiddenLabelIPADDR(address));
}
if (field.getID() == interfaceSchema.ADDRESS)
{
Invid netInvid = (Invid) getFieldValueLocal(interfaceSchema.IPNET);
IPAddress address = (IPAddress) value;
// if the address is being set in response to a network change,
// don't bounce back and set the network again
if (inFinalizeNetChange)
{
return null;
}
if (systemCustom.checkMatchingNet(getDBSession(), netInvid, address))
{
// fine, no change to the network required
return updateHiddenLabelIPADDR(address);
}
// we need to find a new network to match, and to set that
// into our network field
netInvid = getParentSysObj().findMatchingNet(address);
if (netInvid == null)
{
return Ganymede.createErrorDialog(this.getGSession(),
"Unacceptable IP address",
"IP address " + address +
" does not match any network available to you.");
}
// we need to fix up the IP Network link to point to the
// network that matches the new address. We set
// inFinalizeAddrChange to let the recursive call to
// finalizeSetValue() spawned by setFieldValue() know not to
// try and choose a new IP address before we get a chance to
// return and okay the IP address change we are processing.
try
{
inFinalizeAddrChange = true;
ReturnVal retVal = this.setFieldValue(interfaceSchema.IPNET, netInvid);
inFinalizeAddrChange = false;
if (retVal != null && !retVal.didSucceed())
{
return Ganymede.createErrorDialog(this.getGSession(),
"schema error",
"interfaceCustom.finalizeSetValue(): failed to set ip net");
}
retVal = ReturnVal.success();
retVal.addRescanField(this.getInvid(), interfaceSchema.IPNET);
return retVal.merge(updateHiddenLabelIPADDR(address));
}
catch (GanyPermissionsException ex)
{
return Ganymede.createErrorDialog(this.getGSession(),
"permissions", "permissions error setting network " + ex);
}
}
// we also need to update the hidden label if the MAC address or
// interface name was changed
switch (field.getID())
{
case interfaceSchema.ETHERNETINFO:
return updateHiddenLabelMACADDR((String) value);
case interfaceSchema.NAME:
return updateHiddenLabelNAME((String) value);
}
return null;
}
private systemCustom getParentSysObj()
{
if (sysObj == null)
{
Invid sysInvid = (Invid) getFieldValueLocal(SchemaConstants.ContainerField);
// we *have* to use editDBObject() here because we need access to the custom
// object.. it makes no sense for us to be pulled out for editing without
// our parent also being edited.
if (sysInvid != null)
{
sysObj = (systemCustom) getDBSession().editDBObject(sysInvid);
}
}
return sysObj;
}
/**
* <p>This private method returns a vector of invids, being a list
* of other interfaces defined in the system we are defined in.</p>
*/
private Vector<Invid> getSiblingInvids()
{
return getSiblingInvids(this);
}
/**
* <p>This private method returns a vector of invids, being a list of
* other interfaces defined in the system we are defined in.</p>
*
* <p><b>*PSEUDOSTATIC*</b></p>
*/
private Vector<Invid> getSiblingInvids(DBObject object)
{
// we can't use getParentSysObj() because that only works in an editing
// context. The checkRequiredFields() call may be called from a task
// that wants to just sweep through the database looking for incomplete
// objects, so we arrange to find a DBObject reference to parentObj
// so that we can get access to the list of our siblings.
DBObject parentObj = object.getParentObj();
Vector<Invid> result = (Vector<Invid>) parentObj.getFieldValuesLocal(systemSchema.INTERFACES);
// we are not our own sibling.
result.remove(object.getInvid());
if (debug)
{
System.err.println("interfaceCustom.getSiblingInvids(): " + object.getInvid() +
" has return value: " + result);
}
return result;
}
/**
* <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() == interfaceSchema.ETHERNETINFO)
{
// no worries about thread synchronization here, since
// equality and assignment are both atomic operators
String etherString = (String) value;
String transformedString;
if ((etherString == null) || (etherString.equals("")))
{
return null; // okay by us!
}
try
{
transformedString = verifyAndTransformEthernetInfo(etherString);
}
catch (MACAddressException ex)
{
return Ganymede.createErrorDialog(this.getGSession(),
"Bad Ethernet Address",
"You entered an invalid ethernet address (" + etherString +
")\n\nEthernet addresses should be in the form of 6 colon-separated" +
" hexadecimal numbers.\n\nExample:\n01:a2:cc:04:12:2d\n");
}
if (transformedString.equals(etherString))
{
return super.verifyNewValue(field, value); // no change, so no problem
}
// tell the client that we'd like it to take the string that
// they gave us and replace it with the reformatted one we
// crafted.
ReturnVal result = ReturnVal.success();
result.setTransformedValueObject(transformedString, this.getInvid(), field.getID());
return result;
}
return super.verifyNewValue(field, value);
}
/**
* This method verifies and canonicalizes an ethernet info input
* from the client. If the input is a valid MAC address, or can be
* turned into a valid MAC address, the MAC address is returned. If
* the input cannot be made into a properly formatted MAC address, a
* MACAddressException will be thrown instead.
*/
private String verifyAndTransformEthernetInfo(String input) throws MACAddressException
{
String transform1;
/* -- */
if (input == null)
{
return null;
}
input = input.trim();
if (input.equals("0"))
{
return "00:00:00:00:00:00";
}
char [] ary = input.toCharArray();
int digit_count = 0;
for (int i = 0; i < ary.length; i++)
{
if (Character.digit(ary[i], 16) != -1)
{
digit_count++;
}
}
if (digit_count == 12)
{
// yay, we've got precisely enough hex digits for an ethernet
// address, whatever the separators may or may not be. Go
// through and extract them and generate a new string.
StringBuilder result = new StringBuilder();
digit_count = 0;
for (int i = 0; i < ary.length; i++)
{
if (Character.digit(ary[i], 16) != -1)
{
if (digit_count > 0 && digit_count % 2 == 0)
{
result.append(":");
}
result.append(ary[i]);
digit_count++;
}
}
return result.toString().toLowerCase();
}
// we'll try to deal with missing leading zeros on hex bytes, but
// we still need to have between 6 and 12 hex digits
if (digit_count < 6 || digit_count > 12)
{
throw new MACAddressException();
}
// now, even though we have less than 12 hex digits, we may still have
// a valid input, as the user may have skipped leading zeros on
// bytes. in order for us to make sense of such a state, we'll
// need to have some separators. we'll accept seperators of
// spaces, dashes, colons, and periods.
String[] pieces = input.split(":|\\.|\\-|\\s");
if (pieces.length != 6)
{
throw new MACAddressException();
}
StringBuilder result = new StringBuilder();
for (int i = 0; i < pieces.length; i++)
{
if (pieces[i].length() > 2)
{
throw new MACAddressException();
}
if (pieces[i].length() == 1 && Character.digit(pieces[i].charAt(0), 16) != -1)
{
if (result.length() != 0)
{
result.append(":");
}
result.append("0");
result.append(pieces[i].charAt(0));
continue;
}
if (pieces[i].length() == 2 && Character.digit(pieces[i].charAt(0), 16) != -1 && Character.digit(pieces[i].charAt(1), 16) != -1)
{
if (result.length() != 0)
{
result.append(":");
}
result.append(pieces[i]);
continue;
}
throw new MACAddressException();
}
return result.toString().toLowerCase();
}
/**
* <p>Customization method to verify overall consistency of a
* DBObject. This method is intended to be overridden in
* DBEditObject subclasses, and will be called by {@link
* arlut.csd.ganymede.server.DBEditObject#commitPhase1()
* commitPhase1()} to verify the readiness of this object for
* commit. The DBObject passed to this method will be a
* DBEditObject, complete with that object's GanymedeSession
* reference if this method is called during transaction commit, and
* that session reference may be used by the verifying code if the
* code needs to access the database.</p>
*
* <p>This method is for custom checks specific to custom
* DBEditObject subclasses. Standard checking for missing fields
* for which fieldRequired() returns true is done by {@link
* arlut.csd.ganymede.server.DBEditSet#commit_checkObjectMissingFields(arlut.csd.ganymede.server.DBEditObject)}
* during {@link
* arlut.csd.ganymede.server.DBEditSet#commit_handlePhase1()}.</p>
*
* <p>To be overridden on necessity in DBEditObject subclasses.</p>
*
* <p><b>*PSEUDOSTATIC*</b></p>
*
* @return A ReturnVal indicating success or failure. May
* be simply 'null' to indicate success if no feedback need
* be provided.
*/
@Override public ReturnVal consistencyCheck(DBObject object)
{
IPAddress address = (IPAddress) object.getFieldValueLocal(interfaceSchema.ADDRESS);
Invid netInvid = (Invid) object.getFieldValueLocal(interfaceSchema.IPNET);
if (address != null && !systemCustom.checkMatchingNet(getDBSession(), netInvid, address))
{
return Ganymede.createErrorDialog(object.getGSession(),
"Bad IP Address",
"Error, I.P. number/network mismatch in " + object.toString());
}
return null;
}
}
/**
* Context-specific exception for handling parse errors for submitted
* Ethernet Info values.
*/
class MACAddressException extends RuntimeException {
public MACAddressException()
{
}
}