/**
* Copyright (C) 2011 JTalks.org Team
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
* This library 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
* Lesser General Public License for more details.
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package org.jtalks.jcommune.model.entity;
import com.google.common.annotations.VisibleForTesting;
import org.joda.time.DateTime;
import org.jtalks.common.model.entity.Group;
import org.jtalks.common.model.entity.User;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* Stores information about the forum user.
* Used as {@code UserDetails} in spring security for user authentication, authorization.
*
* @author Pavel Vervenko
* @author Kirill Afonin
* @author Alexandre Teterin
* @author Andrey Kluev
*/
public class JCUser extends User {
private int postCount;
private Language language = Language.ENGLISH;
private int pageSize = DEFAULT_PAGE_SIZE;
private String location;
private String signature;
private DateTime registrationDate;
private boolean enabled;
private boolean autosubscribe;
private boolean mentioningNotificationsEnabled;
private boolean sendPmNotification;
public static final int MAX_SIGNATURE_SIZE = 255;
public static final int MAX_LOCATION_SIZE = 30;
public static final int DEFAULT_PAGE_SIZE = 15;
public static final int[] PAGE_SIZES_AVAILABLE = new int[]{15, 25, 50};
private static final long serialVersionUID = 19981017L;
/**
* The {@link org.jtalks.jcommune.model.entity.JCUser} uses serialization for saving own state between
* Tomcat' session restarts. But there is not urgent needs to save state of
* the {@link org.jtalks.jcommune.model.entity.UserContact}, and moreover serialization of this one
* will pull serialization of even more classes which is undesirable.
*
* While we won't serialize full {@link org.jtalks.jcommune.model.entity.UserContact} entity
* we mark {@link org.jtalks.jcommune.model.entity.JCUser#contacts} as transient to avoid the problems
* during serialization of the {@link org.jtalks.common.model.entity.User} and his successors.
*/
private transient Set<UserContact> contacts = new HashSet<>();
private DateTime avatarLastModificationTime = new DateTime(System.currentTimeMillis());
private DateTime allForumMarkedAsReadTime;
/**
* Only for hibernate usage.
*/
protected JCUser() {
}
/**
* Create instance with required fields.
*
* @param username username
* @param email email
* @param password password
*/
public JCUser(String username, String email, String password) {
// passing salt as null until we're not using encrypted passwords
super(username, email, password, null);
}
/**
* Updates login time to current time
*/
public void updateLastLoginTime() {
this.setLastLogin(new DateTime());
}
/**
* @param contact user contact
*/
public void addContact(UserContact contact) {
contact.setOwner(this);
this.getContacts().add(contact);
}
/**
* @param contact user contact
*/
public void removeContact(UserContact contact) {
this.getContacts().remove(contact);
}
/**
* @return user signature
*/
public String getSignature() {
return signature;
}
/**
* @param content user signature
*/
public void setSignature(String content) {
this.signature = content;
}
/**
* @return count post this user
*/
public int getPostCount() {
return this.postCount;
}
/**
* @param postCount count posts this user to set
*/
public void setPostCount(int postCount) {
this.postCount = postCount;
}
/**
* @return user language
*/
public Language getLanguage() {
// add verification if language is not SPANISH. if it is SPANISH, language changed to default ENGLISH
if (language.equals(Language.SPANISH)) {
language = Language.ENGLISH;
}
return language;
}
/**
* @param language of user
*/
public void setLanguage(Language language) {
this.language = language;
}
/**
* @return user page size
*/
public int getPageSize() {
return pageSize;
}
/**
* @param pageSize user page size
*/
public void setPageSize(int pageSize) {
this.pageSize = pageSize;
}
/**
* @return user location
*/
public String getLocation() {
return location;
}
/**
* @param location user location
*/
public void setLocation(String location) {
this.location = location;
}
/**
* @return user registration date
*/
public DateTime getRegistrationDate() {
return registrationDate;
}
/**
* @param registrationDate user registration date
*/
public void setRegistrationDate(DateTime registrationDate) {
this.registrationDate = registrationDate;
}
/**
* @return set contacts of user
*/
public Set<UserContact> getContacts() {
return contacts;
}
/**
* @param contacts contacts of user
*/
protected void setContacts(Set<UserContact> contacts) {
this.contacts = contacts;
}
/**
* After registration user account is disabled by default.
* If not enabled in 24 hours after registration account will be deleted.
* <p/>
* User can activate his account by following the link in email.
*
* @return true, if user account is enabled
*/
public boolean isEnabled() {
return enabled;
}
/**
* Disables or enables the user account. If it's disabled, user won't be able to log in. Usually user is enabled
* during account activation.
*
* @param enabled if set to false, it will prevent user from log in
*/
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
/**
* Determines whether user is automatically subscribed to the topic while posting there.
*
* @return true if user automatically subscribed to the topic while posting there, or false if user switched this
* off in his settings
* @see <a href="http://jira.jtalks.org/browse/JC-1361">Related JIRA Ticket</a>
*/
public boolean isAutosubscribe() {
return autosubscribe;
}
/**
* Set whether after creating a new post user subscribes on update of the topic.
*
* @param autosubscribe set true if you'd like user to subscribe to the topic when she creates it, otherwise set
* false and user won't get automatically subscribed to the topic
* @see <a href="http://jira.jtalks.org/browse/JC-1361">Related JIRA Ticket</a>
*/
public void setAutosubscribe(boolean autosubscribe) {
this.autosubscribe = autosubscribe;
}
/**
* Determines whether email notifications are send to user when he has been mentioned in forum.
*
* @return true user receives email notifications, otherwise false
*/
public boolean isMentioningNotificationsEnabled() {
return mentioningNotificationsEnabled;
}
/**
* Set whether email notifications are send to user when he has been mentioned in forum.
*
* @param mentioningNotificationsEnabled true user receives email notifications, otherwise false
*/
public void setMentioningNotificationsEnabled(boolean mentioningNotificationsEnabled) {
this.mentioningNotificationsEnabled = mentioningNotificationsEnabled;
}
/**
* Returns whether current user is logged in or not. Vast majority of user
* properties is available for logged in users only, anonymous user object
* holds only default settings
*
* @return whether this user is anonymous
*/
public boolean isAnonymous() {
return false;
}
/**
* Determines whether email notifications are send to user when he has new private message.
*
* @return true user receives email notifications, otherwise false
*/
public boolean isSendPmNotification() {
return sendPmNotification;
}
/**
* Set whether email notifications are send to user when he has new private message.
*
* @param sendPmNotification
*/
public void setSendPmNotification(boolean sendPmNotification) {
this.sendPmNotification = sendPmNotification;
}
/**
* @return last modification time of avatar or {@code null} if it's the default avatar that has never changed
*/
public DateTime getAvatarLastModificationTime() {
return avatarLastModificationTime;
}
/**
* @param avatarLastModificationTime time when avatar was last modified
*/
public void setAvatarLastModificationTime(DateTime avatarLastModificationTime) {
this.avatarLastModificationTime = avatarLastModificationTime;
}
/**
* Get the time when forum was marked as all read for this user.
*
* @return if forum was marked as all read for this user it returns time of this action,
* if forum was never marked as all read it returns null
*/
public DateTime getAllForumMarkedAsReadTime() {
return allForumMarkedAsReadTime;
}
/**
* Set the time when forum was marked as all read for this user.
*
* @param forumMarkedAsAllReadTime the time when forum was marked as all read for this user
*/
public void setAllForumMarkedAsReadTime(DateTime forumMarkedAsAllReadTime) {
this.allForumMarkedAsReadTime = forumMarkedAsAllReadTime;
}
/**
* Adds a user to the group and adds group to the user. No checks whether there are such records present here,
* that's what Hibernate will do for us anyway.
*
* @param group a new group to be added to the list of groups this user is in
* @return this
*/
public JCUser addGroup(Group group) {
getGroups().add(group);
group.getUsers().add(this);
return this;
}
/**
* Delete a user from the group and remove group from the user.
*
* @param group a group for delete
* @return this
*/
public JCUser deleteGroup(Group group) {
getGroups().remove(group);
group.getUsers().remove(this);
return this;
}
/**
* Get only IDs for user groups
*
* @return group IDs
*/
public List<Long> getGroupsIDs() {
List<Long> groupIDs = new ArrayList<Long>();
List<Group> groups = getGroups();
for (Group group : groups) {
groupIDs.add(group.getId());
}
return groupIDs;
}
/**
* Creates copy of user needed in plugins. Since TransactionalUserService.getCurrentUser() returns instance
* of JCUser stored in Security Context it is not possible to load contacts lazily, also User contacts are
* not used in this scope, so we just don't need it. But if you need user contacts you can setup fetch.MODE
* on contacts collection in JCUser.hbm.xml
* @param user user to be copied
*/
public static JCUser copyUser(JCUser user) {
if (user == null) {
throw new IllegalArgumentException("User should not be null");
}
JCUser copy = new JCUser(user.getUsername(), user.getEmail(), user.getPassword());
copy.setId(user.getId());
copy.setFirstName(user.getFirstName());
copy.setLastName(user.getLastName());
copy.setLastLogin(user.getLastLogin());
copy.setBanReason(user.getBanReason());
copy.setRole(user.getRole());
copy.setEncodedUsername(user.getEncodedUsername());
copy.setAvatar(user.getAvatar());
copy.setVersion(user.getVersion());
for (Group group : user.getGroups()) {
copy.getGroups().add(copyUserGroup(group,copy));
}
copy.setSalt(user.getSalt());
copy.setPostCount(user.getPostCount());
copy.setLanguage(user.getLanguage());
copy.setPageSize(user.getPageSize());
copy.setLocation(user.getLocation());
copy.setSignature(user.getSignature());
copy.setRegistrationDate(user.getRegistrationDate());
copy.setEnabled(user.isEnabled());
copy.setAutosubscribe(user.isAutosubscribe());
copy.setMentioningNotificationsEnabled(user.isMentioningNotificationsEnabled());
copy.setSendPmNotification(user.isSendPmNotification());
copy.setAvatarLastModificationTime(user.getAvatarLastModificationTime());
copy.setAllForumMarkedAsReadTime(user.getAllForumMarkedAsReadTime());
copy.setUuid(user.getUuid());
/*
for (UserContact contact : user.getContacts()) {
copy.getContacts().add(copyUserContact(contact, copy));
}
*/
return copy;
}
/**
* Copies user contact and sets specified owner for copy. Needed for possibility perform deep copy of user.
* @param contact contact to be copied
* @param owner user which will be set as copy owner
*
* @return copy of specified user contact
*/
@VisibleForTesting
static UserContact copyUserContact(UserContact contact, JCUser owner) {
if (contact == null) {
throw new IllegalArgumentException("User contact should not be null");
}
UserContact copy = new UserContact(contact.getValue(), contact.getType());
copy.setOwner(owner);
copy.setId(contact.getId());
copy.setUuid(contact.getUuid());
return copy;
}
/**
* Copies user group and sets specified user as single user in copied group. We need it to restrict access to users
* from plugins.
* @param group user group to be copied
* @param user user which will be set as single user of copied group
*
* @return copy of specified user
*/
@VisibleForTesting
static Group copyUserGroup(Group group, JCUser user) {
if (group == null) {
throw new IllegalArgumentException("User group should not be null");
}
Group copy = new Group(group.getName(), group.getDescription());
copy.setId(group.getId());
copy.setUuid(group.getUuid());
copy.getUsers().add(user);
return copy;
}
/**
* Customized deserialization of the fields {@link org.jtalks.common.model.entity.Entity#id},
* {@link org.jtalks.common.model.entity.Entity#uuid}
*
* Note: The {@link org.jtalks.common.model.entity.User#groups} is marked as transient and will not be serialized
* (for more details pls. see at <a href="http://jira.jtalks.org/browse/POULPE-528">JIRA</a>).
*
* @serialData {@link org.jtalks.common.model.entity.Entity#id},
* {@link org.jtalks.common.model.entity.Entity#uuid},
* and the hole entities {@link org.jtalks.common.model.entity.User},
* {@link org.jtalks.jcommune.model.entity.JCUser}
* expect for the transient fields {@link org.jtalks.common.model.entity.User#groups} and
* {@link org.jtalks.jcommune.model.entity.JCUser#contacts}
* @param s
* @throws IOException
* @throws ClassNotFoundException
*/
private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException {
s.defaultReadObject();
long id = s.readLong();
String uuid = (String)s.readObject();
setId(id);
setUuid(uuid);
}
/**
* Customized serialization of the fields {@link org.jtalks.common.model.entity.Entity#id},
* {@link org.jtalks.common.model.entity.Entity#uuid}
*
* Note: The {@link org.jtalks.common.model.entity.User#groups} is marked as transient and will not be serialized
* (for more details pls. see at <a href="http://jira.jtalks.org/browse/POULPE-528">JIRA</a>).
*
* @serialData {@link org.jtalks.common.model.entity.Entity#id},
* {@link org.jtalks.common.model.entity.Entity#uuid},
* and the hole entities {@link org.jtalks.common.model.entity.User},
* {@link org.jtalks.jcommune.model.entity.JCUser}
* expect for the transient fields {@link org.jtalks.common.model.entity.User#groups} and
* {@link org.jtalks.jcommune.model.entity.JCUser#contacts}
* @param s
* @throws IOException
*/
private void writeObject(ObjectOutputStream s) throws IOException {
s.defaultWriteObject();
s.writeLong(getId());
s.writeObject(getUuid());
}
}