/*
ownerCustom.java
This file is a management class for owner-group records 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.server;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Vector;
import arlut.csd.Util.TranslationService;
import arlut.csd.Util.VectorUtils;
import arlut.csd.ganymede.common.Invid;
import arlut.csd.ganymede.common.ReturnVal;
import arlut.csd.ganymede.common.SchemaConstants;
/*------------------------------------------------------------------------------
class
ownerCustom
------------------------------------------------------------------------------*/
public class ownerCustom extends DBEditObject implements SchemaConstants {
/**
* TranslationService object for handling string localization in the
* Ganymede server.
*/
static final TranslationService ts = TranslationService.getTranslationService("arlut.csd.ganymede.server.ownerCustom");
/**
* Archived property value to determine if we're going to allow
* admins to give objects they own to owner groups they don't belong
* to.
*/
static private Boolean _donateOK = null;
/**
* <p>This method takes an {@link arlut.csd.ganymede.common.Invid Invid} for
* an Owner Group {@link arlut.csd.ganymede.server.DBObject DBObject}
* and returns a Vector of Strings containing the list
* of email addresses for that owner group.</p>
*/
static public Vector<String> getAddresses(Invid ownerInvid, DBSession session)
{
DBObject ownerGroup;
Vector<String> result = new Vector<String>();
StringDBField externalAddresses;
/* -- */
if (session == null)
{
session = Ganymede.internalSession.getDBSession();
}
ownerGroup = session.viewDBObject(ownerInvid);
if (ownerGroup == null)
{
if (debug)
{
System.err.println("getOwnerGroupAddresses(): Couldn't look up owner group " +
ownerInvid.toString());
}
return result;
}
// should we cc: the admins?
Boolean cc = (Boolean) ownerGroup.getFieldValueLocal(SchemaConstants.OwnerCcAdmins);
if (cc != null && cc.booleanValue())
{
Vector<String> adminList = new Vector();
Vector<Invid> adminInvidList;
adminInvidList = (Vector<Invid>) ownerGroup.getFieldValuesLocal(SchemaConstants.OwnerMembersField);
for (Invid adminInvid: adminInvidList)
{
String adminAddr = adminPersonaCustom.convertAdminInvidToString(adminInvid, session);
if (adminAddr != null)
{
adminList.add(adminAddr);
}
}
result = VectorUtils.union(result, adminList);
}
// do we have any external addresses?
externalAddresses = ownerGroup.getStringField(SchemaConstants.OwnerExternalMail);
if (externalAddresses != null)
{
// we don't have to clone externalAddresses.getValuesLocal()
// since union() will copy the elements rather than just
// setting result to the vector returned by
// externalAddresses.getValuesLocal() if result is currently
// null.
result = VectorUtils.union(result, (Vector<String>) externalAddresses.getValuesLocal());
}
return result;
}
/**
* <p>Returns true if the property 'ganymede.allowdonations' contains
* the value 'true'.</p>
*
* <p>If ganymede.allowdonations is set to 'true', the Ganymede server
* will allow an admin who has edit authority over an object to
* grant access to that object to owner groups that the admin is not
* a member of.</p>
*
* <p>This is used to allow an admin who has edit authority over an
* object to transfer or donate it to another owner group,
* relinquishing responsibility for the object.</p>
*/
static public boolean donateOK()
{
if (_donateOK == null)
{
String donateOKString = System.getProperty("ganymede.allowdonations");
if (donateOKString != null && donateOKString.equalsIgnoreCase("true"))
{
_donateOK = Boolean.valueOf(true);
}
else
{
_donateOK = Boolean.valueOf(false);
}
}
return _donateOK.booleanValue();
}
/**
* Customization Constructor
*/
public ownerCustom(DBObjectBase objectBase) throws RemoteException
{
super(objectBase);
}
/**
* Create new object constructor
*/
public ownerCustom(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 ownerCustom(DBObject original, DBEditSet editset) throws RemoteException
{
super(original, editset);
}
// and now the customizations
/**
* <p>This method provides a hook to allow custom DBEditObject subclasses to
* indicate that the given object is interested in receiving notification
* when changes involving it occur, and can provide one or more addresses for
* such notification to go to.</p>
*
* <p><b>*PSEUDOSTATIC*</b></p>
*/
public boolean hasEmailTarget(DBObject object)
{
return true;
}
/**
* <p>This method provides a hook to allow custom DBEditObject subclasses to
* return a List of Strings comprising a list of addresses to be
* notified above and beyond the normal owner group notification when
* the given object is changed in a transaction. Used for letting end-users
* be notified of changes to their account, etc.</p>
*
* <p>If no email targets are present in this object, either a null value
* or an empty List may be returned.</p>
*
* <p>To be overridden on necessity in DBEditObject subclasses.</p>
*
* <p><b>*PSEUDOSTATIC*</b></p>
*/
public List<String> getEmailTargets(DBObject object)
{
Set<String> set = new HashSet<String>();
DBSession session;
Boolean cc = (Boolean) object.getFieldValueLocal(SchemaConstants.OwnerCcAdmins);
/* -- */
try
{
session = object.getGSession().getDBSession();
}
catch (NullPointerException ex)
{
session = Ganymede.internalSession.getDBSession();
}
if (cc != null && cc.booleanValue())
{
List<Invid> members = (List<Invid>) object.getFieldValuesLocal(SchemaConstants.OwnerMembersField);
if (members != null)
{
for (Invid admin: members)
{
DBObject adminObj = session.viewDBObject(admin, true);
List<String> emailTargets = (List<String>) adminObj.getEmailTargets();
if (emailTargets != null)
{
set.addAll(emailTargets);
}
}
}
}
List<String> externalOwners = (List<String>) object.getFieldValuesLocal(SchemaConstants.OwnerExternalMail);
if (externalOwners != null)
{
set.addAll(externalOwners);
}
return new ArrayList<String>(set);
}
/**
* <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>
*/
public boolean mustChoose(DBField field)
{
// We don't force a choice on the object owned field, because
// it can point to anything.
if (field.getID() == SchemaConstants.OwnerObjectsOwned)
{
return false;
}
return super.mustChoose(field);
}
/**
* <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>If there is no caching key, this method will return null.</p>
*/
public Object obtainChoicesKey(DBField field)
{
// We want to force the client to check the field choices here,
// since the choices will never include itself as a valid choice.
if (field.getID() == SchemaConstants.OwnerListField)
{
return null;
}
return super.obtainChoicesKey(field);
}
/**
* <p>This method provides a hook that can be used to check any values
* to be set in any field in this object. Subclasses of
* DBEditObject should override this method, implementing basically
* a large switch statement to check for any given field whether the
* submitted value is acceptable given the current state of the
* object.</p>
*
* <p>Question: what synchronization issues are going to be needed
* between DBEditObject and DBField to insure that we can have
* a reliable verifyNewValue method here?</p>
*/
public ReturnVal verifyNewValue(DBField field, Object value)
{
// we don't want owner groups to ever explicitly list themselves
// as owners.
if (field.getID() == SchemaConstants.OwnerListField)
{
Invid testInvid = (Invid) value;
if (testInvid != null && testInvid.equals(field.getOwner().getInvid()))
{
// "Owner Object Error"
// "Can''t make an owner group own itself. All owner groups implicitly own themselves, anyway."
return Ganymede.createErrorDialog(ts.l("verifyNewValue.error_title"),
ts.l("verifyNewValue.self_ownership"));
}
}
return super.verifyNewValue(field, value);
}
/**
* <p>This method is used to control whether or not it is acceptable to
* rescind a link to the given field in this DBObject type when the
* user only has editing access for the source InvidDBField and not
* the target.</p>
*
* @param object The object that the link is to be removed from
* @param fieldID The field that the linkk is to be removed from
*/
public boolean anonymousUnlinkOK(DBObject object, short fieldID)
{
// In order to take an admin out of an owner group, you have
// to have permission to edit that owner group, as well as
// the admin.
if (fieldID == SchemaConstants.OwnerMembersField)
{
return false;
}
return super.anonymousUnlinkOK(object, fieldID);
}
/**
* <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>See {@link arlut.csd.ganymede.server.DBEditObject#anonymousLinkOK(arlut.csd.ganymede.server.DBObject,short,
* arlut.csd.ganymede.server.DBObject,short,arlut.csd.ganymede.server.GanymedeSession)
* anonymousLinkOK(obj,short,obj,short,GanymedeSession)} for details on
* anonymousLinkOK() method chaining.</p>
*
* <p>Note that the {@link
* arlut.csd.ganymede.server.DBEditObject#choiceListHasExceptions(arlut.csd.ganymede.server.DBField)
* choiceListHasExceptions()} method will call this version of anonymousLinkOK()
* with a null targetObject, to determine that the client should not
* use its cache for an InvidDBField's choices. Any overriding done
* of this method must be able to handle a null targetObject, or else
* an exception will be thrown inappropriately.</p>
*
* <p>The only reason to consult targetObject in any case is to
* allow or disallow anonymous object linking to a field based on
* the current state of the target object. If you are just writing
* generic anonymous linking rules for a field in this object type,
* targetObject won't concern you anyway. If you do care about the
* targetObject's state, though, you have to be prepared to handle
* a null valued targetObject.</p>
*
* <p><b>*PSEUDOSTATIC*</b></p>
*
* @param targetObject The object that the link is to be created in (may be null)
* @param targetFieldID The field that the link is to be created in
*/
public boolean anonymousLinkOK(DBObject targetObject, short targetFieldID)
{
// If you can edit an object, you have permission to 'donate' that
// object to another owner group, even if you're not a member of
// that owner group.
//
// This is so admins can 'give away' objects to another owner
// group if they need to.
//
// Note that we don't use symmetric links for objects owned by
// owner groups anymore, to avoid locking owner groups all the
// time when database objects are manipulated in Ganymede.
// Because of this, we need to check on the BackLinksField
// constant, even though BackLinksField is virtual these days.
if (donateOK() && targetFieldID == SchemaConstants.BackLinksField)
{
return true;
}
return super.anonymousLinkOK(targetObject, targetFieldID);
}
}