Content3Resource.java
/*
* Copyright © 2022-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.resources.content;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.NoSuchFileException;
import java.util.Map;
import org.ctan.site.services.content.ContentService;
import org.ctan.site.services.content.ContentService.ContentPageTo;
import org.ctan.site.services.content.ContentService.ContentPageTreeTo;
import org.ctan.site.services.content.ContentService.TeaserType;
import org.ctan.site.services.content.LionService;
import org.ctan.site.services.content.LionService.FileListTo;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import jakarta.ws.rs.DefaultValue;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.WebApplicationException;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.Response.Status;
import lombok.NonNull;
/**
* The class <code>Content3Resource</code> contains the controller for the
* content resource.
*
* @author <a href="mailto:gene@ctan.org">Gerd Neugebauer</a>
*/
@Path("/3.0")
@Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
public class Content3Resource {
/**
* The field <code>VERSION</code> contains the current version.
*/
private static final String VERSION = "3.0";
/**
* The field <code>service</code> contains the underlying service.
*/
private ContentService service;
/**
* The field <code>lionService</code> contains the service for the lion
* files.
*/
private LionService lionService;
/**
* This is the constructor for the class <code>Content3Resource</code>.
*
* @param service the underlying service
* @param lionService the service for the lion files
*/
@SuppressFBWarnings(value = {"CT_CONSTRUCTOR_THROW", "EI_EXPOSE_REP2"})
public Content3Resource(@NonNull ContentService service,
@NonNull LionService lionService) {
this.service = service;
this.lionService = lionService;
}
/**
* The method <code>getImage</code> provides means to retrieve an image from
* the content area.
*
* @param path the path in the content archive
* @return the data for the index file
*/
@GET
@Path("/content/img/{path: .*}")
@Produces({"image/png", "image/gif", "image/jpeg"})
public Response getImage(@PathParam("path") String path) {
var fileType = service.getImageType(path);
if (fileType == null) {
throw new WebApplicationException(Status.NOT_FOUND);
}
try {
return Response
.ok()
.entity(service.getImage(path), null)
.type(fileType)
.build();
} catch (FileNotFoundException | NoSuchFileException e) {
throw new WebApplicationException(Status.NOT_FOUND);
} catch (IOException | IllegalArgumentException e) {
throw new WebApplicationException(Status.BAD_REQUEST);
}
}
/**
* The method <code>getLionFile</code> provides means to retrieve a lion
* image.
*
* @param name the file name
* @return the content of the file
*/
@GET
@Path("/content/lion/file/{name}")
@Produces({
"image/png",
"image/tiff",
"application/postscript",
"text/plain"})
public Response getLionFile(@PathParam("name") String name) {
var fileType = service.getImageType(name);
if (fileType == null) {
fileType = "text/plain";
}
try {
return Response
.ok()
.entity(lionService.content(name), null)
.type(fileType)
.build();
} catch (IOException e) {
throw new WebApplicationException(Status.NOT_FOUND);
}
}
/**
* The method <code>getPage</code> provides means to retrieve a HTML page
* from the content area.
*
* @param lang the ISO language code
* @param path the path in the content archive
* @return the data for the index file
*/
@GET
@Path("/content/page/{path: .*}")
public ContentPageTo getPage(@QueryParam("lang") String lang,
@PathParam("path") String path) {
ContentPageTo page;
try {
page = service.getPage(lang, path);
} catch (IllegalArgumentException e) {
throw new WebApplicationException(Status.BAD_REQUEST);
} catch (IOException e) {
throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR);
}
if (page == null) {
throw new WebApplicationException(Status.NOT_FOUND);
}
return page;
}
/**
* The method <code>getPageList</code> provides means to retrieve a listing
* from the content area.
*
* @param lang the ISO language code
* @param path the path in 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
* @return the list of files found
*/
@GET
@Path("/content/list/{path: .*}")
public ContentPageTo getPageList(@QueryParam("lang") String lang,
@PathParam("path") String path) {
ContentPageTo list;
try {
list = service.getPageList(lang, path);
} catch (IllegalArgumentException e) {
throw new WebApplicationException(Status.BAD_REQUEST);
} catch (IOException e) {
throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR);
}
if (list == null) {
throw new WebApplicationException(Status.NOT_FOUND);
}
return list;
}
/**
* The method <code>getPageTree</code> provides means to retrieve a listing
* from the content area.
*
* @param lang the ISO language code
* @param path the path in 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
* @param depth the number of levels to include
* @return the list of files found
*/
@GET
@Path("/content/tree/{path: .*}")
public ContentPageTreeTo getPageTree(@PathParam("path") String path,
@QueryParam("lang") String lang,
@QueryParam("depth") @DefaultValue("2") Integer depth) {
ContentPageTreeTo list;
try {
list = service.getPageTree(lang, path, depth);
} catch (IllegalArgumentException e) {
throw new WebApplicationException(Status.BAD_REQUEST);
} catch (IOException e) {
throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR);
}
if (list == null) {
throw new WebApplicationException(Status.NOT_FOUND);
}
return list;
}
/**
* The method <code>getTeaserForPkg</code> provides means to retrieve a
* teaser image from the content area.
*
* @param key the key of the package
* @return the data for the image file
*/
@GET
@Path("/teaser/pkg/{key}")
@Produces({"image/png", "image/gif", "image/jpeg"})
public byte[] getTeaserForPkg(@PathParam("key") String key) {
try {
return service.getTeaser(TeaserType.PKG, key);
} catch (FileNotFoundException | NoSuchFileException e) {
throw new WebApplicationException(Status.NOT_FOUND);
} catch (IOException | IllegalArgumentException e) {
throw new WebApplicationException(Status.BAD_REQUEST);
}
}
/**
* The method <code>getTeaserForTopic</code> provides means to retrieve a
* teaser image from the content area.
*
* @param key the key of the package
* @return the data for the image file
*/
@GET
@Path("/teaser/topic/{key}")
@Produces({"image/png", "image/gif", "image/jpeg"})
public byte[] getTeaserForTopic(@PathParam("key") String key) {
try {
return service.getTeaser(TeaserType.TOPIC, key);
} catch (FileNotFoundException | NoSuchFileException e) {
throw new WebApplicationException(Status.NOT_FOUND);
} catch (IOException | IllegalArgumentException e) {
throw new WebApplicationException(Status.BAD_REQUEST);
}
}
/**
* The method <code>getVersion</code> provides means to retrieve the version
* number of the API.
*
* @return a Map with a single attribute <code>version</code> containing the
* version number as String.
*/
@GET
@Path("/content/version")
public Map<String, String> getVersion() {
return Map.of("version", VERSION);
}
/**
* The method <code>listLionDir</code> provides means to retrieve the
* directory listing or the lion files.
*
* @return the directory listing
*/
@GET
@Path("/content/lion")
public FileListTo listLionDir() {
return lionService.list();
}
}