1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.mycore.frontend.classeditor.resources;
20
21 import static org.mycore.access.MCRAccessManager.PERMISSION_DELETE;
22 import static org.mycore.access.MCRAccessManager.PERMISSION_WRITE;
23
24 import java.io.IOException;
25 import java.io.InputStream;
26 import java.text.MessageFormat;
27 import java.util.ArrayList;
28 import java.util.Collection;
29 import java.util.Comparator;
30 import java.util.LinkedList;
31 import java.util.List;
32 import java.util.Locale;
33 import java.util.Map;
34 import java.util.UUID;
35
36 import org.apache.solr.client.solrj.SolrClient;
37 import org.apache.solr.client.solrj.SolrServerException;
38 import org.apache.solr.client.solrj.response.QueryResponse;
39 import org.apache.solr.common.SolrDocument;
40 import org.apache.solr.common.SolrDocumentList;
41 import org.apache.solr.common.params.ModifiableSolrParams;
42 import org.glassfish.jersey.media.multipart.FormDataParam;
43 import org.mycore.access.MCRAccessException;
44 import org.mycore.access.MCRAccessManager;
45 import org.mycore.common.MCRJSONManager;
46 import org.mycore.common.config.MCRConfiguration2;
47 import org.mycore.datamodel.classifications2.MCRCategLinkService;
48 import org.mycore.datamodel.classifications2.MCRCategLinkServiceFactory;
49 import org.mycore.datamodel.classifications2.MCRCategory;
50 import org.mycore.datamodel.classifications2.MCRCategoryDAO;
51 import org.mycore.datamodel.classifications2.MCRCategoryDAOFactory;
52 import org.mycore.datamodel.classifications2.MCRCategoryID;
53 import org.mycore.datamodel.classifications2.utils.MCRClassificationUtils;
54 import org.mycore.frontend.classeditor.access.MCRClassificationWritePermission;
55 import org.mycore.frontend.classeditor.access.MCRNewClassificationPermission;
56 import org.mycore.frontend.classeditor.json.MCRJSONCategory;
57 import org.mycore.frontend.classeditor.json.MCRJSONCategoryHelper;
58 import org.mycore.frontend.classeditor.wrapper.MCRCategoryListWrapper;
59 import org.mycore.frontend.jersey.filter.access.MCRRestrictedAccess;
60 import org.mycore.solr.MCRSolrClientFactory;
61 import org.mycore.solr.classification.MCRSolrClassificationUtil;
62 import org.mycore.solr.search.MCRSolrSearchUtils;
63
64 import com.google.gson.Gson;
65 import com.google.gson.JsonArray;
66 import com.google.gson.JsonElement;
67 import com.google.gson.JsonObject;
68 import com.google.gson.JsonPrimitive;
69 import com.google.gson.JsonStreamParser;
70
71 import jakarta.ws.rs.Consumes;
72 import jakarta.ws.rs.DELETE;
73 import jakarta.ws.rs.GET;
74 import jakarta.ws.rs.POST;
75 import jakarta.ws.rs.Path;
76 import jakarta.ws.rs.PathParam;
77 import jakarta.ws.rs.Produces;
78 import jakarta.ws.rs.QueryParam;
79 import jakarta.ws.rs.WebApplicationException;
80 import jakarta.ws.rs.core.Context;
81 import jakarta.ws.rs.core.MediaType;
82 import jakarta.ws.rs.core.Response;
83 import jakarta.ws.rs.core.UriInfo;
84 import jakarta.ws.rs.core.Response.Status;
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105 @Path("classifications")
106 public class MCRClassificationEditorResource {
107 private static final MCRCategoryDAO CATEGORY_DAO = MCRCategoryDAOFactory.getInstance();
108
109 private static final MCRCategLinkService CATEG_LINK_SERVICE = MCRCategLinkServiceFactory.getInstance();
110
111 @Context
112 UriInfo uriInfo;
113
114 @GET
115 @Produces(MediaType.TEXT_PLAIN)
116 @Path("test")
117 public String test() {
118 return "Hallo";
119 }
120
121
122
123
124
125
126 @GET
127 @Path("{rootidStr}")
128 @Produces(MediaType.APPLICATION_JSON)
129 public String get(@PathParam("rootidStr") String rootidStr) {
130 if (rootidStr == null || "".equals(rootidStr)) {
131 throw new WebApplicationException(Status.NOT_FOUND);
132 }
133
134 MCRCategoryID id = MCRCategoryID.rootID(rootidStr);
135 return getCategory(id);
136 }
137
138
139
140
141
142
143 @GET
144 @Path("{rootidStr}/{categidStr}")
145 @Produces(MediaType.APPLICATION_JSON)
146 public String get(@PathParam("rootidStr") String rootidStr, @PathParam("categidStr") String categidStr) {
147
148 if (rootidStr == null || "".equals(rootidStr) || categidStr == null || "".equals(categidStr)) {
149 throw new WebApplicationException(Status.NOT_FOUND);
150 }
151
152 MCRCategoryID id = new MCRCategoryID(rootidStr, categidStr);
153 return getCategory(id);
154 }
155
156 @GET
157 @Path("newID/{rootID}")
158 @Produces(MediaType.APPLICATION_JSON)
159 public String newIDJson(@PathParam("rootID") String rootID) {
160 Gson gson = MCRJSONManager.instance().createGson();
161 return gson.toJson(newRandomUUID(rootID));
162 }
163
164 @GET
165 @Path("newID")
166 @MCRRestrictedAccess(MCRNewClassificationPermission.class)
167 @Produces(MediaType.APPLICATION_JSON)
168 public String newRootIDJson() {
169 Gson gson = MCRJSONManager.instance().createGson();
170 return gson.toJson(newRootID());
171 }
172
173 @GET
174 @Path("export/{rootidStr}")
175 @Produces(MediaType.APPLICATION_XML)
176 public String export(@PathParam("rootidStr") String rootidStr) {
177 if (rootidStr == null || "".equals(rootidStr)) {
178 throw new WebApplicationException(Status.NOT_FOUND);
179 }
180 String classAsString = MCRClassificationUtils.asString(rootidStr);
181 if (classAsString == null) {
182 throw new WebApplicationException(Status.NOT_FOUND);
183 }
184 return classAsString;
185 }
186
187 @GET
188 @Produces(MediaType.APPLICATION_JSON)
189 public Response getClassification() {
190 Gson gson = MCRJSONManager.instance().createGson();
191 List<MCRCategory> rootCategories = new LinkedList<>(CATEGORY_DAO.getRootCategories());
192 rootCategories.removeIf(
193 category -> !MCRAccessManager.checkPermission(category.getId().getRootID(), PERMISSION_WRITE));
194 if (rootCategories.isEmpty()
195 && !MCRAccessManager.checkPermission(MCRClassificationUtils.CREATE_CLASS_PERMISSION)) {
196 return Response.status(Status.UNAUTHORIZED).build();
197 }
198 Map<MCRCategoryID, Boolean> linkMap = CATEG_LINK_SERVICE.hasLinks(null);
199 String json = gson.toJson(new MCRCategoryListWrapper(rootCategories, linkMap));
200 return Response.ok(json).build();
201 }
202
203 @DELETE
204 @Consumes(MediaType.APPLICATION_JSON)
205 public Response deleteCateg(String json) {
206 MCRJSONCategory category = parseJson(json);
207 DeleteOp deleteOp = new DeleteOp(category);
208 deleteOp.run();
209 return deleteOp.getResponse();
210 }
211
212 @POST
213 @Path("save")
214 @MCRRestrictedAccess(MCRClassificationWritePermission.class)
215 @Consumes(MediaType.APPLICATION_JSON)
216 public Response save(String json) {
217 JsonStreamParser jsonStreamParser = new JsonStreamParser(json);
218 if (jsonStreamParser.hasNext()) {
219 JsonArray saveObjArray = jsonStreamParser.next().getAsJsonArray();
220 List<JsonObject> saveList = new ArrayList<>();
221 for (JsonElement jsonElement : saveObjArray) {
222 saveList.add(jsonElement.getAsJsonObject());
223 }
224 saveList.sort(new IndexComperator());
225 for (JsonObject jsonObject : saveList) {
226 String status = getStatus(jsonObject);
227 SaveElement categ = getCateg(jsonObject);
228 MCRJSONCategory parsedCateg = parseJson(categ.getJson());
229 if ("update".equals(status)) {
230 new UpdateOp(parsedCateg, jsonObject).run();
231 } else if ("delete".equals(status)) {
232 deleteCateg(categ.getJson());
233 } else {
234 return Response.status(Status.BAD_REQUEST).build();
235 }
236 }
237
238 return Response.status(Status.OK).build();
239 } else {
240 return Response.status(Status.BAD_REQUEST).build();
241 }
242 }
243
244 @POST
245 @Path("import")
246 @Consumes(MediaType.MULTIPART_FORM_DATA)
247 @Produces(MediaType.TEXT_HTML)
248 public Response importClassification(@FormDataParam("classificationFile") InputStream uploadedInputStream) {
249 try {
250 MCRClassificationUtils.fromStream(uploadedInputStream);
251 } catch (MCRAccessException accessExc) {
252 return Response.status(Status.UNAUTHORIZED).build();
253 } catch (Exception exc) {
254 throw new WebApplicationException(exc);
255 }
256
257
258
259
260 return Response.ok("<html><body><textarea>200</textarea></body></html>").build();
261 }
262
263 @GET
264 @Path("filter/{text}")
265 @Produces(MediaType.APPLICATION_JSON)
266 public Response filter(@PathParam("text") String text) {
267 SolrClient solrClient = MCRSolrClassificationUtil.getCore().getClient();
268 ModifiableSolrParams p = new ModifiableSolrParams();
269 p.set("q", "*" + text + "*");
270 p.set("fl", "id,ancestors");
271
272 JsonArray docList = new JsonArray();
273 MCRSolrSearchUtils.stream(solrClient, p).flatMap(document -> {
274 List<String> ids = new ArrayList<>();
275 ids.add(document.getFirstValue("id").toString());
276 Collection<Object> fieldValues = document.getFieldValues("ancestors");
277 if (fieldValues != null) {
278 for (Object anc : fieldValues) {
279 ids.add(anc.toString());
280 }
281 }
282 return ids.stream();
283 }).distinct().map(JsonPrimitive::new).forEach(docList::add);
284 return Response.ok().entity(docList.toString()).build();
285 }
286
287 @GET
288 @Path("link/{id}")
289 @Produces(MediaType.APPLICATION_JSON)
290 public Response retrieveLinkedObjects(@PathParam("id") String id, @QueryParam("start") Integer start,
291 @QueryParam("rows") Integer rows) throws SolrServerException, IOException {
292
293 SolrClient solrClient = MCRSolrClientFactory.getMainSolrClient();
294 ModifiableSolrParams params = new ModifiableSolrParams();
295 params.set("start", start != null ? start : 0);
296 params.set("rows", rows != null ? rows : 50);
297 params.set("fl", "id");
298 String configQuery = MCRConfiguration2.getString("MCR.Solr.linkQuery").orElse("category.top:{0}");
299 String query = new MessageFormat(configQuery, Locale.ROOT).format(new String[] { id.replaceAll(":", "\\\\:") });
300 params.set("q", query);
301 QueryResponse solrResponse = solrClient.query(params);
302 SolrDocumentList solrResults = solrResponse.getResults();
303
304 JsonObject response = new JsonObject();
305 response.addProperty("numFound", solrResults.getNumFound());
306 response.addProperty("start", solrResults.getStart());
307 JsonArray docList = new JsonArray();
308 for (SolrDocument doc : solrResults) {
309 docList.add(new JsonPrimitive((String) doc.getFieldValue("id")));
310 }
311 response.add("docs", docList);
312 return Response.ok().entity(response.toString()).build();
313 }
314
315 protected MCRCategoryID newRootID() {
316 String uuid = UUID.randomUUID().toString().replaceAll("-", "");
317 return MCRCategoryID.rootID(uuid);
318 }
319
320 private MCRCategoryID newRandomUUID(String rootID) {
321 String newRootID = rootID;
322 if (rootID == null) {
323 newRootID = UUID.randomUUID().toString();
324 }
325 return new MCRCategoryID(newRootID, UUID.randomUUID().toString());
326 }
327
328 private String getCategory(MCRCategoryID id) {
329 if (!CATEGORY_DAO.exist(id)) {
330 throw new WebApplicationException(Status.NOT_FOUND);
331 }
332
333 MCRCategory category = CATEGORY_DAO.getCategory(id, 1);
334 if (!(category instanceof MCRJSONCategory)) {
335 category = new MCRJSONCategory(category);
336 }
337 Gson gson = MCRJSONManager.instance().createGson();
338
339 return gson.toJson(category);
340 }
341
342 private SaveElement getCateg(JsonElement jsonElement) {
343 JsonObject jsonObject = jsonElement.getAsJsonObject();
344 JsonObject categ = jsonObject.get("item").getAsJsonObject();
345 JsonElement parentID = jsonObject.get("parentId");
346 JsonElement position = jsonObject.get("index");
347 boolean hasParent = false;
348
349 if (parentID != null && !parentID.toString().contains("_placeboid_") && position != null) {
350 categ.add(MCRJSONCategoryHelper.PROP_PARENTID, parentID);
351 categ.add(MCRJSONCategoryHelper.PROP_POSITION, position);
352 hasParent = true;
353 }
354
355 return new SaveElement(categ.toString(), hasParent);
356 }
357
358 private String getStatus(JsonElement jsonElement) {
359 return jsonElement.getAsJsonObject().get("state").getAsString();
360 }
361
362 private boolean isAdded(JsonElement jsonElement) {
363 JsonElement added = jsonElement.getAsJsonObject().get("added");
364 return added != null && jsonElement.getAsJsonObject().get("added").getAsBoolean();
365 }
366
367 private MCRJSONCategory parseJson(String json) {
368 Gson gson = MCRJSONManager.instance().createGson();
369 return gson.fromJson(json, MCRJSONCategory.class);
370 }
371
372 protected String buildJsonError(String errorType, MCRCategoryID mcrCategoryID) {
373 Gson gson = MCRJSONManager.instance().createGson();
374 JsonObject error = new JsonObject();
375 error.addProperty("type", errorType);
376 error.addProperty("rootid", mcrCategoryID.getRootID());
377 error.addProperty("categid", mcrCategoryID.getID());
378 return gson.toJson(error);
379 }
380
381 private static class SaveElement {
382 private String categJson;
383
384 private boolean hasParent;
385
386 SaveElement(String categJson, boolean hasParent) {
387 this.setCategJson(categJson);
388 this.setHasParent(hasParent);
389 }
390
391 private void setHasParent(boolean hasParent) {
392 this.hasParent = hasParent;
393 }
394
395 @SuppressWarnings("unused")
396 public boolean hasParent() {
397 return hasParent;
398 }
399
400 private void setCategJson(String categJson) {
401 this.categJson = categJson;
402 }
403
404 public String getJson() {
405 return categJson;
406 }
407 }
408
409 private static class IndexComperator implements Comparator<JsonElement> {
410 @Override
411 public int compare(JsonElement jsonElement1, JsonElement jsonElement2) {
412 if (!jsonElement1.isJsonObject()) {
413 return 1;
414 }
415 if (!jsonElement2.isJsonObject()) {
416 return -1;
417 }
418
419 JsonPrimitive depthLevel1 = jsonElement1.getAsJsonObject().getAsJsonPrimitive("depthLevel");
420 JsonPrimitive depthLevel2 = jsonElement2.getAsJsonObject().getAsJsonPrimitive("depthLevel");
421 if (depthLevel1 == null) {
422 return 1;
423 }
424 if (depthLevel2 == null) {
425 return -1;
426 }
427 if (depthLevel1.getAsInt() != depthLevel2.getAsInt()) {
428 return Integer.compare(depthLevel1.getAsInt(), depthLevel2.getAsInt());
429 }
430
431 JsonPrimitive index1 = jsonElement1.getAsJsonObject().getAsJsonPrimitive("index");
432 JsonPrimitive index2 = jsonElement2.getAsJsonObject().getAsJsonPrimitive("index");
433 if (index1 == null) {
434 return 1;
435 }
436 if (index2 == null) {
437 return -1;
438 }
439 return Integer.compare(index1.getAsInt(), index2.getAsInt());
440 }
441 }
442
443 interface OperationInSession {
444 void run();
445 }
446
447 private class DeleteOp implements OperationInSession {
448
449 private MCRJSONCategory category;
450
451 private Response response;
452
453 DeleteOp(MCRJSONCategory category) {
454 this.category = category;
455 }
456
457 @Override
458 public void run() {
459 MCRCategoryID categoryID = category.getId();
460 if (CATEGORY_DAO.exist(categoryID)) {
461 if (categoryID.isRootID()
462 && !MCRAccessManager.checkPermission(categoryID.getRootID(), PERMISSION_DELETE)) {
463 throw new WebApplicationException(Status.UNAUTHORIZED);
464 }
465 CATEGORY_DAO.deleteCategory(categoryID);
466 setResponse(Response.status(Status.GONE).build());
467 } else {
468 setResponse(Response.notModified().build());
469 }
470 }
471
472 public Response getResponse() {
473 return response;
474 }
475
476 private void setResponse(Response response) {
477 this.response = response;
478 }
479
480 }
481
482 private class UpdateOp implements OperationInSession {
483
484 private MCRJSONCategory category;
485
486 private JsonObject jsonObject;
487
488 UpdateOp(MCRJSONCategory category, JsonObject jsonObject) {
489 this.category = category;
490 this.jsonObject = jsonObject;
491 }
492
493 @Override
494 public void run() {
495 MCRCategoryID mcrCategoryID = category.getId();
496 boolean isAdded = isAdded(jsonObject);
497 if (isAdded && MCRCategoryDAOFactory.getInstance().exist(mcrCategoryID)) {
498
499 throw new WebApplicationException(
500 Response.status(Status.CONFLICT).entity(buildJsonError("duplicateID", mcrCategoryID)).build());
501 }
502
503 MCRCategoryID newParentID = category.getParentID();
504 if (newParentID != null && !CATEGORY_DAO.exist(newParentID)) {
505 throw new WebApplicationException(Status.NOT_FOUND);
506 }
507 if (CATEGORY_DAO.exist(category.getId())) {
508 CATEGORY_DAO.setLabels(category.getId(), category.getLabels());
509 CATEGORY_DAO.setURI(category.getId(), category.getURI());
510 if (newParentID != null) {
511 CATEGORY_DAO.moveCategory(category.getId(), newParentID, category.getPositionInParent());
512 }
513 } else {
514 CATEGORY_DAO.addCategory(newParentID, category.asMCRImpl(), category.getPositionInParent());
515 }
516 }
517
518 }
519 }