/*==========================================================================*\
| $Id: User.java,v 1.16 2012/06/22 16:23:18 aallowat Exp $
|*-------------------------------------------------------------------------*|
| Copyright (C) 2006-2012 Virginia Tech
|
| This file is part of Web-CAT.
|
| Web-CAT is free software; you can redistribute it and/or modify
| it under the terms of the GNU Affero General Public License as published
| by the Free Software Foundation; either version 3 of the License, or
| (at your option) any later version.
|
| Web-CAT 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 Affero General Public License
| along with Web-CAT; if not, see <http://www.gnu.org/licenses/>.
\*==========================================================================*/
package org.webcat.core;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Properties;
import org.apache.log4j.Logger;
import org.webcat.woextensions.WCEC;
import com.webobjects.appserver.WOComponent;
import com.webobjects.eoaccess.EOUtilities;
import com.webobjects.eocontrol.EOEditingContext;
import com.webobjects.eocontrol.EOQualifier;
import com.webobjects.eocontrol.EOSortOrdering;
import com.webobjects.foundation.NSArray;
import com.webobjects.foundation.NSDictionary;
import er.extensions.eof.ERXKey;
import er.extensions.foundation.ERXArrayUtilities;
// -------------------------------------------------------------------------
/**
* A user of the system.
* <p>
* This class also defines constant values for the access levels supported
* by Web-CAT. Each higher access level subsumes all of the rights and
* privileges of all lower levels. The levels, in order from lowest to
* highest, are:
* </p><ul>
* <li> STUDENT_PRIVILEGES
* <li> GRADER_PRIVILEGES
* <li> GTA_PRIVILEGES
* <li> INSTRUCTOR_PRIVILEGES
* <li> WEBCAT_READ_PRIVILEGES
* <li> WEBCAT_RW_PRIVILEGES
* </ul>
*
* @author Stephen Edwards
* @author Last changed by: $Author: aallowat $
* @version $Revision: 1.16 $, $Date: 2012/06/22 16:23:18 $
*/
public class User
extends _User
implements RepositoryProvider
{
//~ Constructors ..........................................................
// ----------------------------------------------------------
/**
* Creates a new User object.
*/
public User()
{
super();
}
//~ Public Constants ......................................................
public static final byte STUDENT_PRIVILEGES = 0;
public static final byte GRADER_PRIVILEGES = 30;
public static final byte GTA_PRIVILEGES = 40;
public static final byte INSTRUCTOR_PRIVILEGES = 50;
public static final byte WEBCAT_READ_PRIVILEGES = 80;
public static final byte WEBCAT_RW_PRIVILEGES = 90;
public static final String TIME_ZONE_NAME_KEY = "timeZoneName";
public static final String TIME_FORMAT_KEY = "timeFormat";
public static final String DATE_FORMAT_KEY = "dateFormat";
public static final ERXKey<String> name_LF =
new ERXKey<String>("name_LF");
//~ Methods ...............................................................
// ----------------------------------------------------------
/**
* Creates a new user.
*
* @param aUserName The new username
* @param aPassword The user's password
* @param domain The domain the user comes from
* @param anAccessLevel The user's access privilege level
* @param ec The editing context in which to create the user
* @return The new user object
*/
public static User createUser(String aUserName,
String aPassword,
AuthenticationDomain domain,
byte anAccessLevel,
EOEditingContext ec)
{
User u = new User();
ec.insertObject(u);
u.setPreferences(new MutableDictionary());
u.setUserName(aUserName);
u.setPassword(aPassword);
u.setAccessLevel(anAccessLevel);
u.setAuthenticationDomainRelationship(domain);
ec.saveChanges();
return u;
}
// ----------------------------------------------------------
/**
* An exception to indicate that multiple users were found for the
* given search.
*/
public static class MultipleUsersFoundException
extends RuntimeException
{
public MultipleUsersFoundException(String msg)
{
super(msg);
}
}
// ----------------------------------------------------------
/**
* Looks up an existing user by user name and domain.
*
* @param ec The editing context in which to lookup the user
* @param aUserName The username to look up
* @param domain The domain the user comes from
* @return The user object, or null if none is found
* @throws MultipleUsersFoundException if multiple users matching the
* search criteria are found.
*/
public static User lookupUser(EOEditingContext ec,
String aUserName,
AuthenticationDomain domain)
{
try
{
return userWithDomainAndName(ec, domain, aUserName);
}
catch (EOUtilities.MoreThanOneException e)
{
throw new MultipleUsersFoundException("Multiple users found when "
+ "searching for userName = " + aUserName + " and domain = "
+ domain);
}
}
// ----------------------------------------------------------
/**
* Looks up an existing user by email address and domain.
*
* @param ec The editing context in which to lookup the user
* @param eMail The email address to look up
* @param domain The domain the user comes from
* @return The user object, or null if none is found
* @throws MultipleUsersFoundException if multiple users matching the
* search criteria are found.
*/
public static User lookupUserByEmail(EOEditingContext ec,
String eMail,
AuthenticationDomain domain)
{
// First, try a raw database lookup
try
{
User user = userWithDomainAndEmail(ec, domain, eMail);
if (user != null)
{
return user;
}
}
catch (EOUtilities.MoreThanOneException e)
{
throw new MultipleUsersFoundException("Multiple users found when "
+ "searching for email = " + eMail + " and domain = "
+ domain);
}
// But if that gives no results, it may be because the user does
// not have an explicit e-mail address stored in the database,
// and is instead using <username>@<domain.default> as their
// e-mail address. So extract the user name from the e-mail address
// and check it instead.
String theUserName = eMail;
int pos = eMail.indexOf('@');
if (pos >= 0)
{
theUserName = theUserName.substring(0, pos);
}
// Look up by user name
User user = lookupUser(ec, theUserName, domain);
// Check that the located user has the correct e-mail address
if (user != null && !eMail.equals(user.email()))
{
// What? e-mail addresses didn't match, so ignore that user
user = null;
}
return user;
}
// ----------------------------------------------------------
/**
* Get a short (no longer than 60 characters) description of this user,
* which currently returns {@link #name()}.
* @return the description
*/
public String userPresentableDescription()
{
return name();
}
// ----------------------------------------------------------
/**
* Return the user's full name as a string, in the format "First Last".
* @return the name
*/
public String name()
{
String last = lastName();
String first = firstName();
boolean lastIsEmpty = (last == null || last.equals(""));
boolean firstIsEmpty = (first == null || first.equals(""));
if (lastIsEmpty && firstIsEmpty)
{
return userName();
}
else if (lastIsEmpty)
{
return first;
}
else if (firstIsEmpty)
{
return last;
}
else
{
return first + " " + last;
}
}
// ----------------------------------------------------------
/**
* Return the user's full name as a string, in the format "Last, First"
* (the meaning of the _LF suffix).
* @return the name
*/
public String name_LF()
{
if (name_LF_cache == null)
{
String last = lastName();
String first = firstName();
boolean lastIsEmpty = (last == null || last.equals(""));
boolean firstIsEmpty = (first == null || first.equals(""));
if (lastIsEmpty && firstIsEmpty)
{
name_LF_cache = userName();
}
else if (lastIsEmpty)
{
name_LF_cache = first;
}
else if (firstIsEmpty)
{
name_LF_cache = last;
}
else
{
name_LF_cache = last + ", " + first;
}
}
return name_LF_cache;
}
// ----------------------------------------------------------
public void setFirstName(String value)
{
super.setFirstName(value);
name_LF_cache = null;
}
// ----------------------------------------------------------
public void setLastName(String value)
{
super.setLastName(value);
name_LF_cache = null;
}
// ----------------------------------------------------------
public void setUserName(String value)
{
super.setUserName(value);
name_LF_cache = null;
}
// ----------------------------------------------------------
/**
* Return the user's full name as a string, in the format "First Last".
* @return the name
*/
public String nameAndUid()
{
String name = name();
if (name == null || name.equals(""))
{
return userName();
}
else
{
return name + " (" + userName() + ")";
}
}
// ----------------------------------------------------------
/**
* Return the user's full name as a string, in the format "Last, First"
* (the meaning of the _LF suffix).
* @return the name
*/
public String shortName()
{
String last = lastName();
if (last == null || last.equals(""))
{
return userName();
}
else
{
return last;
}
}
// ----------------------------------------------------------
/**
* Retrieve this user's e-mail address. This comes
* from the <code>email</code> attribute value, if set. If
* <code>email</code> is not set, the user's pid is combined
* with "@" and the authentication domain's default e-mail domain.
*
* @return the e-mail address
*/
public String email()
{
String result = super.email();
if (result == null || result == "")
{
result = userName();
AuthenticationDomain authDomain = authenticationDomain();
if (authDomain != null)
{
String domain = authDomain.defaultEmailDomain();
if (domain != null && domain != "")
{
result = result + "@" + domain;
}
}
}
return result;
}
// ----------------------------------------------------------
/**
* Returns an href for emailing a user.
* @return a mailto: string containing the e-mail address
*/
public String emailHref()
{
return "mailto:" + email();
}
// ----------------------------------------------------------
/**
* Get the time format this user prefers. If the user has not set a
* preference, the default time format for the user's authentication
* domain will be used. The value should be a format string
* acceptable by {@link NSTimestampFormatter}.
* @see AuthenticationDomain#timeFormat()
* @return the time format pattern
*/
public String timeFormat()
{
String result =
(String)preferences().objectForKey(TIME_FORMAT_KEY);
if (result == null || result.equals(""))
{
result = authenticationDomain().timeFormat();
}
return result;
}
// ----------------------------------------------------------
/**
* Set the time format pattern for this user.
*
* @param value The new value for this property
*/
public void setTimeFormat(String value)
{
preferences().setObjectForKey(value, TIME_FORMAT_KEY);
}
// ----------------------------------------------------------
/**
* Get the time zone name of this user's preferred time zone. If the
* user has not set a time zone preference, the default time zone for
* the user's authentication domain is used instead.
* @see AuthenticationDomain#timeZoneName()
* @return the time zone name
*/
public String timeZoneName()
{
String result =
(String)preferences().objectForKey(TIME_ZONE_NAME_KEY);
if (result == null || result.equals(""))
{
result = authenticationDomain().timeZoneName();
}
return result;
}
// ----------------------------------------------------------
/**
* Set the time zone name for this user's preferred time zone.
*
* @param value The new value for this property
*/
public void setTimeZoneName(String value)
{
preferences().setObjectForKey(value, TIME_ZONE_NAME_KEY);
}
// ----------------------------------------------------------
/**
* Get the date format this user prefers. If the user has not set a
* preference, the default date format for the user's authentication
* domain will be used. The value should be a format string
* acceptable by {@link NSTimestampFormatter}.
* @see AuthenticationDomain#dateFormat()
* @return the date format pattern
*/
public String dateFormat()
{
String result =
(String)preferences().objectForKey(DATE_FORMAT_KEY);
if (result == null || result.equals(""))
{
result = authenticationDomain().dateFormat();
}
return result;
}
// ----------------------------------------------------------
/**
* Set the date format pattern for this user.
*
* @param value The new value for this property
*/
public void setDateFormat(String value)
{
preferences().setObjectForKey(value, DATE_FORMAT_KEY);
}
// ----------------------------------------------------------
/**
* Validate the user with the given password.
* Internally, it uses the <code>CurrentUserAuthenticator</code>
* class to perform authentication.
*
* @param aUserName The user id to validate
* @param aPassword The password to check
* @param domain The domain to which this user belongs
* @param ec The editing context in which to create the user object
* @return True if the username/password combination is valid
*/
public static User validate(
String aUserName,
String aPassword,
AuthenticationDomain domain,
com.webobjects.eocontrol.EOEditingContext ec
)
{
UserAuthenticator authenticator = domain.authenticator();
if (authenticator != null)
{
return authenticator.authenticate(
aUserName, aPassword, domain, ec);
}
else
{
log.error("no registered authenticator called "
+ domain.propertyName());
return null;
}
}
// ----------------------------------------------------------
/**
* Check whether this user can change his/her password.
* @return True if users associated with this authenticator can
* change their password
*/
public boolean canChangePassword()
{
AuthenticationDomain ad = authenticationDomain();
if ( ad == null )
return false;
else
{
UserAuthenticator authenticator = ad.authenticator();
return authenticator != null
&& authenticator.canChangePassword();
}
}
// ----------------------------------------------------------
/**
* Change the user's password, if possible.
* @param newPassword The password to change to
* @return True if the password change was successful
*/
public boolean changePassword(String newPassword)
{
AuthenticationDomain ad = authenticationDomain();
if (ad == null)
{
return false;
}
else
{
UserAuthenticator authenticator = ad.authenticator();
return authenticator != null
&& authenticator.changePassword(this, newPassword);
}
}
// ----------------------------------------------------------
/**
* Reset the user's password to a new random password and e-mail the
* user a message, if possible.
* @return True if the password change was successful
*/
public boolean newRandomPassword()
{
AuthenticationDomain ad = authenticationDomain();
if (ad == null)
{
return false;
}
else
{
UserAuthenticator authenticator = ad.authenticator();
return authenticator != null
&& authenticator.newRandomPassword(this);
}
}
// ----------------------------------------------------------
/**
* Find out if this user can switch between student and staff views.
* @return true if this user can switch
*/
public boolean canChangeViews()
{
return accessLevel() > STUDENT_PRIVILEGES;
}
// ----------------------------------------------------------
/**
* Toggle the student view setting for this user. This only affect's
* the state within the user object, and does not affect any session
* navigation or other UI features. It is intended to be called from
* {@link Session#toggleStudentView()}, which handles updating the
* corresponding session navigation data.
* @return Returns null, to force reloading of the calling page
* (if desired)
*/
public WOComponent toggleStudentView()
{
if (canChangeViews())
{
studentView = !studentView;
graderFor_cache = null;
teaching_cache = null;
graderForButNotStudent_cache = null;
instructorForButNotGraderOrStudent_cache = null;
staffFor_cache = null;
adminForButNotStaff_cache = null;
adminForButNoOtherRelationships_cache = null;
}
return null;
}
// ----------------------------------------------------------
/**
* Get the string label for the "toggle view" button for this user.
* @return The button label text, indicating what view the user will
* toggle to next
*/
public String toggleViewLabel()
{
String result = "Student View";
if (studentView)
{
if (accessLevel() > INSTRUCTOR_PRIVILEGES)
{
result = "Admin View";
}
else if (accessLevel() > GTA_PRIVILEGES)
{
result = "Instructor View";
}
else
{
result = "Grader View";
}
}
return result;
}
// ----------------------------------------------------------
/**
* Determine if this user's view should be restricted to student-only
* features.
* @return true if this user's view is restricted
*/
public boolean restrictToStudentView()
{
return studentView;
}
// ----------------------------------------------------------
/**
* @return the array of course offerings that this user is a grader for
*
* @deprecated Use the {@link #graderFor()} method instead.
*/
@Deprecated
public NSArray<CourseOffering> TAFor()
{
return graderFor();
}
// ----------------------------------------------------------
/**
* @return the array of course offerings that this user is a grader for
*/
public NSArray<CourseOffering> graderFor()
{
return studentView ? NO_COURSES : super.graderFor();
}
// ----------------------------------------------------------
/**
* Returns a sorted list of course offerings that this user is a TA for.
* @param semester Only return courses for this semester. A value of null
* means all courses (same as staffFor()).
* @return a sorted array of the matching course offerings.
*
* @deprecated Use the {@link #graderFor(Semester)} method instead.
*/
@Deprecated
public NSArray<CourseOffering> TAFor(Semester semester)
{
return graderFor(semester);
}
// ----------------------------------------------------------
/**
* Returns a sorted list of course offerings that this user is a TA for.
* @param semester Only return courses for this semester. A value of null
* means all courses (same as staffFor()).
* @return a sorted array of the matching course offerings.
*/
public NSArray<CourseOffering> graderFor(Semester semester)
{
NSArray<CourseOffering> result = graderFor();
if (semester != null)
{
result = CourseOffering.semester.is(semester).filtered(result);
}
return result;
}
// ----------------------------------------------------------
public NSArray<CourseOffering> teaching()
{
return studentView ? NO_COURSES : super.teaching();
}
// ----------------------------------------------------------
/**
* Returns a sorted list of course offerings that this user is teaching.
* @param semester Only return courses for this semester. A value of null
* means all courses (same as staffFor()).
* @return a sorted array of the matching course offerings.
*/
public NSArray<CourseOffering> teaching(Semester semester)
{
NSArray<CourseOffering> result = teaching();
if (semester != null)
{
result = CourseOffering.semester.is(semester).filtered(result);
}
return result;
}
// ----------------------------------------------------------
/**
* Returns true if the user has grader privileges for Web-CAT.
* @return true if user has at least grader access level
*/
public boolean hasGraderPrivileges()
{
return !studentView && accessLevel() >= GTA_PRIVILEGES;
}
// ----------------------------------------------------------
/**
* Returns true if the user has grader privileges for Web-CAT.
* @return true if user has at least grader access level
*
* @deprecated use the {@link #hasGraderPrivileges()} method instead.
*/
@Deprecated
public boolean hasTAPrivileges()
{
return hasGraderPrivileges();
}
// ----------------------------------------------------------
/**
* Returns true if the user has faculty privileges for Web-CAT.
* @return true if user has at least faculty access level
*/
public boolean hasFacultyPrivileges()
{
return !studentView && accessLevel() >= INSTRUCTOR_PRIVILEGES;
}
// ----------------------------------------------------------
/**
* Returns true if the user has faculty privileges for Web-CAT.
* @return true if user has at least faculty access level
*/
public boolean hasAdminPrivileges()
{
return !studentView && accessLevel() >= WEBCAT_RW_PRIVILEGES;
}
// ----------------------------------------------------------
/**
* Adds property definitions to the given properties object containing
* this user's basic information. All properties added begin with
* "user.".
* @param properties The object to which the definitions will be added
*/
public void addPropertiesTo(Properties properties)
{
String value = email();
if (value != null)
{
properties.setProperty(PREFIX + EMAIL_KEY, value);
}
value = firstName();
if (value != null)
{
properties.setProperty(PREFIX + FIRST_NAME_KEY, value);
}
value = lastName();
if (value != null)
{
properties.setProperty(PREFIX + LAST_NAME_KEY, value);
}
value = password();
if (value != null)
{
properties.setProperty(PREFIX + PASSWORD_KEY, value);
}
value = universityIDNo();
if (value != null)
{
properties.setProperty(PREFIX + UNIVERSITY_ID_NO_KEY, value);
}
value = url();
if (value != null)
{
properties.setProperty(PREFIX + URL_KEY, value);
}
value = userName();
if (value != null)
{
properties.setProperty(PREFIX + USER_NAME_KEY, value);
}
}
// ----------------------------------------------------------
/**
* Returns a sorted list of course offerings that this user is a TA for,
* without including any courses where this user is also a student.
* @return a sorted array of the matching course offerings.
*
* @deprecated Use the {@link #graderForButNotStudent()} method instead.
*/
@Deprecated
public NSArray<CourseOffering> TAForButNotStudent()
{
return graderForButNotStudent();
}
// ----------------------------------------------------------
/**
* Returns a sorted list of course offerings that this user is a grader for,
* without including any courses where this user is also a student.
* @return a sorted array of the matching course offerings.
*/
public NSArray<CourseOffering> graderForButNotStudent()
{
if (graderFor_cache != graderFor())
{
graderFor_cache = graderFor();
graderForButNotStudent_cache = null;
}
if (graderFor_cache == null || graderFor_cache.count() == 0)
{
graderForButNotStudent_cache = NO_COURSES;
}
else
{
if (enrolledIn_cache != enrolledIn())
{
enrolledIn_cache = enrolledIn();
graderForButNotStudent_cache = null;
}
if (graderForButNotStudent_cache == null)
{
@SuppressWarnings("unchecked")
NSArray<CourseOffering> cache =
ERXArrayUtilities.filteredArrayWithEntityFetchSpecification(
graderFor_cache,
CourseOffering.ENTITY_NAME,
CourseOffering.OFFERINGS_WITHOUT_STUDENT_FSPEC,
userFilteringDictionary()
);
graderForButNotStudent_cache = cache;
}
}
return graderForButNotStudent_cache;
}
// ----------------------------------------------------------
/**
* Returns a sorted list of course offerings that this user is a TA for,
* without including any courses where this user is also a student.
* @param semester Only return courses for this semester. A value of null
* means all courses (same as staffFor()).
* @return a sorted array of the matching course offerings.
*
* @deprecated Use the {@link #graderForButNotStudent(Semester)} method
* instead.
*/
@Deprecated
public NSArray<CourseOffering> TAForButNotStudent(Semester semester)
{
return graderForButNotStudent(semester);
}
// ----------------------------------------------------------
/**
* Returns a sorted list of course offerings that this user is a grader for,
* without including any courses where this user is also a student.
* @param semester Only return courses for this semester. A value of null
* means all courses (same as staffFor()).
* @return a sorted array of the matching course offerings.
*/
public NSArray<CourseOffering> graderForButNotStudent(Semester semester)
{
NSArray<CourseOffering> result = graderForButNotStudent();
if (semester != null)
{
result = CourseOffering.semester.is(semester).filtered(result);
}
return result;
}
// ----------------------------------------------------------
/**
* Returns a sorted list of course offerings that this user is an
* instructor for, without including any courses where this user is also
* a student or a grader.
* @return a sorted array of the matching course offerings.
*
* @deprecated Use the {@link #instructorForButNotGraderOrStudent()} method
* instead.
*/
@Deprecated
public NSArray<CourseOffering> instructorForButNotTAOrStudent()
{
return instructorForButNotGraderOrStudent();
}
// ----------------------------------------------------------
/**
* Returns a sorted list of course offerings that this user is an
* instructor for, without including any courses where this user is also
* a student or a grader.
* @return a sorted array of the matching course offerings.
*/
public NSArray<CourseOffering> instructorForButNotGraderOrStudent()
{
if (teaching_cache != teaching())
{
teaching_cache = teaching();
instructorForButNotGraderOrStudent_cache = null;
}
if (teaching_cache == null || teaching_cache.count() == 0)
{
instructorForButNotGraderOrStudent_cache = NO_COURSES;
}
else
{
if (enrolledIn_cache != enrolledIn())
{
enrolledIn_cache = enrolledIn();
instructorForButNotGraderOrStudent_cache = null;
}
if (graderFor_cache != graderFor())
{
graderFor_cache = graderFor();
instructorForButNotGraderOrStudent_cache = null;
}
if (instructorForButNotGraderOrStudent_cache == null)
{
@SuppressWarnings("unchecked")
NSArray<CourseOffering> cache =
ERXArrayUtilities
.filteredArrayWithEntityFetchSpecification(
teaching_cache,
CourseOffering.ENTITY_NAME,
CourseOffering
.OFFERINGS_WITHOUT_STUDENT_OR_GRADER_FSPEC,
userFilteringDictionary()
);
instructorForButNotGraderOrStudent_cache = cache;
}
}
return instructorForButNotGraderOrStudent_cache;
}
// ----------------------------------------------------------
/**
* Returns a sorted list of course offerings that this user is an
* instructor for, without including any courses where this user is also
* a student or a grader.
* @param semester Only return courses for this semester. A value of null
* means all courses (same as staffFor()).
* @return a sorted array of the matching course offerings.
*
* @deprecated Use the {@link #instructorForButNotTAOrStudent(Semester)}
* method instead.
*/
@Deprecated
public NSArray<CourseOffering> instructorForButNotTAOrStudent(
Semester semester)
{
return instructorForButNotGraderOrStudent(semester);
}
// ----------------------------------------------------------
/**
* Returns a sorted list of course offerings that this user is an
* instructor for, without including any courses where this user is also
* a student or a grader.
* @param semester Only return courses for this semester. A value of null
* means all courses (same as staffFor()).
* @return a sorted array of the matching course offerings.
*/
public NSArray<CourseOffering> instructorForButNotGraderOrStudent(
Semester semester)
{
NSArray<CourseOffering> result = instructorForButNotGraderOrStudent();
if (semester != null)
{
result = CourseOffering.semester.is(semester).filtered(result);
}
return result;
}
// ----------------------------------------------------------
/**
* Returns a list of administrator users.
* @return an array of administrators.
*/
public static NSArray<User> administrators(EOEditingContext context)
{
return objectsMatchingQualifier(context,
accessLevel.greaterThanOrEqualTo((int)WEBCAT_RW_PRIVILEGES));
}
// ----------------------------------------------------------
/**
* Returns a sorted list of course offerings that this user is either
* an instructor or grader for.
* @return a sorted array of the matching course offerings.
*/
public NSArray<CourseOffering> staffFor()
{
if (graderFor_cache != graderFor())
{
graderFor_cache = graderFor();
staffFor_cache = null;
}
if (teaching_cache != teaching())
{
teaching_cache = teaching();
staffFor_cache = null;
}
if (staffFor_cache == null)
{
staffFor_cache = EOSortOrdering.sortedArrayUsingKeyOrderArray(
teaching_cache.arrayByAddingObjectsFromArray(graderFor_cache),
courseSortOrderings);
}
return staffFor_cache;
}
// ----------------------------------------------------------
/**
* Returns a sorted list of course offerings that this user is either
* an instructor or grader for.
* @param semester Only return courses for this semester. A value of null
* means all courses (same as staffFor()).
* @return a sorted array of the matching course offerings.
*/
public NSArray<CourseOffering> staffFor(Semester semester)
{
NSArray<CourseOffering> result = staffFor();
if (semester != null)
{
result = CourseOffering.semester.is(semester).filtered(result);
}
return result;
}
// ----------------------------------------------------------
/**
* Returns a sorted list of course offerings that this user has
* administrative access to, but is not an instructor or TA
* for.
* @return a sorted array of the matching course offerings.
*/
public NSArray<CourseOffering> adminForButNotStaff()
{
if (!hasAdminPrivileges())
{
return NO_COURSES;
}
if (graderFor_cache != graderFor())
{
graderFor_cache = graderFor();
adminForButNotStaff_cache = null;
}
if (teaching_cache != teaching())
{
teaching_cache = teaching();
adminForButNotStaff_cache = null;
}
if (adminForButNotStaff_cache == null)
{
// For some reason, using the fetch directly does not seem
// to work, at least not when the result is empty.
// adminForButNoOtherRelationships_cache =
// CourseOffering.objectsForWithoutAnyRelationshipToUser(
// editingContext(), this );
@SuppressWarnings("unchecked")
NSArray<CourseOffering> cache =
ERXArrayUtilities.filteredArrayWithEntityFetchSpecification(
EOUtilities.objectsForEntityNamed(editingContext(),
CourseOffering.ENTITY_NAME),
CourseOffering.ENTITY_NAME,
CourseOffering.OFFERINGS_WITHOUT_USER_AS_STAFF_FSPEC,
userFilteringDictionary()
);
adminForButNotStaff_cache = cache;
}
return adminForButNotStaff_cache;
}
// ----------------------------------------------------------
/**
* Returns a sorted list of course offerings that this user has
* administrative access to, but is not an instructor or TA
* for.
* @param semester Only return courses for this semester. A value of null
* means all courses (same as staffFor()).
* @return a sorted array of the matching course offerings.
*/
public NSArray<CourseOffering> adminForButNotStaff(Semester semester)
{
NSArray<CourseOffering> result = adminForButNotStaff();
if (semester != null)
{
result = CourseOffering.semester.is(semester).filtered(result);
}
return result;
}
// ----------------------------------------------------------
/**
* Returns a sorted list of course offerings that this user has
* administrative access to, but is not an instructor, TA, or student
* for.
* @return a sorted array of the matching course offerings.
*/
public NSArray<CourseOffering> adminForButNoOtherRelationships()
{
if (!hasAdminPrivileges())
{
return NO_COURSES;
}
if (enrolledIn_cache != enrolledIn())
{
enrolledIn_cache = enrolledIn();
adminForButNoOtherRelationships_cache = null;
}
if (graderFor_cache != graderFor())
{
graderFor_cache = graderFor();
adminForButNoOtherRelationships_cache = null;
}
if (teaching_cache != teaching())
{
teaching_cache = teaching();
adminForButNoOtherRelationships_cache = null;
}
if (adminForButNoOtherRelationships_cache == null)
{
// For some reason, using the fetch directly does not seem
// to work, at least not when the result is empty.
// adminForButNoOtherRelationships_cache =
// CourseOffering.objectsForWithoutAnyRelationshipToUser(
// editingContext(), this );
@SuppressWarnings("unchecked")
NSArray<CourseOffering> temp =
ERXArrayUtilities.filteredArrayWithEntityFetchSpecification(
CourseOffering.allObjects(editingContext()),
CourseOffering.ENTITY_NAME,
CourseOffering
.OFFERINGS_WITHOUT_ANY_RELATIONSHIP_TO_USER_FSPEC,
userFilteringDictionary());
adminForButNoOtherRelationships_cache = temp;
}
return adminForButNoOtherRelationships_cache;
}
// ----------------------------------------------------------
/**
* Returns a sorted list of course offerings that this user has
* administrative access to, but is not an instructor, TA, or student
* for.
* @param semester Only return courses for this semester. A value of null
* means all courses (same as staffFor()).
* @return a sorted array of the matching course offerings.
*/
public NSArray<CourseOffering> adminForButNoOtherRelationships(
Semester semester)
{
NSArray<CourseOffering> result = adminForButNoOtherRelationships();
if (semester != null)
{
result = CourseOffering.semester.is(semester).filtered(result);
}
return result;
}
// ----------------------------------------------------------
/**
* Retrieve the CoreSelections object associated with this user,
* creating one if necessary.
* @return This user's core selections object
*/
public CoreSelections getMyCoreSelections()
{
NSArray<CoreSelections> cs = coreSelections();
if (cs.count() == 0)
{
EOEditingContext ec = WCEC.newEditingContext();
try
{
ec.lock();
CoreSelections newCoreSelections = new CoreSelections();
ec.insertObject(newCoreSelections);
newCoreSelections.setUserRelationship(localInstance(ec));
ec.saveChanges();
editingContext().refreshObject(this);
cs = coreSelections();
}
finally
{
ec.unlock();
ec.dispose();
}
}
return cs.objectAtIndex(0);
}
// ----------------------------------------------------------
/**
* Use a separate editing context to save this user's preferences data,
* if possible.
*/
public void savePreferences()
{
boolean usingFreshEC = (ecForPrefs == null);
if (usingFreshEC)
{
ecForPrefs = WCEC.newEditingContext();
}
EOEditingContext ec = ecForPrefs;
ec.lock();
try
{
// Use a separate EC to store the changed preferences
User me = localInstance(ec);
me.setPreferences(preferences());
ec.saveChanges();
// Now refresh the session's user object so that it loads
// this saved preferences value
editingContext().refreshObject(this);
}
catch (Exception e)
{
// If there was an error saving ...
ecForPrefs = null;
try
{
// Try to unlock first, if possible
try
{
ec.unlock();
}
catch (Exception eee)
{
// nothing
}
// Try to clean up the broken editing context, if possible
ec.dispose();
}
catch (Exception ee)
{
// if there is an error, ignore it since we're not going to
// use this ec any more anyway
}
ec = null;
if (!usingFreshEC)
{
savePreferences();
}
}
finally
{
if (ec != null)
{
ec.unlock();
}
}
}
// ----------------------------------------------------------
public static String scriptRoot()
{
// I don't like having this here, but it's necessary to get the same
// existing behavior in the function below, and it's only used for
// migrating data from the old location into the new Git repositories.
if (scriptRoot == null)
{
scriptRoot = org.webcat.core.Application
.configurationProperties().getProperty("grader.scriptsroot");
if (scriptRoot == null)
{
scriptRoot = org.webcat.core.Application
.configurationProperties()
.getProperty("grader.submissiondir") + "/UserScripts";
}
}
return scriptRoot;
}
// ----------------------------------------------------------
public static String userDataRoot()
{
if (userDataRoot == null)
{
userDataRoot = org.webcat.core.Application.configurationProperties()
.getProperty("grader.scriptsdataroot");
if (userDataRoot == null)
{
userDataRoot = scriptRoot() + "Data";
}
}
return userDataRoot;
}
// ----------------------------------------------------------
public static User findObjectWithApiId(EOEditingContext ec, String apiId)
throws EOUtilities.MoreThanOneException
{
int dotIndex = apiId.indexOf('.');
EOQualifier qualifier = null;
if (dotIndex != -1)
{
AuthenticationDomain domain = AuthenticationDomain
.authDomainBySubdirName(apiId.substring(0, dotIndex));
if (domain != null)
{
String name = apiId.substring(dotIndex + 1);
qualifier = userName.is(name).and(
authenticationDomain.is(domain));
}
}
if (qualifier == null)
{
qualifier = userName.is(apiId);
}
return uniqueObjectMatchingQualifier(ec, qualifier);
}
// ----------------------------------------------------------
@Override
public String apiId()
{
return authenticationDomain().subdirName() + "." + userName();
}
// ----------------------------------------------------------
public void initializeRepositoryContents(File location) throws IOException
{
// Migrate the user's existing home directory from the deprecated
// grader location.
File oldLocation = new File(userDataRoot(),
authenticationDomain().subdirName() + "/" + userName());
if (oldLocation.exists())
{
FileUtilities.copyDirectoryContents(oldLocation, location);
}
else
{
log.warn("Location " + oldLocation.getAbsolutePath() + " does not "
+ "exist; not copying any files into repository");
}
// Create a welcome file that describes the repository.
String welcomeFilename = "README.txt";
if (new File(location, welcomeFilename).exists())
{
welcomeFilename = "REPOSITORY-README.txt";
}
File readme = new File(location, welcomeFilename);
PrintWriter writer = new PrintWriter(readme);
writer.print(
"This Git repository has been created to manage the personal files\n"
+ "for Web-CAT user \"" + userName() + "\" ("
+ authenticationDomain().displayableName() + ").\n\n");
writer.print(
"You can store any files you wish here. You can also delete this\n"
+ "readme file if you like; it was provided merely for informational\n"
+ "purposes.");
writer.close();
}
// ----------------------------------------------------------
public static NSArray<User> repositoriesPresentedToUser(User user,
EOEditingContext ec)
{
if (user.hasTAPrivileges())
{
return new NSArray<User>(user.localInstance(ec));
}
else
{
return NSArray.<User>emptyArray();
}
}
// ----------------------------------------------------------
public boolean userCanAccessRepository(User user)
{
return user.equals(this);
}
/*
* The following two overrides cause changes to preferences to be
* auto-saved when the modification is made by pushing a value into a KVC
* binding.
*/
// ----------------------------------------------------------
@Override
public void takeValueForKey(Object value, String key)
{
super.takeValueForKey(value, key);
if (PREFERENCES_KEY.equals(key))
{
savePreferences();
}
}
// ----------------------------------------------------------
@Override
public void takeValueForKeyPath(Object value, String keyPath)
{
super.takeValueForKeyPath(value, keyPath);
int dotIndex = keyPath.indexOf('.');
if (dotIndex == -1 && PREFERENCES_KEY.equals(keyPath)
|| PREFERENCES_KEY.equals(keyPath.substring(0, dotIndex)))
{
savePreferences();
}
}
//~ Private Methods .......................................................
// ----------------------------------------------------------
private NSDictionary<String, User> userFilteringDictionary()
{
if (userIsMe == null)
{
userIsMe = new NSDictionary<String, User>(this, "user");
}
return userIsMe;
}
//~ Instance/static variables .............................................
private NSArray<CourseOffering> enrolledIn_cache;
private NSArray<CourseOffering> graderFor_cache;
private NSArray<CourseOffering> teaching_cache;
private NSArray<CourseOffering> graderForButNotStudent_cache;
private NSArray<CourseOffering> instructorForButNotGraderOrStudent_cache;
private NSArray<CourseOffering> staffFor_cache;
private NSArray<CourseOffering> adminForButNotStaff_cache;
private NSArray<CourseOffering> adminForButNoOtherRelationships_cache;
private String name_LF_cache;
private static String scriptRoot;
private static String userDataRoot;
private EOEditingContext ecForPrefs;
private NSDictionary<String, User> userIsMe;
private boolean studentView = false;
private static final String PREFIX = "user.";
private static final NSArray<CourseOffering> NO_COURSES =
new NSArray<CourseOffering>();
private static final NSArray<EOSortOrdering> courseSortOrderings =
CourseOffering.course.dot(Course.number).asc().then(
CourseOffering.crn.asc());
static Logger log = Logger.getLogger(User.class);
}