/*
* Copyright (C) 2012 maartenl
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public Licenseguldbean
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package mmud.database.entities.characters;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import java.util.logging.Logger;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.DiscriminatorValue;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.JoinColumns;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.OneToMany;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
import mmud.Attributes;
import mmud.Utils;
import mmud.database.entities.game.DisplayInterface;
import mmud.database.entities.game.Guild;
import mmud.database.entities.game.Guildrank;
import mmud.database.entities.game.Macro;
import mmud.database.enums.God;
import mmud.encryption.Hash;
import mmud.encryption.HexEncoder;
import mmud.exceptions.MudException;
/**
* A user in the game. Might be an administrator.
*
* @author maartenl
*/
@Entity
@DiscriminatorValue("0")
@NamedQueries(
{
@NamedQuery(name = "User.findByName", query = "SELECT p FROM User p WHERE lower(p.name) = lower(:name)")
,
@NamedQuery(name = "User.findActiveByName", query = "SELECT p FROM User p WHERE lower(p.name) = lower(:name) and p.active=1")
,
@NamedQuery(name = "User.fortunes", query = "SELECT p.name, p.copper FROM Person p WHERE p.god = 0 ORDER by p.copper DESC, p.name ASC")
,
@NamedQuery(name = "User.who", query = "SELECT p FROM User p WHERE p.god <=1 and p.active=1 ")
,
@NamedQuery(name = "User.status", query = "select p from Person p, Admin a WHERE a.name = p.name AND a.validuntil > CURRENT_DATE")
,
@NamedQuery(name = "User.authorise", query = "select u from User u WHERE u.name = :name and u.password = FUNCTION('sha1', :password)")
})
public class User extends Person
{
private static final int SECONDS_IN_A_MINUTE = 60;
private static final int MILLISECONDS_IN_A_SECOND = 1000;
private static final String PASSWORD_REGEXP = ".{5,}";
private static final Logger itsLog = java.util.logging.Logger.getLogger(User.class.getName());
/**
* Max idle time is currently set to 60 minutes.
*/
private static final long MAX_IDLE_TIME = 60;
@Size(min = 5, max = 200)
@Column(name = "address")
private String address;
@Size(max = 40)
// TODO get this fixed properly with a sha1 hash code upon insert.
@Column(name = "password")
@Pattern(regexp = PASSWORD_REGEXP, message = "Invalid password")
private String password;
@Size(max = 128)
@Column(name = "newpassword")
private String newpassword;
@Size(max = 80)
@Column(name = "realname")
private String realname;
// @Pattern(regexp="[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?", message="Invalid email")//if the field contains email address consider using this annotation to enforce field validation
@Size(max = 40)
@Column(name = "email")
private String email;
@Column(name = "punishment")
private Integer punishment;
@Column(name = "lastlogin")
@Temporal(TemporalType.TIMESTAMP)
private Date lastlogin;
@Column(name = "timeout")
@Temporal(TemporalType.TIMESTAMP)
private Date timeout;
@Size(max = 40)
@Column(name = "cgiServerSoftware")
private String cgiServerSoftware;
@Size(max = 40)
@Column(name = "cgiServerName")
private String cgiServerName;
@Size(max = 40)
@Column(name = "cgiGatewayInterface")
private String cgiGatewayInterface;
@Size(max = 40)
@Column(name = "cgiServerProtocol")
private String cgiServerProtocol;
@Size(max = 40)
@Column(name = "cgiServerPort")
private String cgiServerPort;
@Size(max = 40)
@Column(name = "cgiRequestMethod")
private String cgiRequestMethod;
@Size(max = 40)
@Column(name = "cgiPathInfo")
private String cgiPathInfo;
@Size(max = 40)
@Column(name = "cgiPathTranslated")
private String cgiPathTranslated;
@Size(max = 40)
@Column(name = "cgiScriptName")
private String cgiScriptName;
@Size(max = 40)
@Column(name = "cgiRemoteHost")
private String cgiRemoteHost;
@Size(max = 40)
@Column(name = "cgiRemoteAddr")
private String cgiRemoteAddr;
@Size(max = 40)
@Column(name = "cgiAuthType")
private String cgiAuthType;
@Size(max = 40)
@Column(name = "cgiRemoteUser")
private String cgiRemoteUser;
@Size(max = 40)
@Column(name = "cgiRemoteIdent")
private String cgiRemoteIdent;
@Size(max = 40)
@Column(name = "cgiContentType")
private String cgiContentType;
@Size(max = 40)
@Column(name = "cgiAccept")
private String cgiAccept;
@Size(max = 40)
@Column(name = "cgiUserAgent")
private String cgiUserAgent;
@Column(name = "lastcommand")
@Temporal(TemporalType.TIMESTAMP)
private Date lastcommand;
@Column(name = "rrribbits")
private Integer rrribbits;
@Column(name = "heehaws")
private Integer heehaws;
@Column(name = "ooc")
private Boolean ooc;
@OneToMany(cascade = CascadeType.ALL, mappedBy = "person", orphanRemoval = true)
private Set<Macro> macroCollection;
/**
* The list of people that you are ignoring.
*/
@ManyToMany(targetEntity = User.class, fetch = FetchType.LAZY, cascade =
{
CascadeType.ALL
})
@JoinTable(name = "mm_ignore", joinColumns =
{
@JoinColumn(name = "fromperson", referencedColumnName = "name")
}, inverseJoinColumns =
{
@JoinColumn(name = "toperson", referencedColumnName = "name")
})
private Set<User> ignoringSet = new HashSet<>();
/**
* The list of people that is ignoring you.
*/
@ManyToMany(mappedBy = "ignoringSet", targetEntity = User.class, fetch = FetchType.LAZY, cascade =
{
CascadeType.ALL
})
private Set<User> ignoredSet = new HashSet<>();
@JoinColumn(name = "guild", referencedColumnName = "name")
@ManyToOne(fetch = FetchType.LAZY, cascade =
{
CascadeType.PERSIST
})
private Guild guild;
/**
* The guild rank of this person. Let me remind you that the "guild" is already
* used twice here. This means that one of the two is going to be read-only
* by means of the insertable and updatable as false.
*/
@JoinColumns(
{
@JoinColumn(name = "guildlevel", referencedColumnName = "guildlevel")
,
@JoinColumn(name = "guild", referencedColumnName = "guildname", insertable = false, updatable = false)
})
@ManyToOne
private Guildrank guildrank;
/**
* The list of people that you are ignoring.
*
* @return
*/
public Set<User> getIgnoringSet()
{
return Collections.unmodifiableSet(ignoringSet);
}
/**
* The list of people that are ignoring you.
*
* @return
*/
public Set<User> getIgnoredSet()
{
return Collections.unmodifiableSet(ignoredSet);
}
public User()
{
super();
this.punishment = 0;
this.lastlogin = null;
}
public String getAddress()
{
return address;
}
/**
* Sets the ip address or hostname of the person playing the game.
* Usually done when logging in.
*
* @param address the ip address or hostname.
*/
public void setAddress(String address)
{
this.address = address;
}
/**
* @deprecated
* @return
*/
@Deprecated
public String getPassword()
{
return password;
}
/**
* Returns the SHA-512 hash of the password in the form of a 128 length
* String containing the HEX encoding of this hash.
*
* @return 128 character hex-encoded SHA-512 hash of the password.
*/
public String getNewpassword()
{
return newpassword;
}
/**
* Sets the password of the person. Can contain any character, but has to
* have at least size of 5. You cannot set a password this way, you can only
* set it for the first time, i.e. when creating a new character.
*
* @param password the new password.
* @throws MudException if the password is not allowed.
* @deprecated
*/
@Deprecated
public void setPassword(String password) throws MudException
{
if (this.password != null)
{
return;
}
if (password != null)
{
Utils.checkRegexp(PASSWORD_REGEXP, password);
}
this.password = new HexEncoder(40).encrypt(password, Hash.SHA_1);
}
/**
* Sets the password of the person. Can contain any character, but has to
* have at least size of 5. You cannot set a password this way, you can only
* set it for the first time, i.e. when creating a new character.
*
* @param newpassword the new password.
* @throws MudException if the password is not allowed.
*/
public void setNewpassword(String newpassword) throws MudException
{
if (this.newpassword != null)
{
return;
}
if (newpassword != null)
{
Utils.checkRegexp(PASSWORD_REGEXP, newpassword);
}
this.newpassword = new HexEncoder(128).encrypt(newpassword, Hash.SHA_512);
}
public String getRealname()
{
return realname;
}
public void setRealname(String realname)
{
this.realname = realname;
}
public String getEmail()
{
return email;
}
public void setEmail(String email)
{
this.email = email;
}
public Integer getPunishment()
{
return punishment;
}
public void setPunishment(Integer punishment)
{
this.punishment = punishment;
}
/**
* Returns the last time the user was logged in. Can return null, which
* means the user has never once logged on and is new. (or not a user)
*
* @return the date of last logged on.
*/
public Date getLastlogin()
{
return lastlogin;
}
public void setLastlogin(Date lastlogin)
{
this.lastlogin = lastlogin;
this.lastcommand = lastlogin;
}
public String getCgiServerSoftware()
{
return cgiServerSoftware;
}
public void setCgiServerSoftware(String cgiServerSoftware)
{
this.cgiServerSoftware = cgiServerSoftware;
}
public String getCgiServerName()
{
return cgiServerName;
}
public void setCgiServerName(String cgiServerName)
{
this.cgiServerName = cgiServerName;
}
public String getCgiGatewayInterface()
{
return cgiGatewayInterface;
}
public void setCgiGatewayInterface(String cgiGatewayInterface)
{
this.cgiGatewayInterface = cgiGatewayInterface;
}
public String getCgiServerProtocol()
{
return cgiServerProtocol;
}
public void setCgiServerProtocol(String cgiServerProtocol)
{
this.cgiServerProtocol = cgiServerProtocol;
}
public String getCgiServerPort()
{
return cgiServerPort;
}
public void setCgiServerPort(String cgiServerPort)
{
this.cgiServerPort = cgiServerPort;
}
public String getCgiRequestMethod()
{
return cgiRequestMethod;
}
public void setCgiRequestMethod(String cgiRequestMethod)
{
this.cgiRequestMethod = cgiRequestMethod;
}
public String getCgiPathInfo()
{
return cgiPathInfo;
}
public void setCgiPathInfo(String cgiPathInfo)
{
this.cgiPathInfo = cgiPathInfo;
}
public String getCgiPathTranslated()
{
return cgiPathTranslated;
}
public void setCgiPathTranslated(String cgiPathTranslated)
{
this.cgiPathTranslated = cgiPathTranslated;
}
public String getCgiScriptName()
{
return cgiScriptName;
}
public void setCgiScriptName(String cgiScriptName)
{
this.cgiScriptName = cgiScriptName;
}
public String getCgiRemoteHost()
{
return cgiRemoteHost;
}
public void setCgiRemoteHost(String cgiRemoteHost)
{
this.cgiRemoteHost = cgiRemoteHost;
}
public String getCgiRemoteAddr()
{
return cgiRemoteAddr;
}
public void setCgiRemoteAddr(String cgiRemoteAddr)
{
this.cgiRemoteAddr = cgiRemoteAddr;
}
public String getCgiAuthType()
{
return cgiAuthType;
}
public void setCgiAuthType(String cgiAuthType)
{
this.cgiAuthType = cgiAuthType;
}
public String getCgiRemoteUser()
{
return cgiRemoteUser;
}
public void setCgiRemoteUser(String cgiRemoteUser)
{
this.cgiRemoteUser = cgiRemoteUser;
}
public String getCgiRemoteIdent()
{
return cgiRemoteIdent;
}
public void setCgiRemoteIdent(String cgiRemoteIdent)
{
this.cgiRemoteIdent = cgiRemoteIdent;
}
public String getCgiContentType()
{
return cgiContentType;
}
public void setCgiContentType(String cgiContentType)
{
this.cgiContentType = cgiContentType;
}
public String getCgiAccept()
{
return cgiAccept;
}
public void setCgiAccept(String cgiAccept)
{
this.cgiAccept = cgiAccept;
}
public String getCgiUserAgent()
{
return cgiUserAgent;
}
public void setCgiUserAgent(String cgiUserAgent)
{
this.cgiUserAgent = cgiUserAgent;
}
/**
* Returns the last time that a command was issued. Used for determining
* idle time.
*
* @return the date of the last time a command was entered.
*/
public Date getLastcommand()
{
return lastcommand;
}
/**
* Sets the last time that a command was issued to *now*.
*/
public void setNow()
{
this.lastcommand = new Date();
}
/**
* activate a character
*
* @see #deactivate()
* @see #isActive()
*/
public void activate() throws MudException
{
if (!isUser())
{
throw new MudException("user not a user");
}
this.setLastlogin(new Date());
this.setActive(true);
createLog();
}
/**
* deactivate a character (usually because someone typed quit.)
*
* @see #activate(java.lang.String)
* @see #isActive()
*/
public void deactivate()
{
setActive(false);
setLastlogin(new Date());
}
public boolean isNewUser()
{
return lastlogin == null;
}
public DisplayInterface writeMacros()
{
return new DisplayInterface()
{
@Override
public String getMainTitle() throws MudException
{
return "Macros";
}
@Override
public String getImage() throws MudException
{
// TODO : create a macro icon.
return null;
}
@Override
public String getBody() throws MudException
{
StringBuilder sb = new StringBuilder("<table><tr><td><b>macro</b></td><td><b>contents</b></td></tr>");
for (Macro m : macroCollection)
{
sb.append("<tr><td>").append(m.getMacroname()).append("</td><td>").append(m.getContents()).append("</td></tr>");
}
sb.append("</table>");
return sb.toString();
}
};
}
/**
* Removes a macro from the existing macros. Returns false
* if the removal failed (for instance if the macro did not exist.)
*
* @param string the name of the macro to remove
* @return true if success, otherwise false.
*/
public boolean removeMacro(String string)
{
Macro found = getMacro(string);
if (found == null)
{
return false;
}
return macroCollection.remove(found);
}
/**
* Adds a macro to the list of macros. Will do nothing if the macro
* already exists.
*
* @param macro the macro to add.
*/
public void addMacro(Macro macro)
{
if (getMacro(macro.getMacroname()) != null)
{
return;
}
macroCollection.add(macro);
}
/**
* Returns the macro based on the name of the macro.
*
* @param string the name of the macro
* @return the macro itself.
*/
public Macro getMacro(String string)
{
Macro found = null;
for (Macro macro : macroCollection)
{
if (macro.getMacroname().equalsIgnoreCase(string))
{
found = macro;
break;
}
}
return found;
}
/**
* Adds a user to be ignored to the big ignore list.
*
* @param aUser
*/
public void addAnnoyingUser(User aUser)
{
if (aUser == null)
{
return;
}
ignoringSet.add(aUser);
aUser.ignoredSet.add(this);
}
/**
* Removes a user from the big ignore list. Assumedly, because he
* is no longer annoying.
*
* @param aUser
* @return true if the user was found, and successfully removed from the set.
*/
public boolean removeAnnoyingUser(User aUser)
{
if (aUser == null)
{
return false;
}
boolean result = ignoringSet.remove(aUser);
if (result == false)
{
return false;
}
return aUser.ignoredSet.remove(aUser);
}
/**
* Whether or not you are ignoring a certain person.
* The default is that nobody is ignoring you.
*
* @param aPerson the person to check in your ignore list.
* @return true if the person should be ignored, false otherwise.
*/
@Override
protected boolean isIgnoring(Person aPerson)
{
return ignoringSet.contains(aPerson);
}
/**
* Retrieves the idle time in minutes, i,e. between the last entered command and the
* current time. Returns null if the last command date/time is not
* available.
*
* @return Long containing minutes, or null.
*/
public Long getIdleTime()
{
Date date = getLastcommand();
Date now = new Date();
if (date == null)
{
return null;
}
return (now.getTime() - date.getTime()) / MILLISECONDS_IN_A_SECOND / SECONDS_IN_A_MINUTE;
}
/**
* Check to see if the User was inactive for more than an hour.
*
* @return boolean, true if the User was inactive (i.e. has not entered a
* command) for more than an hour.
*/
public boolean isIdleTooLong()
{
return this.getIdleTime() != null && this.getIdleTime() > MAX_IDLE_TIME;
}
/**
* Returns the time that a user was inactive.
*
* @return String the number of minutes and seconds that the player has been
* idle in the following format: "(<min> min, <sec> sec idle)".
*/
public String getIdleTimeInMinAndSeconds()
{
Calendar now = Calendar.getInstance();
long timeDiff = (now.getTimeInMillis() - getLastcommand().getTime()) / 1000;
return "(" + timeDiff / 60 + " min, " + timeDiff % 60 + " sec idle)";
}
public Guild getGuild()
{
return guild;
}
/**
* Sets the guild a person (user) belongs to. Also can be used to remove
* someone from the guild by providing a "null". When the latter case, also
* the guildrank is automatically cleared.
*
* @param guild the new guild, or null if the person is removed from the guild.
*/
public void setGuild(Guild guild)
{
if (guild == null)
{
this.guildrank = null;
}
this.guild = guild;
}
public Guildrank getGuildrank()
{
return guildrank;
}
public void setGuildrank(Guildrank guildrank)
{
if (this.guild == null)
{
throw new MudException("Unable to set guildrank for " + this.getName() + ". User is not part of a guild yet.");
}
this.guildrank = guildrank;
}
@Override
public String getStatistics() throws MudException
{
StringBuilder stuff = new StringBuilder();
if (getGuild() != null)
{
if (getGuild().getBoss().getName().equals(getName()))
{
stuff.append("You are the Guildmaster of <b>").append(getGuild().getTitle()).append("</b>.<br/>");
} else
{
stuff.append("You are a member of <b>").append(getGuild().getTitle()).append("</b>.<br/>");
}
}
return super.getStatistics() + stuff.toString();
}
@Override
public boolean canReceive()
{
return !verifyAttribute(Attributes.CANRECEIVE, "false");
}
@Override
public God getGod()
{
return God.DEFAULT_USER;
}
/**
* Sets the timeout, i.e. the amount of minutes that a user is not allowed
* to login to the game.
*
* @param minutes the number of minutes
*/
public void setTimeout(int minutes)
{
if (minutes <= 0)
{
timeout = null;
return;
}
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.MINUTE, minutes);
timeout = calendar.getTime();
}
/**
* Returns the number of minutes that a user is not allowed
* to login to the game.
*
* @return
*/
public int getTimeout()
{
if (timeout == null)
{
return 0;
}
Calendar calendar = Calendar.getInstance();
if (timeout.compareTo(calendar.getTime()) < 0)
{
return 0;
}
return (int) ((timeout.getTime() - calendar.getTime().getTime()) / 60000);
}
public int getFrogging()
{
if (rrribbits == null)
{
rrribbits = 0;
}
return rrribbits;
}
public int getJackassing()
{
if (heehaws == null)
{
heehaws = 0;
}
return heehaws;
}
public void setFrogging(int i)
{
if (i < 0)
{
throw new MudException("Frogging should not be less than 0, but was " + i + ".");
}
rrribbits = i;
}
public void lessFrogging()
{
rrribbits--;
if (rrribbits < 0)
{
rrribbits = 0;
}
}
public void setJackassing(int i)
{
if (i < 0)
{
throw new MudException("Jackassing should not be less than 0, but was " + i + ".");
}
heehaws = i;
}
public void lessJackassing()
{
heehaws--;
if (heehaws < 0)
{
heehaws = 0;
}
}
@Override
public String getRace()
{
if (getFrogging() > 0)
{
return "frog";
}
if (getJackassing() > 0)
{
return "jackass";
}
return super.getRace();
}
public boolean getOoc()
{
return ooc == null ? false : ooc;
}
public void setOoc(boolean b)
{
this.ooc = b;
}
}