1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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:
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 }