/*
mapEntryCustom.java
This file is a management class for Automounter map entry objects in Ganymede.
Created: 9 December 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.HashSet;
import java.util.Vector;
import arlut.csd.ganymede.common.Invid;
import arlut.csd.ganymede.common.NotLoggedInException;
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;
/*------------------------------------------------------------------------------
class
mapEntryCustom
------------------------------------------------------------------------------*/
/**
* This class represents the automounter entry objects that are
* embedded in user objects in the Ganymede schema.
*/
public class mapEntryCustom extends DBEditObject implements SchemaConstants, mapEntrySchema {
static PermEntry noEditPerm = new PermEntry(true, false, false, false);
// ---
/**
* Customization Constructor
*/
public mapEntryCustom(DBObjectBase objectBase)
{
super(objectBase);
}
/**
* Create new object constructor
*/
public mapEntryCustom(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 mapEntryCustom(DBObject original, DBEditSet editset)
{
super(original, editset);
}
/**
* <p>Customization method to allow this Ganymede object type to
* override the default permissions mechanism for special
* purposes.</p>
*
* <p>If this method returns null, the default permissions mechanism
* will be followed. If not, the permissions system will grant the
* permissions specified by this method for access to the given
* field, and no further elaboration of the permission will be
* performed. If permOverride() returns a non-null value for a
* given field, permExpand() will not be consulted for that field.
* Just as with permExpand(), this method can never cause greater
* permissions to be granted to a field than is available to the
* object as a whole, and this override capability does not
* apply to operations performed in supergash mode.</p>
*
* <p>This method should be used very sparingly.</p>
*
* <p>To be overridden on necessity in DBEditObject subclasses.</p>
*
* <p><b>*PSEUDOSTATIC*</b></p>
*/
@Override public PermEntry permOverride(GanymedeSession session, DBObject object, short fieldid)
{
if (fieldid != mapEntrySchema.MAP)
{
return null;
}
DBField field = object.getField(fieldid);
if (field == null)
{
return null;
}
String label = field.getValueString();
// XXX Note: this schema assumes that all users will have entries in auto.home.default
if (label.equals("auto.home.default"))
{
return noEditPerm;
}
return null;
}
/**
* <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 mapEntrySchema.MAP:
case mapEntrySchema.VOLUME:
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)
{
if (field.getID() == mapEntrySchema.MAP)
{
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() != mapEntrySchema.MAP)
{
return super.obtainChoiceList(field);
}
InvidDBField invf = getInvidField(mapEntrySchema.MAP);
if (invf.getValueString().equals("auto.home.default"))
{
return new QueryResult(); // can't change a map reference set to auto.home.default
}
// ok, we are returning the list of choices for what
// map to put this entry into. We don't want it
// to include any maps that already have an entry
// for this user.
// first, get the list of map entries contained in this
// object.
HashSet<Invid> mapsToSkip = new HashSet<Invid>();
for (Invid sibling: getSiblingInvids())
{
DBObject entry = getDBSession().viewDBObject(sibling);
Invid mapInvid = (Invid) entry.getFieldValueLocal(mapEntrySchema.MAP);
mapsToSkip.add(mapInvid);
}
// ok, mapsToSkip has a list of invid's to skip in our choice list.
QueryResult result = new QueryResult();
QueryResult baseList = super.obtainChoiceList(field);
for (int i = 0; i < baseList.size(); i++)
{
Invid mapInvid = baseList.getInvid(i);
if (!mapsToSkip.contains(mapInvid))
{
result.addRow(baseList.getObjectHandle(i));
}
}
return result;
}
/**
* <p>This method is the hook that DBEditObject subclasses use to interpose
* {@link arlut.csd.ganymede.server.GanymediatorWizard wizards} when a field's
* value is being changed.</p>
*
* <p>Whenever a field is changed in this object, this method will be
* called with details about the change. This method can refuse to
* perform the operation, it can make changes to other objects in
* the database in response to the requested operation, or it can
* choose to allow the operation to continue as requested.</p>
*
* <p>In the latter two cases, the wizardHook code may specify a list
* of fields and/or objects that the client may need to update in
* order to maintain a consistent view of the database.</p>
*
* <p>If server-local code has called
* {@link arlut.csd.ganymede.server.GanymedeSession#enableOversight(boolean)
* enableOversight(false)},
* this method will never be
* called. This mode of operation is intended only for initial
* bulk-loading of the database.</p>
*
* <p>This method may also be bypassed when server-side code uses
* setValueLocal() and the like to make changes in the database.</p>
*
* <p>This method is called before the finalize*() methods.. the finalize*()
* methods is where last minute cascading changes should be performed..
* Note as well that wizardHook() is called before the namespace checking
* for the proposed value is performed, while the finalize*() methods are
* called after the namespace checking.</p>
*
* <p>The operation parameter will be a small integer, and should hold one of the
* following values:</p>
*
* <dl>
* <dt>1 - SETVAL</dt>
* <dd>This operation is used whenever a simple scalar field is having
* it's value set. param1 will be the value being placed into the field.</dd>
* <dt>2 - SETELEMENT</dt>
* <dd>This operation is used whenever a value in a vector field is being
* set. param1 will be an Integer holding the element index, and
* param2 will be the value being set.</dd>
* <dt>3 - ADDELEMENT</dt>
* <dd>This operation is used whenever a value is being added to the
* end of a vector field. param1 will be the value being added.</dd>
* <dt>4 - DELELEMENT</dt>
* <dd>This operation is used whenever a value in a vector field is being
* deleted. param1 will be an Integer holding the element index.</dd>
* <dt>5 - ADDELEMENTS</dt>
* <dd>This operation is used whenever a set of elements is being
* added to a vector field en masse. param1 will be a Vector containing
* the values that are being added.</dd>
* <dt>6 - DELELEMENTS</dt>
* <dd>This operation is used whenever a set of elements is being
* deleted from a vector field en masse. param1 will be a Vector containing
* the values that are being deleted.</dd>
* <dt>7 - SETPASSPLAIN</dt>
* <dd>This operation is used when a password field is having its password
* set using a plaintext source. param1 will be a String containing the
* submitted password, or null if the password is being cleared.</dd>
* <dt>8 - SETPASSCRYPT</dt>
* <dd>This operation is used when a password field is having its password
* set using a UNIX crypt() hashed source. param1 will be a String containing the
* submitted hashed password, or null if the password is being cleared.</dd>
* <dt>9 - SETPASSMD5</dt>
* <dd>This operation is used when a password field is having its password
* set using an md5Ccrypt() hashed source. param1 will be a String containing the
* submitted hashed password, or null if the password is being cleared.</dd>
* <dt>10 - SETPASSWINHASHES</dt>
* <dd>This operation is used when a password field is having its password
* set using Windows style password hashes. param1 will be the password in
* LANMAN hash form, param2 will be the password in NT Unicode MD4 hash
* form. Either or both of param1 and param2 may be null.</dd>
* <dt>11 - SETPASSAPACHEMD5</dt>
* <dd>This operation is used when a password field is having its
* password set using the Apache variant of the md5crypt algorithm.
* param1 will be the password in Apache md5crypt hash form, or null
* if the password hash is being cleared. param2 will be null.</dd>
* <dt>12 - SETPASSSSHA</dt>
* <dd>This operation is used when a password field is having its
* password set using the OpenLDAP-style SSHA password hash. param1
* will be the password in SSHA form, or null if the password is
* being cleared. param2 will be null.</dd>
* <dt>13 - SETPASS_SHAUNIXCRYPT</dt>
* <dd>This operation is used when a password field is having its
* password set using Ulrich Drepper's SHA256 or SHA512 Unix Crypt
* algorithms. param1 will be the password in SHA Unix Crypt form,
* or null if the password is being cleared. param2 will be
* null.</dd>
* <dt>14 - SETPASS_BCRYPT</dt>
* <dd>This operation is used when a password field is having its
* password set using the OpenBSD-style BCrypt password hash. param1
* will be the password in BCrypt form, or null if the password is
* being cleared. param2 will be null.</dd>
* </dl>
*
* <p>To be overridden on necessity in DBEditObject subclasses.</p>
*
* @return null if the operation is approved without comment, or a
* ReturnVal object indicating success or failure, objects and
* fields to be rescanned by the client, and a doNormalProcessing
* flag that will indicate to the field code whether or not the
* operation should continue to completion using the field's
* standard logic. <b>It is very important that wizardHook return a
* new ReturnVal(true, true) if the wizardHook wishes to simply
* specify rescan information while having the field perform its
* standard operation.</b> wizardHook() may return new
* ReturnVal(true, false) if the wizardHook performs the operation
* (or a logically related operation) itself. The same holds true
* for the respond() method in GanymediatorWizard subclasses.
*/
@Override public ReturnVal wizardHook(DBField field, int operation, Object param1, Object param2)
{
if ((field.getID() != mapEntrySchema.MAP) ||
(operation != DBEditObject.SETVAL))
{
return null; // by default, we just ok whatever
}
InvidDBField invf = getInvidField(mapEntrySchema.MAP);
// if we aren't deleting this entry, reject any attempt to unlink
// us from auto.home.default, if we are linked there.
if (!isDeleting() && invf.getValueString().equals("auto.home.default"))
{
return Ganymede.createErrorDialog(this.getGSession(),
"Error, auto.home.default is required",
"Sorry, it is mandatory to have a directory entry on the auto.home.default map.");
}
// ok, we want to go ahead and approve the operation, but we want
// to cause the client to rescan the MAP field in all of our
// siblings so that their choice list gets updated to not show
// whatever map *we* just chose.
// Create a ReturnVal which will cause the field.setValue() call
// which triggered us to continue normal processing, and return
// our list of rescan preferences to the client.
ReturnVal result = new ReturnVal(true, true);
for (Invid sibling: getSiblingInvids())
{
result.addRescanField(sibling, mapEntrySchema.MAP);
}
return result;
}
/**
* <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 field, field2;
StringBuilder buff = new StringBuilder();
/*
Okay, here's the deal. For automounter map entries contained in
user objects, our XML_Label is comprised of the name of the
containing user / the name of the map that this entry defines a
membership in.
For display in the client, the user information is not helpful,
because that's already evident in the client because the entry
is embedded in a user object. So we're generating a synthetic
"map name/volume name" label for display in the client.
Note that this can't actually be the unique XML_Label for this
entry, because many users may have automounter map entries with
the same map name and volume name, and labels need to be unique.
*/
if ((object == null) || (object.getTypeID() != getTypeID()))
{
return null;
}
field = object.getInvidField(MAP); // map name
field2 = object.getInvidField(VOLUME); // volume
try
{
if (field != null)
{
buff.append(field.getValueString() + ":");
}
if (field2 != null)
{
buff.append(field2.getValueString());
}
}
catch (IllegalArgumentException ex)
{
buff.append("<?:?>");
}
return buff.toString();
}
/**
* <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() == mapEntrySchema.XMLLABEL)
{
return false;
}
return super.canSeeField(session, field);
}
private Vector<Invid> getSiblingInvids()
{
Vector<Invid> result = (Vector<Invid>) getParentObj().getFieldValuesLocal(userSchema.VOLUMES);
result.remove(getInvid()); // we are not our own sibling.
return result;
}
/**
* <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 either of our displayed values are changed, tell the client
// to rescan the field that contains us in our parent, to refresh
// the label
switch (field.getID())
{
case mapEntrySchema.MAP:
case mapEntrySchema.VOLUME:
return ReturnVal.success().addRescanField(this.getParentInvid(), userSchema.VOLUMES);
default:
// we may be setting the containing invid, in which case we
// can't call getParentInvid() because the containing invid
// hasn't been set yet. ;-)
return null;
}
}
String getMapName()
{
return getDBSession().getObjectLabel((Invid) getFieldValueLocal(mapEntrySchema.MAP));
}
String getOriginalMapName()
{
return getDBSession().getObjectLabel((Invid) getOriginal().getFieldValueLocal(mapEntrySchema.MAP));
}
Invid getMapInvid()
{
return (Invid) getFieldValueLocal(mapEntrySchema.MAP);
}
Invid getOriginalMapInvid()
{
return (Invid) getOriginal().getFieldValueLocal(mapEntrySchema.MAP);
}
String getVolumeName()
{
return getDBSession().getObjectLabel((Invid) getFieldValueLocal(mapEntrySchema.VOLUME));
}
String getOriginalVolumeName()
{
return getDBSession().getObjectLabel((Invid) getOriginal().getFieldValueLocal(mapEntrySchema.VOLUME));
}
Invid getVolumeInvid()
{
return (Invid) getFieldValueLocal(mapEntrySchema.VOLUME);
}
Invid getOriginalVolumeInvid()
{
return (Invid) getOriginal().getFieldValueLocal(mapEntrySchema.VOLUME);
}
}