/*
* EPerson.java
*
* Version: $Revision: 4742 $
*
* Date: $Date: 2010-02-05 12:11:05 +0000 (Fri, 05 Feb 2010) $
*
* Copyright (c) 2002-2009, The DSpace Foundation. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* - Neither the name of the DSpace Foundation nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*/
package org.dspace.eperson;
import java.sql.SQLException;
import java.util.List;
import java.util.Vector;
import org.apache.log4j.Logger;
import org.dspace.authorize.AuthorizeException;
import org.dspace.authorize.AuthorizeManager;
import org.dspace.content.DSpaceObject;
import org.dspace.core.ConfigurationManager;
import org.dspace.core.Constants;
import org.dspace.core.Context;
import org.dspace.core.LogManager;
import org.dspace.core.Utils;
import org.dspace.event.Event;
import org.dspace.storage.rdbms.DatabaseManager;
import org.dspace.storage.rdbms.TableRow;
import org.dspace.storage.rdbms.TableRowIterator;
/**
* Class representing an e-person.
*
* @author David Stuve
* @version $Revision: 4742 $
*/
public class EPerson extends DSpaceObject
{
/** The e-mail field (for sorting) */
public static final int EMAIL = 1;
/** The last name (for sorting) */
public static final int LASTNAME = 2;
/** The e-mail field (for sorting) */
public static final int ID = 3;
/** The netid field (for sorting) */
public static final int NETID = 4;
/** The e-mail field (for sorting) */
public static final int LANGUAGE = 5;
/** log4j logger */
private static Logger log = Logger.getLogger(EPerson.class);
/** Our context */
private Context myContext;
/** The row in the table representing this eperson */
private TableRow myRow;
/** Flag set when data is modified, for events */
private boolean modified;
/** Flag set when metadata is modified, for events */
private boolean modifiedMetadata;
/**
* Construct an EPerson
*
* @param context
* the context this object exists in
* @param row
* the corresponding row in the table
*/
EPerson(Context context, TableRow row)
{
myContext = context;
myRow = row;
// Cache ourselves
context.cache(this, row.getIntColumn("eperson_id"));
modified = modifiedMetadata = false;
clearDetails();
}
/**
* Get an EPerson from the database.
*
* @param context
* DSpace context object
* @param id
* ID of the EPerson
*
* @return the EPerson format, or null if the ID is invalid.
*/
public static EPerson find(Context context, int id) throws SQLException
{
// First check the cache
EPerson fromCache = (EPerson) context.fromCache(EPerson.class, id);
if (fromCache != null)
{
return fromCache;
}
TableRow row = DatabaseManager.find(context, "eperson", id);
if (row == null)
{
return null;
}
else
{
return new EPerson(context, row);
}
}
/**
* Find the eperson by their email address
*
* @return EPerson
*/
public static EPerson findByEmail(Context context, String email)
throws SQLException, AuthorizeException
{
if (email == null)
{
return null;
}
// All email addresses are stored as lowercase, so ensure that the email address is lowercased for the lookup
TableRow row = DatabaseManager.findByUnique(context, "eperson",
"email", email.toLowerCase());
if (row == null)
{
return null;
}
else
{
// First check the cache
EPerson fromCache = (EPerson) context.fromCache(EPerson.class, row
.getIntColumn("eperson_id"));
if (fromCache != null)
{
return fromCache;
}
else
{
return new EPerson(context, row);
}
}
}
/**
* Find the eperson by their netid
*
* @param context
* DSpace context
* @param netid
* Network ID
*
* @return corresponding EPerson, or <code>null</code>
*/
public static EPerson findByNetid(Context context, String netid)
throws SQLException
{
if (netid == null)
return null;
TableRow row = DatabaseManager.findByUnique(context, "eperson",
"netid", netid);
if (row == null)
{
return null;
}
else
{
// First check the cache
EPerson fromCache = (EPerson) context.fromCache(EPerson.class, row
.getIntColumn("eperson_id"));
if (fromCache != null)
{
return fromCache;
}
else
{
return new EPerson(context, row);
}
}
}
/**
* Find the epeople that match the search query across firstname, lastname or email
*
* @param context
* DSpace context
* @param query
* The search string
*
* @return array of EPerson objects
*/
public static EPerson[] search(Context context, String query)
throws SQLException
{
return search(context, query, -1, -1);
}
/**
* Find the epeople that match the search query across firstname, lastname or email.
* This method also allows offsets and limits for pagination purposes.
*
* @param context
* DSpace context
* @param query
* The search string
* @param offset
* Inclusive offset
* @param limit
* Maximum number of matches returned
*
* @return array of EPerson objects
*/
public static EPerson[] search(Context context, String query, int offset, int limit)
throws SQLException
{
String params = "%"+query.toLowerCase()+"%";
StringBuffer queryBuf = new StringBuffer();
queryBuf.append("SELECT * FROM eperson WHERE eperson_id = ? OR ");
queryBuf.append("LOWER(firstname) LIKE LOWER(?) OR LOWER(lastname) LIKE LOWER(?) OR LOWER(email) LIKE LOWER(?) ORDER BY lastname, firstname ASC ");
// Add offset and limit restrictions - Oracle requires special code
if ("oracle".equals(ConfigurationManager.getProperty("db.name")))
{
// First prepare the query to generate row numbers
if (limit > 0 || offset > 0)
{
queryBuf.insert(0, "SELECT /*+ FIRST_ROWS(n) */ rec.*, ROWNUM rnum FROM (");
queryBuf.append(") ");
}
// Restrict the number of rows returned based on the limit
if (limit > 0)
{
queryBuf.append("rec WHERE rownum<=? ");
// If we also have an offset, then convert the limit into the maximum row number
if (offset > 0)
limit += offset;
}
// Return only the records after the specified offset (row number)
if (offset > 0)
{
queryBuf.insert(0, "SELECT * FROM (");
queryBuf.append(") WHERE rnum>?");
}
}
else
{
if (limit > 0)
queryBuf.append(" LIMIT ? ");
if (offset > 0)
queryBuf.append(" OFFSET ? ");
}
String dbquery = queryBuf.toString();
// When checking against the eperson-id, make sure the query can be made into a number
Integer int_param;
try {
int_param = Integer.valueOf(query);
}
catch (NumberFormatException e) {
int_param = new Integer(-1);
}
// Create the parameter array, including limit and offset if part of the query
Object[] paramArr = new Object[] {int_param,params,params,params};
if (limit > 0 && offset > 0)
paramArr = new Object[] {int_param,params,params,params,limit,offset};
else if (limit > 0)
paramArr = new Object[] {int_param,params,params,params,limit};
else if (offset > 0)
paramArr = new Object[] {int_param,params,params,params,offset};
// Get all the epeople that match the query
TableRowIterator rows = DatabaseManager.query(context, dbquery, paramArr);
try
{
List epeopleRows = rows.toList();
EPerson[] epeople = new EPerson[epeopleRows.size()];
for (int i = 0; i < epeopleRows.size(); i++)
{
TableRow row = (TableRow) epeopleRows.get(i);
// First check the cache
EPerson fromCache = (EPerson) context.fromCache(EPerson.class, row
.getIntColumn("eperson_id"));
if (fromCache != null)
{
epeople[i] = fromCache;
}
else
{
epeople[i] = new EPerson(context, row);
}
}
return epeople;
}
finally
{
if (rows != null)
rows.close();
}
}
/**
* Returns the total number of epeople returned by a specific query, without the overhead
* of creating the EPerson objects to store the results.
*
* @param context
* DSpace context
* @param query
* The search string
*
* @return the number of epeople mathching the query
*/
public static int searchResultCount(Context context, String query)
throws SQLException
{
String dbquery = "%"+query.toLowerCase()+"%";
Long count;
// When checking against the eperson-id, make sure the query can be made into a number
Integer int_param;
try {
int_param = Integer.valueOf(query);
}
catch (NumberFormatException e) {
int_param = new Integer(-1);
}
// Get all the epeople that match the query
TableRow row = DatabaseManager.querySingle(context,
"SELECT count(*) as epcount FROM eperson WHERE eperson_id = ? OR " +
"LOWER(firstname) LIKE LOWER(?) OR LOWER(lastname) LIKE LOWER(?) OR LOWER(email) LIKE LOWER(?)",
new Object[] {int_param,dbquery,dbquery,dbquery});
// use getIntColumn for Oracle count data
if ("oracle".equals(ConfigurationManager.getProperty("db.name")))
{
count = new Long(row.getIntColumn("epcount"));
}
else //getLongColumn works for postgres
{
count = new Long(row.getLongColumn("epcount"));
}
return count.intValue();
}
/**
* Find all the epeople that match a particular query
* <ul>
* <li><code>ID</code></li>
* <li><code>LASTNAME</code></li>
* <li><code>EMAIL</code></li>
* <li><code>NETID</code></li>
* </ul>
*
* @return array of EPerson objects
*/
public static EPerson[] findAll(Context context, int sortField)
throws SQLException
{
String s;
switch (sortField)
{
case ID:
s = "eperson_id";
break;
case EMAIL:
s = "email";
break;
case LANGUAGE:
s = "language";
break;
case NETID:
s = "netid";
break;
default:
s = "lastname";
}
// NOTE: The use of 's' in the order by clause can not cause an sql
// injection because the string is derived from constant values above.
TableRowIterator rows = DatabaseManager.query(context,
"SELECT * FROM eperson ORDER BY "+s);
try
{
List epeopleRows = rows.toList();
EPerson[] epeople = new EPerson[epeopleRows.size()];
for (int i = 0; i < epeopleRows.size(); i++)
{
TableRow row = (TableRow) epeopleRows.get(i);
// First check the cache
EPerson fromCache = (EPerson) context.fromCache(EPerson.class, row
.getIntColumn("eperson_id"));
if (fromCache != null)
{
epeople[i] = fromCache;
}
else
{
epeople[i] = new EPerson(context, row);
}
}
return epeople;
}
finally
{
if (rows != null)
rows.close();
}
}
/**
* Create a new eperson
*
* @param context
* DSpace context object
*/
public static EPerson create(Context context) throws SQLException,
AuthorizeException
{
// authorized?
if (!AuthorizeManager.isAdmin(context))
{
throw new AuthorizeException(
"You must be an admin to create an EPerson");
}
// Create a table row
TableRow row = DatabaseManager.create(context, "eperson");
EPerson e = new EPerson(context, row);
log.info(LogManager.getHeader(context, "create_eperson", "eperson_id="
+ e.getID()));
context.addEvent(new Event(Event.CREATE, Constants.EPERSON, e.getID(), null));
return e;
}
/**
* Delete an eperson
*
*/
public void delete() throws SQLException, AuthorizeException,
EPersonDeletionException
{
// authorized?
if (!AuthorizeManager.isAdmin(myContext))
{
throw new AuthorizeException(
"You must be an admin to delete an EPerson");
}
// check for presence of eperson in tables that
// have constraints on eperson_id
Vector constraintList = getDeleteConstraints();
// if eperson exists in tables that have constraints
// on eperson, throw an exception
if (constraintList.size() > 0)
{
throw new EPersonDeletionException(constraintList);
}
myContext.addEvent(new Event(Event.DELETE, Constants.EPERSON, getID(), getEmail()));
// Remove from cache
myContext.removeCached(this, getID());
// XXX FIXME: This sidesteps the object model code so it won't
// generate REMOVE events on the affected Groups.
// Remove any group memberships first
DatabaseManager.updateQuery(myContext,
"DELETE FROM EPersonGroup2EPerson WHERE eperson_id= ? ",
getID());
// Remove any subscriptions
DatabaseManager.updateQuery(myContext,
"DELETE FROM subscription WHERE eperson_id= ? ",
getID());
// Remove ourself
DatabaseManager.delete(myContext, myRow);
log.info(LogManager.getHeader(myContext, "delete_eperson",
"eperson_id=" + getID()));
}
/**
* Get the e-person's internal identifier
*
* @return the internal identifier
*/
public int getID()
{
return myRow.getIntColumn("eperson_id");
}
/**
* Get the e-person's language
*
* @return language
*/
public String getLanguage()
{
return myRow.getStringColumn("language");
}
/**
* Set the EPerson's language. Value is expected to be a Unix/POSIX
* Locale specification of the form {language} or {language}_{territory},
* e.g. "en", "en_US", "pt_BR" (the latter is Brazilian Portugese).
*
* @param s
* language
*/
public void setLanguage(String language)
{
myRow.setColumn("language", language);
}
public String getHandle()
{
// No Handles for e-people
return null;
}
/**
* Get the e-person's email address
*
* @return their email address
*/
public String getEmail()
{
return myRow.getStringColumn("email");
}
/**
* Set the EPerson's email
*
* @param s
* the new email
*/
public void setEmail(String s)
{
if (s != null)
{
s = s.toLowerCase();
}
myRow.setColumn("email", s);
modified = true;
}
/**
* Get the e-person's netid
*
* @return their netid
*/
public String getNetid()
{
return myRow.getStringColumn("netid");
}
/**
* Set the EPerson's netid
*
* @param s
* the new netid
*/
public void setNetid(String s)
{
myRow.setColumn("netid", s);
modified = true;
}
/**
* Get the e-person's full name, combining first and last name in a
* displayable string.
*
* @return their full name
*/
public String getFullName()
{
String f = myRow.getStringColumn("firstname");
String l = myRow.getStringColumn("lastname");
if ((l == null) && (f == null))
{
return getEmail();
}
else if (f == null)
{
return l;
}
else
{
return (f + " " + l);
}
}
/**
* Get the eperson's first name.
*
* @return their first name
*/
public String getFirstName()
{
return myRow.getStringColumn("firstname");
}
/**
* Set the eperson's first name
*
* @param firstname
* the person's first name
*/
public void setFirstName(String firstname)
{
myRow.setColumn("firstname", firstname);
modified = true;
}
/**
* Get the eperson's last name.
*
* @return their last name
*/
public String getLastName()
{
return myRow.getStringColumn("lastname");
}
/**
* Set the eperson's last name
*
* @param lastname
* the person's last name
*/
public void setLastName(String lastname)
{
myRow.setColumn("lastname", lastname);
modified = true;
}
/**
* Indicate whether the user can log in
*
* @param login
* boolean yes/no
*/
public void setCanLogIn(boolean login)
{
myRow.setColumn("can_log_in", login);
modified = true;
}
/**
* Can the user log in?
*
* @return boolean, yes/no
*/
public boolean canLogIn()
{
return myRow.getBooleanColumn("can_log_in");
}
/**
* Set require cert yes/no
*
* @param isrequired
* boolean yes/no
*/
public void setRequireCertificate(boolean isrequired)
{
myRow.setColumn("require_certificate", isrequired);
modified = true;
}
/**
* Get require certificate or not
*
* @return boolean, yes/no
*/
public boolean getRequireCertificate()
{
return myRow.getBooleanColumn("require_certificate");
}
/**
* Indicate whether the user self-registered
*
* @param sr
* boolean yes/no
*/
public void setSelfRegistered(boolean sr)
{
myRow.setColumn("self_registered", sr);
modified = true;
}
/**
* Can the user log in?
*
* @return boolean, yes/no
*/
public boolean getSelfRegistered()
{
return myRow.getBooleanColumn("self_registered");
}
/**
* Get the value of a metadata field
*
* @param field
* the name of the metadata field to get
*
* @return the value of the metadata field
*
* @exception IllegalArgumentException
* if the requested metadata field doesn't exist
*/
public String getMetadata(String field)
{
return myRow.getStringColumn(field);
}
/**
* Set a metadata value
*
* @param field
* the name of the metadata field to get
* @param value
* value to set the field to
*
* @exception IllegalArgumentException
* if the requested metadata field doesn't exist
*/
public void setMetadata(String field, String value)
{
myRow.setColumn(field, value);
modifiedMetadata = true;
addDetails(field);
}
/**
* Set the EPerson's password
*
* @param s
* the new email
*/
public void setPassword(String s)
{
// FIXME: encoding
String encoded = Utils.getMD5(s);
myRow.setColumn("password", encoded);
modified = true;
}
/**
* Check EPerson's password
*
* @param attempt
* the password attempt
* @return boolean successful/unsuccessful
*/
public boolean checkPassword(String attempt)
{
String encoded = Utils.getMD5(attempt);
return (encoded.equals(myRow.getStringColumn("password")));
}
/**
* Update the EPerson
*/
public void update() throws SQLException, AuthorizeException
{
// Check authorisation - if you're not the eperson
// see if the authorization system says you can
if (!myContext.ignoreAuthorization()
&& ((myContext.getCurrentUser() == null) || (getID() != myContext
.getCurrentUser().getID())))
{
AuthorizeManager.authorizeAction(myContext, this, Constants.WRITE);
}
DatabaseManager.update(myContext, myRow);
log.info(LogManager.getHeader(myContext, "update_eperson",
"eperson_id=" + getID()));
if (modified)
{
myContext.addEvent(new Event(Event.MODIFY, Constants.EPERSON, getID(), null));
modified = false;
}
if (modifiedMetadata)
{
myContext.addEvent(new Event(Event.MODIFY_METADATA, Constants.EPERSON, getID(), getDetails()));
modifiedMetadata = false;
clearDetails();
}
}
/**
* Return <code>true</code> if <code>other</code> is the same EPerson as
* this object, <code>false</code> otherwise
*
* @param other
* object to compare to
*
* @return <code>true</code> if object passed in represents the same
* eperson as this object
*/
public boolean obsolete_equals(Object other)
{
if (!(other instanceof EPerson))
{
return false;
}
return (getID() == ((EPerson) other).getID());
}
/**
* return type found in Constants
*/
public int getType()
{
return Constants.EPERSON;
}
/**
* Check for presence of EPerson in tables that have constraints on
* EPersons. Called by delete() to determine whether the eperson can
* actually be deleted.
*
* An EPerson cannot be deleted if it exists in the item, workflowitem, or
* tasklistitem tables.
*
* @return Vector of tables that contain a reference to the eperson.
*/
public Vector<String> getDeleteConstraints() throws SQLException
{
Vector<String> tableList = new Vector<String>();
// check for eperson in item table
TableRowIterator tri = DatabaseManager.query(myContext,
"SELECT * from item where submitter_id= ? ",
getID());
try
{
if (tri.hasNext())
{
tableList.add("item");
}
}
finally
{
// close the TableRowIterator to free up resources
if (tri != null)
tri.close();
}
// check for eperson in workflowitem table
tri = DatabaseManager.query(myContext,
"SELECT * from workflowitem where owner= ? ",
getID());
try
{
if (tri.hasNext())
{
tableList.add("workflowitem");
}
}
finally
{
// close the TableRowIterator to free up resources
if (tri != null)
tri.close();
}
// check for eperson in tasklistitem table
tri = DatabaseManager.query(myContext,
"SELECT * from tasklistitem where eperson_id= ? ",
getID());
try
{
if (tri.hasNext())
{
tableList.add("tasklistitem");
}
}
finally
{
// close the TableRowIterator to free up resources
if (tri != null)
tri.close();
}
// the list of tables can be used to construct an error message
// explaining to the user why the eperson cannot be deleted.
return tableList;
}
public String getName()
{
return getEmail();
}
}