View Javadoc
1   /*
2    * This file is part of ***  M y C o R e  ***
3    * See http://www.mycore.de/ for details.
4    *
5    * MyCoRe is free software: you can redistribute it and/or modify
6    * it under the terms of the GNU General Public License as published by
7    * the Free Software Foundation, either version 3 of the License, or
8    * (at your option) any later version.
9    *
10   * MyCoRe is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   * GNU General Public License for more details.
14   *
15   * You should have received a copy of the GNU General Public License
16   * along with MyCoRe.  If not, see <http://www.gnu.org/licenses/>.
17   */
18  
19  package org.mycore.frontend.cli;
20  
21  import java.io.File;
22  import java.io.FileNotFoundException;
23  import java.io.FileOutputStream;
24  import java.io.IOException;
25  import java.nio.file.FileVisitResult;
26  import java.nio.file.Files;
27  import java.nio.file.Path;
28  import java.nio.file.PathMatcher;
29  import java.nio.file.SimpleFileVisitor;
30  import java.nio.file.StandardCopyOption;
31  import java.nio.file.attribute.BasicFileAttributes;
32  import java.util.ArrayList;
33  import java.util.Collection;
34  import java.util.HashMap;
35  import java.util.List;
36  import java.util.Map;
37  import java.util.function.Predicate;
38  import java.util.stream.Collectors;
39  import java.util.stream.Stream;
40  
41  import javax.xml.transform.Transformer;
42  import javax.xml.transform.TransformerException;
43  import javax.xml.transform.stream.StreamResult;
44  
45  import org.apache.logging.log4j.LogManager;
46  import org.apache.logging.log4j.Logger;
47  import org.jdom2.Document;
48  import org.jdom2.Element;
49  import org.jdom2.JDOMException;
50  import org.jdom2.output.XMLOutputter;
51  import org.jdom2.transform.JDOMSource;
52  import org.mycore.access.MCRAccessInterface;
53  import org.mycore.access.MCRAccessException;
54  import org.mycore.access.MCRRuleAccessInterface;
55  import org.mycore.access.MCRAccessManager;
56  import org.mycore.common.MCRException;
57  import org.mycore.common.MCRPersistenceException;
58  import org.mycore.common.content.MCRContent;
59  import org.mycore.common.content.MCRPathContent;
60  import org.mycore.common.content.transformer.MCRXSLTransformer;
61  import org.mycore.common.events.MCREvent;
62  import org.mycore.common.events.MCREventManager;
63  import org.mycore.common.xml.MCRXMLHelper;
64  import org.mycore.datamodel.classifications2.MCRCategoryDAO;
65  import org.mycore.datamodel.classifications2.MCRCategoryDAOFactory;
66  import org.mycore.datamodel.classifications2.MCRCategoryID;
67  import org.mycore.datamodel.common.MCRActiveLinkException;
68  import org.mycore.datamodel.common.MCRXMLMetadataManager;
69  import org.mycore.datamodel.metadata.MCRDerivate;
70  import org.mycore.datamodel.metadata.MCRMetaClassification;
71  import org.mycore.datamodel.metadata.MCRMetaEnrichedLinkID;
72  import org.mycore.datamodel.metadata.MCRMetaEnrichedLinkIDFactory;
73  import org.mycore.datamodel.metadata.MCRMetaLinkID;
74  import org.mycore.datamodel.metadata.MCRMetadataManager;
75  import org.mycore.datamodel.metadata.MCRObject;
76  import org.mycore.datamodel.metadata.MCRObjectID;
77  import org.mycore.datamodel.niofs.MCRAbstractFileSystem;
78  import org.mycore.datamodel.niofs.MCRPath;
79  import org.mycore.datamodel.niofs.utils.MCRDerivateUtil;
80  import org.mycore.datamodel.niofs.utils.MCRTreeCopier;
81  import org.mycore.frontend.cli.annotation.MCRCommand;
82  import org.mycore.frontend.cli.annotation.MCRCommandGroup;
83  import org.xml.sax.SAXException;
84  import org.xml.sax.SAXParseException;
85  
86  /**
87   * Provides static methods that implement commands for the MyCoRe command line
88   * interface.
89   *
90   * @author Jens Kupferschmidt
91   * @author Frank Lützenkirchen
92   * @version $Revision$ $Date: 2010-10-29 15:17:03 +0200 (Fri, 29 Oct
93   *          2010) $
94   */
95  @MCRCommandGroup(name = "Derivate Commands")
96  public class MCRDerivateCommands extends MCRAbstractCommands {
97  
98      /** The logger */
99      private static Logger LOGGER = LogManager.getLogger(MCRDerivateCommands.class);
100 
101     /** The ACL interface */
102     private static final MCRAccessInterface ACCESS_IMPL = MCRAccessManager.getAccessImpl();
103 
104     /** Default transformer script */
105     public static final String DEFAULT_STYLE = "save-derivate.xsl";
106 
107     /** Static compiled transformer stylesheets */
108     private static final Map<String, Transformer> TRANSFORMER_CACHE = new HashMap<>();
109 
110     /**
111      * deletes all MCRDerivate from the datastore.
112      */
113     @MCRCommand(syntax = "delete all derivates", help = "Removes all derivates from the repository", order = 10)
114     public static List<String> deleteAllDerivates() {
115         return MCRCommandUtils.getIdsForType("derivate")
116             .map(id -> "delete derivate " + id)
117             .collect(Collectors.toList());
118     }
119 
120     /**
121      * Delete an MCRDerivate from the datastore.
122      *
123      * @param id
124      *            the ID of the MCRDerivate that should be deleted
125      * @throws MCRActiveLinkException
126      * @throws MCRAccessException see {@link MCRMetadataManager#delete(MCRDerivate)}
127      */
128     @MCRCommand(syntax = "delete derivate {0}",
129         help = "The command remove a derivate with the MCRObjectID {0}",
130         order = 30)
131     public static void delete(String id) throws MCRPersistenceException, MCRActiveLinkException, MCRAccessException {
132         MCRObjectID objectID = MCRObjectID.getInstance(id);
133         MCRMetadataManager.deleteMCRDerivate(objectID);
134         LOGGER.info("{} deleted.", objectID);
135     }
136 
137     /**
138      * Delete MCRDerivates form ID to ID from the datastore.
139      *
140      * @param idFrom
141      *            the start ID for deleting the MCRDerivate
142      * @param idTo
143      *            the stop ID for deleting the MCRDerivate
144      * @throws MCRAccessException see {@link MCRMetadataManager#delete(MCRDerivate)}
145      */
146     @MCRCommand(syntax = "delete derivate from {0} to {1}",
147         help = "The command remove derivates in the number range between the MCRObjectID {0} and {1}.",
148         order = 20)
149     public static List<String> delete(String idFrom, String idTo)
150         throws MCRPersistenceException, MCRActiveLinkException, MCRAccessException {
151         return MCRCommandUtils.getIdsFromIdToId(idFrom, idTo)
152             .map(id -> "delete derivate " + id)
153             .collect(Collectors.toList());
154     }
155 
156     /**
157      * Loads MCRDerivates from all XML files in a directory.
158      *
159      * @param directory
160      *            the directory containing the XML files
161      */
162     @MCRCommand(syntax = "load all derivates from directory {0}",
163         help = "Loads all MCRDerivates from the directory {0} to the system. " +
164             "If the numerical part of a provided ID is zero, a new ID with the same project ID and type is assigned.",
165         order = 60)
166     public static List<String> loadFromDirectory(String directory) {
167         return processFromDirectory(directory, false);
168     }
169 
170     /**
171      * Updates MCRDerivates from all XML files in a directory.
172      *
173      * @param directory
174      *            the directory containing the XML files
175      */
176     @MCRCommand(syntax = "update all derivates from directory {0}",
177         help = "The command update all derivates form the directory {0} in the system.",
178         order = 70)
179     public static List<String> updateFromDirectory(String directory) {
180         return processFromDirectory(directory, true);
181     }
182 
183     /**
184      * Loads or updates MCRDerivates from all XML files in a directory.
185      *
186      * @param directory
187      *            the directory containing the XML files
188      * @param update
189      *            if true, object will be updated, else object is created
190      */
191     private static List<String> processFromDirectory(String directory, boolean update) {
192         File dir = new File(directory);
193 
194         if (!dir.isDirectory()) {
195             LOGGER.warn("{} ignored, is not a directory.", directory);
196             return null;
197         }
198 
199         File[] list = dir.listFiles();
200 
201         if (list.length == 0) {
202             LOGGER.warn("No files found in directory {}", directory);
203             return null;
204         }
205 
206         List<String> cmds = new ArrayList<>();
207         for (File file : list) {
208             String name = file.getName();
209             if (!(name.endsWith(".xml") && name.contains("derivate"))) {
210                 continue;
211             }
212             name = name.substring(0, name.length() - 4); // remove ".xml"
213             File contentDir = new File(dir, name);
214             if (!(contentDir.exists() && contentDir.isDirectory())) {
215                 continue;
216             }
217             cmds.add((update ? "update" : "load") + " derivate from file " + file.getAbsolutePath());
218         }
219 
220         return cmds;
221     }
222 
223     /**
224      * Loads an MCRDerivates from an XML file.
225      *
226      * @param file
227      *            the location of the xml file
228      * @throws MCRAccessException see {@link MCRMetadataManager#create(MCRDerivate)}
229      */
230     @MCRCommand(syntax = "load derivate from file {0}",
231         help = "Loads an MCRDerivate from the file {0} to the system. " +
232             "If the numerical part of the provided ID is zero, a new ID with the same project ID and type is assigned.",
233         order = 40)
234     public static boolean loadFromFile(String file)
235         throws SAXParseException, IOException, MCRPersistenceException, MCRAccessException {
236         return loadFromFile(file, true);
237     }
238 
239     /**
240      * Loads an MCRDerivates from an XML file.
241      *
242      * @param file
243      *            the location of the xml file
244      * @param importMode
245      *            if true, servdates are taken from xml file
246      * @throws MCRAccessException see {@link MCRMetadataManager#create(MCRDerivate)}
247      * @throws MCRPersistenceException
248      */
249     public static boolean loadFromFile(String file, boolean importMode)
250         throws SAXParseException, IOException, MCRPersistenceException, MCRAccessException {
251         return processFromFile(new File(file), false, importMode);
252     }
253 
254     /**
255      * Updates an MCRDerivates from an XML file.
256      *
257      * @param file
258      *            the location of the xml file
259      * @throws MCRAccessException see {@link MCRMetadataManager#update(MCRDerivate)}
260      * @throws MCRPersistenceException
261      */
262 
263     @MCRCommand(syntax = "update derivate from file {0}",
264         help = "The command update a derivate form the file {0} in the system.",
265         order = 50)
266     public static boolean updateFromFile(String file)
267         throws SAXParseException, IOException, MCRPersistenceException, MCRAccessException {
268         return updateFromFile(file, true);
269     }
270 
271     /**
272      * Updates an MCRDerivates from an XML file.
273      *
274      * @param file
275      *            the location of the xml file
276      * @param importMode
277      *            if true, servdates are taken from xml file
278      * @throws MCRAccessException see {@link MCRMetadataManager#update(MCRDerivate)}
279      */
280     public static boolean updateFromFile(String file, boolean importMode)
281         throws SAXParseException, IOException, MCRPersistenceException, MCRAccessException {
282         return processFromFile(new File(file), true, importMode);
283     }
284 
285     /**
286      * Load or update an MCRDerivate from an XML file. If the numerical part of the contained ID is zero,
287      * a new ID with the same project ID and type is assigned.
288      *
289      * @param file
290      *            the location of the xml file
291      * @param update
292      *            if true, derivate will be updated, else derivate is created
293      * @param importMode
294      *            if true, servdates are taken from xml file
295      * @throws SAXParseException
296      * @throws MCRAccessException see {@link MCRMetadataManager#update(MCRDerivate)}
297      * @throws MCRPersistenceException
298      */
299     private static boolean processFromFile(File file, boolean update, boolean importMode) throws SAXParseException,
300         IOException, MCRPersistenceException, MCRAccessException {
301         if (!file.getName().endsWith(".xml")) {
302             LOGGER.warn("{} ignored, does not end with *.xml", file);
303             return false;
304         }
305 
306         if (!file.isFile()) {
307             LOGGER.warn("{} ignored, is not a file.", file);
308             return false;
309         }
310 
311         LOGGER.info("Reading file {} ...", file);
312 
313         MCRDerivate derivate = new MCRDerivate(file.toURI());
314         derivate.setImportMode(importMode);
315 
316         // Replace relative path with absolute path of files
317         if (derivate.getDerivate().getInternals() != null) {
318             String path = derivate.getDerivate().getInternals().getSourcePath();
319             if (path == null) {
320                 path = "";
321             } else {
322                 path = path.replace('/', File.separatorChar).replace('\\', File.separatorChar);
323             }
324             if (path.trim().length() <= 1) {
325                 // the path is the path name plus the name of the derivate -
326                 path = derivate.getId().toString();
327             }
328             File sPath = new File(path);
329 
330             if (!sPath.isAbsolute()) {
331                 // only change path to absolute path when relative
332                 String prefix = file.getParent();
333 
334                 if (prefix != null) {
335                     path = prefix + File.separator + path;
336                 }
337             }
338 
339             derivate.getDerivate().getInternals().setSourcePath(path);
340             LOGGER.info("Source path --> {}", path);
341         }
342 
343         if (update) {
344             MCRMetadataManager.update(derivate);
345             LOGGER.info("{} updated.", derivate.getId());
346         } else {
347             MCRMetadataManager.create(derivate);
348             LOGGER.info("{} loaded.", derivate.getId());
349         }
350 
351         return true;
352     }
353 
354     /**
355      * Export an MCRDerivate to a file named <em>MCRObjectID</em>.xml in a
356      * directory named <em>dirname</em> and store the derivate files in a
357      * nested directory named <em>MCRObjectID</em>. The IFS-Attribute of the
358      * derivate files aren't saved, for reloading purpose after deleting a
359      * derivate in the datastore
360      *
361      * @param id
362      *            the ID of the MCRDerivate to be save.
363      * @param dirname
364      *            the dirname to store the derivate
365      */
366     @MCRCommand(syntax = "show loadable derivate of {0} to directory {1}",
367         help = "The command store the derivate with the MCRObjectID {0} to the directory {1}, without ifs-metadata",
368         order = 130)
369     public static void show(String id, String dirname) {
370         exportWithStylesheet(id, id, dirname, "save");
371     }
372 
373     /**
374      * Export an MCRDerivate to a file named <em>MCRObjectID</em>.xml in a
375      * directory named <em>dirname</em> and store the derivate files in a
376      * nested directory named <em>MCRObjectID</em>. The method uses the
377      * converter stylesheet <em>style</em>.xsl.
378      *
379      * @param id
380      *            the ID of the MCRDerivate to be save.
381      * @param dirname
382      *            the dirname to store the derivate
383      * @param style
384      *            the type of the stylesheet
385      */
386     @MCRCommand(syntax = "export derivate {0} to directory {1} with stylesheet {2}",
387         help = "Stores the derivate with the MCRObjectID {0} to the directory {1}"
388             + " with the stylesheet {2}-derivate.xsl. For {2}, the default is xsl/save.",
389         order = 90)
390     public static void exportWithStylesheet(String id, String dirname, String style) {
391         exportWithStylesheet(id, id, dirname, style);
392     }
393 
394     /**
395      * Export any MCRDerivate's to files named <em>MCRObjectID</em>.xml in a
396      * directory named <em>dirname</em> and the derivate files in nested directories
397      * named <em>MCRObjectID</em>. Exporting starts with <em>fromID</em> and ends with <em>toID</em>.
398      * IDs that aren't found will be skipped. The method use the converter
399      * stylesheet <em>style</em>.xsl.
400      *
401      * @param fromID
402      *            the ID of the MCRObject from be save.
403      * @param toID
404      *            the ID of the MCRObject to be save.
405      * @param dirname
406      *            the filename to store the object
407      * @param style
408      *            the type of the stylesheet
409      */
410 
411     @MCRCommand(syntax = "export derivates from {0} to {1} to directory {2} with stylesheet {3}",
412         help = "Stores all derivates with MCRObjectID's between {0} and {1} to the directory {2}"
413             + " with the stylesheet {3}-derivate.xsl. For {3}, the default is xsl/save.",
414         order = 80)
415     public static void exportWithStylesheet(String fromID, String toID, String dirname, String style) {
416         // check fromID and toID
417         MCRObjectID fid = null;
418         MCRObjectID tid = null;
419 
420         try {
421             fid = MCRObjectID.getInstance(fromID);
422         } catch (Exception ex) {
423             LOGGER.error("FromID : {}", ex.getMessage());
424 
425             return;
426         }
427 
428         try {
429             tid = MCRObjectID.getInstance(toID);
430         } catch (Exception ex) {
431             LOGGER.error("ToID : {}", ex.getMessage());
432 
433             return;
434         }
435 
436         // check dirname
437         File dir = new File(dirname);
438 
439         if (dir.isFile()) {
440             LOGGER.error("{} is not a dirctory.", dirname);
441 
442             return;
443         }
444 
445         Transformer trans = getTransformer(style != null ? style + "-derivate" : null);
446 
447         int k = 0;
448 
449         try {
450             for (int i = fid.getNumberAsInteger(); i < tid.getNumberAsInteger() + 1; i++) {
451 
452                 exportDerivate(dir, trans, MCRObjectID.formatID(fid.getProjectId(), fid.getTypeId(), i));
453 
454                 k++;
455             }
456         } catch (Exception ex) {
457             LOGGER.error(ex.getMessage());
458             LOGGER.error("Exception while store file or objects to {}", dir.getAbsolutePath(), ex);
459 
460             return;
461         }
462 
463         LOGGER.info("{} Object's stored under {}.", k, dir.getAbsolutePath());
464     }
465 
466     /**
467      * This command looks for all derivates in the application and builds export
468      * commands.
469      *
470      * @param dirname
471      *            the filename to store the object
472      * @param style
473      *            the type of the stylesheet
474      * @return a list of export commands for each derivate
475      */
476     @MCRCommand(syntax = "export all derivates to directory {0} with stylesheet {1}",
477         help = "Stores all derivates to the directory {0} with the stylesheet {1}-derivate.xsl."
478             + " For {1}, the default is xsl/save.",
479         order = 100)
480     public static List<String> exportAllDerivatesWithStylesheet(String dirname, String style) {
481         return MCRCommandUtils.getIdsForType("derivate")
482             .map(id -> "export derivate " + id + " to directory " + dirname + " with stylesheet " + style)
483             .collect(Collectors.toList());
484     }
485 
486     /**
487      * This command looks for all derivates starting with project name in the
488      * application and builds export commands.
489      *
490      * @param dirname
491      *            the filename to store the object
492      * @param style
493      *            the type of the stylesheet
494      * @return a list of export commands for derivates with project name
495      */
496     @MCRCommand(syntax = "export all derivates of project {0} to directory {1} with stylesheet {2}",
497         help = "Stores all derivates of project {0} to the directory {1} with the stylesheet {2}-derivate.xsl."
498             + " For {2}, the default is xsl/save.",
499         order = 110)
500     public static List<String> exportAllDerivatesOfProjectWithStylesheet(String project, String dirname, String style) {
501         return MCRCommandUtils.getIdsForProjectAndType(project, "derivate")
502             .map(id -> "export derivate " + id + " to directory " + dirname + " with stylesheet " + style)
503             .collect(Collectors.toList());
504     }
505 
506     /**
507      * @param dir
508      * @param trans
509      * @param nid
510      * @throws FileNotFoundException
511      * @throws TransformerException
512      * @throws IOException
513      */
514     private static void exportDerivate(File dir, Transformer trans, String nid)
515         throws TransformerException, IOException {
516         // store the XML file
517         Document xml = null;
518         MCRDerivate obj;
519 
520         MCRObjectID derivateID = MCRObjectID.getInstance(nid);
521         try {
522             obj = MCRMetadataManager.retrieveMCRDerivate(derivateID);
523             String path = obj.getDerivate().getInternals().getSourcePath();
524             // reset from the absolute to relative path, for later reload
525             LOGGER.info("Old Internal Path ====>{}", path);
526             obj.getDerivate().getInternals().setSourcePath(nid);
527             LOGGER.info("New Internal Path ====>{}", nid);
528             // add ACL's
529             if (ACCESS_IMPL instanceof MCRRuleAccessInterface) {
530                 Collection<String> l = ((MCRRuleAccessInterface) ACCESS_IMPL).getPermissionsForID(nid);
531                 for (String permission : l) {
532                     Element rule = ((MCRRuleAccessInterface) ACCESS_IMPL).getRule(nid, permission);
533                     obj.getService().addRule(permission, rule);
534                 }
535             }
536 
537             // build JDOM
538             xml = obj.createXML();
539 
540         } catch (MCRException ex) {
541             LOGGER.warn("Could not read {}, continue with next ID", nid);
542             return;
543         }
544         File xmlOutput = new File(dir, derivateID + ".xml");
545         FileOutputStream out = new FileOutputStream(xmlOutput);
546         dir = new File(dir, derivateID.toString());
547 
548         if (trans != null) {
549             trans.setParameter("dirname", dir.getPath());
550             StreamResult sr = new StreamResult(out);
551             trans.transform(new JDOMSource(xml), sr);
552         } else {
553             new XMLOutputter().output(xml, out);
554             out.flush();
555             out.close();
556         }
557 
558         LOGGER.info("Object {} stored under {}.", nid, xmlOutput);
559 
560         // store the derivate file under dirname
561         if (!dir.isDirectory()) {
562             dir.mkdir();
563         }
564         MCRPath rootPath = MCRPath.getPath(derivateID.toString(), "/");
565         Files.walkFileTree(rootPath, new MCRTreeCopier(rootPath, dir.toPath()));
566 
567         LOGGER.info("Derivate {} saved under {} and {}.", nid, dir, xmlOutput);
568     }
569 
570     /**
571      * This method searches for the stylesheet <em>style</em>.xsl and builds the transformer. Default is
572      * <em>save-derivate.xsl</em> if no stylesheet is given or the stylesheet couldn't be resolved.
573      *
574      * @param style
575      *            the name of the style to be used when resolving the stylesheet
576      * @return the transformer
577      */
578     private static Transformer getTransformer(String style) {
579         return MCRCommandUtils.getTransformer(style, DEFAULT_STYLE, TRANSFORMER_CACHE);
580     }
581 
582     /**
583      * The method start the repair the content search index for all derivates.
584      */
585     @MCRCommand(syntax = "repair derivate search of type derivate",
586         help = "The command read the Content store and reindex the derivate search stores.",
587         order = 140)
588     public static List<String> repairDerivateSearch() {
589         LOGGER.info("Start the repair for type derivate.");
590         return MCRCommandUtils.getIdsForType("derivate")
591             .map(id -> "repair derivate search of ID " + id)
592             .collect(Collectors.toList());
593     }
594 
595     /**
596      * Repairing the content search index for all derivates in project {0}.
597      * 
598      * @param project
599      *            the project part of a MCRObjectID e.g. *DocPortal*_derivate
600      */
601     @MCRCommand(syntax = "repair derivate search of project {0}",
602         help = "Reads the Content store for project {0} and reindexes the derivate search stores.",
603         order = 141)
604     public static List<String> repairDerivateSearchForBase(String project) {
605         LOGGER.info("Start the repair for project {}.", project);
606         return MCRCommandUtils.getIdsForProjectAndType(project, "derivate")
607             .map(id -> "repair derivate search of ID " + id)
608             .collect(Collectors.toList());
609     }
610 
611     /**
612      * The method start the repair the content search index for one.
613      *
614      * @param id
615      *            the MCRObjectID as String
616      */
617     @MCRCommand(syntax = "repair derivate search of ID {0}",
618         help = "The command read the Content store for MCRObjectID {0} and reindex the derivate search store.",
619         order = 150)
620     public static void repairDerivateSearchForID(String id) throws IOException {
621         LOGGER.info("Start the repair for the ID {}", id);
622         doForChildren(MCRPath.getPath(id, "/"));
623         LOGGER.info("Repaired {}", id);
624     }
625 
626     /**
627      * This is a recursive method to start an event handler for each file.
628      *
629      * @param rootPath
630      *            a IFS node (file or directory)
631      */
632     private static void doForChildren(Path rootPath) throws IOException {
633         Files.walkFileTree(rootPath, new SimpleFileVisitor<Path>() {
634             @Override
635             public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
636                 // handle events
637                 MCREvent evt = new MCREvent(MCREvent.ObjectType.PATH, MCREvent.EventType.REPAIR);
638                 evt.put(MCREvent.PATH_KEY, file);
639                 evt.put(MCREvent.FILEATTR_KEY, attrs);
640                 MCREventManager.instance().handleEvent(evt);
641                 LOGGER.debug("repaired file {}", file);
642                 return super.visitFile(file, attrs);
643             }
644 
645         });
646     }
647 
648     /**
649      * The method start the repair the content search index for all derivates.
650      */
651     @MCRCommand(syntax = "synchronize all derivates",
652         help = "The command read each derivate and synchronize the xlink:label with "
653             + "the derivate entry of the mycoreobject.",
654         order = 160)
655     public static List<String> synchronizeAllDerivates() {
656         LOGGER.info("Start the synchronization for derivates.");
657         return MCRCommandUtils.getIdsForType("derivate")
658             .map(id -> "synchronize derivate with ID " + id)
659             .collect(Collectors.toList());
660     }
661 
662     /**
663      * Links the given derivate to the given object.
664      */
665     @MCRCommand(syntax = "link derivate {0} to {1}",
666         help = "links the given derivate {0} to the given mycore object {1}",
667         order = 180)
668     public static void linkDerivateToObject(String derivateId, String objectId) throws Exception {
669         if (derivateId == null || objectId == null) {
670             LOGGER.error("Either derivate id or object id is null. Derivate={}, object={}", derivateId, objectId);
671             return;
672         }
673         MCRObjectID derID = MCRObjectID.getInstance(derivateId);
674         MCRObjectID objID = MCRObjectID.getInstance(objectId);
675 
676         if (!MCRMetadataManager.exists(objID)) {
677             throw new Exception("The object with id " + objID + " does not exist");
678         }
679 
680         if (!MCRMetadataManager.exists(derID)) {
681             throw new Exception("The derivate with id " + derID + " does not exist");
682         }
683 
684         MCRDerivate derObj = MCRMetadataManager.retrieveMCRDerivate(derID);
685         MCRMetaLinkID oldDerivateToObjectLink = derObj.getDerivate().getMetaLink();
686         MCRObjectID oldOwnerId = oldDerivateToObjectLink.getXLinkHrefID();
687 
688         /* set link to new parent in the derivate object */
689         LOGGER.info("Setting {} as parent for derivate {}", objID, derID);
690         derObj.getDerivate().getMetaLink()
691             .setReference(objID, oldDerivateToObjectLink.getXLinkLabel(), oldDerivateToObjectLink.getXLinkTitle());
692         MCRMetadataManager.update(derObj);
693 
694         /* set link to derivate in the new parent */
695         MCRObject oldOwner = MCRMetadataManager.retrieveMCRObject(oldOwnerId);
696         List<MCRMetaEnrichedLinkID> derivates = oldOwner.getStructure().getDerivates();
697         MCRMetaLinkID oldObjectToDerivateLink = null;
698         for (MCRMetaLinkID derivate : derivates) {
699             if (derivate.getXLinkHrefID().equals(derID)) {
700                 oldObjectToDerivateLink = derivate;
701             }
702         }
703         if (oldObjectToDerivateLink == null) {
704             oldObjectToDerivateLink = new MCRMetaLinkID();
705         }
706         LOGGER.info("Linking derivate {} to {}", derID, objID);
707         MCRMetaEnrichedLinkID derivateLink = MCRMetaEnrichedLinkIDFactory.getInstance().getDerivateLink(derObj);
708         MCRMetadataManager.addOrUpdateDerivateToObject(objID, derivateLink);
709 
710         /* removing link from old parent */
711         boolean flag = oldOwner.getStructure().removeDerivate(derID);
712         LOGGER.info("Unlinking derivate {} from object {}. Success={}", derID, oldOwnerId, flag);
713         MCRMetadataManager.fireUpdateEvent(oldOwner);
714     }
715 
716     /**
717      * Check the object links in derivates of MCR base ID for existing.
718      * It looks to the XML store on the disk to get all object IDs.
719      *
720      * @param baseId
721      *            the base part of a MCRObjectID e.g. DocPortal_derivate
722      */
723     @MCRCommand(syntax = "check object entries in derivates for base {0}",
724         help = "check in all derivates of MCR base ID {0} for existing linked objects",
725         order = 400)
726     public static void checkObjectsInDerivates(String baseId) throws IOException {
727         if (baseId == null || baseId.length() == 0) {
728             LOGGER.error("Base ID missed for check object entries in derivates for base {0}");
729             return;
730         }
731         int projectPartPosition = baseId.indexOf('_');
732         if (projectPartPosition == -1) {
733             LOGGER.error("The given base ID {} has not the syntax of project_type", baseId);
734             return;
735         }
736         MCRXMLMetadataManager mgr = MCRXMLMetadataManager.instance();
737         List<String> idList = mgr.listIDsForBase(baseId.substring(0, projectPartPosition + 1) + "derivate");
738         int counter = 0;
739         int maxresults = idList.size();
740         for (String derid : idList) {
741             counter++;
742             LOGGER.info("Processing dataset {} from {} with ID: {}", counter, maxresults, derid);
743             // get from data
744             MCRObjectID mcrderid = MCRObjectID.getInstance(derid);
745             MCRDerivate der = MCRMetadataManager.retrieveMCRDerivate(mcrderid);
746             MCRObjectID objid = der.getOwnerID();
747             if (!mgr.exists(objid)) {
748                 LOGGER.error("   !!! Missing object {} in database for derivate ID {}", objid, mcrderid);
749             }
750         }
751         LOGGER.info("Check done for {} entries", Integer.toString(counter));
752     }
753 
754     @MCRCommand(syntax = "transform xml matching file name pattern {0} in derivate {1} with stylesheet {2}",
755         help = "Finds all files in Derivate {1} which match the pattern {0} "
756             + "(the complete path with regex: or glob:*.xml syntax) and transforms them with stylesheet {2}")
757     public static void transformXMLMatchingPatternWithStylesheet(String pattern, String derivate, String stylesheet)
758         throws IOException {
759         MCRXSLTransformer transformer = new MCRXSLTransformer(stylesheet);
760         MCRPath derivateRoot = MCRPath.getPath(derivate, "/");
761         PathMatcher matcher = derivateRoot.getFileSystem().getPathMatcher(pattern);
762 
763         Files.walkFileTree(derivateRoot, new SimpleFileVisitor<Path>() {
764             @Override
765             public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
766                 throws IOException {
767                 if (matcher.matches(file)) {
768                     LOGGER.info("The file {} matches the pattern {}", file, pattern);
769                     MCRContent sourceContent = new MCRPathContent(file);
770 
771                     MCRContent resultContent = transformer.transform(sourceContent);
772                     try {
773                         Document source = sourceContent.asXML();
774                         Document result = resultContent.asXML();
775                         LOGGER.info("Transforming complete!");
776 
777                         if (!MCRXMLHelper.deepEqual(source, result)) {
778                             LOGGER.info("Writing result..");
779                             resultContent.sendTo(file, StandardCopyOption.REPLACE_EXISTING);
780                         } else {
781                             LOGGER.info("Result and Source is the same..");
782                         }
783 
784                     } catch (JDOMException | SAXException e) {
785                         throw new IOException("Error while processing file : " + file, e);
786                     }
787 
788                 }
789 
790                 return FileVisitResult.CONTINUE;
791             }
792         });
793 
794     }
795 
796     @MCRCommand(syntax = "set main file of {0} to {1}",
797         help = "Sets the main file of the derivate with the id {0} to "
798             + "the file with the path {1}")
799     public static void setMainFile(final String derivateIDString, final String filePath) throws MCRAccessException {
800         if (!MCRObjectID.isValid(derivateIDString)) {
801             LOGGER.error("{} is not valid. ", derivateIDString);
802             return;
803         }
804 
805         // check for derivate exist
806         final MCRObjectID derivateID = MCRObjectID.getInstance(derivateIDString);
807         if (!MCRMetadataManager.exists(derivateID)) {
808             LOGGER.error("{} does not exist!", derivateIDString);
809             return;
810         }
811 
812         // remove leading slash
813         String cleanPath = filePath;
814         if (filePath.startsWith(String.valueOf(MCRAbstractFileSystem.SEPARATOR))) {
815             cleanPath = filePath.substring(1);
816         }
817 
818         // check for file exist
819         final MCRPath path = MCRPath.getPath(derivateID.toString(), cleanPath);
820         if (!Files.exists(path)) {
821             LOGGER.error("File {} does not exist!", cleanPath);
822             return;
823         }
824 
825         final MCRDerivate derivate = MCRMetadataManager.retrieveMCRDerivate(derivateID);
826         derivate.getDerivate().getInternals().setMainDoc(cleanPath);
827         MCRMetadataManager.update(derivate);
828         LOGGER.info("The main file of {} is now '{}'!", derivateIDString, cleanPath);
829     }
830 
831     @MCRCommand(syntax = "rename files from derivate {0} with {1} to {2}",
832         help = "Renames multiple files in one Derivate with the ID {0} the given RegEx pattern {1} and the replacement"
833             + " {2}. You can try out your pattern with the command: 'test rename file {0} with {1} to {2}'.")
834     public static void renameFiles(String derivate, String pattern, String newName)
835         throws IOException {
836         MCRDerivateUtil.renameFiles(derivate, pattern, newName);
837     }
838 
839     @MCRCommand(syntax = "test rename file {0} with {1} to {2}",
840         help = "Tests the rename pattern {1} on one file {0} and replaces it with {2}, so you can try the rename"
841             + " before renaming all files. This command does not change any files.")
842     public static void testRenameFile(String filename, String pattern, String newName) {
843         MCRDerivateUtil.testRenameFile(filename, pattern, newName);
844     }
845 
846     @MCRCommand(syntax = "set order of derivate {0} to {1}",
847         help = "Sets the order of derivate {0} to the number {1} see also MCR-2003")
848     public static void setOrderOfDerivate(String derivateIDStr, String orderStr) throws MCRAccessException {
849         final int order = Integer.parseInt(orderStr);
850 
851         final MCRObjectID derivateID = MCRObjectID.getInstance(derivateIDStr);
852 
853         if (!MCRMetadataManager.exists(derivateID)) {
854             throw new MCRException("The derivate " + derivateIDStr + " does not exist!");
855         }
856 
857         final MCRDerivate derivate = MCRMetadataManager.retrieveMCRDerivate(derivateID);
858         derivate.setOrder(order);
859         MCRMetadataManager.update(derivate);
860 
861     }
862 
863     @MCRCommand(syntax = "set classification of derivate {0} to {1}",
864         help = "Sets the classification of derivate {0} to the categories {1} (comma separated) "
865             + "of classification 'derivate_types' or any fully qualified category, removing any previous definition.")
866     public static void setClassificationOfDerivate(String derivateIDStr, String categoriesCommaList)
867         throws MCRAccessException {
868         final MCRCategoryDAO categoryDAO = MCRCategoryDAOFactory.getInstance();
869         final List<MCRCategoryID> derivateTypes = Stream.of(categoriesCommaList.split(","))
870             .map(String::trim)
871             .map(category -> category.contains(":") ? MCRCategoryID.fromString(category)
872                 : new MCRCategoryID("derivate_types", category))
873             .collect(Collectors.toList());
874 
875         final String nonExistingCategoriesCommaList = derivateTypes.stream()
876             .filter(Predicate.not(categoryDAO::exist))
877             .map(MCRCategoryID::getID)
878             .collect(Collectors.joining(", "));
879         if (!nonExistingCategoriesCommaList.isEmpty()) {
880             throw new MCRPersistenceException("Categories do not exist: " + nonExistingCategoriesCommaList);
881         }
882 
883         final MCRObjectID derivateID = MCRObjectID.getInstance(derivateIDStr);
884         final MCRDerivate derivate = MCRMetadataManager.retrieveMCRDerivate(derivateID);
885         derivate.getDerivate().getClassifications().clear();
886         derivate.getDerivate().getClassifications()
887             .addAll(
888                 derivateTypes.stream()
889                     .map(categoryID -> new MCRMetaClassification("classification", 0, null, categoryID.getRootID(),
890                         categoryID.getID()))
891                     .collect(Collectors.toList()));
892         MCRMetadataManager.update(derivate);
893     }
894 
895 }