/*
IRISListCustom.java
Custom plug-in for managing fields in the IRIS email list object type.
Created: 2 February 2011
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.gasharl;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import arlut.csd.Util.VectorUtils;
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.Ganymede;
import arlut.csd.ganymede.server.GanymedeSession;
/*------------------------------------------------------------------------------
class
IRISListCustom
------------------------------------------------------------------------------*/
/**
* <p>Custom plug-in for managing fields in the IRIS email list object
* type.</p>
*/
public class IRISListCustom extends DBEditObject implements SchemaConstants, IRISListSchema {
/**
* Customization Constructor
*/
public IRISListCustom(DBObjectBase objectBase)
{
super(objectBase);
}
/**
* Create new object constructor
*/
public IRISListCustom(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 IRISListCustom(DBObject original, DBEditSet editset)
{
super(original, editset);
}
/**
* <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() == IRISListSchema.MEMBERS)
{
return new QueryResult();
}
return super.obtainChoiceList(field);
}
/**
* <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)
{
if (field.getID() == IRISListSchema.MEMBERS)
{
return false; // don't force choice
}
return super.mustChoose(field);
}
/**
* <p>This method is used to control whether or not it is acceptable to
* make a link to the given field in this
* {@link arlut.csd.ganymede.server.DBObject DBObject} type when the
* user only has editing access for the source
* {@link arlut.csd.ganymede.server.InvidDBField InvidDBField} and not
* the target.</p>
*
* <p>This version of anonymousLinkOK takes additional parameters
* to allow an object type to decide that it does or does not want
* to allow a link based on what field of what object wants to link
* to it.</P>
*
* <p>By default, the 3 variants of the DBEditObject
* anonymousLinkOK() method are chained together, so that the
* customizer can choose which level of detail he is interested in.
* {@link arlut.csd.ganymede.server.InvidDBField InvidDBField}'s
* {@link
* arlut.csd.ganymede.server.InvidDBField#bind(arlut.csd.ganymede.common.Invid,arlut.csd.ganymede.common.Invid,boolean)
* bind()} method calls this version. This version calls the three
* parameter version, which calls the two parameter version, which
* returns false by default. Customizers can implement any of the
* three versions, but unless you maintain the version chaining
* yourself, there's no point to implementing more than one of
* them.</P>
*
* <p><b>*PSEUDOSTATIC*</b></p>
*
* @param targetObject The object that the link is to be created in
* @param targetFieldID The field that the link is to be created in
* @param sourceObject The object on the other side of the proposed link
* @param sourceFieldID The field on the other side of the proposed link
* @param gsession Who is trying to do this linking?
*/
@Override public boolean anonymousLinkOK(DBObject targetObject, short targetFieldID,
DBObject sourceObject, short sourceFieldID,
GanymedeSession gsession)
{
// if someone tries to put this list in another email list, let
// them.
if ((targetFieldID == SchemaConstants.BackLinksField) &&
(sourceObject.getTypeID() == emailListSchema.BASE) &&
(sourceFieldID == emailListSchema.MEMBERS))
{
return true;
}
// the default anonymousLinkOK() method returns false
return super.anonymousLinkOK(targetObject, targetFieldID,
sourceObject, sourceFieldID, gsession);
}
/**
* <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>
*
* <p>Note that this method will not be called if the controlling
* GanymedeSession's enableOversight is turned off, as in
* bulk loading.</p>
*
* <p><b>*PSEUDOSTATIC*</b></p>
*/
@Override public boolean fieldRequired(DBObject object, short fieldid)
{
// the email list name is required
if (fieldid == LISTNAME)
{
return true;
}
return super.fieldRequired(object, fieldid);
}
/**
* <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() == QUERY)
{
String queryString = (String) value;
if (queryString != null && !queryString.trim().equals(""))
{
if (queryString.toLowerCase().contains("update"))
{
return Ganymede.createErrorDialog(this.getGSession(),
null,
"Update statements not allowed in Query field.");
}
}
}
return super.verifyNewValue(field, value);
}
/**
* <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)
{
try
{
if (field.getID() == QUERY)
{
String queryString = (String) value;
if (queryString == null || queryString.trim().equals(""))
{
// set query members to the empty list
return setQueryMembers(new HashSet<String>());
}
else
{
try
{
return setQueryMembers(IRISLink.getUsernames(queryString));
}
catch (java.sql.SQLException ex)
{
return Ganymede.createErrorDialog(this.getGSession(),
null,
ex.getMessage());
}
}
}
}
catch (NotLoggedInException ex)
{
}
return null;
}
/**
* <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 == MEMBERS)
{
return PermEntry.getPermEntry(true, false, true, true);
}
return null;
}
/**
* <p>This custom server-side method is used to set the members in the
* members field to the list of users generated by the query string.</p>
*
* <p>Returns a non-null ReturnVal indicating success or throws a
* RuntimeException on failure.</p>
*/
public ReturnVal setQueryMembers(Set<String> users) throws NotLoggedInException
{
DBField memberField = this.getField(MEMBERS);
Date x = new Date();
try
{
editset.checkpoint(x.toString());
memberField.setUndefined(true);
for (String username: users)
{
Invid userInvid = getGSession().findLabeledObject(username, SchemaConstants.UserBase);
if (userInvid != null)
{
memberField.addElementLocal(userInvid);
}
}
ReturnVal result = ReturnVal.success();
result.requestRefresh(this.getInvid(), MEMBERS);
return result;
}
catch (Exception ex)
{
editset.rollback(x.toString());
throw new RuntimeException(ex);
}
finally
{
editset.popCheckpoint(x.toString());
}
}
/**
* <p>Queries IRIS to update the list of members in the passed
* listObject.</p>
*
* <p>Returns null if no change was made, a successful ReturnVal if
* a change was made, and a failure ReturnVal if there was some
* problem.</p>
*/
static public ReturnVal handleUpdate(DBObject listObject, GanymedeSession gsession) throws NotLoggedInException
{
String queryString = (String) listObject.getFieldValueLocal(IRISListSchema.QUERY);
if (queryString == null || queryString.trim().equals(""))
{
return null;
}
Set<String> userNames = null;
try
{
userNames = IRISLink.getUsernames(queryString);
}
catch (java.sql.SQLException ex)
{
return Ganymede.createErrorDialog(listObject.getGSession(),
null,
ex.getMessage());
}
List<Invid> userInvids = new ArrayList<Invid>();
for (String user: userNames)
{
Invid userInvid = gsession.findLabeledObject(user, SchemaConstants.UserBase);
if (userInvid != null)
{
userInvids.add(userInvid);
}
}
if (VectorUtils.equalMembers(userInvids, listObject.getFieldValuesLocal(IRISListSchema.MEMBERS)))
{
return null;
}
// we need to make a change
ReturnVal retVal = gsession.edit_db_object(listObject.getInvid());
if (!ReturnVal.didSucceed(retVal))
{
return retVal;
}
IRISListCustom listEditObject = (IRISListCustom) retVal.getObject();
return listEditObject.setQueryMembers(userNames);
}
}