AuthorStore.java
/*
* Copyright © 2021-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.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.ctan.site.domain.Gender;
import org.ctan.site.domain.catalogue.Author;
import org.ctan.site.domain.catalogue.Author.NameFormat;
import org.ctan.site.domain.catalogue.AuthorRef;
import org.ctan.site.services.search.base.IndexType;
import org.ctan.site.services.search.base.IndexingSession;
import org.ctan.site.stores.base.AbstractIndexingStore;
import org.ctan.site.stores.base.GeneralPage;
import org.hibernate.SessionFactory;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Join;
import jakarta.persistence.criteria.JoinType;
import jakarta.persistence.criteria.Root;
import lombok.NonNull;
/**
* The class <code>AuthorStore</code> contains the repository for authors.
*
* @author <a href="mailto:gene@ctan.org">Gerd Neugebauer</a>
*/
public class AuthorStore extends AbstractIndexingStore<Author> {
/**
* This is the constructor for the <code>AuthorStore</code>.
*
* @param sessionFactory the session factory
* @param indexingSession the indexing session
*/
public AuthorStore(SessionFactory sessionFactory,
IndexingSession indexingSession) {
super(sessionFactory, indexingSession);
}
/**
* The method <code>findAll</code> provides means to retrieve authors.
*
* @return the list of authors sorted by key
*/
@Override
public List<Author> findAll() {
CriteriaBuilder cb = currentSession().getCriteriaBuilder();
var query = criteriaQuery();
Root<Author> author = query.from(Author.class);
query.orderBy(cb.asc(author.get("key")));
return list(query);
}
/**
* The method <code>findAllByKeyStartingWith</code> provides means to
* retrieve authors where the key is starting with a given string. The
* comparison is done case-insensitive.
*
* @param s the initial segment
* @return the list of authors ordered by the key
*/
public List<Author> findAllByKeyStartingWith(String s) {
var query = criteriaQuery();
CriteriaBuilder cb = currentSession().getCriteriaBuilder();
Root<Author> author = query.from(Author.class);
query.where(cb.like(author.get("key"), s.toLowerCase() + "%"))
.orderBy(cb.asc(author.get("key")));
return list(query);
}
/**
* The method <code>findAllByNameContaining</code> provides means to search
* for authors.
*
* @param s the string contained in the name
* @return the list of authors ordered by the key
*/
public List<Author> findAllByNameContaining(String s) {
var query = criteriaQuery();
CriteriaBuilder cb = currentSession().getCriteriaBuilder();
Root<Author> author = query.from(Author.class);
query.where(cb.like(author.get("sortKey"), "%" + s.toLowerCase() + "%"))
.orderBy(cb.asc(author.get("sortKey")));
return list(query);
}
/**
* The method <code>findAllByNameStartingWith</code> provides means to
* retrieve authors where the sort text is starting with a given string. The
* comparison is done case-insensitive.
*
* @param s the initial segment
* @return the list of authors ordered by the sort text
*/
public List<Author> findAllByNameStartingWith(String s) {
var query = criteriaQuery();
CriteriaBuilder cb = currentSession().getCriteriaBuilder();
Root<Author> author = query.from(Author.class);
if (s != null && !"".equals(s)) {
query.where(cb.like(author.get("sortText"), s.toLowerCase() + "%"));
}
query.orderBy(cb.asc(author.get("sortText")));
return list(query);
}
/**
* The method <code>getByEmail</code> provides means to find an author by it
* email address.
*
* @param email the email address
* @return the author or {@code null}
*/
public Author getByEmail(String email) {
var query = criteriaQuery();
CriteriaBuilder cb = currentSession().getCriteriaBuilder();
Root<Author> author = query.from(Author.class);
query.where(cb.equal(author.get("email"), email.toLowerCase()));
return uniqueResult(query);
}
/**
* The method <code>getByKey</code> provides means to find an author by its
* key.
*
* @param key the key
* @return the author or {@code null}
*/
public Author getByKey(String key) {
var query = criteriaQuery();
CriteriaBuilder cb = currentSession().getCriteriaBuilder();
Root<Author> author = query.from(Author.class);
Join<Author, AuthorRef> refs = author.join("refs", JoinType.LEFT);
refs.join("pkg", JoinType.LEFT);
// conditions.add(cb.equal(pkg.get("id"), associateId));
// conditions.add(cb.isNull(refs.get("ack_date")));
query.where(cb.equal(author.get("key"), key));
return uniqueResult(query);
}
/**
* {@inheritDoc}
*
* @see org.ctan.site.stores.base.AbstractIndexingStore#indexType()
*/
@Override
protected IndexType indexType() {
return IndexType.AUTHORS;
}
/**
* 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
* @return the paged results
*/
@Override
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<Author> author = query.from(Author.class);
if (term != null && !term.isBlank()) {
var t = "%" + term.toLowerCase() + "%";
query.where(
cb.or(
cb.like(cb.lower(author.get("key")), t),
cb.like(cb.lower(author.get("name")), t),
cb.like(cb.lower(author.get("email")), t)));
}
if (orderBy != null && !orderBy.isBlank()) {
if (asc) {
query.orderBy(cb.asc(author.get(orderBy)));
} else {
query.orderBy(cb.desc(author.get(orderBy)));
}
}
var hits = list(query);
var hitCount = hits.size();
List<Map<String, Object>> list = hits
.subList(Math.min(page * pageSize, hitCount),
Math.min((page + 1) * pageSize, hitCount))
.stream()
.map(a -> a.toMap())
.collect(Collectors.toList());
return GeneralPage.builder()
.size(1)
.list(list)
.build();
}
/**
* {@inheritDoc}
*
* @see org.ctan.site.stores.base.AbstractStore#listQuery(java.lang.String,
* jakarta.persistence.criteria.CriteriaBuilder,
* jakarta.persistence.criteria.CriteriaQuery)
*/
@Override
protected Root<Author> listQuery(String term, CriteriaBuilder cb,
CriteriaQuery<Author> query) {
// TODO unimplemented
throw new UnsupportedOperationException();
}
/**
* {@inheritDoc}
*
* @see org.ctan.site.stores.base.AbstractStore#map(java.util.List)
*/
@Override
protected List<Map<String, Object>> map(List<Author> list) {
return list
.stream()
.map(it -> it.toMap())
.collect(Collectors.toList());
}
/**
* The method <code>set</code> provides means to set a single attribute to a
* new value.
*
* @param key the key
* @param attribute the attribute name
* @param value the attribute value
* @return {@code true} iff the change has been saved
*/
public Author set(@NonNull String key, String attribute, String value) {
var author = getByKey(key);
if (author == null) {
throw new IllegalArgumentException();
}
switch (attribute) {
case "familyname":
author.setFamilyname(value);
break;
case "key":
author.setKey(value);
break;
case "format":
author.setFormat(NameFormat.valueOf(value.toUpperCase()));
break;
case "givenname":
author.setGivenname(value);
break;
case "von":
author.setVon(value);
break;
case "junior":
author.setJunior(value);
break;
case "title":
author.setTitle(value);
break;
case "pseudonym":
author.setPseudonym(value);
break;
case "died":
author.setDied("true".equals(value));
break;
case "gender":
author.setGender(Gender.of(value));
break;
// case "refs":
// break;
// case "emails":
// author.setEmails(value.split("[,; ]+"));
// break;
default:
throw new IllegalArgumentException(attribute);
}
return save(author);
}
}