/////////////////////////////////////////////////////////////////////////////
//
// Project ProjectForge Community Edition
// www.projectforge.org
//
// Copyright (C) 2001-2014 Kai Reinhard (k.reinhard@micromata.de)
//
// ProjectForge is dual-licensed.
//
// This community edition 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; version 3 of the License.
//
// This community edition 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 org.projectforge.user;
import java.io.Serializable;
import java.sql.Timestamp;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.FetchType;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.Transient;
import javax.persistence.UniqueConstraint;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.ObjectUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.Validate;
import org.hibernate.search.annotations.Field;
import org.hibernate.search.annotations.Index;
import org.hibernate.search.annotations.Indexed;
import org.hibernate.search.annotations.Store;
import org.joda.time.DateTimeZone;
import org.projectforge.common.ReflectionToString;
import org.projectforge.common.TimeNotation;
import org.projectforge.core.AbstractBaseDO;
import org.projectforge.core.AbstractHistorizableBaseDO;
import org.projectforge.core.BaseDO;
import org.projectforge.core.Configuration;
import org.projectforge.core.DefaultBaseDO;
import org.projectforge.core.ModificationStatus;
import org.projectforge.core.ShortDisplayNameCapable;
/**
*
* @author Kai Reinhard (k.reinhard@micromata.de)
*
*/
@Entity
@Indexed
@Table(name = "T_PF_USER", uniqueConstraints = { @UniqueConstraint(columnNames = { "username"})})
public class PFUserDO extends DefaultBaseDO implements ShortDisplayNameCapable
{
private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(PFUserDO.class);
private static final long serialVersionUID = 6680346054753032534L;
private static final String NOPASSWORD = "--- none ---";
static {
AbstractHistorizableBaseDO.putNonHistorizableProperty(PFUserDO.class, "loginFailures", "lastLogin", "stayLoggedInKey", "passwordSalt",
"password");
}
private transient Map<String, Object> attributeMap;
@Field(index = Index.TOKENIZED, store = Store.NO)
private String username;
@Field(index = Index.TOKENIZED, store = Store.NO)
private String jiraUsername;
private String password;
private Date lastPasswordChange;
private boolean localUser;
private boolean restrictedUser;
private boolean deactivated;
@Field(index = Index.TOKENIZED, store = Store.NO)
private String firstname;
@Field(index = Index.TOKENIZED, store = Store.NO)
private String lastname;
@Field(index = Index.TOKENIZED, store = Store.NO)
private String description;
@Field(index = Index.TOKENIZED, store = Store.NO)
private String email;
private String stayLoggedInKey;
private String authenticationToken;
private String passwordSalt;
private Timestamp lastLogin;
private int loginFailures;
private Locale locale;
private TimeZone timeZone;
private Locale clientLocale;
private String dateFormat;
private String excelDateFormat;
private Integer firstDayOfWeek;
private TimeNotation timeNotation;
@Field(index = Index.TOKENIZED, store = Store.NO)
private String organization;
@Field(index = Index.TOKENIZED, store = Store.NO)
private String personalPhoneIdentifiers;
@Field(index = Index.TOKENIZED, store = Store.NO)
private String personalMebMobileNumbers;
private Set<UserRightDO> rights = new HashSet<UserRightDO>();
private boolean hrPlanning = true;
@Field(index = Index.TOKENIZED, store = Store.NO)
private String ldapValues;
@Field(index = Index.TOKENIZED, store = Store.NO)
private String sshPublicKey;
@Transient
public String getShortDisplayName()
{
return getUsername();
}
@Column(length = 255)
public String getOrganization()
{
return organization;
}
public PFUserDO setOrganization(final String organization)
{
this.organization = organization;
return this;
}
/**
* @return For example "Europe/Berlin" if time zone is given otherwise empty string.
*/
@Transient
public String getTimeZoneDisplayName()
{
if (timeZone == null) {
return "";
}
return timeZone.getDisplayName();
}
/**
* @return For example "Europe/Berlin" if time zone is given otherwise empty string.
*/
@Column(name = "time_zone")
public String getTimeZone()
{
if (timeZone == null) {
return "";
}
return timeZone.getID();
}
public void setTimeZone(final String timeZoneId)
{
if (StringUtils.isNotBlank(timeZoneId) == true) {
setTimeZone(TimeZone.getTimeZone(timeZoneId));
}
}
/**
* @param timeZone
* @return this for chaining.
*/
public PFUserDO setTimeZone(final TimeZone timeZone)
{
this.timeZone = timeZone;
return this;
}
@Transient
public TimeZone getTimeZoneObject()
{
if (timeZone != null) {
return this.timeZone;
} else {
return Configuration.getInstance().getDefaultTimeZone();
}
}
public void setTimeZoneObject(final TimeZone timeZone)
{
this.timeZone = timeZone;
}
@Transient
public DateTimeZone getDateTimeZone()
{
final TimeZone timeZone = getTimeZoneObject();
return DateTimeZone.forID(timeZone.getID());
}
@Column
public Locale getLocale()
{
return locale;
}
/**
* @param locale
* @return this for chaining.
*/
public PFUserDO setLocale(final Locale locale)
{
this.locale = locale;
return this;
}
/**
* Default date format for the user. Examples:
* <ul>
* <li>yyyy-MM-dd: 2011-02-21, ISO format.</li>
* <li>dd.MM.yyyy: 21.02.2011, German format (day of month first)</li>
* <li>dd/MM/yyyy: 21/02/2011, British and French format (day of month first)</li>
* <li>MM/dd/yyyy: 02/21/2011, American format (month first)</li>
* </ul>
* @return
*/
@Column(name = "date_format", length = 20)
public String getDateFormat()
{
return dateFormat;
}
public void setDateFormat(final String dateFormat)
{
this.dateFormat = dateFormat;
}
/**
* Default excel date format for the user. Examples:
* <ul>
* <li>DD.MM.YYYY: 21.02.2011, German format (day of month first)</li>
* <li>DD/MM/YYYY: 21/02/2011, British and French format (day of month first)</li>
* <li>MM/DD/YYYY: 02/21/2011, American format (month first)</li>
* </ul>
* @return
*/
@Column(name = "excel_date_format", length = 20)
public String getExcelDateFormat()
{
return excelDateFormat;
}
public void setExcelDateFormat(final String excelDateFormat)
{
this.excelDateFormat = excelDateFormat;
}
@Enumerated(EnumType.STRING)
@Column(name = "time_notation", length = 6)
public TimeNotation getTimeNotation()
{
return timeNotation;
}
/**
* 0 - sunday, 1 - monday etc.
* @return the firstDayOfWeek
*/
@Column(name = "first_day_of_week")
public Integer getFirstDayOfWeek()
{
return firstDayOfWeek;
}
/**
* @param firstDayOfWeek the firstDayOfWeek to set
* @return this for chaining.
*/
public PFUserDO setFirstDayOfWeek(final Integer firstDayOfWeek)
{
this.firstDayOfWeek = firstDayOfWeek;
return this;
}
public void setTimeNotation(final TimeNotation timeNotation)
{
this.timeNotation = timeNotation;
}
/**
* Eine kommaseparierte Liste mit den Kennungen des/der Telefon(e) des Mitarbeiters an der unterstützten Telefonanlage, &zB; zur
* Direktwahl aus ProjectForge heraus.
*/
@Column(name = "personal_phone_identifiers", length = 255)
public String getPersonalPhoneIdentifiers()
{
return personalPhoneIdentifiers;
}
public void setPersonalPhoneIdentifiers(final String personalPhoneIdentifiers)
{
this.personalPhoneIdentifiers = personalPhoneIdentifiers;
}
/**
* A comma separated list of all personal mobile numbers from which SMS can be send. Those SMS will be assigned to this user. <br/>
* This is a feature from the Mobile Enterprise Blogging.
*/
@Column(name = "personal_meb_identifiers", length = 255)
public String getPersonalMebMobileNumbers()
{
return personalMebMobileNumbers;
}
public void setPersonalMebMobileNumbers(final String personalMebMobileNumbers)
{
this.personalMebMobileNumbers = personalMebMobileNumbers;
}
/**
* Returns string containing all fields (except the password) of given user object (via ReflectionToStringBuilder).
* @param user
* @return
*/
@Override
public String toString()
{
return (new ReflectionToString(this) {
@Override
protected boolean accept(final java.lang.reflect.Field f)
{
return super.accept(f)
&& !"password".equals(f.getName())
&& !"stayLoggedInKey".equals(f.getName())
&& !"passwordSalt".equals(f.getName())
&& !"authenticationToken".equals(f.getName());
}
}).toString();
}
@Transient
public String getUserDisplayname()
{
final String str = getFullname();
if (StringUtils.isNotBlank(str) == true) {
return str + " (" + getUsername() + ")";
}
return getUsername();
}
@Override
public boolean equals(final Object o)
{
if (o instanceof PFUserDO) {
final PFUserDO other = (PFUserDO) o;
if (ObjectUtils.equals(this.getUsername(), other.getUsername()) == false)
return false;
return true;
}
return false;
}
@Override
public int hashCode()
{
return getUsername() == null ? 0 : getUsername().hashCode();
}
@Override
public ModificationStatus copyValuesFrom(final BaseDO< ? extends Serializable> src, String... ignoreFields)
{
ignoreFields = (String[]) ArrayUtils.add(ignoreFields, "password"); // NPE save considering ignoreFields
final PFUserDO user = (PFUserDO) src;
ModificationStatus modificationStatus = AbstractBaseDO.copyValues(user, this, ignoreFields);
if (user.getPassword() != null) {
if (user.getPassword().equals(getPassword()) == false) {
modificationStatus = ModificationStatus.MAJOR;
}
setPassword(user.getPassword());
checkAndFixPassword();
}
return modificationStatus;
}
/**
* If password is not given as "SHA{..." then it will be set to null due to security reasons.
*/
protected void checkAndFixPassword()
{
if (StringUtils.isNotEmpty(getPassword()) == true
&& getPassword().startsWith("SHA{") == false
&& getPassword().equals(NOPASSWORD) == false) {
setPassword(null);
log.error("Password for user '" + getUsername() + "' is not given SHA encrypted. Ignoring it.");
}
}
/**
* The locale given from the client (e. g. from the browser by the http request). This locale is needed by PFUserContext for getting the
* browser locale if the user's locale is null and the request's locale is not available.
* @return
*/
@Transient
public Locale getClientLocale()
{
return clientLocale;
}
public PFUserDO setClientLocale(final Locale clientLocale)
{
this.clientLocale = clientLocale;
return this;
}
/**
* Do nothing.
* @see org.projectforge.core.ExtendedBaseDO#recalculate()
*/
@Override
public void recalculate()
{
}
@Override
public Object getAttribute(final String key)
{
if (attributeMap == null) {
return null;
}
return attributeMap.get(key);
}
@Override
public void setAttribute(final String key, final Object value)
{
synchronized (this) {
if (attributeMap == null) {
attributeMap = new HashMap<String, Object>();
}
attributeMap.put(key, value);
}
}
public void removeAttribute(final String key)
{
if (attributeMap == null) {
return;
}
attributeMap.remove(key);
}
/**
* @return Returns the username.
*/
@Column(length = 255, nullable = false)
public String getUsername()
{
return username;
}
/**
* @param username The username to set.
* @return this for chaining.
*/
public PFUserDO setUsername(final String username)
{
this.username = username;
return this;
}
/**
* Die E-Mail Adresse des Benutzers, falls vorhanden.
* @return Returns the email.
*/
@Column(length = 255)
public String getEmail()
{
return email;
}
/**
* @param email The email to set.
* @return this for chaining.
*/
public PFUserDO setEmail(final String email)
{
Validate.isTrue(email == null || email.length() <= 255, email);
this.email = email;
return this;
}
/**
* Key stored in the cookies for the functionality of stay logged in.
*/
@Column(name = "stay_logged_in_key", length = 255)
public String getStayLoggedInKey()
{
return stayLoggedInKey;
}
public void setStayLoggedInKey(final String stayLoggedInKey)
{
this.stayLoggedInKey = stayLoggedInKey;
}
/**
* @return the saltString for giving salt to hashed password.
*/
@Column(name = "password_salt", length = 40)
public String getPasswordSalt()
{
return passwordSalt;
}
/**
* @param passwordSalt the saltString to set
* @return this for chaining.
*/
public PFUserDO setPasswordSalt(final String passwordSalt)
{
this.passwordSalt = passwordSalt;
return this;
}
/**
* The authentication token is usable for download links of the user (without further login). This is used e. g. for ics download links of
* the team calendars.
* @return the authenticationToken
*/
@Column(name = "authentication_token", length = 100)
public String getAuthenticationToken()
{
return authenticationToken;
}
/**
* @param authenticationToken the authenticationToken to set
* @return this for chaining.
*/
public PFUserDO setAuthenticationToken(final String authenticationToken)
{
this.authenticationToken = authenticationToken;
return this;
}
/**
* Der Vorname des Benutzer.
* @return Returns the firstname.
*/
@Column(length = 255)
public String getFirstname()
{
return firstname;
}
/**
* @param firstname The firstname to set.
* @return this for chaining.
*/
public PFUserDO setFirstname(final String firstname)
{
Validate.isTrue(firstname == null || firstname.length() <= 255, firstname);
this.firstname = firstname;
return this;
}
/**
* Gibt den Vor- und Nachnamen zurück, falls gegeben. Vor- und Nachname sind durch ein Leerzeichen getrennt.
* @return String
*/
@Transient
public String getFullname()
{
final StringBuffer name = new StringBuffer();
if (this.firstname != null) {
name.append(this.firstname);
name.append(" ");
}
if (this.lastname != null) {
name.append(this.lastname);
}
return name.toString();
}
/**
* Zeitstempel des letzten erfolgreichen Logins.
* @return Returns the lastLogin.
*/
@Column
public Timestamp getLastLogin()
{
return lastLogin;
}
/**
* @return Returns the lastname.
*/
@Column(length = 255)
public String getLastname()
{
return lastname;
}
/**
* @return Returns the description.
*/
@Column(length = 255)
public String getDescription()
{
return description;
}
/**
* @param description The description to set.
* @return this for chaining.
*/
public PFUserDO setDescription(final String description)
{
Validate.isTrue(description == null || description.length() <= 255, description);
this.description = description;
return this;
}
/**
* Die Anzahl der erfolglosen Logins. Dieser Wert wird bei dem nächsten erfolgreichen Login auf 0 zurück gesetzt.
* @return Returns the loginFailures.
*/
@Column
public int getLoginFailures()
{
return loginFailures;
}
/**
* JIRA user name (if differ from the ProjectForge's user name) is used e. g. in MEB for creating new issues.
*/
@Column(name = "jira_username", length = 100)
public String getJiraUsername()
{
return jiraUsername;
}
public void setJiraUsername(final String jiraUsername)
{
this.jiraUsername = jiraUsername;
}
/**
* @return The JIRA user name or if not given the user name (assuming that the JIRA user name is same as ProjectForge user name).
*/
@Transient
public String getJiraUsernameOrUsername()
{
if (StringUtils.isNotEmpty(jiraUsername) == true) {
return this.jiraUsername;
} else {
return this.username;
}
}
/**
* Encoded password of the user (SHA-1).
* @return Returns the password.
*/
@Column(length = 50)
public String getPassword()
{
return password;
}
/**
* @param password The password to set.
* @return this for chaining.
*/
public PFUserDO setPassword(final String password)
{
this.password = password;
return this;
}
/**
* @param password The password to set.
* @return this for chaining.
*/
public PFUserDO setNoPassword()
{
this.password = NOPASSWORD;
return this;
}
/**
* @return the lastPasswordChange.
*/
@Column(name = "last_password_change")
public Date getLastPasswordChange()
{
return lastPasswordChange;
}
/**
* @param lastPasswordChange the lastPasswordChange to set
* @return this for chaining.
*/
public PFUserDO setLastPasswordChange(final Date lastPasswordChange)
{
this.lastPasswordChange = lastPasswordChange;
return this;
}
/**
* @param lastLogin The lastLogin to set.
*/
public void setLastLogin(final Timestamp lastLogin)
{
this.lastLogin = lastLogin;
}
/**
* @param lastname The lastname to set.
* @return this for chaining.
*/
public PFUserDO setLastname(final String lastname)
{
this.lastname = lastname;
return this;
}
/**
* @param loginFailures The loginFailures to set.
*/
public void setLoginFailures(final int loginFailures)
{
this.loginFailures = loginFailures;
}
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true, mappedBy = "user")
public Set<UserRightDO> getRights()
{
return this.rights;
}
public void setRights(final Set<UserRightDO> rights)
{
this.rights = rights;
}
public PFUserDO addRight(final UserRightDO right)
{
if (this.rights == null) {
setRights(new HashSet<UserRightDO>());
}
this.rights.add(right);
right.setUser(this);
return this;
}
@Transient
public UserRightDO getRight(final UserRightId rightId)
{
if (this.rights == null) {
return null;
}
for (final UserRightDO right : this.rights) {
if (right.getRightId().equals(rightId) == true) {
return right;
}
}
return null;
}
/**
* If true (default) then the user is highlighted in the human resource planning page if not planned for the actual week.
* @return the hrPlanning
*/
@Column(name = "hr_planning", nullable = false)
public boolean isHrPlanning()
{
return hrPlanning;
}
/**
* @param hrPlanning the hrPlanning to set
* @return this for chaining.
*/
public PFUserDO setHrPlanning(final boolean hrPlanning)
{
this.hrPlanning = hrPlanning;
return this;
}
/**
* A local user will not be synchronized with any external user management system.
* @return the localUser
*/
@Column(name = "local_user", nullable = false)
public boolean isLocalUser()
{
return localUser;
}
/**
* @param localUser the localUser to set
* @return this for chaining.
*/
public PFUserDO setLocalUser(final boolean localUser)
{
this.localUser = localUser;
return this;
}
/**
* A restricted user has only the ability to log-in and to change his password. This is useful if ProjectForge runs in master mode for
* managing an external LDAP system. Then this user is a LDAP user but has no other functionality than change password in the ProjectForge
* system itself.
* @return the restrictedUser
*/
@Column(name = "restricted_user", nullable = false)
public boolean isRestrictedUser()
{
return restrictedUser;
}
/**
* @param restrictedUser the restrictedUser to set
* @return this for chaining.
*/
public PFUserDO setRestrictedUser(final boolean restrictedUser)
{
this.restrictedUser = restrictedUser;
return this;
}
/**
* A deactivated user has no more system access.
* @return the deactivated
*/
@Column(nullable = false)
public boolean isDeactivated()
{
return deactivated;
}
/**
* @param deactivated the deactivated to set
* @return this for chaining.
*/
public PFUserDO setDeactivated(final boolean deactivated)
{
this.deactivated = deactivated;
return this;
}
/**
* LDAP values as key-value-pairs, e. g. gidNumber=1000,uidNumber=1001,homeDirectory="/home/kai",shell="/bin/bash". For handling of string
* values see {@link org.apache.commons.csv.writer.CSVWriter}. This field is handled by the ldap package and has no further effect in
* ProjectForge's core package.
* @return the ldapValues
*/
@Column(name = "ldap_values", length = 4000)
public String getLdapValues()
{
return ldapValues;
}
/**
* @param ldapValues the ldapValues to set
* @return this for chaining.
*/
public PFUserDO setLdapValues(final String ldapValues)
{
this.ldapValues = ldapValues;
return this;
}
/**
* @return the sshPublicKey
*/
@Column(name = "ssh_public_key", length = 1000)
public String getSshPublicKey()
{
return sshPublicKey;
}
/**
* @param sshPublicKey the sshPublicKey to set
* @return this for chaining.
*/
public PFUserDO setSshPublicKey(final String sshPublicKey)
{
this.sshPublicKey = sshPublicKey;
return this;
}
/**
* @return true only and only if the user isn't either deleted nor deactivated, otherwise false.
*/
@Transient
public boolean hasSystemAccess()
{
return isDeleted() == false && isDeactivated() == false;
}
@Transient
public String getDisplayUsername()
{
return getShortDisplayName();
}
}