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.restapi.v2;
20  
21  import static org.mycore.restapi.v2.MCRRestAuthorizationFilter.PARAM_CLASSID;
22  
23  import java.io.IOException;
24  import java.util.Date;
25  import java.util.List;
26  import java.util.Map;
27  import java.util.Optional;
28  import java.util.concurrent.TimeUnit;
29  import java.util.function.Function;
30  import java.util.stream.Collectors;
31  
32  import org.jdom2.Document;
33  import org.mycore.common.content.MCRJDOMContent;
34  import org.mycore.datamodel.classifications2.MCRCategory;
35  import org.mycore.datamodel.classifications2.MCRCategoryDAO;
36  import org.mycore.datamodel.classifications2.MCRCategoryDAOFactory;
37  import org.mycore.datamodel.classifications2.MCRCategoryID;
38  import org.mycore.datamodel.classifications2.MCRLabel;
39  import org.mycore.datamodel.classifications2.model.MCRClass;
40  import org.mycore.datamodel.classifications2.model.MCRClassCategory;
41  import org.mycore.datamodel.classifications2.model.MCRClassURL;
42  import org.mycore.datamodel.classifications2.utils.MCRSkosTransformer;
43  import org.mycore.frontend.jersey.MCRCacheControl;
44  import org.mycore.restapi.annotations.MCRRequireTransaction;
45  import org.mycore.restapi.converter.MCRDetailLevel;
46  
47  import io.swagger.v3.oas.annotations.OpenAPIDefinition;
48  import io.swagger.v3.oas.annotations.Operation;
49  import io.swagger.v3.oas.annotations.media.ArraySchema;
50  import io.swagger.v3.oas.annotations.media.Content;
51  import io.swagger.v3.oas.annotations.media.Schema;
52  import io.swagger.v3.oas.annotations.responses.ApiResponse;
53  import io.swagger.v3.oas.annotations.tags.Tag;
54  import jakarta.ws.rs.Consumes;
55  import jakarta.ws.rs.GET;
56  import jakarta.ws.rs.PUT;
57  import jakarta.ws.rs.Path;
58  import jakarta.ws.rs.PathParam;
59  import jakarta.ws.rs.Produces;
60  import jakarta.ws.rs.container.ContainerRequestContext;
61  import jakarta.ws.rs.core.Context;
62  import jakarta.ws.rs.core.GenericEntity;
63  import jakarta.ws.rs.core.MediaType;
64  import jakarta.ws.rs.core.Response;
65  import jakarta.xml.bind.annotation.XmlElementWrapper;
66  
67  @Path("/classifications")
68  @OpenAPIDefinition(
69      tags = @Tag(name = MCRRestUtils.TAG_MYCORE_CLASSIFICATION, description = "Operations on classifications"))
70  public class MCRRestClassifications {
71  
72      private static final String PARAM_CATEGID = "categid";
73  
74      @Context
75      ContainerRequestContext request;
76  
77      @GET
78      @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON + ";charset=UTF-8" })
79      @MCRCacheControl(maxAge = @MCRCacheControl.Age(time = 1, unit = TimeUnit.HOURS),
80          sMaxAge = @MCRCacheControl.Age(time = 1, unit = TimeUnit.HOURS))
81      @Operation(
82          summary = "Lists all classifications in this repository",
83          responses = @ApiResponse(
84              content = @Content(array = @ArraySchema(schema = @Schema(implementation = MCRClass.class)))),
85          tags = MCRRestUtils.TAG_MYCORE_CLASSIFICATION)
86      @XmlElementWrapper(name = "classifications")
87      public Response listClassifications() {
88          MCRCategoryDAO categoryDAO = MCRCategoryDAOFactory.getInstance();
89          Date lastModified = new Date(categoryDAO.getLastModified());
90          Optional<Response> cachedResponse = MCRRestUtils.getCachedResponse(request.getRequest(), lastModified);
91          if (cachedResponse.isPresent()) {
92              return cachedResponse.get();
93          }
94          GenericEntity<List<MCRClass>> entity = new GenericEntity<>(
95              categoryDAO.getRootCategories()
96                  .stream()
97                  .map(MCRRestClassifications::convertToClass)
98                  .collect(Collectors.toList())) {
99          };
100         return Response.ok(entity)
101             .lastModified(lastModified)
102             .build();
103     }
104 
105     private static MCRClass convertToClass(MCRCategory cat) {
106         MCRClass mcrClass = new MCRClass();
107         mcrClass.setID(cat.getId().getRootID());
108         mcrClass.getLabel().addAll(cat.getLabels().stream().map(MCRLabel::clone).collect(Collectors.toList()));
109         Optional.ofNullable(cat.getURI())
110             .map(MCRClassURL::getInstance)
111             .ifPresent(mcrClass::setUrl);
112         return mcrClass;
113     }
114 
115     @GET
116     @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON + ";charset=UTF-8", "application/rdf+xml" })
117     @MCRCacheControl(maxAge = @MCRCacheControl.Age(time = 1, unit = TimeUnit.DAYS),
118         sMaxAge = @MCRCacheControl.Age(time = 1, unit = TimeUnit.DAYS))
119     @Path("/{" + PARAM_CLASSID + "}")
120     @Operation(
121         summary = "Returns Classification with the given " + PARAM_CLASSID + ".",
122         responses = @ApiResponse(
123             content = @Content(schema = @Schema(implementation = MCRClass.class))),
124         tags = MCRRestUtils.TAG_MYCORE_CLASSIFICATION)
125     public Response getClassification(@PathParam(PARAM_CLASSID) String classId) {
126         return getClassification(classId, dao -> dao.getCategory(MCRCategoryID.rootID(classId), -1));
127     }
128 
129     @GET
130     @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON + ";charset=UTF-8", "application/rdf+xml" })
131     @MCRCacheControl(maxAge = @MCRCacheControl.Age(time = 1, unit = TimeUnit.DAYS),
132         sMaxAge = @MCRCacheControl.Age(time = 1, unit = TimeUnit.DAYS))
133     @Path("/{" + PARAM_CLASSID + "}/{" + PARAM_CATEGID + "}")
134     @Operation(summary = "Returns Classification with the given " + PARAM_CLASSID + " and " + PARAM_CATEGID + ".",
135         responses = @ApiResponse(content = {
136             @Content(schema = @Schema(implementation = MCRClass.class)),
137             @Content(schema = @Schema(implementation = MCRClassCategory.class))
138         },
139             description = "If media type parameter " + MCRDetailLevel.MEDIA_TYPE_PARAMETER
140                 + " is 'summary' an MCRClassCategory is returned. "
141                 + "In other cases MCRClass with different detail level."),
142         tags = MCRRestUtils.TAG_MYCORE_CLASSIFICATION)
143 
144     public Response getClassification(@PathParam(PARAM_CLASSID) String classId,
145         @PathParam(PARAM_CATEGID) String categId) {
146 
147         MCRDetailLevel detailLevel = request.getAcceptableMediaTypes()
148             .stream()
149             .flatMap(m -> m.getParameters().entrySet().stream()
150                 .filter(e -> MCRDetailLevel.MEDIA_TYPE_PARAMETER.equals(e.getKey())))
151             .map(Map.Entry::getValue)
152             .findFirst()
153             .map(MCRDetailLevel::valueOf).orElse(MCRDetailLevel.normal);
154 
155         MCRCategoryID categoryID = new MCRCategoryID(classId, categId);
156         switch (detailLevel) {
157             case detailed:
158                 return getClassification(classId, dao -> dao.getRootCategory(categoryID, -1));
159             case summary:
160                 return getClassification(classId, dao -> dao.getCategory(categoryID, 0));
161             case normal: //default case
162             default:
163                 return getClassification(classId, dao -> dao.getRootCategory(categoryID, 0));
164         }
165     }
166 
167     private Response getClassification(String classId, Function<MCRCategoryDAO, MCRCategory> categorySupplier) {
168         MCRCategoryDAO categoryDAO = MCRCategoryDAOFactory.getInstance();
169         Date lastModified = getLastModifiedDate(classId, categoryDAO);
170         Optional<Response> cachedResponse = MCRRestUtils.getCachedResponse(request.getRequest(), lastModified);
171         if (cachedResponse.isPresent()) {
172             return cachedResponse.get();
173         }
174         MCRCategory classification = categorySupplier.apply(categoryDAO);
175         if (classification == null) {
176             throw MCRErrorResponse.fromStatus(Response.Status.NOT_FOUND.getStatusCode())
177                 .withErrorCode(MCRErrorCodeConstants.MCRCLASS_NOT_FOUND)
178                 .withMessage("Could not find classification or category in " + classId + ".")
179                 .toException();
180         }
181         if (request.getAcceptableMediaTypes().contains(MediaType.valueOf("application/rdf+xml"))) {
182             Document docSKOS = MCRSkosTransformer.getSkosInRDFXML(classification, MCRCategoryID.fromString(classId));
183             MCRJDOMContent content = new MCRJDOMContent(docSKOS);
184             try {
185                 return Response.ok(content.asString()).type("application/rdf+xml; charset=UTF-8").build();
186             } catch (IOException e) {
187                 throw MCRErrorResponse.fromStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode())
188                     .withErrorCode(MCRErrorCodeConstants.MCRCLASS_NOT_FOUND)
189                     .withMessage("Could not find classification or category in " + classId + ".")
190                     .toException();
191             }
192         }
193 
194         return Response.ok()
195             .entity(classification.isClassification() ? MCRClass.getClassification(classification)
196                 : MCRClassCategory.getInstance(classification))
197             .lastModified(lastModified)
198             .build();
199     }
200 
201     private static Date getLastModifiedDate(@PathParam(PARAM_CLASSID) String classId, MCRCategoryDAO categoryDAO) {
202         long categoryLastModified = categoryDAO.getLastModified(classId);
203         return new Date(categoryLastModified > 0 ? categoryLastModified : categoryDAO.getLastModified());
204     }
205 
206     @PUT
207     @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON + ";charset=UTF-8" })
208     @Path("/{" + PARAM_CLASSID + "}")
209     @Produces(MediaType.APPLICATION_XML)
210     @Operation(
211         summary = "Creates Classification with the given " + PARAM_CLASSID + ".",
212         responses = {
213             @ApiResponse(responseCode = "400",
214                 content = { @Content(mediaType = MediaType.TEXT_PLAIN) },
215                 description = "'MCRCategoryID mismatch'"),
216             @ApiResponse(responseCode = "201", description = "Classification successfully created"),
217             @ApiResponse(responseCode = "204", description = "Classification successfully updated"),
218         },
219         tags = MCRRestUtils.TAG_MYCORE_CLASSIFICATION)
220     @MCRRequireTransaction
221     public Response createClassification(@PathParam(PARAM_CLASSID) String classId, MCRClass mcrClass) {
222         if (!classId.equals(mcrClass.getID())) {
223             throw MCRErrorResponse.fromStatus(Response.Status.BAD_REQUEST.getStatusCode())
224                 .withErrorCode(MCRErrorCodeConstants.MCRCLASS_ID_MISMATCH)
225                 .withMessage("Classification " + classId + " cannot be overwritten by " + mcrClass.getID() + ".")
226                 .toException();
227         }
228         MCRCategoryDAO categoryDAO = MCRCategoryDAOFactory.getInstance();
229         Response.Status status;
230         if (!categoryDAO.exist(MCRCategoryID.rootID(classId))) {
231             categoryDAO.addCategory(null, mcrClass.toCategory());
232             status = Response.Status.CREATED;
233         } else {
234             Optional<Response> cachedResponse = MCRRestUtils.getCachedResponse(request.getRequest(),
235                 getLastModifiedDate(classId, categoryDAO));
236             if (cachedResponse.isPresent()) {
237                 return cachedResponse.get();
238             }
239             categoryDAO.replaceCategory(mcrClass.toCategory());
240             status = Response.Status.NO_CONTENT;
241         }
242         Date lastModifiedDate = getLastModifiedDate(classId, categoryDAO);
243         return Response.status(status).lastModified(lastModifiedDate).build();
244     }
245 
246 }