View Javadoc
1   /*
2    * This file is part of ***  M y C o R e  ***
3    * See http://www.mycore.de/ for details.
4    *
5    * MyCoRe is free software: you can redistribute it and/or modify
6    * it under the terms of the GNU General Public License as published by
7    * the Free Software Foundation, either version 3 of the License, or
8    * (at your option) any later version.
9    *
10   * MyCoRe is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   * GNU General Public License for more details.
14   *
15   * You should have received a copy of the GNU General Public License
16   * along with MyCoRe.  If not, see <http://www.gnu.org/licenses/>.
17   */
18  
19  package org.mycore.lod.controller;
20  
21  import java.io.IOException;
22  import java.net.URI;
23  import java.util.Date;
24  import java.util.List;
25  import java.util.Optional;
26  import java.util.concurrent.TimeUnit;
27  import java.util.function.Function;
28  
29  import org.jdom2.Document;
30  import org.jdom2.Element;
31  import org.mycore.common.MCRConstants;
32  import org.mycore.common.content.MCRJDOMContent;
33  import org.mycore.datamodel.classifications2.MCRCategory;
34  import org.mycore.datamodel.classifications2.MCRCategoryDAO;
35  import org.mycore.datamodel.classifications2.MCRCategoryDAOFactory;
36  import org.mycore.datamodel.classifications2.MCRCategoryID;
37  import org.mycore.datamodel.classifications2.model.MCRClass;
38  import org.mycore.datamodel.classifications2.model.MCRClassCategory;
39  import org.mycore.datamodel.classifications2.utils.MCRSkosTransformer;
40  import org.mycore.frontend.MCRFrontendUtil;
41  import org.mycore.frontend.jersey.MCRCacheControl;
42  import org.mycore.lod.MCRJerseyLodApp;
43  import org.mycore.restapi.converter.MCRDetailLevel;
44  import org.mycore.restapi.v2.MCRErrorResponse;
45  import org.mycore.restapi.v2.MCRRestUtils;
46  
47  import io.swagger.v3.oas.annotations.Operation;
48  import io.swagger.v3.oas.annotations.media.Content;
49  import io.swagger.v3.oas.annotations.media.Schema;
50  import io.swagger.v3.oas.annotations.responses.ApiResponse;
51  import jakarta.ws.rs.GET;
52  import jakarta.ws.rs.Path;
53  import jakarta.ws.rs.PathParam;
54  import jakarta.ws.rs.container.ContainerRequestContext;
55  import jakarta.ws.rs.core.Context;
56  import jakarta.ws.rs.core.MediaType;
57  import jakarta.ws.rs.core.Response;
58  
59  /**
60   * Linked Open Data: Classification End point
61   * 
62   * @author Robert Stephan
63   */
64  @Path("/classification")
65  public class MCRLodClassification {
66  
67      /** error code for error response */
68      public static final String ERROR_MCRCLASS_NOT_FOUND = "MCRCLASS_NOT_FOUND";
69  
70      /** error code for error response */
71      public static final String ERROR_MCRCLASS_ID_MISMATCH = "MCRCLASS_ID_MISMATCH";
72  
73      /** error code for error response */
74      public static final String ERROR_MCRCLASS_TRANSFORMATION = "MCRCLASS_TRANSFORMATION";
75  
76      /** parameter key in request url paths */
77      private static final String PARAM_CLASSID = "classid";
78  
79      /** parameter key in request url paths */
80      private static final String PARAM_CATEGID = "categid";
81  
82      @Context
83      ContainerRequestContext request;
84  
85      /**
86       * return the list of available classifications as Linked Open Data 
87       * 
88       * TODO Is there a reasonable response on the base path of an LOD URI,
89       * or remove this endpoint completely?
90       * 
91       * @return a jersey response with the list of classifications
92       */
93      @GET
94      @MCRCacheControl(maxAge = @MCRCacheControl.Age(time = 1, unit = TimeUnit.HOURS),
95          sMaxAge = @MCRCacheControl.Age(time = 1, unit = TimeUnit.HOURS))
96      public Response outputLODClassificationRoot() {
97          MCRCategoryDAO categoryDAO = MCRCategoryDAOFactory.getInstance();
98          Date lastModified = new Date(categoryDAO.getLastModified());
99          Optional<Response> cachedResponse = MCRRestUtils.getCachedResponse(request.getRequest(), lastModified);
100         if (cachedResponse.isPresent()) {
101             return cachedResponse.get();
102         }
103 
104         try {
105             Document classRdfxml = createClassList();
106             String rdfxmlString = new MCRJDOMContent(classRdfxml).asString();
107             List<String> mimeTypes = request.getAcceptableMediaTypes().parallelStream().map(x -> x.toString()).toList();
108             URI uri = request.getUriInfo().getBaseUri();
109             return MCRJerseyLodApp.returnLinkedData(rdfxmlString, uri, mimeTypes);
110         } catch (IOException e) {
111             throw MCRErrorResponse.fromStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode())
112                 .withErrorCode(ERROR_MCRCLASS_TRANSFORMATION)
113                 .withMessage("Could create classification list.")
114                 .toException();
115         }
116     }
117 
118     /**
119      * return a classification (with its categories on the first hierarchy level as linked open data)
120      * @param classId - the classification ID
121      * @return the Response with the classification as linked open data 
122      */
123     @GET
124     @MCRCacheControl(maxAge = @MCRCacheControl.Age(time = 1, unit = TimeUnit.DAYS),
125         sMaxAge = @MCRCacheControl.Age(time = 1, unit = TimeUnit.DAYS))
126     @Path("/{" + PARAM_CLASSID + "}")
127     public Response getClassification(@PathParam(PARAM_CLASSID) String classId) {
128         List<MediaType> mediaTypes = request.getAcceptableMediaTypes();
129         return getClassification(MCRCategoryID.rootID(classId),
130             dao -> dao.getCategory(MCRCategoryID.rootID(classId), 1), mediaTypes,
131             request.getUriInfo().getBaseUri());
132     }
133 
134     /**
135      * return a category and its children on the first hierarchy level as linked open data
136      * 
137      * @param classId - the class ID
138      * @param categId - the category ID
139      * @return the Response with the category as linked open data
140      */
141     @GET
142     @MCRCacheControl(maxAge = @MCRCacheControl.Age(time = 1, unit = TimeUnit.DAYS),
143         sMaxAge = @MCRCacheControl.Age(time = 1, unit = TimeUnit.DAYS))
144     @Path("/{" + PARAM_CLASSID + "}/{" + PARAM_CATEGID + "}")
145     @Operation(summary = "Returns Classification with the given " + PARAM_CLASSID + " and " + PARAM_CATEGID + ".",
146         responses = @ApiResponse(content = {
147             @Content(schema = @Schema(implementation = MCRClass.class)),
148             @Content(schema = @Schema(implementation = MCRClassCategory.class))
149         },
150             description = "If media type parameter " + MCRDetailLevel.MEDIA_TYPE_PARAMETER
151                 + " is 'summary' an MCRClassCategory is returned. "
152                 + "In other cases MCRClass with different detail level."),
153         tags = MCRRestUtils.TAG_MYCORE_CLASSIFICATION)
154 
155     public Response getClassification(@PathParam(PARAM_CLASSID) String classId,
156         @PathParam(PARAM_CATEGID) String categId) {
157 
158         MCRCategoryID categoryId = new MCRCategoryID(classId, categId);
159         return getClassification(categoryId, dao -> dao.getRootCategory(categoryId, 0),
160             request.getAcceptableMediaTypes(),
161             request.getUriInfo().getBaseUri());
162     }
163 
164     private Response getClassification(MCRCategoryID categId, Function<MCRCategoryDAO, MCRCategory> categorySupplier,
165         List<MediaType> acceptMediaTypes, URI uri) {
166         MCRCategoryDAO categoryDAO = MCRCategoryDAOFactory.getInstance();
167         String classId = categId.getRootID();
168         Date lastModified = getLastModifiedDate(classId, categoryDAO);
169         Optional<Response> cachedResponse = MCRRestUtils.getCachedResponse(request.getRequest(), lastModified);
170         if (cachedResponse.isPresent()) {
171             return cachedResponse.get();
172         }
173         MCRCategory classification = categorySupplier.apply(categoryDAO);
174         if (classification == null) {
175             throw MCRErrorResponse.fromStatus(Response.Status.NOT_FOUND.getStatusCode())
176                 .withErrorCode(ERROR_MCRCLASS_NOT_FOUND)
177                 .withMessage("Could not find classification or category in " + classId + ".")
178                 .toException();
179         }
180         try {
181             Document classRdfXml = MCRSkosTransformer.getSkosInRDFXML(classification, categId);
182             String rdfxmlString = new MCRJDOMContent(classRdfXml).asString();
183             List<String> mimeTypes = acceptMediaTypes.parallelStream().map(x -> x.toString()).toList();
184             return MCRJerseyLodApp.returnLinkedData(rdfxmlString, uri, mimeTypes);
185         } catch (IOException e) {
186             throw MCRErrorResponse.fromStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode())
187                 .withErrorCode(ERROR_MCRCLASS_TRANSFORMATION)
188                 .withMessage("Could not find classification or category in " + classId + ".")
189                 .toException();
190         }
191     }
192 
193     private Document createClassList() {
194         Element eBag = new Element("Bag", MCRConstants.RDF_NAMESPACE);
195         MCRCategoryDAO categoryDAO = MCRCategoryDAOFactory.getInstance();
196         for (MCRCategory categ : categoryDAO.getRootCategories()) {
197             eBag.addContent(new Element("li", MCRConstants.RDF_NAMESPACE)
198                 .setAttribute("resource",
199                     MCRFrontendUtil.getBaseURL() + "open-data/classification/" + categ.getId().toString(),
200                     MCRConstants.RDF_NAMESPACE));
201         }
202         return new Document(eBag);
203     }
204 
205     private static Date getLastModifiedDate(@PathParam(PARAM_CLASSID) String classId, MCRCategoryDAO categoryDAO) {
206         long categoryLastModified = categoryDAO.getLastModified(classId);
207         return new Date(categoryLastModified > 0 ? categoryLastModified : categoryDAO.getLastModified());
208     }
209 
210 }