/*
* Copyright 2009-2012 by KNURT Systeme (http://www.knurt.de)
*
* Licensed under the Creative Commons License Attribution-NonCommercial-ShareAlike 3.0 Unported;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://creativecommons.org/licenses/by-nc-sa/3.0/
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.knurt.fam.core.persistence.dao;
import java.security.InvalidParameterException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.springframework.dao.DataIntegrityViolationException;
import de.knurt.fam.core.aspects.logging.FamLog;
import de.knurt.fam.core.aspects.security.auth.FamAuth;
import de.knurt.fam.core.model.config.Facility;
import de.knurt.fam.core.model.config.Role;
import de.knurt.fam.core.model.persist.Address;
import de.knurt.fam.core.model.persist.ContactDetail;
import de.knurt.fam.core.model.persist.LogbookEntry;
import de.knurt.fam.core.model.persist.User;
import de.knurt.fam.core.model.persist.UserMail;
import de.knurt.fam.core.model.persist.booking.Booking;
import de.knurt.fam.core.util.UserFactory;
import de.knurt.heinzelmann.util.auth.RandomPasswordFactory;
import de.knurt.heinzelmann.util.time.TimeFrame;
/**
* a dao resolving all about {@link User}s. resolve {@link UserMail}s as well.
*
* @author Daniel Oltmanns
* @since 0.20090323
*/
public abstract class UserDao extends AbstractFamDao<User> {
/**
* return the user that have the given unique username. return null, if no user with given username exists.
*
* @param username representing a user
* @return the user that have the given unique username.
*/
public abstract User getUserFromUsername(String username);
/** {@inheritDoc} */
@Override
protected void logAndThrowDataIntegrityViolationException(User user) {
String mess = "insert fail on " + user + ".";
DataIntegrityViolationException ex = new DataIntegrityViolationException(mess);
FamLog.logException(UserDao.class, ex, mess, 200903270956l);
throw ex;
}
/** {@inheritDoc} */
@Override
protected void logInsert(User user) {
FamLog.logInfo(UserDao.class, "insert " + user + ".", 200903270957l);
}
/** {@inheritDoc} */
@Override
protected void logUpdate(User user) {
FamLog.logInfo(UserDao.class, "update " + user + ".", 200911181630l);
}
/** {@inheritDoc} */
@Override
protected void setIdToNextId(User user) {
user.setUserId(this.getAll().size() + 1);
}
private String getNewUsername(User user) {
String result = "";
if (user != null) {
String fname = this.prepareUsername(user.getFname());
String sname = this.prepareUsername(user.getSname());
if (fname != null) {
if (fname.length() >= 2) {
result += fname.substring(0, 2);
} else {
result += fname;
}
}
if (sname != null) {
if (sname.length() >= 6) {
result += sname.substring(0, 6);
} else {
result += sname;
}
}
}
if (result.equals("")) {
result = "user" + this.prepareUsername(user.getMail());
}
return result;
}
/**
* return a unique username for given user username has 8 chars and is unique compared with all other users in the database. if the given user has a
* username, this is the base to create the unique name. Otherwise the first and second name of the user is taken. If nothing is set at all, the
* hashcode of the user object is taken. If the username is not unique, it gets a number: pemuelle, pemuell1, pemuell2, [...], pemuelle99.
*
* #TODO Issue #29
*
* @param user a unique username is created for
* @return a unique username for user
*/
public String getUniqueUsername(User user) {
String result = "";
if (user.getUsername() == null || user.getUsername().equals("")) {
result = this.getNewUsername(user);
} else {
result = user.getUsername().replaceAll("[0-9]", "");
}
result = this.makeUniqueUsername(result);
return result;
}
// XXX on many equal usernames performance is not acceptable here!
// TODO #29 kill that crazy stuff
private String makeUniqueUsername(String username) {
username = username.trim().toLowerCase();
User example = UserFactory.me().getUserWithUsername(username);
int i = 1;
String baseusername = username;
while (this.userLikeExists(example) && i < 101) {
if (i < 10) {
if (baseusername.length() < 8) {
username = baseusername + i;
} else {
username = baseusername.substring(0, 7) + i;
}
} else if (i < 100) {
if (baseusername.length() < 7) {
username = baseusername + i;
} else {
username = baseusername.substring(0, 6) + i;
}
} else {
username = this.getUniqueUsername(UserFactory.me().blank()); // return
// a
// username at random
}
example.setUsername(username);
i++;
}
if (i >= 101) {
// there are already to many users
char[] possibleChars = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' };
while (this.userLikeExists(example)) {
String randomUsername = RandomPasswordFactory.me().getPassword(8, possibleChars);
example = UserFactory.me().getUserWithUsername(randomUsername);
}
}
return username;
}
/**
* Return true, if another user <code>like</code> exists, that is not the same user as <code>andIsNotThis</code>.
*
* @param like example user
* @param andIsNotThis user that is the same user
* @return true, if another user <code>like</code> exists, that is not the same user as <code>andIsNotThis</code>.
*/
public boolean userLikeExists(User like, User andIsNotThis) {
int likecount = this.getObjectsLike(like).size();
boolean result = likecount > 1; // there are several like given
if (likecount == 1) { // there is exactly one user like given
User found = this.getOneLike(like); // get the one
result = andIsNotThis != found; // user is not the same user then
// given
}
return result;
}
/**
* Return true, if another user <code>like</code> given user exists. If the found user is the same user like the given, this returns false.
*
* @param like user as example
* @return true, if another user <code>like</code> given user exists.
*/
public boolean userLikeExists(User like) {
return this.userLikeExists(like, like);
}
/** {@inheritDoc} */
@Override
protected boolean isDataIntegrityViolation(User user, boolean onInsert) {
boolean result = false;
if (user.getMail() == null) {
result = true;
FamLog.debug("users mail is null " + user.getUsername(), 201011120948l);
} else if (user.getRoleId() == null) {
result = true;
FamLog.debug("users roleid is null " + user.getUsername(), 201011120949l);
} else if (user.getUsedPlattformLang() == null) {
result = true;
FamLog.debug("users plattformlang is null " + user.getUsername(), 201011120950l);
} else if (user.getRegistration() == null) {
result = true;
FamLog.debug("users registration is null " + user.getUsername(), 201011120951l);
} else if (user.getUsername() == null || user.getUsername().equals("")) {
result = true;
FamLog.debug("users username is null ", 201011120952l);
} else if (user.getPassword() == null || user.getPassword().equals("")) {
result = true;
FamLog.debug("users password is null " + user.getUsername(), 201011120953l);
}
if (result == false && onInsert) {
User testuser = UserFactory.me().blank();
testuser.setMail(user.getMail());
if (this.userLikeExists(testuser, user)) {
result = true;
FamLog.debug("users email exists" + user.getMail(), 201011120954l);
}
testuser.setMail(null);
testuser.setUsername(user.getUsername());
if (this.userLikeExists(testuser, user)) {
result = true;
FamLog.debug("users username exists" + user.getUsername(), 201011120955l);
}
}
return result;
}
/**
* store the given user if and only if it does not violate the data integrity. after validating the user succeeded, it delegates to
* {@link #internInsert(de.knurt.fam.core.model.persist.Storeable)} always invoke {@link User#encodePassword}, so that only encoded passes are
* saved.
*
* @param user to store
* @throws org.springframework.dao.DataIntegrityViolationException if storing would violate data integrity
*/
@Override
public synchronized boolean insert(User user) throws DataIntegrityViolationException {
user.encodePassword();
return super.insert(user);
}
/**
* update the given user
*
* @param user to be updated
* @throws DataIntegrityViolationException if the user is not storeable (like it is when user has no name or mail address).
*/
@Override
public synchronized boolean update(User user) throws DataIntegrityViolationException {
user.encodePassword();
return super.update(user);
}
/**
* insert the given mail into the database.
*
* @param mail the given mail into the database.
*/
public abstract boolean insert(UserMail mail);
/**
* return all users, that have an account and that are not barred from the system.
*
* @return all users, that have an account and that are not barred from the system.
*/
public List<User> getNotExcludedUsersWithAccount() {
List<User> result = new ArrayList<User>();
List<User> candidates = this.getAll();
for (User candidate : candidates) {
if (candidate.hasVarifiedActiveAccount()) {
result.add(candidate);
}
}
return result;
}
/**
* return all mails, that must be sent now.
*
* @return all mails, that must be sent now.
*/
public abstract List<UserMail> getUserMailsThatMustBeSendNow();
/**
* update the given mail
*
* @param mail to be updated
*/
public abstract boolean update(UserMail mail);
/**
* return all user emails.
*
* @return all user emails.
*/
public abstract List<UserMail> getAllUserMails();
private String prepareUsername(String string) {
// whatever you do here must be done in register.js in
// Register.Username.getPresumablyUsernameUncut as well
if (string == null) {
return "";
} else {
return string.trim().toLowerCase().replaceAll("ü", "ue").replaceAll("ä", "ae").replaceAll("ö", "oe").replaceAll("ß", "ss").replaceAll("[^a-z]", "");
}
}
/**
* return the address with the given id.
*
* @param id of the address
* @return the address with the given id.
*/
public abstract Address getAddress(Integer id);
/**
* return all users, which accounts are verified yet.
*
* @see User#hasVarifiedAccount()
* @return all users, which accounts are verified yet.
*/
public List<User> getAllUsersWithAccount() {
List<User> result = new ArrayList<User>();
List<User> candidates = this.getAll();
for (User candidate : candidates) {
if (candidate.hasVarifiedAccount()) {
result.add(candidate);
}
}
return result;
}
/**
* return all users, which accounts are <strong>not</strong> verified yet.
*
* @see User#hasVarifiedAccount()
* @return all users, which accounts are <strong>not</strong> verified yet.
*/
public List<User> getAllUsersWithoutAccount() {
List<User> result = new ArrayList<User>();
List<User> candidates = this.getAll();
for (User candidate : candidates) {
if (!candidate.hasVarifiedAccount()) {
result.add(candidate);
}
}
return result;
}
/**
* insert a given contact detail.
*
* @see ContactDetail
* @param contactDetail to insert
*/
public abstract boolean insert(ContactDetail contactDetail);
/**
* update a given contact detail.
*
* @see ContactDetail
* @param contactDetail to update
*/
public abstract boolean update(ContactDetail contactDetail);
/**
* delete a given existing contact detail.
*
* @see ContactDetail
* @param contactDetail to delete
*/
public abstract boolean delete(ContactDetail contactDetail);
/**
* return all contact details matching the example.
*
* @see ContactDetail
* @param example for comparing
* @return all contact details matching the example.
*/
public abstract List<ContactDetail> getAllLike(ContactDetail example);
public User getUserWithId(int userId) {
User example = UserFactory.me().blank();
example.setId(userId);
return this.getOneLike(example);
}
public synchronized boolean delete(List<ContactDetail> contactDetails) {
boolean result = true;
boolean tmp = false;
setChanged();
notifyObservers(contactDetails);
for (ContactDetail contactDetail : contactDetails) {
tmp = this.delete(contactDetail);
if (!tmp) result = false;
}
return result;
}
public UserMail getUserMailWithId(Integer id) {
UserMail result = null;
if (id != null) {
// XXX write sql to avoid performance leaks
List<UserMail> candidates = this.getAllUserMails();
for (UserMail candidate : candidates) {
if (candidate.getId().equals(id)) {
result = candidate;
}
}
}
return result;
}
public abstract List<User> getUsersAccountExpired(Date day);
public abstract List<User> getUserAccountExpiresIn(TimeFrame from);
public abstract List<User> getUsersRegistrationIsIn(TimeFrame from);
public abstract List<User> getUsersWithRealName(String firstname, String sirname) throws InvalidParameterException;
public abstract List<User> getUsersWithEMail(String email) throws InvalidParameterException;
/**
* set all personal user data to something anonym and update all relevated user data to this anonym user. delete all mails of the user and delete
* all contactdetails.
*
* set firstname and lastname to "Anonym".
*
* do not anonymize this statistical information
* <ul>
* <li> {@link User#getTitle()}</li>
* <li> {@link User#getCountry()}</li>
* <li> {@link User#getCity()}</li>
* <li> {@link User#getMale()}</li>
* <li> {@link User#getDepartmentKey()}</li>
* <li> {@link User#getDepartmentLabel()}</li>
* </ul>
*
* only change username of user's {@link LogbookEntry}s and {@link Booking}s to preserve logical information.
*
* if the user <code>auth</code> does not have the right {@link FamAuth#ANONYMIZE_USER}, do nothing and return <code>false</code>.
*
* @param user to anonymize
* @param auth user calling this
* @return true if it succeeded.
*/
public abstract boolean anonymize(User user, User auth);
/**
* return a list with users that are responsible for the given facility.
*
* @param facility "for"
* @see Facility#getKey()
* @return a list with users that are responsible for the given facility.
*/
public abstract List<User> getResponsibleUsers(Facility facility);
/**
* return all users with the given role or an empty list if there is no user with the given role.
*
* @param role the users have
* @return all users with given role
*/
public abstract List<User> getUserWithRole(Role role);
}