1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.mycore.restapi.v1;
20
21 import java.io.IOException;
22 import java.io.StringWriter;
23 import java.util.Date;
24 import java.util.concurrent.TimeUnit;
25
26 import org.apache.logging.log4j.LogManager;
27 import org.apache.logging.log4j.Logger;
28 import org.apache.solr.client.solrj.SolrClient;
29 import org.apache.solr.client.solrj.SolrQuery;
30 import org.apache.solr.client.solrj.SolrServerException;
31 import org.apache.solr.client.solrj.response.QueryResponse;
32 import org.apache.solr.common.SolrDocumentList;
33 import org.glassfish.jersey.server.ContainerRequest;
34 import org.jdom2.Document;
35 import org.jdom2.Element;
36 import org.jdom2.Namespace;
37 import org.jdom2.filter.Filters;
38 import org.jdom2.output.Format;
39 import org.jdom2.output.XMLOutputter;
40 import org.jdom2.xpath.XPathExpression;
41 import org.jdom2.xpath.XPathFactory;
42 import org.mycore.datamodel.classifications2.MCRCategory;
43 import org.mycore.datamodel.classifications2.MCRCategoryDAO;
44 import org.mycore.datamodel.classifications2.MCRCategoryID;
45 import org.mycore.datamodel.classifications2.impl.MCRCategoryDAOImpl;
46 import org.mycore.datamodel.classifications2.utils.MCRCategoryTransformer;
47 import org.mycore.frontend.jersey.MCRCacheControl;
48 import org.mycore.frontend.jersey.MCRJerseyUtil;
49 import org.mycore.restapi.v1.errors.MCRRestAPIError;
50 import org.mycore.restapi.v1.errors.MCRRestAPIException;
51 import org.mycore.solr.MCRSolrClientFactory;
52 import org.mycore.solr.MCRSolrUtils;
53
54 import com.google.gson.stream.JsonWriter;
55
56 import jakarta.ws.rs.DefaultValue;
57 import jakarta.ws.rs.GET;
58 import jakarta.ws.rs.Path;
59 import jakarta.ws.rs.PathParam;
60 import jakarta.ws.rs.Produces;
61 import jakarta.ws.rs.QueryParam;
62 import jakarta.ws.rs.core.Context;
63 import jakarta.ws.rs.core.MediaType;
64 import jakarta.ws.rs.core.Response;
65 import jakarta.ws.rs.core.UriInfo;
66 import jakarta.ws.rs.core.Response.Status;
67
68
69
70
71
72
73
74
75 @Path("/classifications")
76 public class MCRRestAPIClassifications {
77
78 public static final String FORMAT_JSON = "json";
79
80 public static final String FORMAT_XML = "xml";
81
82 private static final MCRCategoryDAO DAO = new MCRCategoryDAOImpl();
83
84 private static Logger LOGGER = LogManager.getLogger(MCRRestAPIClassifications.class);
85
86 @Context
87 ContainerRequest request;
88
89 private Date lastModified = new Date(DAO.getLastModified());
90
91
92
93
94
95
96
97
98 private static String writeXML(Element eRoot, String lang) throws IOException {
99 StringWriter sw = new StringWriter();
100 if (lang != null) {
101
102 XPathExpression<Element> xpE = XPathFactory.instance().compile("//label[@xml:lang!='" + lang + "']",
103 Filters.element(), null, Namespace.XML_NAMESPACE);
104 for (Element e : xpE.evaluate(eRoot)) {
105 e.getParentElement().removeContent(e);
106 }
107 }
108 XMLOutputter xout = new XMLOutputter(Format.getPrettyFormat());
109 Document docOut = new Document(eRoot.detach());
110 xout.output(docOut, sw);
111 return sw.toString();
112 }
113
114
115
116
117
118
119
120
121
122 private static void writeChildrenAsJSON(Element eParent, JsonWriter writer, String lang) throws IOException {
123 if (eParent.getChildren("category").size() == 0) {
124 return;
125 }
126
127 writer.name("categories");
128 writer.beginArray();
129 for (Element e : eParent.getChildren("category")) {
130 writer.beginObject();
131 writer.name("ID").value(e.getAttributeValue("ID"));
132 writer.name("labels").beginArray();
133 for (Element eLabel : e.getChildren("label")) {
134 if (lang == null || lang.equals(eLabel.getAttributeValue("lang", Namespace.XML_NAMESPACE))) {
135 writer.beginObject();
136 writer.name("lang").value(eLabel.getAttributeValue("lang", Namespace.XML_NAMESPACE));
137 writer.name("text").value(eLabel.getAttributeValue("text"));
138 if (eLabel.getAttributeValue("description") != null) {
139 writer.name("description").value(eLabel.getAttributeValue("description"));
140 }
141 writer.endObject();
142 }
143 }
144 writer.endArray();
145
146 if (e.getChildren("category").size() > 0) {
147 writeChildrenAsJSON(e, writer, lang);
148 }
149 writer.endObject();
150 }
151 writer.endArray();
152 }
153
154
155
156
157
158
159
160
161
162
163 private static void writeChildrenAsJSONCBTree(Element eParent, JsonWriter writer, String lang, boolean checked)
164 throws IOException {
165 writer.beginArray();
166 for (Element e : eParent.getChildren("category")) {
167 writer.beginObject();
168 writer.name("ID").value(e.getAttributeValue("ID"));
169 for (Element eLabel : e.getChildren("label")) {
170 if (lang == null || lang.equals(eLabel.getAttributeValue("lang", Namespace.XML_NAMESPACE))) {
171 writer.name("text").value(eLabel.getAttributeValue("text"));
172 }
173 }
174 writer.name("checked").value(checked);
175 if (e.getChildren("category").size() > 0) {
176 writer.name("children");
177 writeChildrenAsJSONCBTree(e, writer, lang, checked);
178 }
179 writer.endObject();
180 }
181 writer.endArray();
182 }
183
184
185
186
187
188
189
190
191
192
193
194
195
196 private static void writeChildrenAsJSONJSTree(Element eParent, JsonWriter writer, String lang, boolean opened,
197 boolean disabled, boolean selected) throws IOException {
198 writer.beginArray();
199 for (Element e : eParent.getChildren("category")) {
200 writer.beginObject();
201 writer.name("id").value(e.getAttributeValue("ID"));
202 for (Element eLabel : e.getChildren("label")) {
203 if (lang == null || lang.equals(eLabel.getAttributeValue("lang", Namespace.XML_NAMESPACE))) {
204 writer.name("text").value(eLabel.getAttributeValue("text"));
205 }
206 }
207 if (opened || disabled || selected) {
208 writer.name("state");
209 writer.beginObject();
210 if (opened) {
211 writer.name("opened").value(true);
212 }
213 if (disabled) {
214 writer.name("disabled").value(true);
215 }
216 if (selected) {
217 writer.name("selected").value(true);
218 }
219 writer.endObject();
220 }
221 if (e.getChildren("category").size() > 0) {
222 writer.name("children");
223 writeChildrenAsJSONJSTree(e, writer, lang, opened, disabled, selected);
224 }
225 writer.endObject();
226 }
227 writer.endArray();
228 }
229
230
231
232
233
234
235
236
237 @GET
238 @Produces({ MediaType.TEXT_XML + ";charset=UTF-8", MediaType.APPLICATION_JSON + ";charset=UTF-8" })
239 @MCRCacheControl(maxAge = @MCRCacheControl.Age(time = 1, unit = TimeUnit.HOURS),
240 sMaxAge = @MCRCacheControl.Age(time = 1, unit = TimeUnit.HOURS))
241 public Response listClassifications(@Context UriInfo info,
242 @QueryParam("format") @DefaultValue("json") String format) {
243
244 Response.ResponseBuilder builder = request.evaluatePreconditions(lastModified);
245 if (builder != null) {
246 return builder.build();
247 }
248
249 if (FORMAT_XML.equals(format)) {
250 StringWriter sw = new StringWriter();
251
252 XMLOutputter xout = new XMLOutputter(Format.getPrettyFormat());
253 Document docOut = new Document();
254 Element eRoot = new Element("mycoreclassifications");
255 docOut.setRootElement(eRoot);
256
257 for (MCRCategory cat : DAO.getRootCategories()) {
258 eRoot.addContent(new Element("mycoreclass").setAttribute("ID", cat.getId().getRootID()).setAttribute(
259 "href", info.getAbsolutePathBuilder().path(cat.getId().getRootID()).build().toString()));
260 }
261 try {
262 xout.output(docOut, sw);
263 return Response.ok(sw.toString())
264 .lastModified(lastModified)
265 .type("application/xml; charset=UTF-8")
266 .build();
267 } catch (IOException e) {
268
269 }
270 }
271
272 if (FORMAT_JSON.equals(format)) {
273 StringWriter sw = new StringWriter();
274 try {
275 JsonWriter writer = new JsonWriter(sw);
276 writer.setIndent(" ");
277 writer.beginObject();
278 writer.name("mycoreclass");
279 writer.beginArray();
280 for (MCRCategory cat : DAO.getRootCategories()) {
281 writer.beginObject();
282 writer.name("ID").value(cat.getId().getRootID());
283 writer.name("href")
284 .value(info.getAbsolutePathBuilder().path(cat.getId().getRootID()).build().toString());
285 writer.endObject();
286 }
287 writer.endArray();
288 writer.endObject();
289
290 writer.close();
291
292 return Response.ok(sw.toString())
293 .type(MCRJerseyUtil.APPLICATION_JSON_UTF8)
294 .lastModified(lastModified)
295 .build();
296 } catch (IOException e) {
297
298 }
299 }
300 return Response.status(Status.BAD_REQUEST).build();
301 }
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325 @GET
326
327 @Path("/{classID}")
328 @Produces({ MediaType.TEXT_XML + ";charset=UTF-8", MediaType.APPLICATION_JSON + ";charset=UTF-8" })
329 @MCRCacheControl(maxAge = @MCRCacheControl.Age(time = 1, unit = TimeUnit.HOURS),
330 sMaxAge = @MCRCacheControl.Age(time = 1, unit = TimeUnit.HOURS))
331 public Response showObject(@PathParam("classID") String classID,
332 @QueryParam("format") @DefaultValue("xml") String format, @QueryParam("filter") @DefaultValue("") String filter,
333 @QueryParam("style") @DefaultValue("") String style,
334 @QueryParam("callback") @DefaultValue("") String callback) {
335
336 Response.ResponseBuilder builder = request.evaluatePreconditions(lastModified);
337 if (builder != null) {
338 return builder.build();
339 }
340
341 String rootCateg = null;
342 String lang = null;
343 boolean filterNonEmpty = false;
344 boolean filterNoChildren = false;
345
346 for (String f : filter.split(";")) {
347 if (f.startsWith("root:")) {
348 rootCateg = f.substring(5);
349 }
350 if (f.startsWith("lang:")) {
351 lang = f.substring(5);
352 }
353 if (f.startsWith("nonempty")) {
354 filterNonEmpty = true;
355 }
356 if (f.startsWith("nochildren")) {
357 filterNoChildren = true;
358 }
359 }
360
361 if (format == null || classID == null) {
362 return Response.serverError().status(Status.BAD_REQUEST).build();
363
364
365 }
366 try {
367 MCRCategory cl = DAO.getCategory(MCRCategoryID.rootID(classID), -1);
368 if (cl == null) {
369 throw new MCRRestAPIException(Status.NOT_FOUND,
370 new MCRRestAPIError(MCRRestAPIError.CODE_NOT_FOUND, "Classification not found.",
371 "There is no classification with the given ID."));
372 }
373 Document docClass = MCRCategoryTransformer.getMetaDataDocument(cl, false);
374 Element eRoot = docClass.getRootElement();
375 if (rootCateg != null) {
376 XPathExpression<Element> xpe = XPathFactory.instance().compile("//category[@ID='" + rootCateg + "']",
377 Filters.element());
378 Element e = xpe.evaluateFirst(docClass);
379 if (e != null) {
380 eRoot = e;
381 } else {
382 throw new MCRRestAPIException(Status.NOT_FOUND,
383 new MCRRestAPIError(MCRRestAPIError.CODE_NOT_FOUND, "Category not found.",
384 "The classfication does not contain a category with the given ID."));
385 }
386 }
387 if (filterNonEmpty) {
388 Element eFilter = eRoot;
389 if (eFilter.getName().equals("mycoreclass")) {
390 eFilter = eFilter.getChild("categories");
391 }
392 filterNonEmpty(docClass.getRootElement().getAttributeValue("ID"), eFilter);
393 }
394 if (filterNoChildren) {
395 eRoot.removeChildren("category");
396 }
397
398 if (FORMAT_JSON.equals(format)) {
399 String json = writeJSON(eRoot, lang, style);
400
401 if (callback.length() > 0) {
402 return Response.ok(callback + "(" + json + ")")
403 .lastModified(lastModified)
404 .type(MCRJerseyUtil.APPLICATION_JSON_UTF8)
405 .build();
406 } else {
407 return Response.ok(json).type("application/json; charset=UTF-8")
408 .build();
409 }
410 }
411
412 if (FORMAT_XML.equals(format)) {
413 String xml = writeXML(eRoot, lang);
414 return Response.ok(xml)
415 .lastModified(lastModified)
416 .type(MCRJerseyUtil.APPLICATION_XML_UTF8)
417 .build();
418 }
419 } catch (Exception e) {
420 LogManager.getLogger(this.getClass()).error("Error outputting classification", e);
421
422 }
423 return null;
424 }
425
426
427
428
429
430
431
432
433
434 private String writeJSON(Element eRoot, String lang, String style) throws IOException {
435 StringWriter sw = new StringWriter();
436 JsonWriter writer = new JsonWriter(sw);
437 writer.setIndent(" ");
438 if (style.contains("checkboxtree")) {
439 if (lang == null) {
440 lang = "de";
441 }
442 writer.beginObject();
443 writer.name("identifier").value(eRoot.getAttributeValue("ID"));
444 for (Element eLabel : eRoot.getChildren("label")) {
445 if (lang.equals(eLabel.getAttributeValue("lang", Namespace.XML_NAMESPACE))) {
446 writer.name("label").value(eLabel.getAttributeValue("text"));
447 }
448 }
449 writer.name("items");
450 eRoot = eRoot.getChild("categories");
451 writeChildrenAsJSONCBTree(eRoot, writer, lang, style.contains("checked"));
452 writer.endObject();
453 } else if (style.contains("jstree")) {
454 if (lang == null) {
455 lang = "de";
456 }
457 eRoot = eRoot.getChild("categories");
458 writeChildrenAsJSONJSTree(eRoot, writer, lang, style.contains("opened"),
459 style.contains("disabled"), style.contains("selected"));
460 } else {
461 writer.beginObject();
462 writer.name("ID").value(eRoot.getAttributeValue("ID"));
463 writer.name("label");
464 writer.beginArray();
465 for (Element eLabel : eRoot.getChildren("label")) {
466 if (lang == null || lang.equals(eLabel.getAttributeValue("lang", Namespace.XML_NAMESPACE))) {
467 writer.beginObject();
468 writer.name("lang").value(eLabel.getAttributeValue("lang", Namespace.XML_NAMESPACE));
469 writer.name("text").value(eLabel.getAttributeValue("text"));
470 if (eLabel.getAttributeValue("description") != null) {
471 writer.name("description").value(eLabel.getAttributeValue("description"));
472 }
473 writer.endObject();
474 }
475 }
476 writer.endArray();
477
478 if (eRoot.equals(eRoot.getDocument().getRootElement())) {
479 writeChildrenAsJSON(eRoot.getChild("categories"), writer, lang);
480 } else {
481 writeChildrenAsJSON(eRoot, writer, lang);
482 }
483
484 writer.endObject();
485 }
486 writer.close();
487 return sw.toString();
488 }
489
490 private void filterNonEmpty(String classId, Element e) {
491 SolrClient solrClient = MCRSolrClientFactory.getMainSolrClient();
492 Element[] categories = e.getChildren("category").toArray(Element[]::new);
493 for (Element cat : categories) {
494 SolrQuery solrQquery = new SolrQuery();
495 solrQquery.setQuery(
496 "category:\"" + MCRSolrUtils.escapeSearchValue(classId + ":" + cat.getAttributeValue("ID")) + "\"");
497 solrQquery.setRows(0);
498 try {
499 QueryResponse response = solrClient.query(solrQquery);
500 SolrDocumentList solrResults = response.getResults();
501 if (solrResults.getNumFound() == 0) {
502 cat.detach();
503 } else {
504 filterNonEmpty(classId, cat);
505 }
506 } catch (SolrServerException | IOException exc) {
507 LOGGER.error(exc);
508 }
509
510 }
511 }
512
513 }