1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.mycore.frontend.cli;
20
21 import java.io.File;
22 import java.io.FileOutputStream;
23 import java.io.IOException;
24 import java.io.OutputStream;
25 import java.io.UncheckedIOException;
26 import java.text.MessageFormat;
27 import java.text.SimpleDateFormat;
28 import java.util.ArrayList;
29 import java.util.Arrays;
30 import java.util.Collections;
31 import java.util.HashMap;
32 import java.util.List;
33 import java.util.Locale;
34 import java.util.Map;
35 import java.util.Map.Entry;
36 import java.util.Objects;
37 import java.util.Optional;
38 import java.util.concurrent.atomic.AtomicInteger;
39 import java.util.function.Function;
40 import java.util.function.Predicate;
41 import java.util.stream.Collectors;
42 import java.util.stream.Stream;
43
44 import javax.xml.parsers.ParserConfigurationException;
45 import javax.xml.transform.OutputKeys;
46 import javax.xml.transform.Source;
47 import javax.xml.transform.Transformer;
48 import javax.xml.transform.TransformerException;
49 import javax.xml.transform.TransformerFactory;
50 import javax.xml.transform.sax.SAXSource;
51 import javax.xml.transform.stream.StreamResult;
52 import javax.xml.transform.stream.StreamSource;
53
54 import org.apache.commons.lang3.function.FailableBiConsumer;
55 import org.apache.logging.log4j.LogManager;
56 import org.apache.logging.log4j.Logger;
57 import org.jdom2.Document;
58 import org.jdom2.JDOMException;
59 import org.jdom2.filter.Filters;
60 import org.jdom2.transform.JDOMResult;
61 import org.jdom2.transform.JDOMSource;
62 import org.jdom2.xpath.XPathExpression;
63 import org.jdom2.xpath.XPathFactory;
64 import org.mycore.access.MCRAccessException;
65 import org.mycore.backend.jpa.MCREntityManagerProvider;
66 import org.mycore.common.MCRConstants;
67 import org.mycore.common.MCRException;
68 import org.mycore.common.MCRPersistenceException;
69 import org.mycore.common.MCRSessionMgr;
70 import org.mycore.common.MCRStreamUtils;
71 import org.mycore.common.config.MCRConfiguration2;
72 import org.mycore.common.content.MCRBaseContent;
73 import org.mycore.common.content.MCRContent;
74 import org.mycore.common.content.MCRJDOMContent;
75 import org.mycore.common.content.MCRSourceContent;
76 import org.mycore.common.content.transformer.MCRContentTransformer;
77 import org.mycore.common.content.transformer.MCRContentTransformerFactory;
78 import org.mycore.common.xml.MCREntityResolver;
79 import org.mycore.common.xml.MCRLayoutTransformerFactory;
80 import org.mycore.common.xml.MCRURIResolver;
81 import org.mycore.common.xml.MCRXMLHelper;
82 import org.mycore.common.xml.MCRXMLParserFactory;
83 import org.mycore.common.xsl.MCRErrorListener;
84 import org.mycore.datamodel.common.MCRAbstractMetadataVersion;
85 import org.mycore.datamodel.common.MCRActiveLinkException;
86 import org.mycore.datamodel.common.MCRLinkTableManager;
87 import org.mycore.datamodel.common.MCRXMLMetadataManager;
88 import org.mycore.datamodel.metadata.MCRBase;
89 import org.mycore.datamodel.metadata.MCRDerivate;
90 import org.mycore.datamodel.metadata.MCRMetaEnrichedLinkID;
91 import org.mycore.datamodel.metadata.MCRMetaLinkID;
92 import org.mycore.datamodel.metadata.MCRMetadataManager;
93 import org.mycore.datamodel.metadata.MCRObject;
94 import org.mycore.datamodel.metadata.MCRObjectID;
95 import org.mycore.datamodel.metadata.MCRObjectUtils;
96 import org.mycore.frontend.cli.annotation.MCRCommand;
97 import org.mycore.frontend.cli.annotation.MCRCommandGroup;
98 import org.mycore.tools.MCRTopologicalSort;
99 import org.xml.sax.SAXException;
100 import org.xml.sax.SAXParseException;
101 import org.xml.sax.XMLReader;
102
103 import jakarta.persistence.EntityManager;
104 import jakarta.persistence.TypedQuery;
105
106
107
108
109
110
111
112
113
114
115
116
117
118 @MCRCommandGroup(name = "Object Commands")
119 public class MCRObjectCommands extends MCRAbstractCommands {
120
121 private static final String EXPORT_OBJECT_TO_DIRECTORY_WITH_STYLESHEET_COMMAND
122 = "export object {0} to directory {1} with stylesheet {2}";
123
124
125 private static final Logger LOGGER = LogManager.getLogger(MCRObjectCommands.class);
126
127
128 public static final String DEFAULT_STYLE = "save-object.xsl";
129
130
131 private static final Map<String, Transformer> TRANSFORMER_CACHE = new HashMap<>();
132
133 public static void setSelectedObjectIDs(List<String> selected) {
134 LOGGER.info("{} objects selected", selected.size());
135 MCRSessionMgr.getCurrentSession().put("mcrSelectedObjects", selected);
136 }
137
138 @SuppressWarnings("unchecked")
139 public static List<String> getSelectedObjectIDs() {
140 final List<String> list = (List<String>) MCRSessionMgr.getCurrentSession().get("mcrSelectedObjects");
141 if (list == null) {
142 return Collections.EMPTY_LIST;
143 }
144 return list;
145 }
146
147 @MCRCommand(
148 syntax = "select objects with xpath {0}",
149 help = "Selects MCRObjects with XPath {0}, if that XPath evaluates to a non-empty result list" +
150 " (this command may take a while, use with care in case of a large number of objects)",
151 order = 10)
152 public static void selectObjectsWithXpath(String xPath) throws Exception {
153
154 XPathExpression<Object> xPathExpression = XPathFactory
155 .instance()
156 .compile(xPath, Filters.fpassthrough(), null, MCRConstants.getStandardNamespaces());
157
158 List<String> selectedObjectIds = MCRXMLMetadataManager
159 .instance()
160 .listIDs()
161 .stream()
162 .filter(id -> !id.contains("_derivate_"))
163 .map(MCRObjectID::getInstance)
164 .map(MCRMetadataManager::retrieveMCRObject)
165 .filter(mcrObject -> !xPathExpression.evaluate(mcrObject.createXML()).isEmpty())
166 .map(MCRObject::getId)
167 .map(MCRObjectID::toString)
168 .collect(Collectors.toList());
169
170 MCRObjectCommands.setSelectedObjectIDs(selectedObjectIds);
171
172 }
173
174 @MCRCommand(
175 syntax = "select descendants of object {0}",
176 help = "Selects MCRObjects that are descendants of {0} (children, grandchildren, ...) and {0} itself.",
177 order = 15)
178 public static void selectDescendantObjects(String id) throws Exception {
179 List<String> descendants = new ArrayList<String>();
180 if (MCRMetadataManager.exists(MCRObjectID.getInstance(id))) {
181 fillWithDescendants(id, descendants);
182 }
183 MCRObjectCommands.setSelectedObjectIDs(descendants);
184 }
185
186 private static void fillWithDescendants(String mcrObjID, List<String> descendants) {
187 descendants.add(mcrObjID);
188
189 for (String childID : MCRLinkTableManager.instance().getSourceOf(mcrObjID,
190 MCRLinkTableManager.ENTRY_TYPE_PARENT)) {
191 descendants.add(childID);
192 fillWithDescendants(childID, descendants);
193 }
194 }
195
196
197
198
199
200
201
202 @MCRCommand(
203 syntax = "delete all objects of type {0}",
204 help = "Removes MCRObjects of type {0}.",
205 order = 20)
206 public static List<String> deleteAllObjects(String type) {
207 return MCRCommandUtils.getIdsForType(type)
208 .map(id -> "delete object " + id)
209 .collect(Collectors.toList());
210 }
211
212
213
214
215
216 @MCRCommand(
217 syntax = "delete all objects in topological order",
218 help = "Removes all MCRObjects in topological order.",
219 order = 25)
220 public static List<String> deleteTopologicalAllObjects() {
221 final List<String> objectIds = MCRXMLMetadataManager.instance().listIDs();
222 String[] objects = objectIds.stream().filter(id -> !id.contains("_derivate_")).toArray(String[]::new);
223 MCRTopologicalSort<String> ts = new MCRTopologicalSort<>();
224 MCRTopologicalSort.prepareMCRObjects(ts, objects);
225 int[] order = ts.doTopoSort();
226
227 List<String> cmds = new ArrayList<>(objectIds.size());
228 if (order != null) {
229
230 for (int o = order.length - 1; o >= 0; o--) {
231 cmds.add("delete object " + ts.getNodeName(order[o]));
232 }
233 }
234 return cmds;
235 }
236
237 @MCRCommand(
238 syntax = "check for circles in topological order",
239 help = "Checks if there are circular dependencies in the parent child relationships of MCRObjects.",
240 order = 25)
241 public static void checkForCircles() {
242 final List<String> objectIds = MCRXMLMetadataManager.instance().listIDs();
243 String[] objects = objectIds.stream().filter(id -> !id.contains("_derivate_")).toArray(String[]::new);
244 MCRTopologicalSort<String> ts = new MCRTopologicalSort<>();
245 MCRTopologicalSort.prepareMCRObjects(ts, objects);
246 int[] order = ts.doTopoSort();
247 if (order != null) {
248 LOGGER.info("OK - No circles detected!");
249 }
250 }
251
252
253
254
255
256
257
258
259
260
261 @MCRCommand(
262 syntax = "delete object {0}",
263 help = "Removes a MCRObject with the MCRObjectID {0}",
264 order = 40)
265 public static void delete(String id) throws MCRPersistenceException, MCRActiveLinkException, MCRAccessException {
266 MCRObjectID mcrId = MCRObjectID.getInstance(id);
267 MCRMetadataManager.deleteMCRObject(mcrId);
268 LOGGER.info("{} deleted.", mcrId);
269 }
270
271
272
273
274
275
276
277
278
279 @MCRCommand(
280 syntax = "clear links of object {0}",
281 help = "removes all links of this object, including parent/child relations"
282 + " and all MetaLinkID's in the metadata section",
283 order = 45)
284 public static void clearLinks(String id) throws MCRPersistenceException {
285 final MCRObjectID mcrId = MCRObjectID.getInstance(id);
286 AtomicInteger counter = new AtomicInteger(0);
287 MCRObjectUtils.removeLinks(mcrId).forEach(linkedObject -> {
288 try {
289 LOGGER.info("removing link '{}' of '{}'.", mcrId, linkedObject.getId());
290 MCRMetadataManager.update(linkedObject);
291 counter.incrementAndGet();
292 } catch (Exception exc) {
293 LOGGER.error(String.format(Locale.ROOT, "Unable to update object '%s'", linkedObject), exc);
294 }
295 });
296 LOGGER.info("{} link(s) removed of {}.", counter.get(), mcrId);
297 }
298
299
300
301
302
303
304
305
306
307
308 @MCRCommand(
309 syntax = "delete object from {0} to {1}",
310 help = "Removes MCRObjects in the number range between the MCRObjectID {0} and {1}.",
311 order = 30)
312 public static List<String> deleteFromTo(String idFrom, String idTo) {
313 return MCRCommandUtils.getIdsFromIdToId(idFrom, idTo)
314 .map(id -> "delete object " + id)
315 .collect(Collectors.toList());
316 }
317
318
319
320
321
322
323
324 @MCRCommand(
325 syntax = "load all objects in topological order from directory {0}",
326 help = "Loads all MCRObjects form the directory {0} to the system "
327 + "respecting the order of parents and children.",
328 order = 75)
329 public static List<String> loadTopologicalFromDirectory(String directory) {
330 return processFromDirectory(true, directory, false);
331 }
332
333
334
335
336
337
338
339 @MCRCommand(
340 syntax = "update all objects in topological order from directory {0}",
341 help = "Updates all MCRObjects from the directory {0} in the system "
342 + "respecting the order of parents and children.",
343 order = 95)
344 public static List<String> updateTopologicalFromDirectory(String directory) {
345 return processFromDirectory(true, directory, true);
346 }
347
348
349
350
351
352
353
354 @MCRCommand(
355 syntax = "load all objects from directory {0}",
356 help = "Loads all MCRObjects from the directory {0} to the system. " +
357 "If the numerical part of a provided ID is zero, a new ID with the same project ID and type is assigned.",
358 order = 70)
359 public static List<String> loadFromDirectory(String directory) {
360 return processFromDirectory(false, directory, false);
361 }
362
363
364
365
366
367
368
369 @MCRCommand(
370 syntax = "update all objects from directory {0}",
371 help = "Updates all MCRObjects from the directory {0} in the system.",
372 order = 90)
373 public static List<String> updateFromDirectory(String directory) {
374 return processFromDirectory(false, directory, true);
375 }
376
377
378
379
380
381
382
383
384
385
386
387 private static List<String> processFromDirectory(boolean topological, String directory, boolean update) {
388 File dir = new File(directory);
389
390 if (!dir.isDirectory()) {
391 LOGGER.warn("{} ignored, is not a directory.", directory);
392 return null;
393 }
394
395 String[] list = dir.list();
396 if (list == null || list.length == 0) {
397 LOGGER.warn("No files found in directory {}", directory);
398 return null;
399 }
400
401 Predicate<String> isMetaXML = file -> file.endsWith(".xml") && !file.contains("derivate");
402 Function<String, String> cmdFromFile = file -> (update ? "update" : "load") + " object from file "
403 + new File(dir, file).getAbsolutePath();
404 if (topological) {
405 MCRTopologicalSort<String> ts = new MCRTopologicalSort<>();
406 MCRTopologicalSort.prepareData(ts, list, dir.toPath());
407 return Optional.ofNullable(ts.doTopoSort())
408 .map(Arrays::stream)
409 .map(is -> is.mapToObj(i -> list[i]))
410 .orElse(Stream.empty())
411 .filter(isMetaXML)
412 .map(cmdFromFile)
413 .collect(Collectors.toList());
414 } else {
415 return Arrays.stream(list)
416 .filter(isMetaXML)
417 .sorted()
418 .map(cmdFromFile)
419 .collect(Collectors.toList());
420 }
421 }
422
423
424
425
426
427
428
429
430 @MCRCommand(
431 syntax = "load object from file {0}",
432 help = "Loads an MCRObject from the file {0} to the system. " +
433 "If the numerical part of the provided ID is zero, a new ID with the same project ID and type is assigned.",
434 order = 60)
435 public static boolean loadFromFile(String file) throws MCRException, SAXParseException,
436 IOException, MCRAccessException {
437 return loadFromFile(file, true);
438 }
439
440
441
442
443
444
445
446
447
448
449 public static boolean loadFromFile(String file, boolean importMode) throws MCRException,
450 SAXParseException, IOException, MCRAccessException {
451 return processFromFile(new File(file), false, importMode);
452 }
453
454
455
456
457
458
459
460
461 @MCRCommand(
462 syntax = "update object from file {0}",
463 help = "Updates a MCRObject from the file {0} in the system.",
464 order = 80)
465 public static boolean updateFromFile(String file) throws MCRException, SAXParseException,
466 IOException, MCRAccessException {
467 return updateFromFile(file, true);
468 }
469
470
471
472
473
474
475
476
477
478
479 public static boolean updateFromFile(String file, boolean importMode) throws MCRException,
480 SAXParseException, IOException, MCRAccessException {
481 return processFromFile(new File(file), true, importMode);
482 }
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501 private static boolean processFromFile(File file, boolean update, boolean importMode)
502 throws MCRException, SAXParseException, IOException, MCRAccessException {
503 if (!file.getName().endsWith(".xml")) {
504 LOGGER.warn("{} ignored, does not end with *.xml", file);
505 return false;
506 }
507
508 if (!file.isFile()) {
509 LOGGER.warn("{} ignored, is not a file.", file);
510 return false;
511 }
512
513 LOGGER.info("Reading file {} ...", file);
514
515 MCRObject mcrObject = new MCRObject(file.toURI());
516 if (mcrObject.hasParent()) {
517 MCRObjectID parentID = mcrObject.getStructure().getParentID();
518 if (!MCRMetadataManager.exists(mcrObject.getStructure().getParentID())) {
519 throw new MCRException("The parent object " + parentID + "does not exist for " + mcrObject + ".");
520 }
521 }
522 mcrObject.setImportMode(importMode);
523 LOGGER.debug("Label --> {}", mcrObject.getLabel());
524
525 if (update) {
526 MCRMetadataManager.update(mcrObject);
527 LOGGER.info("{} updated.", mcrObject.getId());
528 } else {
529 MCRMetadataManager.create(mcrObject);
530 LOGGER.info("{} loaded.", mcrObject.getId());
531 }
532
533 return true;
534 }
535
536
537
538
539
540
541
542 public static void showNextID(String base) {
543
544 try {
545 LOGGER.info("The next free ID is {}", MCRObjectID.getNextFreeId(base));
546 } catch (MCRException ex) {
547 LOGGER.error(ex.getMessage());
548 }
549 }
550
551
552
553
554
555
556
557 public static void showLastID(String base) {
558 try {
559 LOGGER.info("The last used ID is {}", MCRObjectID.getLastID(base));
560 } catch (MCRException ex) {
561 LOGGER.error(ex.getMessage());
562 }
563 }
564
565
566
567
568
569
570
571
572
573
574
575
576 @MCRCommand(
577 syntax = EXPORT_OBJECT_TO_DIRECTORY_WITH_STYLESHEET_COMMAND,
578 help = "Stores the MCRObject with the MCRObjectID {0} to the directory {1} with the stylesheet {2}-object.xsl."
579 + " For {2}, the default is xsl/save.",
580 order = 110)
581 public static void exportWithStylesheet(String id, String dirname, String style) {
582 exportWithStylesheet(id, id, dirname, style);
583 }
584
585
586
587
588
589
590
591
592
593
594
595
596 @MCRCommand(
597 syntax = "export object {0} to directory {1} with transformer {2}",
598 help = "Stores the MCRObject with the MCRObjectID {0} to the directory {1} with the transformer {2}.",
599 order = 110)
600 public static void exportWithTransformer(String id, String dirname, String transname) {
601 exportWithTransformer(id, id, dirname, transname);
602 }
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618 @MCRCommand(
619 syntax = "export objects from {0} to {1} to directory {2} with stylesheet {3}",
620 help = "Stores all MCRObjects with MCRObjectID's between {0} and {1} to the directory {2} "
621 + "with the stylesheet {3}-object.xsl. For {3}, the default is xsl/save.",
622 order = 100)
623 public static void exportWithStylesheet(String fromID, String toID, String dirname, String style) {
624 Transformer transformer = getTransformer(style != null ? style + "-object" : null);
625 exportWith(fromID, toID, dirname, (content, out) -> {
626 StreamResult sr = new StreamResult(out);
627 JDOMSource doc = new JDOMSource(MCRXMLParserFactory.getNonValidatingParser().parseXML(content));
628 transformer.transform(doc, sr);
629 });
630 }
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646 @MCRCommand(
647 syntax = "export objects from {0} to {1} to directory {2} with transformer {3}",
648 help = "Stores all MCRObjects with MCRObjectID's between {0} and {1} to the directory {2} "
649 + "with the transformer {3}.",
650 order = 100)
651 public static void exportWithTransformer(String fromID, String toID, String dirname, String transname) {
652 MCRContentTransformer transformer = MCRContentTransformerFactory.getTransformer(transname);
653 exportWith(fromID, toID, dirname, transformer::transform);
654 }
655
656 private static void exportWith(String fromID, String toID, String dirname,
657 FailableBiConsumer<MCRContent, OutputStream, Exception> trans) {
658 MCRObjectID fid, tid;
659
660
661 try {
662 fid = MCRObjectID.getInstance(fromID);
663 tid = MCRObjectID.getInstance(toID);
664 } catch (Exception ex) {
665 LOGGER.error("FromID : {}", ex.getMessage());
666 return;
667 }
668
669 File dir = new File(dirname);
670 if (!dir.isDirectory()) {
671 LOGGER.error("{} is not a directory.", dirname);
672 return;
673 }
674
675 int k = 0;
676 try {
677 for (int i = fid.getNumberAsInteger(); i < tid.getNumberAsInteger() + 1; i++) {
678 String id = MCRObjectID.formatID(fid.getProjectId(), fid.getTypeId(), i);
679 if (!MCRMetadataManager.exists(MCRObjectID.getInstance(id))) {
680 continue;
681 }
682 if (!exportMCRObject(dir, trans, id)) {
683 continue;
684 }
685 k++;
686 }
687 } catch (Exception ex) {
688 LOGGER.error(ex.getMessage());
689 LOGGER.error("Exception while store file to {}", dir.getAbsolutePath());
690 return;
691 }
692 LOGGER.info("{} Object's stored under {}.", k, dir.getAbsolutePath());
693 }
694
695
696
697
698
699
700
701
702
703
704
705
706 @MCRCommand(
707 syntax = "export all objects of type {0} to directory {1} with stylesheet {2}",
708 help = "Stores all MCRObjects of type {0} to directory {1} with the stylesheet {2}-object.xsl."
709 + "For {2}, the default is xsl/save.",
710 order = 120)
711 public static List<String> exportAllObjectsOfTypeWithStylesheet(String type, String dirname, String style) {
712 List<String> objectIds = MCRXMLMetadataManager.instance().listIDsOfType(type);
713 return buildExportCommands(new File(dirname), style, objectIds);
714 }
715
716
717
718
719
720
721
722
723
724
725
726
727 @MCRCommand(
728 syntax = "export all objects of base {0} to directory {1} with stylesheet {2}",
729 help = "Stores all MCRObjects of base {0} to directory {1} with the stylesheet {2}-object.xsl."
730 + " For {2}, the default is xsl/save.",
731 order = 130)
732 public static List<String> exportAllObjectsOfBaseWithStylesheet(String base, String dirname, String style) {
733 List<String> objectIds = MCRXMLMetadataManager.instance().listIDsForBase(base);
734 return buildExportCommands(new File(dirname), style, objectIds);
735 }
736
737 private static List<String> buildExportCommands(File dir, String style, List<String> objectIds) {
738 if (dir.isFile()) {
739 LOGGER.error("{} is not a dirctory.", dir);
740 return Collections.emptyList();
741 }
742 List<String> cmds = new ArrayList<>(objectIds.size());
743 for (String id : objectIds) {
744 String command = new MessageFormat(EXPORT_OBJECT_TO_DIRECTORY_WITH_STYLESHEET_COMMAND, Locale.ROOT)
745 .format(new Object[] { id, dir.getAbsolutePath(), style });
746 cmds.add(command);
747 }
748 return cmds;
749 }
750
751
752
753
754
755
756
757
758
759 private static Transformer getTransformer(String style) {
760 return MCRCommandUtils.getTransformer(style, DEFAULT_STYLE, TRANSFORMER_CACHE);
761 }
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787 private static boolean exportMCRObject(File dir, FailableBiConsumer<MCRContent, OutputStream, Exception> trans,
788 String nid) throws IOException, MCRException {
789 MCRContent content;
790 try {
791
792 content = MCRXMLMetadataManager.instance().retrieveContent(MCRObjectID.getInstance(nid));
793 } catch (MCRException ex) {
794 return false;
795 }
796
797 File xmlOutput = new File(dir, nid + ".xml");
798
799 if (trans != null) {
800 FileOutputStream out = new FileOutputStream(xmlOutput);
801 try {
802 trans.accept(content, out);
803 } catch (UncheckedIOException e) {
804 throw e.getCause();
805 } catch (IOException | RuntimeException e) {
806 throw e;
807 } catch (Exception e) {
808 throw new MCRException(e);
809 }
810 } else {
811 content.sendTo(xmlOutput);
812 }
813 LOGGER.info("Object {} saved to {}.", nid, xmlOutput.getCanonicalPath());
814 return true;
815 }
816
817
818
819
820
821
822
823 @MCRCommand(
824 syntax = "get next ID for base {0}",
825 help = "Returns the next free MCRObjectID for the ID base {0}.",
826 order = 150)
827 public static void getNextID(String base) {
828 try {
829 LOGGER.info(MCRObjectID.getNextFreeId(base));
830 } catch (MCRException ex) {
831 LOGGER.error(ex.getMessage());
832 }
833 }
834
835
836
837
838
839
840
841
842 @MCRCommand(
843 syntax = "get last ID for base {0}",
844 help = "Returns the last used MCRObjectID for the ID base {0}.",
845 order = 140)
846 public static void getLastID(String base) {
847 LOGGER.info(MCRObjectID.getLastID(base));
848 }
849
850
851
852
853 @MCRCommand(
854 syntax = "list selected",
855 help = "Prints the id of selected objects",
856 order = 190)
857 public static void listSelected() {
858 LOGGER.info("List selected MCRObjects");
859 if (getSelectedObjectIDs().isEmpty()) {
860 LOGGER.info("No Resultset to work with, use command \"select objects with solr query {0} in core {1}\"" +
861 " or \"select objects with xpath {0}\" to build one");
862 return;
863 }
864 StringBuilder out = new StringBuilder();
865 for (String id : getSelectedObjectIDs()) {
866 out.append(id).append(" ");
867 }
868 LOGGER.info(out.toString());
869 }
870
871
872
873
874
875
876
877 @MCRCommand(
878 syntax = "list revisions of {0}",
879 help = "List revisions of MCRObject.",
880 order = 260)
881 public static void listRevisions(String id) {
882 MCRObjectID mcrId = MCRObjectID.getInstance(id);
883 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.ROOT);
884 try {
885 StringBuilder log = new StringBuilder("Revisions:\n");
886 List<? extends MCRAbstractMetadataVersion<?>> revisions = MCRXMLMetadataManager.instance()
887 .listRevisions(mcrId);
888 for (MCRAbstractMetadataVersion<?> revision : revisions) {
889 log.append(revision.getRevision()).append(" ");
890 log.append(revision.getType()).append(" ");
891 log.append(sdf.format(revision.getDate())).append(" ");
892 log.append(revision.getUser());
893 log.append("\n");
894 }
895 LOGGER.info(log.toString());
896 } catch (Exception exc) {
897 LOGGER.error("While print revisions.", exc);
898 }
899 }
900
901
902
903
904
905
906
907
908
909
910 @MCRCommand(
911 syntax = "restore {0} to revision {1}",
912 help = "Restores the selected MCRObject to the selected revision.",
913 order = 270)
914 public static void restoreToRevision(String id, String revision) {
915 LOGGER.info("Try to restore object {} with revision {}", id, revision);
916 MCRObjectID mcrId = MCRObjectID.getInstance(id);
917 try {
918 MCRObjectUtils.restore(mcrId, revision);
919 LOGGER.info("Object {} successfully restored!", id);
920 } catch (Exception exc) {
921 LOGGER.error("While retrieving object {} with revision {}", id, revision, exc);
922 }
923 }
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965 @MCRCommand(
966 syntax = "xslt {0} with file {1}",
967 help = "transforms a mycore object {0} with the given file or URI {1}",
968 order = 280)
969 public static void xslt(String objectId, String xslFilePath) throws IOException, JDOMException, SAXException,
970 TransformerException, MCRPersistenceException, MCRAccessException,
971 ParserConfigurationException {
972 xslt(objectId, xslFilePath, false);
973 }
974
975
976
977
978
979
980
981
982
983
984
985
986
987 @MCRCommand(
988 syntax = "force xslt {0} with file {1}",
989 help = "transforms a mycore object {0} with the given file or URI {1}. Overwrites anyway if original "
990 + "root name and result root name are different.",
991 order = 285)
992 public static void forceXSLT(String objectId, String xslFilePath) throws IOException, JDOMException, SAXException,
993 TransformerException, MCRPersistenceException, MCRAccessException,
994 ParserConfigurationException {
995 xslt(objectId, xslFilePath, true);
996 }
997
998 private static void xslt(String objectId, String xslFilePath, boolean force) throws IOException, JDOMException,
999 SAXException, TransformerException, MCRPersistenceException, MCRAccessException, ParserConfigurationException {
1000 File xslFile = new File(xslFilePath);
1001 Source xslSource;
1002 if (xslFile.exists()) {
1003 xslSource = new StreamSource(xslFile);
1004 } else {
1005 xslSource = MCRURIResolver.instance().resolve(xslFilePath, null);
1006 if (xslSource == null) {
1007 xslSource = new StreamSource(xslFilePath);
1008 }
1009 }
1010 MCRSourceContent style = new MCRSourceContent(xslSource);
1011 MCRObjectID mcrId = MCRObjectID.getInstance(objectId);
1012 Document document = MCRXMLMetadataManager.instance().retrieveXML(mcrId);
1013
1014 TransformerFactory transformerFactory = TransformerFactory.newInstance();
1015 transformerFactory.setErrorListener(MCRErrorListener.getInstance());
1016 transformerFactory.setURIResolver(MCRURIResolver.instance());
1017 XMLReader xmlReader = MCRXMLParserFactory.getNonValidatingParser().getXMLReader();
1018 xmlReader.setEntityResolver(MCREntityResolver.instance());
1019 SAXSource styleSource = new SAXSource(xmlReader, style.getInputSource());
1020 Transformer transformer = transformerFactory.newTransformer(styleSource);
1021 for (Entry<String, String> property : MCRConfiguration2.getPropertiesMap().entrySet()) {
1022 transformer.setParameter(property.getKey(), property.getValue());
1023 }
1024 transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
1025 transformer.setOutputProperty(OutputKeys.INDENT, "no");
1026 JDOMResult result = new JDOMResult();
1027 transformer.transform(new JDOMSource(document), result);
1028 Document resultDocument = Objects.requireNonNull(result.getDocument(), "Could not get transformation result");
1029
1030 String originalName = document.getRootElement().getName();
1031 String resultName = resultDocument.getRootElement().getName();
1032 if (!force && !originalName.equals(resultName)) {
1033 LOGGER.error("{}: root name '{}' does not match result name '{}'.", objectId, originalName, resultName);
1034 return;
1035 }
1036
1037
1038 if (MCRXMLHelper.deepEqual(document, resultDocument)) {
1039 return;
1040 }
1041 switch (resultName) {
1042 case MCRObject.ROOT_NAME:
1043 MCRMetadataManager.update(new MCRObject(resultDocument));
1044 break;
1045 case MCRDerivate.ROOT_NAME:
1046 MCRMetadataManager.update(new MCRDerivate(resultDocument));
1047 break;
1048 default:
1049 LOGGER.error("Unable to transform '{}' because unknown result root name '{}'.", objectId, resultName);
1050 break;
1051 }
1052 }
1053
1054 @MCRCommand(syntax = "transform object {0} with transformer {1}",
1055 help = "Transforms the object with the id {0} using the transformer with the id {1} and" +
1056 " updates the object with the result")
1057 public static void transformObject(String objectIDStr, String transformer)
1058 throws IOException, JDOMException, SAXException, MCRAccessException {
1059 MCRObjectID objectID = MCRObjectID.getInstance(objectIDStr);
1060 if (!MCRMetadataManager.exists(objectID)) {
1061 LOGGER.error("The object {} does not exist!", objectID);
1062 return;
1063 }
1064
1065 MCRObject mcrObject = MCRMetadataManager.retrieveMCRObject(objectID);
1066
1067 MCRBaseContent baseContent = new MCRBaseContent(mcrObject);
1068 MCRContent result = new MCRLayoutTransformerFactory().getTransformer(transformer).transform(baseContent);
1069 Document resultXML = result.asXML();
1070 MCRObject resulting = new MCRObject(resultXML);
1071
1072 if (!MCRXMLHelper.deepEqual(mcrObject.createXML(), resultXML)) {
1073 LOGGER.info("The Object changed with the transformation. Execute Update.");
1074 MCRMetadataManager.update(resulting);
1075 } else {
1076 LOGGER.info("The Object did not change with the transformation. Skip Update.");
1077 }
1078 }
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089 @MCRCommand(
1090 syntax = "set parent of {0} to {1}",
1091 help = "replaces a parent of an object (first parameter) to the given new one (second parameter)",
1092 order = 300)
1093 public static void replaceParent(String sourceId, String newParentId) throws MCRPersistenceException,
1094 MCRAccessException {
1095
1096 MCRObject sourceMCRObject = MCRMetadataManager.retrieveMCRObject(MCRObjectID.getInstance(sourceId));
1097
1098 MCRObjectID oldParentId = sourceMCRObject.getStructure().getParentID();
1099 MCRObjectID newParentObjectID = MCRObjectID.getInstance(newParentId);
1100
1101 if (newParentObjectID.equals(oldParentId)) {
1102 LOGGER.info("Object {} is already child of {}", sourceId, newParentId);
1103 return;
1104 }
1105
1106 MCRObject oldParentMCRObject = null;
1107
1108 if (oldParentId != null) {
1109 try {
1110 oldParentMCRObject = MCRMetadataManager.retrieveMCRObject(oldParentId);
1111 } catch (Exception exc) {
1112 LOGGER.error("Unable to get old parent object {}, its probably deleted.", oldParentId, exc);
1113 }
1114 }
1115
1116
1117 LOGGER.info("Setting link in \"{}\" to parent \"{}\"", sourceId, newParentObjectID);
1118 MCRMetaLinkID parentLinkId = new MCRMetaLinkID("parent", 0);
1119 parentLinkId.setReference(newParentObjectID, null, null);
1120 sourceMCRObject.getStructure().setParent(parentLinkId);
1121
1122 if (oldParentMCRObject != null) {
1123
1124 LOGGER.info("Remove child \"{}\" in old parent \"{}\"", sourceId, oldParentId);
1125 oldParentMCRObject.getStructure().removeChild(sourceMCRObject.getId());
1126
1127 LOGGER.info("Update old parent \"{}\n", oldParentId);
1128 MCRMetadataManager.update(oldParentMCRObject);
1129 }
1130
1131 LOGGER.info("Update \"{}\" in datastore (saving new link)", sourceId);
1132 MCRMetadataManager.update(sourceMCRObject);
1133 if (LOGGER.isDebugEnabled()) {
1134 LOGGER.debug("Structure: {}", sourceMCRObject.getStructure().isValid());
1135 LOGGER.debug("Object: {}", sourceMCRObject.isValid());
1136 }
1137 }
1138
1139
1140
1141
1142
1143
1144
1145
1146 @MCRCommand(
1147 syntax = "check derivate entries in objects for base {0}",
1148 help = "check in all objects with MCR base ID {0} for existing linked derivates",
1149 order = 400)
1150 public static void checkDerivatesInObjects(String baseId) {
1151 if (baseId == null || baseId.length() == 0) {
1152 LOGGER.error("Base ID missed for check derivate entries in objects for base {0}");
1153 return;
1154 }
1155 MCRXMLMetadataManager mgr = MCRXMLMetadataManager.instance();
1156 List<String> idList = mgr.listIDsForBase(baseId);
1157 int counter = 0;
1158 int maxresults = idList.size();
1159 for (String objid : idList) {
1160 counter++;
1161 LOGGER.info("Processing dataset {} from {} with ID: {}", counter, maxresults, objid);
1162
1163 MCRObjectID mcrobjid = MCRObjectID.getInstance(objid);
1164 MCRObject obj = MCRMetadataManager.retrieveMCRObject(mcrobjid);
1165 List<MCRMetaEnrichedLinkID> derivateEntries = obj.getStructure().getDerivates();
1166 for (MCRMetaLinkID derivateEntry : derivateEntries) {
1167 String derid = derivateEntry.getXLinkHref();
1168 if (!mgr.exists(MCRObjectID.getInstance(derid))) {
1169 LOGGER.error(" !!! Missing derivate {} in database for base ID {}", derid, baseId);
1170 }
1171 }
1172 }
1173 LOGGER.info("Check done for {} entries", Integer.toString(counter));
1174 }
1175
1176
1177
1178
1179
1180
1181
1182 @MCRCommand(
1183 syntax = "validate object schema for base {0}",
1184 help = "Validates all objects of base {0} against their specified schema.",
1185 order = 401)
1186 public static List<String> validateObjectsOfBase(String baseID) {
1187 return MCRCommandUtils.getIdsForBaseId(baseID)
1188 .map(id -> "validate object schema for ID " + id)
1189 .collect(Collectors.toList());
1190 }
1191
1192
1193
1194
1195
1196
1197
1198 @MCRCommand(
1199 syntax = "validate object schema for type {0}",
1200 help = "Validates all object of type {0} against their specified schema.",
1201 order = 402)
1202 public static List<String> validateObjectsOfType(String type) {
1203 return MCRCommandUtils.getIdsForType(type)
1204 .map(id -> "validate object schema for ID " + id)
1205 .collect(Collectors.toList());
1206 }
1207
1208
1209
1210
1211
1212
1213
1214 @MCRCommand(
1215 syntax = "validate object schema for ID {0}",
1216 help = "Checks if object {0} validates against its specified schema.",
1217 order = 404)
1218 public static void validateObject(String objectID) {
1219 validateObjectWithTransformer(objectID, null);
1220 }
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230 @MCRCommand(
1231 syntax = "validate object schema for ID {0} after transformer {1}",
1232 help = "Checks if object {0} validates against its specified schema, "
1233 + "after being transformed through {1}.xsl.",
1234 order = 403)
1235 public static void validateObjectWithTransformer(String objectID, String transformerType) {
1236 if (objectID == null || objectID.length() == 0) {
1237 throw new MCRException("ID of an object required to check its schema validity.");
1238 }
1239 LOGGER.info("validate object schema for ID " + objectID);
1240 Transformer trafo = null;
1241 if (transformerType != null) {
1242
1243
1244 trafo = getTransformer(transformerType);
1245 LOGGER.debug("Transformer {} has been loaded.", transformerType);
1246 }
1247 MCRObjectID objID = MCRObjectID.getInstance(objectID);
1248 try {
1249 doValidateObjectAgainstSchema(objID, trafo);
1250 LOGGER.info("Object {} successfully validated.", objectID);
1251 } catch (MCRException e) {
1252 LOGGER.error("Object {} failed its validation!", objectID);
1253 throw e;
1254 }
1255 }
1256
1257 private static void doValidateObjectAgainstSchema(MCRObjectID objID, Transformer trans) {
1258
1259
1260
1261 MCRXMLMetadataManager mgr = MCRXMLMetadataManager.instance();
1262 Document doc;
1263 try {
1264 doc = mgr.retrieveXML(objID);
1265 } catch (IOException | JDOMException | SAXException e) {
1266 throw new MCRException(
1267 "Object " + objID.toString() + " could not be retrieved, unable to validate against schema!", e);
1268 }
1269 if (doc == null) {
1270 throw new MCRException("Could not get object " + objID.toString() + " from XML store");
1271 }
1272 MCRObject object = new MCRObject(doc);
1273 try {
1274 object.validate();
1275 } catch (MCRException e) {
1276 throw new MCRException(
1277 "Object " + objID.toString()
1278 + " does not pass basic self-validation, unable to validate against schema!",
1279 e);
1280 }
1281 String schema = object.getSchema();
1282 if (schema == null) {
1283 throw new MCRException(
1284 "Object " + objID.toString() + " has no assigned schema, unable to validate against it!");
1285 }
1286 if (trans != null) {
1287 JDOMResult res = new JDOMResult();
1288 try {
1289 trans.transform(new JDOMSource(doc), res);
1290 doc = Objects.requireNonNull(res.getDocument(), "Could not get transformation result");
1291 } catch (TransformerException | MCRException | NullPointerException e) {
1292 throw new MCRException("Object " + objID.toString()
1293 + " could not be transformed, unable to validate against schema!",
1294 e);
1295 }
1296 LOGGER.info("Object {} successfully transformed.", objID.toString());
1297 }
1298 try {
1299 MCRXMLParserFactory.getValidatingParser().parseXML(new MCRJDOMContent(doc));
1300 } catch (MCRException | SAXException e) {
1301 throw new MCRException("Object " + objID.toString() + " failed to parse against its schema!", e);
1302 }
1303 }
1304
1305 @MCRCommand(
1306 syntax = "execute for selected {0}",
1307 help = "Calls the given command multiple times for all selected objects." +
1308 " The replacement is defined by an {x}.E.g. 'execute for selected set" +
1309 " parent of {x} to myapp_container_00000001'",
1310 order = 450)
1311 public static List<String> executeForSelected(String command) {
1312 if (!command.contains("{x}")) {
1313 LOGGER.info("No replacement defined. Use the {x} variable in order to execute your command with all "
1314 + "selected objects.");
1315 return Collections.emptyList();
1316 }
1317 return getSelectedObjectIDs().stream()
1318 .map(objID -> command.replaceAll("\\{x}", objID))
1319 .collect(Collectors.toList());
1320 }
1321
1322
1323
1324
1325
1326
1327
1328 @MCRCommand(
1329 syntax = "repair metadata search of type {0}",
1330 help = "Scans the metadata store for MCRObjects of type {0} and restores them in the search store.",
1331 order = 170)
1332 public static List<String> repairMetadataSearch(String type) {
1333 LOGGER.info("Start the repair for type {}", type);
1334 return MCRCommandUtils.getIdsForType(type)
1335 .map(id -> "repair metadata search of ID " + id)
1336 .collect(Collectors.toList());
1337 }
1338
1339
1340
1341
1342
1343
1344
1345 @MCRCommand(
1346 syntax = "repair metadata search of base {0}",
1347 help = "Scans the metadata store for MCRObjects of base {0} and restores them in the search store.",
1348 order = 171)
1349 public static List<String> repairMetadataSearchForBase(String baseID) {
1350 LOGGER.info("Start the repair for base {}", baseID);
1351 return MCRCommandUtils.getIdsForBaseId(baseID)
1352 .map(id -> "repair metadata search of ID " + id)
1353 .collect(Collectors.toList());
1354 }
1355
1356
1357
1358
1359
1360
1361
1362 @MCRCommand(
1363 syntax = "repair metadata search of ID {0}",
1364 help = "Retrieves the MCRObject with the MCRObjectID {0} and restores it in the search store.",
1365 order = 180)
1366 public static void repairMetadataSearchForID(String id) {
1367 LOGGER.info("Start the repair for the ID {}", id);
1368 if (!MCRObjectID.isValid(id)) {
1369 LOGGER.error("The String {} is not a MCRObjectID.", id);
1370 return;
1371 }
1372 MCRObjectID mid = MCRObjectID.getInstance(id);
1373 MCRBase obj = MCRMetadataManager.retrieve(mid);
1374 MCRMetadataManager.fireRepairEvent(obj);
1375 LOGGER.info("Repaired {}", mid);
1376 }
1377
1378 @MCRCommand(
1379 syntax = "repair mcrlinkhref table",
1380 help = "Runs through the whole table and checks for already deleted mcr objects and deletes them.",
1381 order = 185)
1382 public static void repairMCRLinkHrefTable() {
1383 EntityManager em = MCREntityManagerProvider.getCurrentEntityManager();
1384 TypedQuery<String> fromQuery = em.createQuery("SELECT DISTINCT m.key.mcrfrom FROM MCRLINKHREF m", String.class);
1385 TypedQuery<String> toQuery = em.createQuery("SELECT DISTINCT m.key.mcrto FROM MCRLINKHREF m", String.class);
1386 String query = "DELETE FROM MCRLINKHREF m WHERE m.key.mcrfrom IN (:invalidIds) or m.key.mcrto IN (:invalidIds)";
1387
1388 try (Stream<String> fromStream = fromQuery.getResultStream()) {
1389 try (Stream<String> toStream = toQuery.getResultStream()) {
1390 List<String> invalidIds = Stream.concat(fromStream, toStream)
1391 .distinct()
1392 .filter(MCRObjectID::isValid)
1393 .map(MCRObjectID::getInstance)
1394 .filter(MCRStreamUtils.not(MCRMetadataManager::exists))
1395 .map(MCRObjectID::toString)
1396 .collect(Collectors.toList());
1397
1398 em.createQuery(query).setParameter("invalidIds", invalidIds).executeUpdate();
1399 }
1400 }
1401 }
1402
1403 @MCRCommand(
1404 syntax = "rebuild mcrlinkhref table for object {0}",
1405 help = "Rebuilds (remove/create) all entries of the link href table for the given object id.",
1406 order = 188)
1407 public static void rebuildMCRLinkHrefTableForObject(String objectId) {
1408 MCRLinkTableManager.instance().update(MCRObjectID.getInstance(objectId));
1409 }
1410
1411 }