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_DERID;
22 import static org.mycore.restapi.v2.MCRRestAuthorizationFilter.PARAM_MCRID;
23
24 import java.io.IOException;
25 import java.io.InputStream;
26 import java.lang.annotation.Annotation;
27 import java.nio.file.FileSystemException;
28 import java.nio.file.Files;
29 import java.util.Date;
30 import java.util.List;
31 import java.util.Optional;
32 import java.util.concurrent.TimeUnit;
33 import java.util.stream.Collectors;
34
35 import org.apache.logging.log4j.LogManager;
36 import org.apache.logging.log4j.Logger;
37 import org.jdom2.JDOMException;
38 import org.mycore.access.MCRAccessException;
39 import org.mycore.common.MCRException;
40 import org.mycore.common.config.MCRConfiguration2;
41 import org.mycore.common.content.MCRContent;
42 import org.mycore.common.content.MCRStreamContent;
43 import org.mycore.datamodel.classifications2.MCRCategoryID;
44 import org.mycore.datamodel.common.MCRXMLMetadataManager;
45 import org.mycore.datamodel.metadata.MCRDerivate;
46 import org.mycore.datamodel.metadata.MCRMetaClassification;
47 import org.mycore.datamodel.metadata.MCRMetaEnrichedLinkID;
48 import org.mycore.datamodel.metadata.MCRMetaIFS;
49 import org.mycore.datamodel.metadata.MCRMetaLangText;
50 import org.mycore.datamodel.metadata.MCRMetaLinkID;
51 import org.mycore.datamodel.metadata.MCRMetadataManager;
52 import org.mycore.datamodel.metadata.MCRObject;
53 import org.mycore.datamodel.metadata.MCRObjectID;
54 import org.mycore.datamodel.niofs.MCRPath;
55 import org.mycore.frontend.jersey.MCRCacheControl;
56 import org.mycore.restapi.annotations.MCRAccessControlExposeHeaders;
57 import org.mycore.restapi.annotations.MCRApiDraft;
58 import org.mycore.restapi.annotations.MCRParam;
59 import org.mycore.restapi.annotations.MCRParams;
60 import org.mycore.restapi.annotations.MCRRequireTransaction;
61 import org.mycore.restapi.converter.MCRContentAbstractWriter;
62 import org.mycore.restapi.converter.MCRObjectIDParamConverterProvider;
63 import org.xml.sax.SAXException;
64
65 import com.fasterxml.jackson.annotation.JsonProperty;
66
67 import io.swagger.v3.oas.annotations.Operation;
68 import io.swagger.v3.oas.annotations.Parameter;
69 import io.swagger.v3.oas.annotations.headers.Header;
70 import io.swagger.v3.oas.annotations.media.ArraySchema;
71 import io.swagger.v3.oas.annotations.media.Content;
72 import io.swagger.v3.oas.annotations.media.ExampleObject;
73 import io.swagger.v3.oas.annotations.media.Schema;
74 import io.swagger.v3.oas.annotations.parameters.RequestBody;
75 import io.swagger.v3.oas.annotations.responses.ApiResponse;
76 import jakarta.ws.rs.BeanParam;
77 import jakarta.ws.rs.Consumes;
78 import jakarta.ws.rs.DELETE;
79 import jakarta.ws.rs.DefaultValue;
80 import jakarta.ws.rs.FormParam;
81 import jakarta.ws.rs.GET;
82 import jakarta.ws.rs.POST;
83 import jakarta.ws.rs.PUT;
84 import jakarta.ws.rs.PATCH;
85 import jakarta.ws.rs.Path;
86 import jakarta.ws.rs.PathParam;
87 import jakarta.ws.rs.Produces;
88 import jakarta.ws.rs.core.Context;
89 import jakarta.ws.rs.core.GenericEntity;
90 import jakarta.ws.rs.core.HttpHeaders;
91 import jakarta.ws.rs.core.MediaType;
92 import jakarta.ws.rs.core.Request;
93 import jakarta.ws.rs.core.Response;
94 import jakarta.ws.rs.core.UriInfo;
95 import jakarta.xml.bind.annotation.XmlElementWrapper;
96
97 @Path("/objects/{" + PARAM_MCRID + "}/derivates")
98 public class MCRRestDerivates {
99
100 public static final Logger LOGGER = LogManager.getLogger();
101
102 @Context
103 Request request;
104
105 @Context
106 UriInfo uriInfo;
107
108 @Parameter(example = "mir_mods_00004711")
109 @PathParam(PARAM_MCRID)
110 MCRObjectID mcrId;
111
112 private static void validateDerivateRelation(MCRObjectID mcrId, MCRObjectID derId) {
113 MCRObjectID objectId = MCRMetadataManager.getObjectId(derId, 1, TimeUnit.DAYS);
114 if (objectId != null && !mcrId.equals(objectId)) {
115 objectId = MCRMetadataManager.getObjectId(derId, 0, TimeUnit.SECONDS);
116 }
117 if (mcrId.equals(objectId)) {
118 return;
119 }
120 if (objectId == null) {
121 throw MCRErrorResponse.fromStatus(Response.Status.NOT_FOUND.getStatusCode())
122 .withErrorCode(MCRErrorCodeConstants.MCRDERIVATE_NOT_FOUND)
123 .withMessage("MCRDerivate " + derId + " not found")
124 .toException();
125 }
126 throw MCRErrorResponse.fromStatus(Response.Status.NOT_FOUND.getStatusCode())
127 .withErrorCode(MCRErrorCodeConstants.MCRDERIVATE_NOT_FOUND_IN_OBJECT)
128 .withMessage("MCRDerivate " + derId + " not found in object " + mcrId + ".")
129 .toException();
130 }
131
132 @GET
133 @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON + ";charset=UTF-8" })
134 @MCRCacheControl(maxAge = @MCRCacheControl.Age(time = 1, unit = TimeUnit.DAYS),
135 sMaxAge = @MCRCacheControl.Age(time = 1, unit = TimeUnit.DAYS))
136 @Operation(
137 summary = "Lists all derivates in the given object",
138 responses = {
139 @ApiResponse(
140 content = @Content(array = @ArraySchema(schema = @Schema(implementation = MCRMetaLinkID.class)))),
141 @ApiResponse(responseCode = "" + MCRObjectIDParamConverterProvider.CODE_INVALID,
142 description = MCRObjectIDParamConverterProvider.MSG_INVALID),
143
144 },
145 tags = MCRRestUtils.TAG_MYCORE_DERIVATE)
146 @XmlElementWrapper(name = "derobjects")
147 public Response listDerivates()
148 throws IOException {
149 long modified = MCRXMLMetadataManager.instance().getLastModified(mcrId);
150 if (modified < 0) {
151 throw MCRErrorResponse.fromStatus(Response.Status.NOT_FOUND.getStatusCode())
152 .withErrorCode(MCRErrorCodeConstants.MCROBJECT_NOT_FOUND)
153 .withMessage("MCRObject " + mcrId + " not found")
154 .toException();
155 }
156 Date lastModified = new Date(modified);
157 Optional<Response> cachedResponse = MCRRestUtils.getCachedResponse(request, lastModified);
158 if (cachedResponse.isPresent()) {
159 return cachedResponse.get();
160 }
161 MCRObject obj = MCRMetadataManager.retrieveMCRObject(mcrId);
162 List<MCRMetaEnrichedLinkID> derivates = obj.getStructure().getDerivates();
163 return Response.ok()
164 .entity(new GenericEntity<List<MCRMetaEnrichedLinkID>>(derivates) {
165 })
166 .lastModified(lastModified)
167 .build();
168 }
169
170 @GET
171 @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON + ";charset=UTF-8" })
172 @MCRCacheControl(maxAge = @MCRCacheControl.Age(time = 1, unit = TimeUnit.DAYS),
173 sMaxAge = @MCRCacheControl.Age(time = 1, unit = TimeUnit.DAYS))
174 @Operation(
175 summary = "Returns given derivate in the given object",
176 tags = MCRRestUtils.TAG_MYCORE_DERIVATE)
177 @Path("/{" + PARAM_DERID + "}")
178 public Response getDerivate(@Parameter(example = "mir_derivate_00004711") @PathParam(PARAM_DERID) MCRObjectID derid)
179 throws IOException {
180 validateDerivateRelation(mcrId, derid);
181 long modified = MCRXMLMetadataManager.instance().getLastModified(derid);
182 Date lastModified = new Date(modified);
183 Optional<Response> cachedResponse = MCRRestUtils.getCachedResponse(request, lastModified);
184 if (cachedResponse.isPresent()) {
185 return cachedResponse.get();
186 }
187 MCRContent mcrContent = MCRXMLMetadataManager.instance().retrieveContent(derid);
188 return Response.ok()
189 .entity(mcrContent,
190 new Annotation[] { MCRParams.Factory
191 .get(MCRParam.Factory.get(MCRContentAbstractWriter.PARAM_OBJECTTYPE, derid.getTypeId())) })
192 .lastModified(lastModified)
193 .build();
194 }
195
196 @PUT
197 @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON + ";charset=UTF-8" })
198 @Operation(summary = "Creates or updates MCRDerivate with the body of this request",
199 tags = MCRRestUtils.TAG_MYCORE_DERIVATE,
200 responses = {
201 @ApiResponse(responseCode = "400",
202 content = { @Content(mediaType = MediaType.TEXT_PLAIN) },
203 description = "'Invalid body content' or 'MCRObjectID mismatch'"),
204 @ApiResponse(responseCode = "201", description = "MCRDerivate successfully created"),
205 @ApiResponse(responseCode = "204", description = "MCRDerivate successfully updated"),
206 })
207 @MCRRequireTransaction
208 @Path("/{" + PARAM_DERID + "}")
209 public Response updateDerivate(
210 @Parameter(example = "mir_derivate_00004711") @PathParam(PARAM_DERID) MCRObjectID derid,
211 @Parameter(required = true,
212 description = "MCRObject XML",
213 examples = @ExampleObject("<mycoreobject ID=\"{mcrid}\" ..>\n...\n</mycorobject>")) InputStream xmlSource)
214 throws IOException {
215
216 try {
217 long lastModified = MCRXMLMetadataManager.instance().getLastModified(derid);
218 if (lastModified >= 0) {
219 Date lmDate = new Date(lastModified);
220 Optional<Response> cachedResponse = MCRRestUtils.getCachedResponse(request, lmDate);
221 if (cachedResponse.isPresent()) {
222 return cachedResponse.get();
223 }
224 }
225 } catch (Exception e) {
226
227 }
228 boolean create = true;
229 if (MCRMetadataManager.exists(derid)) {
230 validateDerivateRelation(mcrId, derid);
231 create = false;
232 }
233 MCRStreamContent inputContent = new MCRStreamContent(xmlSource, null, MCRDerivate.ROOT_NAME);
234 MCRDerivate derivate;
235 try {
236 derivate = new MCRDerivate(inputContent.asXML());
237 derivate.validate();
238 } catch (JDOMException | SAXException | MCRException e) {
239 throw MCRErrorResponse.fromStatus(Response.Status.BAD_REQUEST.getStatusCode())
240 .withErrorCode(MCRErrorCodeConstants.MCRDERIVATE_INVALID)
241 .withMessage("MCRDerivate " + derid + " is not valid")
242 .withDetail(e.getMessage())
243 .withCause(e)
244 .toException();
245 }
246 if (!derid.equals(derivate.getId())) {
247 throw MCRErrorResponse.fromStatus(Response.Status.BAD_REQUEST.getStatusCode())
248 .withErrorCode(MCRErrorCodeConstants.MCRDERIVATE_ID_MISMATCH)
249 .withMessage("MCRDerivate " + derid + " cannot be overwritten by " + derivate.getId() + ".")
250 .toException();
251 }
252 try {
253 if (create) {
254 MCRMetadataManager.create(derivate);
255 MCRPath rootDir = MCRPath.getPath(derid.toString(), "/");
256 if (Files.notExists(rootDir)) {
257 rootDir.getFileSystem().createRoot(derid.toString());
258 }
259 return Response.status(Response.Status.CREATED).build();
260 } else {
261 MCRMetadataManager.update(derivate);
262 return Response.status(Response.Status.NO_CONTENT).build();
263 }
264 } catch (MCRAccessException e) {
265 throw MCRErrorResponse.fromStatus(Response.Status.FORBIDDEN.getStatusCode())
266 .withErrorCode(MCRErrorCodeConstants.MCRDERIVATE_NO_PERMISSION)
267 .withMessage("You may not modify or create MCRDerivate " + derid + ".")
268 .withDetail(e.getMessage())
269 .withCause(e)
270 .toException();
271 }
272 }
273
274 @DELETE
275 @Operation(summary = "Deletes MCRDerivate {" + PARAM_DERID + "}",
276 tags = MCRRestUtils.TAG_MYCORE_DERIVATE,
277 responses = {
278 @ApiResponse(responseCode = "204", description = "MCRDerivate successfully deleted"),
279 })
280 @MCRRequireTransaction
281 @Path("/{" + PARAM_DERID + "}")
282 public Response deleteDerivate(
283 @Parameter(example = "mir_derivate_00004711") @PathParam(PARAM_DERID) MCRObjectID derid) {
284 if (!MCRMetadataManager.exists(derid)) {
285 throw MCRErrorResponse.fromStatus(Response.Status.NOT_FOUND.getStatusCode())
286 .withErrorCode(MCRErrorCodeConstants.MCRDERIVATE_NOT_FOUND)
287 .withMessage("MCRDerivate " + derid + " not found")
288 .toException();
289 }
290 try {
291 MCRMetadataManager.deleteMCRDerivate(derid);
292 return Response.noContent().build();
293 } catch (MCRAccessException e) {
294 throw MCRErrorResponse.fromStatus(Response.Status.FORBIDDEN.getStatusCode())
295 .withErrorCode(MCRErrorCodeConstants.MCRDERIVATE_NO_PERMISSION)
296 .withMessage("You may not delete MCRDerivate " + derid + ".")
297 .withDetail(e.getMessage())
298 .withCause(e)
299 .toException();
300 }
301 }
302
303 @POST
304 @Operation(
305 summary = "Adds a new derivate (with defaults for 'display-enabled', 'main-doc', 'label') in the given object",
306 responses = @ApiResponse(responseCode = "201",
307 headers = @Header(name = HttpHeaders.LOCATION, description = "URL of the new derivate")),
308 tags = MCRRestUtils.TAG_MYCORE_DERIVATE)
309 @MCRRequireTransaction
310 @MCRAccessControlExposeHeaders(HttpHeaders.LOCATION)
311 public Response createDefaultDerivate() {
312 return doCreateDerivate(new DerivateMetadata());
313 }
314
315 @POST
316 @Operation(
317 summary = "Adds a new derivate in the given object",
318 responses = @ApiResponse(responseCode = "201",
319 headers = @Header(name = HttpHeaders.LOCATION, description = "URL of the new derivate")),
320 tags = MCRRestUtils.TAG_MYCORE_DERIVATE)
321 @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
322 @RequestBody(required = true,
323 content = @Content(mediaType = MediaType.APPLICATION_FORM_URLENCODED,
324 schema = @Schema(implementation = DerivateMetadata.class)))
325 @MCRRequireTransaction
326 @MCRAccessControlExposeHeaders(HttpHeaders.LOCATION)
327 public Response createDerivate(@BeanParam DerivateMetadata der) {
328 return doCreateDerivate(der);
329 }
330
331 private Response doCreateDerivate(@BeanParam DerivateMetadata der) {
332 LOGGER.debug(der);
333 String projectID = mcrId.getProjectId();
334 MCRObjectID derId = MCRObjectID.getNextFreeId(projectID + "_derivate");
335 MCRDerivate derivate = new MCRDerivate();
336 derivate.setId(derId);
337
338 derivate.setOrder(der.getOrder());
339
340 derivate.getDerivate().getClassifications()
341 .addAll(der.getClassifications().stream()
342 .map(categId -> new MCRMetaClassification("classification", 0, null, categId))
343 .collect(Collectors.toList()));
344
345 derivate.getDerivate().getTitles()
346 .addAll(der.getTitles().stream()
347 .map(DerivateTitle::toMetaLangText)
348 .collect(Collectors.toList()));
349
350 String schema = MCRConfiguration2.getString("MCR.Metadata.Config.derivate")
351 .orElse("datamodel-derivate.xml")
352 .replaceAll(".xml", ".xsd");
353 derivate.setSchema(schema);
354
355 MCRMetaLinkID linkId = new MCRMetaLinkID();
356 linkId.setSubTag("linkmeta");
357 linkId.setReference(mcrId, null, null);
358 derivate.getDerivate().setLinkMeta(linkId);
359
360 MCRMetaIFS ifs = new MCRMetaIFS();
361 ifs.setSubTag("internal");
362 ifs.setSourcePath(null);
363 ifs.setMainDoc(der.getMainDoc());
364 derivate.getDerivate().setInternals(ifs);
365
366 LOGGER.debug("Creating new derivate with ID {}", derId);
367 try {
368 MCRMetadataManager.create(derivate);
369 } catch (MCRAccessException e) {
370 throw MCRErrorResponse.fromStatus(Response.Status.FORBIDDEN.getStatusCode())
371 .withErrorCode(MCRErrorCodeConstants.MCRDERIVATE_NO_PERMISSION)
372 .withMessage("You may not create MCRDerivate " + derId + ".")
373 .withDetail(e.getMessage())
374 .withCause(e)
375 .toException();
376 }
377 MCRPath rootDir = MCRPath.getPath(derId.toString(), "/");
378 if (Files.notExists(rootDir)) {
379 try {
380 rootDir.getFileSystem().createRoot(derId.toString());
381 } catch (FileSystemException e) {
382 throw MCRErrorResponse.fromStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode())
383 .withErrorCode(MCRErrorCodeConstants.MCRDERIVATE_CREATE_DIRECTORY)
384 .withMessage("Could not create root directory for MCRDerivate " + derId + ".")
385 .withDetail(e.getMessage())
386 .withCause(e)
387 .toException();
388 }
389 }
390 return Response.created(uriInfo.getAbsolutePathBuilder().path(derId.toString()).build()).build();
391 }
392
393 @PATCH
394 @Operation(
395 summary = "Updates the metadata (or partial metadata) of the given derivate",
396 responses = @ApiResponse(responseCode = "204"),
397 tags = MCRRestUtils.TAG_MYCORE_DERIVATE)
398 @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
399 @RequestBody(required = true,
400 content = @Content(mediaType = MediaType.APPLICATION_FORM_URLENCODED,
401 schema = @Schema(implementation = DerivateMetadata.class)))
402 @MCRRequireTransaction
403 @MCRAccessControlExposeHeaders(HttpHeaders.LOCATION)
404 @Path("/{" + PARAM_DERID + "}")
405 @MCRApiDraft("MCRPatchDerivate")
406 public Response patchDerivate(@BeanParam DerivateMetadata der,
407 @Parameter(example = "mir_derivate_00004711") @PathParam(PARAM_DERID) MCRObjectID derid) {
408
409 LOGGER.debug(der);
410 MCRDerivate derivate = MCRMetadataManager.retrieveMCRDerivate(derid);
411 boolean modified = false;
412
413 if (der.getOrder() != -1
414 && derivate.getOrder() != der.getOrder()) {
415 modified = true;
416 derivate.setOrder(der.getOrder());
417 }
418
419 if (der.getMainDoc() != null
420 && !der.getMainDoc().equals(derivate.getDerivate().getInternals().getMainDoc())) {
421 modified = true;
422 derivate.getDerivate().getInternals().setMainDoc(der.getMainDoc());
423 }
424
425 List<MCRCategoryID> oldClassifications = derivate.getDerivate().getClassifications().stream()
426 .map(x -> MCRCategoryID.fromString(x.getClassId() + ":" + x.getCategId()))
427 .collect(Collectors.toList());
428 if (!der.getClassifications().isEmpty()
429 && (oldClassifications.size() != der.getClassifications().size()
430 || !oldClassifications.containsAll(der.getClassifications()))) {
431 modified = true;
432 derivate.getDerivate().getClassifications().clear();
433 derivate.getDerivate().getClassifications()
434 .addAll(der.getClassifications().stream()
435 .map(categId -> new MCRMetaClassification("classification", 0, null, categId))
436 .collect(Collectors.toList()));
437 }
438
439 List<MCRMetaLangText> newTitles = der.getTitles().stream()
440 .map(DerivateTitle::toMetaLangText)
441 .collect(Collectors.toList());
442 if (!newTitles.isEmpty()
443 && (derivate.getDerivate().getTitleSize() != newTitles.size()
444 || !derivate.getDerivate().getTitles().containsAll(newTitles))) {
445 modified = true;
446 derivate.getDerivate().getTitles().clear();
447 derivate.getDerivate().getTitles().addAll(newTitles);
448 }
449
450 if (modified) {
451 try {
452 MCRMetadataManager.update(derivate);
453 } catch (MCRAccessException e) {
454 throw MCRErrorResponse.fromStatus(Response.Status.FORBIDDEN.getStatusCode())
455 .withErrorCode(MCRErrorCodeConstants.MCRDERIVATE_NO_PERMISSION)
456 .withMessage("You may not update MCRDerivate " + derivate.getId() + ".")
457 .withDetail(e.getMessage())
458 .withCause(e)
459 .toException();
460 }
461 }
462 return Response.noContent().build();
463 }
464
465 @PUT
466 @Path("/{" + PARAM_DERID + "}/try")
467 @Operation(summary = "pre-flight target to test write operation on {" + PARAM_DERID + "}",
468 tags = MCRRestUtils.TAG_MYCORE_DERIVATE,
469 responses = {
470 @ApiResponse(responseCode = "202", description = "You have write permission"),
471 @ApiResponse(responseCode = "401",
472 description = "You do not have write permission and need to authenticate first"),
473 @ApiResponse(responseCode = "403", description = "You do not have write permission"),
474 })
475 public Response testUpdateDerivate(@PathParam(PARAM_DERID) MCRObjectID id)
476 throws IOException {
477 return Response.status(Response.Status.ACCEPTED).build();
478 }
479
480 @DELETE
481 @Path("/{" + PARAM_DERID + "}/try")
482 @Operation(summary = "pre-flight target to test delete operation on {" + PARAM_DERID + "}",
483 tags = MCRRestUtils.TAG_MYCORE_DERIVATE,
484 responses = {
485 @ApiResponse(responseCode = "202", description = "You have delete permission"),
486 @ApiResponse(responseCode = "401",
487 description = "You do not have delete permission and need to authenticate first"),
488 @ApiResponse(responseCode = "403", description = "You do not have delete permission"),
489 })
490 public Response testDeleteDerivate(@PathParam(PARAM_DERID) MCRObjectID id)
491 throws IOException {
492 return Response.status(Response.Status.ACCEPTED).build();
493 }
494
495 public static class DerivateTitle {
496 private String lang;
497
498 private String text;
499
500
501 public static DerivateTitle fromString(String value) {
502 final DerivateTitle derivateTitle = new DerivateTitle();
503 if (value.length() >= 4 && value.charAt(0) == '(') {
504 int pos = value.indexOf(')');
505 if (pos > 1) {
506 derivateTitle.setLang(value.substring(1, pos));
507 derivateTitle.setText(value.substring(pos + 1));
508 return derivateTitle;
509 }
510 }
511 derivateTitle.setText(value);
512 return derivateTitle;
513 }
514
515 public String getLang() {
516 return lang;
517 }
518
519 public void setLang(String lang) {
520 this.lang = lang;
521 }
522
523 public String getText() {
524 return text;
525 }
526
527 public void setText(String text) {
528 this.text = text;
529 }
530
531 public MCRMetaLangText toMetaLangText() {
532 return new MCRMetaLangText("title", getLang(), null, 0, null, getText());
533 }
534 }
535
536 public static class DerivateMetadata {
537 private String mainDoc;
538
539 private int order = 1;
540
541 private List<MCRCategoryID> classifications = List.of();
542
543 private List<DerivateTitle> titles = List.of();
544
545 String getMainDoc() {
546 return mainDoc;
547 }
548
549 @FormParam("maindoc")
550 @JsonProperty("maindoc")
551 public void setMainDoc(String mainDoc) {
552 this.mainDoc = mainDoc;
553 }
554
555 public int getOrder() {
556 return order;
557 }
558
559 @JsonProperty
560 @FormParam("order")
561 @DefaultValue("1")
562 public void setOrder(int order) {
563 this.order = order;
564 }
565
566 public List<MCRCategoryID> getClassifications() {
567 return classifications;
568 }
569
570 @JsonProperty
571 @FormParam("classification")
572 public void setClassifications(List<MCRCategoryID> classifications) {
573 this.classifications = classifications;
574 }
575
576 public List<DerivateTitle> getTitles() {
577 return titles;
578 }
579
580 @FormParam("title")
581 public void setTitles(List<DerivateTitle> titles) {
582 this.titles = titles;
583 }
584
585 }
586 }