UserStore.java
/*
* Copyright © 2023-2025 The CTAN Team and individual authors
*
* This file is distributed under the 3-clause BSD license.
* See file LICENSE for details.
*/
package org.ctan.site.stores;
import java.lang.reflect.InvocationTargetException;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.ctan.site.domain.Gender;
import org.ctan.site.domain.account.Role;
import org.ctan.site.domain.account.User;
import org.ctan.site.stores.base.GeneralPage;
import org.hibernate.SessionFactory;
import io.dropwizard.hibernate.AbstractDAO;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.Root;
/**
* The class <code>UserStore</code> contains the repository for users.
*
* @author <a href="mailto:gene@ctan.org">Gerd Neugebauer</a>
*/
public class UserStore extends AbstractDAO<User> {
/**
* This is the constructor for the <code>UserStore</code>.
*
* @param sessionFactory the session factory
*/
public UserStore(SessionFactory sessionFactory) {
super(sessionFactory);
}
/**
* The method <code>getByAccount</code> provides means to find an user by
* its account name.
*
* @param account the account name
* @return the user or {@code null}
*/
public User getByAccount(String account) {
var query = criteriaQuery();
CriteriaBuilder cb = currentSession().getCriteriaBuilder();
Root<User> user = query.from(User.class);
query.where(cb.equal(user.get("account"), account));
return uniqueResult(query);
}
/**
* The method <code>getByEmail</code> provides means to find an user by its
* email.
*
* @param email the email
* @return the user or {@code null}
*/
public User getByEmail(String email) {
var query = criteriaQuery();
CriteriaBuilder cb = currentSession().getCriteriaBuilder();
Root<User> user = query.from(User.class);
query.where(cb.equal(user.get("email"), email.toLowerCase()));
return uniqueResult(query);
}
/**
* The method <code>get</code> provides means to find an user by its id.
*
* @param id the is
* @return the user or {@code null}
*/
public User getById(Long id) {
return get(id);
}
/**
* The method <code>list</code> provides means to extract a page of items.
*
* @param term the search term
* @param page the current page
* @param pageSize the page size
* @param orderBy the name or the column to sort by
* @param asc the indicator for ascending/descending sort order
* @return a page with the results
*/
public GeneralPage list(String term, int page, int pageSize,
String orderBy, boolean asc) {
if (page < 0 || pageSize < 1) {
return null;
}
CriteriaBuilder cb = currentSession().getCriteriaBuilder();
var query = criteriaQuery();
Root<User> user = query.from(User.class);
if (term != null && !term.isBlank()) {
var t = "%" + term.toLowerCase() + "%";
query.where(
cb.or(
cb.like(cb.lower(user.get("account")), t),
cb.like(cb.lower(user.get("name")), t),
cb.like(cb.lower(user.get("email")), t)));
}
if (orderBy != null && !orderBy.isBlank()) {
if (asc) {
query.orderBy(cb.asc(user.get(orderBy)));
} else {
query.orderBy(cb.desc(user.get(orderBy)));
}
}
var hits = list(query);
var hitCount = hits.size();
var list = hits
.subList(Math.min(page * pageSize, hitCount),
Math.min((page + 1) * pageSize, hitCount))
.stream()
.map(u -> Map.of("account", u.getAccount(),
"name", u.getName(),
"email", u.getEmail(),
"enabled", u.getEnabled(),
"locked", u.getAccountLocked(),
"authorKey", u.getAuthorKey() != null ? u.getAuthorKey() : "",
"roles", u.getRoles().stream()
.map(r -> r.toString())
.collect(Collectors.toList())))
.collect(Collectors.toList());
return GeneralPage.builder()
.size(1)
.list(list)
.build();
}
/**
* The method <code>remove</code> provides means to delete an account.
*
* @param user the account instance
*/
public void remove(User user) {
currentSession().remove(user);
}
/**
* The method <code>removeByAccount</code> provides means to delete an
* account by account name.
*
* @param account the account name
* @return {@code true} iff the account has been found
*/
public boolean removeByAccount(String account) {
var user = getByAccount(account);
if (user == null) {
return false;
}
currentSession().remove(user);
return true;
}
/**
* The method <code>save</code> provides means to store a user in the
* database.
*
* @param user the user
* @return the user
*/
public User save(User user) {
currentSession().persist(user);
return user;
}
/**
* The method <code>set</code> provides means to alter a single attribute
* and store the user instance.
*
* @param account the account
* @param key the name of the attribute
* @param value the string representation of the value
* @return {@code true} iff the attribute has been changed
*/
public boolean set(String account, String key, String value) {
var user = getByAccount(account);
if (user == null) {
return false;
}
String setter;
switch (key) {
case "accountExpired":
case "accountLocked":
case "enabled":
case "htmlEmail":
case "hyphenate":
case "passwordExpired":
case "showEmail":
case "showName":
setter = "set" + Character.toUpperCase(key.charAt(0))
+ key.substring(1);
try {
User.class
.getMethod(setter, String.class)
.invoke(user, Boolean.valueOf(value));
} catch (NoSuchMethodException | SecurityException
| IllegalAccessException
| InvocationTargetException e) {
return false;
}
break;
case "avatar":
return false;
case "dateCreated":
return false;
case "gender":
user.setGender(Gender.valueOf(value.toUpperCase()));
break;
case "roles":
Set<Role> roles = new HashSet<>();
for (var r : value.split("[,:;]")) {
roles.add(Role.valueOf(r));
}
user.setRoles(roles);
break;
case "account":
case "authorKey":
case "avatarType":
case "email":
case "location":
case "name":
case "selfDescription":
case "skin":
setter = "set" + Character.toUpperCase(key.charAt(0))
+ key.substring(1);
try {
User.class
.getMethod(setter, String.class)
.invoke(user, value);
} catch (NoSuchMethodException | SecurityException
| IllegalAccessException
| InvocationTargetException e) {
return false;
}
break;
// case "password":
default:
return false;
}
save(user);
return true;
}
}