PkgService.java
/*
* Copyright © 2024-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.services.texarchive;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.ctan.site.CtanConfiguration;
import org.ctan.site.domain.catalogue.AuthorRef;
import org.ctan.site.domain.catalogue.License;
import org.ctan.site.domain.catalogue.Pkg;
import org.ctan.site.domain.catalogue.PkgCaption;
import org.ctan.site.domain.catalogue.PkgCopyright;
import org.ctan.site.domain.catalogue.PkgDescription;
import org.ctan.site.domain.catalogue.PkgDoc;
import org.ctan.site.domain.catalogue.Topic;
import org.ctan.site.services.content.ContentService;
import org.ctan.site.services.content.ContentService.TeaserType;
import org.ctan.site.services.util.ConfigUtils;
import org.ctan.site.stores.PkgStore;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NonNull;
/**
* The class <code>TexarchiveService</code> contains the service to access the
* <span>T<span style=
* "text-transform:uppercase;font-size:90%;vertical-align:-0.4ex;
* margin-left:-0.2em;margin-right:-0.1em;line-height: 0;" >e</span>X</span>
* archive directory.
*
* @author <a href="mailto:gene@ctan.org">Gerd Neugebauer</a>
*/
public class PkgService {
/**
* The class <code>PkgTo</code> contains a transport object for the package.
*/
@Getter
@Builder
@AllArgsConstructor
@SuppressFBWarnings(value = "EI_EXPOSE_REP")
public static class PkgTo {
private String key;
private String name;
private String caption;
private String captionLang;
private String description;
private String descriptionLang;
private String versionDate;
private String versionNumber;
private String miktexLocation;
private String texliveLocation;
private String tlContribLocation;
private List<Map<String, String>> authors;
private List<Map<String, String>> licenses;
private List<Map<String, String>> copyrights;
private List<Map<String, String>> docs;
private boolean zip;
private String install;
private boolean hasTeaser;
private List<Map<String, String>> topics;
}
/**
* The field <code>pkgStore</code> contains the package store.
*/
private PkgStore pkgStore;
/**
* The field <code>defaultLang</code> contains the default language.
*/
private String defaultLang;
/**
* The field <code>contentService</code> contains the content service.
*/
private ContentService contentService;
/**
* This is the constructor for the class <code>PkgService</code>.
*
* @param config the CTAN configuration
* @param contentService the content service
* @param pkgStore the package store
*/
@SuppressFBWarnings(value = {"CT_CONSTRUCTOR_THROW", "EI_EXPOSE_REP2"})
public PkgService(@NonNull CtanConfiguration config,
@NonNull ContentService contentService,
@NonNull PkgStore pkgStore) {
this.contentService = contentService;
this.pkgStore = pkgStore;
this.defaultLang = ConfigUtils.defaultLanguage(config);
}
/**
* The method <code>failsave</code> provides means to savely access a sting.
*
* @param s the string
* @return s or the empty string if s is {@code null}
*/
private String failsave(String s) {
return s == null ? "" : s;
}
/**
* The method <code>getPkgByKey</code> provides means to retrieve a package
* by its key.
*
* @param key the package key
* @param lang the language for the notes
* @return the transport object or {@code null}
*/
public PkgTo getPkgByKey(String key, String lang) {
return ("".equals(key) || key == null
? null
: mapPkg(pkgStore.getByKey(key), lang));
}
/**
* The method <code>getPkgByPath</code> provides means to retrieve a package
* by its CTAN path.
*
* @param path the CTAN path of the package
* @param lang the language for the notes
* @return the transport object or {@code null}
*/
public PkgTo getPkgByPath(String path, String lang) {
return ("".equals(path) || path == null
? null
: mapPkg(pkgStore.getByCtanPath(path), lang));
}
/**
* The method <code>mapAuthors</code> provides means to map persisted
* authors to transport objects.
*
* @param authors the persisted author data
* @return a list of transport objects for authors
*/
private List<Map<String, String>> mapAuthors(List<AuthorRef> authors) {
if (authors == null) {
return List.of();
}
return authors.stream()
.map(a -> Map.of("key", a.getAuthor().getKey(),
"name", a.getAuthor().toString(),
"isActive", a.isActive() ? "true" : "false"))
.collect(Collectors.toList());
}
/**
* The method <code>mapCopyright</code> provides means to map persisted
* copyright infos to transport objects.
*
* @param copy the list of copyrights
* @return the list of transport objects
*/
private List<Map<String, String>> mapCopyright(List<PkgCopyright> copy) {
if (copy == null) {
return List.of();
}
return copy.stream()
.map(a -> Map.of(
"year", a.getYear(),
"owner", a.getOwner()))
.collect(Collectors.toList());
}
/**
* The method <code>mapDocs</code> provides means to map persisted
* documentation references to transport objects.
*
* @param docs the persisted documentation references
* @return a list of transport objects for docs
*/
private List<Map<String, String>> mapDocs(List<PkgDoc> docs) {
if (docs == null) {
return List.of();
}
return docs.stream()
.map(doc -> Map.of(
"title", failsave(doc.getTitle()),
"lang", failsave(doc.getLang()),
"href", failsave(doc.getHref()),
"author", failsave(doc.getAuthor())))
.collect(Collectors.toList());
}
/**
* The method <code>mapLicenses</code> provides means to map persisted
* licenses to transport objects.
*
* @param licenses the persisted license data
* @return a list of transport objects for licenses
*/
private List<Map<String, String>> mapLicenses(List<License> licenses) {
if (licenses == null) {
return List.of();
}
return licenses.stream()
.map(a -> Map.of(
"key", a.getKey(),
"name", failsave(a.getName())))
.collect(Collectors.toList());
}
/**
* The method <code>mapPkg</code> provides means to map a persisted package
* to a transport object.
*
* @param pkg the package or {@code null}
* @param lang the language for the notes
* @return the transport object
*/
private PkgTo mapPkg(Pkg pkg, String lang) {
if (pkg == null) {
return null;
}
if (lang == null) {
lang = defaultLang;
}
var key = pkg.getKey();
PkgCaption caption = pkg.getCaption(lang);
PkgDescription description = pkg.getDescription(lang);
return PkgTo.builder()
.key(key)
.authors(mapAuthors(pkg.getAuthors()))
.caption(caption == null ? null : caption.getCaption())
.captionLang(caption == null ? null : caption.getLang())
.copyrights(mapCopyright(pkg.getCopy()))
.description(description == null
? null
: description.getDescription())
.descriptionLang(description == null
? null
: description.getLang())
.docs(mapDocs(pkg.getDocs()))
.hasTeaser(contentService.hasTeaser(TeaserType.PKG, key))
.licenses(mapLicenses(pkg.getLicenses()))
.miktexLocation(pkg.getMiktexLocation())
.name(pkg.getName() != null ? pkg.getName() : key)
.texliveLocation(pkg.getTexliveLocation())
.tlContribLocation(pkg.getTlContribLocation())
.topics(mapTopics(pkg.getTopics(), lang))
.versionDate(pkg.getVersionDate())
.versionNumber(pkg.getVersionNumber())
.zip(pkg.isCtanZip())
.build();
}
/**
* The method <code>mapTopics</code> provides means to map persisted topics
* to transport objects.
*
* @param topics the list of topics or {@code null}
* @param lang the locale
* @return the list of transport objects
*/
private List<Map<String, String>> mapTopics(List<Topic> topics,
String lang) {
if (topics == null) {
return List.of();
}
return topics.stream()
.map(t -> Map.of("key", t.getKey(),
"title", failsave(t.getTitle(lang))))
.collect(Collectors.toList());
}
}