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.datamodel.metadata;
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.nio.file.Files;
26  import java.nio.file.Path;
27  import java.nio.file.Paths;
28  import java.util.Collection;
29  import java.util.Date;
30  import java.util.List;
31  import java.util.Optional;
32  import java.util.concurrent.TimeUnit;
33  import java.util.function.BiConsumer;
34  import java.util.stream.Collectors;
35  
36  import org.apache.logging.log4j.LogManager;
37  import org.apache.logging.log4j.Logger;
38  import org.jdom2.Document;
39  import org.jdom2.JDOMException;
40  import org.mycore.access.MCRAccessException;
41  import org.mycore.access.MCRAccessManager;
42  import org.mycore.common.MCRCache;
43  import org.mycore.common.MCRCache.ModifiedHandle;
44  import org.mycore.common.MCRException;
45  import org.mycore.common.MCRPersistenceException;
46  import org.mycore.common.events.MCREvent;
47  import org.mycore.common.events.MCREventManager;
48  import org.mycore.datamodel.common.MCRActiveLinkException;
49  import org.mycore.datamodel.common.MCRLinkTableManager;
50  import org.mycore.datamodel.common.MCRMarkManager;
51  import org.mycore.datamodel.common.MCRMarkManager.Operation;
52  import org.mycore.datamodel.common.MCRXMLMetadataManager;
53  import org.mycore.datamodel.metadata.share.MCRMetadataShareAgent;
54  import org.mycore.datamodel.metadata.share.MCRMetadataShareAgentFactory;
55  import org.mycore.datamodel.niofs.MCRPath;
56  import org.mycore.datamodel.niofs.utils.MCRRecursiveDeleter;
57  import org.mycore.datamodel.niofs.utils.MCRTreeCopier;
58  import org.xml.sax.SAXException;
59  
60  import jakarta.persistence.PersistenceException;
61  
62  /**
63   * Delivers persistence operations for {@link MCRObject} and {@link MCRDerivate} .
64   * 
65   * @author Thomas Scheffler (yagee)
66   * @since 2.0.92
67   */
68  public final class MCRMetadataManager {
69  
70      private static final Logger LOGGER = LogManager.getLogger();
71  
72      private static final MCRCache<MCRObjectID, MCRObjectID> DERIVATE_OBJECT_MAP = new MCRCache<>(10000,
73          "derivate objectid cache");
74  
75      private static final MCRCache<MCRObjectID, List<MCRObjectID>> OBJECT_DERIVATE_MAP = new MCRCache<>(10000,
76          "derivate objectid cache");
77  
78      private static final MCRXMLMetadataManager XML_MANAGER = MCRXMLMetadataManager.instance();
79  
80      private MCRMetadataManager() {
81  
82      }
83  
84      /**
85       * Returns the MCRObjectID of the object containing derivate with the given ID.
86       * 
87       * @param derivateID
88       *            derivateID
89       * @param expire
90       *            when should lastModified information expire
91       * @return null if derivateID has no object referenced
92       * @see #getDerivateIds(MCRObjectID, long, TimeUnit)
93       */
94      public static MCRObjectID getObjectId(final MCRObjectID derivateID, final long expire, TimeUnit unit) {
95          ModifiedHandle modifiedHandle = XML_MANAGER.getLastModifiedHandle(derivateID, expire, unit);
96          MCRObjectID mcrObjectID = null;
97          try {
98              mcrObjectID = DERIVATE_OBJECT_MAP.getIfUpToDate(derivateID, modifiedHandle);
99          } catch (IOException e) {
100             LOGGER.warn("Could not determine last modified timestamp of derivate {}", derivateID);
101         }
102         if (mcrObjectID != null) {
103             return mcrObjectID;
104         }
105         //one cheap db query
106         Collection<String> list = MCRLinkTableManager.instance().getSourceOf(derivateID,
107             MCRLinkTableManager.ENTRY_TYPE_DERIVATE);
108         if (!(list == null || list.isEmpty())) {
109             mcrObjectID = MCRObjectID.getInstance(list.iterator().next());
110         } else {
111             //one expensive process
112             if (MCRMetadataManager.exists(derivateID)) {
113                 mcrObjectID = MCRMetadataManager.retrieveMCRDerivate(derivateID).getOwnerID();
114             }
115         }
116         if (mcrObjectID == null) {
117             return null;
118         }
119         DERIVATE_OBJECT_MAP.put(derivateID, mcrObjectID);
120         return mcrObjectID;
121     }
122 
123     /**
124      * Returns a list of MCRObjectID of the derivates contained in the object with the given ID.
125      * 
126      * @param objectId
127      *            objectId
128      * @param expire
129      *            when should lastModified information expire
130      * @return null if object with objectId does not exist
131      * @see #getObjectId(MCRObjectID, long, TimeUnit)
132      */
133     public static List<MCRObjectID> getDerivateIds(final MCRObjectID objectId, final long expire, final TimeUnit unit) {
134         ModifiedHandle modifiedHandle = XML_MANAGER.getLastModifiedHandle(objectId, expire, unit);
135         List<MCRObjectID> derivateIds = null;
136         try {
137             derivateIds = OBJECT_DERIVATE_MAP.getIfUpToDate(objectId, modifiedHandle);
138         } catch (IOException e) {
139             LOGGER.warn("Could not determine last modified timestamp of derivate {}", objectId);
140         }
141         if (derivateIds != null) {
142             return derivateIds;
143         }
144         derivateIds = MCRObjectUtils.getDerivates(objectId);
145         if (!derivateIds.isEmpty()) {
146             return derivateIds;
147         } else {
148             if (MCRMetadataManager.exists(objectId)) {
149                 MCRObject mcrObject = MCRMetadataManager.retrieveMCRObject(objectId);
150                 List<MCRMetaEnrichedLinkID> derivates = mcrObject.getStructure().getDerivates();
151                 for (MCRMetaEnrichedLinkID der : derivates) {
152                     derivateIds.add(der.getXLinkHrefID());
153                 }
154             }
155         }
156         return derivateIds;
157     }
158 
159     /**
160      * Stores the derivate.
161      * 
162      * @param mcrDerivate
163      *            derivate instance to store
164      * @throws MCRPersistenceException
165      *            if a persistence problem is occurred
166      * @throws MCRAccessException
167      *            if write permission to object is missing
168      */
169     public static void create(final MCRDerivate mcrDerivate) throws MCRPersistenceException, MCRAccessException {
170 
171         MCRObjectID derivateId = mcrDerivate.getId();
172 
173         if (MCRMetadataManager.exists(derivateId)) {
174             throw new MCRPersistenceException("The derivate " + derivateId + " already exists, nothing done.");
175         }
176 
177         // assign new id if necessary
178         if (derivateId.getNumberAsInteger() == 0) {
179             derivateId = MCRObjectID.getNextFreeId(derivateId.getBase());
180             mcrDerivate.setId(derivateId);
181             LOGGER.info("Assigned new derivate id {}", derivateId);
182         }
183 
184         try {
185             mcrDerivate.validate();
186         } catch (MCRException exc) {
187             throw new MCRPersistenceException("The derivate " + derivateId + " is not valid.", exc);
188         }
189 
190         final MCRObjectID objectId = mcrDerivate.getDerivate().getMetaLink().getXLinkHrefID();
191         if (!MCRAccessManager.checkPermission(objectId, PERMISSION_WRITE)) {
192             throw MCRAccessException.missingPermission("Add derivate " + derivateId + " to object.",
193                 objectId.toString(),
194                 PERMISSION_WRITE);
195         }
196 
197         byte[] objectBackup;
198         try {
199             objectBackup = MCRXMLMetadataManager.instance().retrieveBLOB(objectId);
200         } catch (IOException ioExc) {
201             throw new MCRPersistenceException("Unable to retrieve xml blob of " + objectId);
202         }
203         if (objectBackup == null) {
204             throw new MCRPersistenceException(
205                 "Cannot find " + objectId + " to attach derivate " + derivateId + " to it.");
206         }
207 
208         // prepare the derivate metadata and store under the XML table
209         if (mcrDerivate.getService().getDate("createdate") == null || !mcrDerivate.isImportMode()) {
210             mcrDerivate.getService().setDate("createdate");
211         }
212         if (mcrDerivate.getService().getDate("modifydate") == null || !mcrDerivate.isImportMode()) {
213             mcrDerivate.getService().setDate("modifydate");
214         }
215 
216         // handle events
217         fireEvent(mcrDerivate, null, MCREvent.EventType.CREATE);
218 
219         // create data in IFS
220         if (mcrDerivate.getDerivate().getInternals() != null) {
221             MCRPath rootPath = MCRPath.getPath(derivateId.toString(), "/");
222             if (mcrDerivate.getDerivate().getInternals().getSourcePath() == null) {
223                 try {
224                     rootPath.getFileSystem().createRoot(rootPath.getOwner());
225                 } catch (IOException ioExc) {
226                     throw new MCRPersistenceException(
227                         "Cannot create root of '" + rootPath.getOwner() + "'.", ioExc);
228                 }
229             } else {
230                 final String sourcepath = mcrDerivate.getDerivate().getInternals().getSourcePath();
231                 final Path f = Paths.get(sourcepath);
232                 if (Files.exists(f)) {
233                     try {
234                         if (LOGGER.isDebugEnabled()) {
235                             LOGGER.debug("Starting File-Import");
236                         }
237                         importDerivate(derivateId.toString(), f);
238                     } catch (final Exception e) {
239                         if (Files.exists(rootPath)) {
240                             deleteDerivate(derivateId.toString());
241                         }
242                         MCRMetadataManager.restore(mcrDerivate, objectId, objectBackup);
243                         throw new MCRPersistenceException("Can't add derivate to the IFS", e);
244                     }
245                 } else {
246                     LOGGER.warn("Empty derivate, the File or Directory -->{}<--  was not found.", sourcepath);
247                 }
248             }
249         }
250 
251         // add the link to metadata
252         final MCRMetaEnrichedLinkID der = MCRMetaEnrichedLinkIDFactory.getInstance().getDerivateLink(mcrDerivate);
253 
254         try {
255             if (LOGGER.isDebugEnabled()) {
256                 LOGGER.debug("adding Derivate in data store");
257             }
258             MCRMetadataManager.addOrUpdateDerivateToObject(objectId, der);
259         } catch (final Exception e) {
260             MCRMetadataManager.restore(mcrDerivate, objectId, objectBackup);
261             // throw final exception
262             throw new MCRPersistenceException("Error while creating link to MCRObject " + objectId + ".", e);
263         }
264     }
265 
266     private static void deleteDerivate(String derivateID) throws MCRPersistenceException {
267         try {
268             MCRPath rootPath = MCRPath.getPath(derivateID, "/");
269             if (!Files.exists(rootPath)) {
270                 LOGGER.info("Derivate does not exist: {}", derivateID);
271                 return;
272             }
273             Files.walkFileTree(rootPath, MCRRecursiveDeleter.instance());
274             rootPath.getFileSystem().removeRoot(derivateID);
275         } catch (Exception exc) {
276             throw new MCRPersistenceException("Unable to delete derivate " + derivateID, exc);
277         }
278     }
279 
280     private static void importDerivate(String derivateID, Path sourceDir) throws MCRPersistenceException {
281         try {
282             MCRPath rootPath = MCRPath.getPath(derivateID, "/");
283             if (Files.exists(rootPath)) {
284                 LOGGER.info("Derivate does already exist: {}", derivateID);
285             }
286             rootPath.getFileSystem().createRoot(derivateID);
287             Files.walkFileTree(sourceDir, new MCRTreeCopier(sourceDir, rootPath));
288         } catch (Exception exc) {
289             throw new MCRPersistenceException(
290                 "Unable to import derivate " + derivateID + " from source " + sourceDir.toAbsolutePath(),
291                 exc);
292         }
293     }
294 
295     /**
296      * Stores the object.
297      * 
298      * @param mcrObject
299      *            object instance to store
300      * @exception MCRPersistenceException
301      *                if a persistence problem is occured
302      * @throws MCRAccessException if "create-{objectType}" privilege is missing
303      */
304     public static void create(final MCRObject mcrObject) throws MCRPersistenceException, MCRAccessException {
305 
306         MCRObjectID objectId = mcrObject.getId();
307 
308         String createBasePrivilege = "create-" + objectId.getBase();
309         String createTypePrivilege = "create-" + objectId.getTypeId();
310         if (!MCRAccessManager.checkPermission(createBasePrivilege)
311             && !MCRAccessManager.checkPermission(createTypePrivilege)) {
312             throw MCRAccessException.missingPrivilege("Create object with id " + objectId, createBasePrivilege,
313                 createTypePrivilege);
314         }
315         // exist the object?
316         if (MCRMetadataManager.exists(objectId)) {
317             throw new MCRPersistenceException("The object " + objectId + " allready exists, nothing done.");
318         }
319 
320         // assign new id if necessary
321         if (objectId.getNumberAsInteger() == 0) {
322             objectId = MCRObjectID.getNextFreeId(objectId.getBase());
323             mcrObject.setId(objectId);
324             LOGGER.info("Assigned new object id {}", objectId);
325         }
326 
327         // create this object in datastore
328         if (mcrObject.getService().getDate("createdate") == null) {
329             mcrObject.getService().setDate("createdate");
330         }
331         if (mcrObject.getService().getDate("modifydate") == null) {
332             mcrObject.getService().setDate("modifydate");
333         }
334 
335         // prepare this object with parent metadata
336         receiveMetadata(mcrObject);
337 
338         final MCRObjectID parentId = mcrObject.getStructure().getParentID();
339         MCRObject parent = null;
340         if (parentId != null) {
341             if (LOGGER.isDebugEnabled()) {
342                 LOGGER.debug("Parent ID = {}", parentId);
343             }
344             parent = MCRMetadataManager.retrieveMCRObject(parentId);
345         }
346 
347         // handle events
348         fireEvent(mcrObject, null, MCREvent.EventType.CREATE);
349 
350         // add the MCRObjectID to the child list in the parent object
351         if (parentId != null) {
352             parent.getStructure().addChild(new MCRMetaLinkID("child", objectId,
353                 mcrObject.getStructure().getParent().getXLinkLabel(), mcrObject.getLabel()));
354             MCRMetadataManager.fireUpdateEvent(parent);
355         }
356     }
357 
358     /**
359      * Deletes MCRDerivate.
360      * 
361      * @param mcrDerivate
362      *            to be deleted
363      * @throws MCRPersistenceException
364      *            if persistence problem occurs
365      * @throws MCRAccessException
366      *            if delete permission is missing
367      */
368     public static void delete(final MCRDerivate mcrDerivate) throws MCRPersistenceException, MCRAccessException {
369         MCRObjectID id = mcrDerivate.getId();
370         if (!MCRAccessManager.checkDerivateContentPermission(id, PERMISSION_DELETE)) {
371             throw MCRAccessException.missingPermission("Delete derivate", id.toString(), PERMISSION_DELETE);
372         }
373         // mark for deletion
374         MCRMarkManager.instance().mark(id, Operation.DELETE);
375 
376         // remove link
377         MCRObjectID metaId = null;
378         try {
379             metaId = mcrDerivate.getDerivate().getMetaLink().getXLinkHrefID();
380             if (MCRMetadataManager.removeDerivateFromObject(metaId, id)) {
381                 LOGGER.info("Link in MCRObject {} to MCRDerivate {} is deleted.", metaId, id);
382             } else {
383                 LOGGER.warn("Link in MCRObject {} to MCRDerivate {} could not be deleted.", metaId, id);
384             }
385         } catch (final Exception e) {
386             LOGGER.warn("Can't delete link for MCRDerivate {} from MCRObject {}. Error ignored.", id, metaId);
387         }
388 
389         // delete data from IFS
390         if (mcrDerivate.getDerivate().getInternals() != null) {
391             try {
392                 deleteDerivate(id.toString());
393                 LOGGER.info("IFS entries for MCRDerivate {} are deleted.", id);
394             } catch (final Exception e) {
395                 throw new MCRPersistenceException("Error while delete MCRDerivate " + id + " in IFS", e);
396             }
397         }
398 
399         // handle events
400         fireEvent(mcrDerivate, null, MCREvent.EventType.DELETE);
401 
402         // remove mark
403         MCRMarkManager.instance().remove(id);
404     }
405 
406     /**
407      * Deletes MCRObject.
408      * 
409      * @param mcrObject
410      *            to be deleted
411      * @throws MCRActiveLinkException
412      *            cannot be deleted cause its still linked
413      * @throws MCRPersistenceException
414      *            if persistence problem occurs
415      * @throws MCRAccessException
416      *            if delete permission is missing
417      */
418     public static void delete(final MCRObject mcrObject)
419         throws MCRPersistenceException, MCRActiveLinkException, MCRAccessException {
420         delete(mcrObject, MCRMetadataManager::removeChildObject);
421     }
422 
423     /**
424      * Deletes the <code>mcrObject</code>.
425      * 
426      * @param mcrObject
427      *            the object to be deleted
428      * @param parentOperation
429      *            function to handle the parent of the object @see {@link #removeChildObject(MCRObject, MCRObjectID)}
430      * @throws MCRPersistenceException
431      *            if persistence problem occurs
432      * @throws MCRActiveLinkException
433      *            object couldn't  be deleted because its linked somewhere
434      * @throws MCRAccessException
435      *            if delete permission is missing
436      */
437     private static void delete(final MCRObject mcrObject, BiConsumer<MCRObject, MCRObjectID> parentOperation)
438         throws MCRPersistenceException, MCRActiveLinkException, MCRAccessException {
439         MCRObjectID id = mcrObject.getId();
440         if (id == null) {
441             throw new MCRPersistenceException("The MCRObjectID is null.");
442         }
443         if (!MCRAccessManager.checkPermission(id, PERMISSION_DELETE)) {
444             throw MCRAccessException.missingPermission("Delete object", mcrObject.getId().toString(),
445                 PERMISSION_DELETE);
446         }
447 
448         // check for active links
449         final Collection<String> sources = MCRLinkTableManager.instance().getSourceOf(mcrObject.mcrId,
450             MCRLinkTableManager.ENTRY_TYPE_REFERENCE);
451         if (LOGGER.isDebugEnabled()) {
452             LOGGER.debug("Sources size:{}", sources.size());
453         }
454         if (sources.size() > 0) {
455             final MCRActiveLinkException activeLinks = new MCRActiveLinkException("Error while deleting object " + id
456                 + ". This object is still referenced by other objects and "
457                 + "can not be removed until all links are released.");
458             for (final String curSource : sources) {
459                 activeLinks.addLink(curSource, id.toString());
460             }
461             throw activeLinks;
462         }
463 
464         // mark for deletion
465         MCRMarkManager.instance().mark(id, Operation.DELETE);
466 
467         // remove child from parent
468         final MCRObjectID parentId = mcrObject.getStructure().getParentID();
469         if (parentId != null) {
470             parentOperation.accept(mcrObject, parentId);
471         }
472 
473         // remove all children
474         for (MCRMetaLinkID child : mcrObject.getStructure().getChildren()) {
475             MCRObjectID childId = child.getXLinkHrefID();
476             if (!MCRMetadataManager.exists(childId)) {
477                 LOGGER.warn("Unable to remove not existing object {} of parent {}", childId, id);
478                 continue;
479             }
480             MCRMetadataManager.deleteMCRObject(childId, (MCRObject o, MCRObjectID p) -> {
481                 // Do nothing with the parent, because its removed anyway.
482             });
483         }
484 
485         // remove all derivates
486         for (MCRMetaLinkID derivate : mcrObject.getStructure().getDerivates()) {
487             MCRObjectID derivateId = derivate.getXLinkHrefID();
488             if (!MCRMetadataManager.exists(derivateId)) {
489                 LOGGER.warn("Unable to remove not existing derivate {} of object {}", derivateId, id);
490                 continue;
491             }
492             MCRMetadataManager.deleteMCRDerivate(derivateId);
493         }
494 
495         // handle events
496         fireEvent(mcrObject, null, MCREvent.EventType.DELETE);
497 
498         // remove mark
499         MCRMarkManager.instance().remove(id);
500     }
501 
502     /**
503      * Helper method to remove the <code>mcrObject</code> of the given parent. This does just
504      * remove the linking between both objects.
505      * 
506      * @param mcrObject
507      *            the object (child) to remove
508      * @param
509      *            parentId the parent id
510      * @throws PersistenceException
511      *            when the child cannot be removed due persistent problems
512      */
513     private static void removeChildObject(final MCRObject mcrObject, final MCRObjectID parentId)
514         throws PersistenceException {
515         if (LOGGER.isDebugEnabled()) {
516             LOGGER.debug("Parent ID = {}", parentId);
517         }
518         try {
519             if (MCRXMLMetadataManager.instance().exists(parentId)) {
520                 final MCRObject parent = MCRMetadataManager.retrieveMCRObject(parentId);
521                 parent.getStructure().removeChild(mcrObject.getId());
522                 MCRMetadataManager.fireUpdateEvent(parent);
523             } else {
524                 LOGGER.warn("Unable to find parent {} of {}", parentId, mcrObject.getId());
525             }
526         } catch (Exception exc) {
527             throw new PersistenceException(
528                 "Error while deleting object. Unable to remove child " + mcrObject.getId() + " from parent " + parentId
529                     + ".",
530                 exc);
531         }
532     }
533 
534     /**
535      * Delete the derivate. The order of delete steps is:<br>
536      * <ul>
537      * <li>remove link in object metadata</li>
538      * <li>remove all files from IFS</li>
539      * <li>remove derivate</li>
540      * </ul>
541      * 
542      * @param id
543      *            the object ID
544      * @exception MCRPersistenceException
545      *                if a persistence problem is occurred
546      * @throws MCRAccessException if delete permission is missing
547      */
548     public static void deleteMCRDerivate(final MCRObjectID id) throws MCRPersistenceException, MCRAccessException {
549         final MCRDerivate derivate = MCRMetadataManager.retrieveMCRDerivate(id);
550         MCRMetadataManager.delete(derivate);
551     }
552 
553     /**
554      * Deletes the object.
555      * 
556      * @exception MCRPersistenceException
557      *                if a persistence problem is occurred
558      * @throws MCRActiveLinkException
559      *             if object is referenced by other objects
560      * @throws MCRAccessException if delete permission is missing
561      */
562     public static void deleteMCRObject(final MCRObjectID id)
563         throws MCRPersistenceException, MCRActiveLinkException, MCRAccessException {
564         deleteMCRObject(id, MCRMetadataManager::removeChildObject);
565     }
566 
567     /**
568      * Deletes the mcr object with the given <code>id</code>.
569      * 
570      * @param id
571      *            the object to be deleted
572      * @param parentOperation
573      *            function to handle the parent of the object @see {@link #removeChildObject(MCRObject, MCRObjectID)}
574      * @throws MCRPersistenceException
575      *            if persistence problem occurs
576      * @throws MCRActiveLinkException
577      *            object couldn't  be deleted because its linked somewhere
578      * @throws MCRAccessException if delete permission is missing
579      */
580     private static void deleteMCRObject(final MCRObjectID id, BiConsumer<MCRObject, MCRObjectID> parentOperation)
581         throws MCRPersistenceException, MCRActiveLinkException, MCRAccessException {
582         final MCRObject object = retrieveMCRObject(id);
583         MCRMetadataManager.delete(object, parentOperation);
584     }
585 
586     /**
587      * Tells if the object or derivate with <code>id</code> exists.
588      *
589      * @param id
590      *            the object ID
591      * @throws MCRPersistenceException
592      *            the xml couldn't be read
593      */
594     public static boolean exists(final MCRObjectID id) throws MCRPersistenceException {
595         return MCRXMLMetadataManager.instance().exists(id);
596     }
597 
598     /**
599      * Fires {@link MCREvent.EventType#REPAIR} for given derivate.
600      * 
601      */
602     public static void fireRepairEvent(final MCRDerivate mcrDerivate) throws MCRPersistenceException {
603         // handle events
604         fireEvent(mcrDerivate, null, MCREvent.EventType.REPAIR);
605     }
606 
607     /**
608      * Fires {@link MCREvent.EventType#REPAIR} for given object.
609      * 
610      */
611     public static void fireRepairEvent(final MCRBase mcrBaseObj) throws MCRPersistenceException {
612         if (mcrBaseObj instanceof MCRDerivate) {
613             MCRMetadataManager.fireRepairEvent((MCRDerivate) mcrBaseObj);
614         } else if (mcrBaseObj instanceof MCRObject) {
615             MCRMetadataManager.fireRepairEvent((MCRObject) mcrBaseObj);
616         }
617     }
618 
619     /**
620      * Fires {@link MCREvent.EventType#REPAIR} for given object.
621      * 
622      */
623     public static void fireRepairEvent(final MCRObject mcrObject) throws MCRPersistenceException {
624         // check derivate link
625         for (MCRMetaLinkID derivate : mcrObject.getStructure().getDerivates()) {
626             if (!MCRMetadataManager.exists(derivate.getXLinkHrefID())) {
627                 LOGGER.error("Can't find MCRDerivate {}", derivate.getXLinkHrefID());
628             }
629         }
630         // handle events
631         fireEvent(mcrObject, null, MCREvent.EventType.REPAIR);
632     }
633 
634     /**
635      * Fires {@link MCREvent.EventType#UPDATE} for given object. If {@link MCRObject#isImportMode()} modifydate 
636      * will not be updated.
637      * 
638      * @param mcrObject
639      *            mycore object which is updated
640      */
641     public static void fireUpdateEvent(final MCRObject mcrObject) throws MCRPersistenceException {
642         if (!mcrObject.isImportMode() || mcrObject.getService().getDate("modifydate") == null) {
643             mcrObject.getService().setDate("modifydate");
644         }
645         // remove ACL if it is set from data source
646         mcrObject.getService().getRules().clear();
647         // handle events
648         fireEvent(mcrObject, retrieveMCRObject(mcrObject.getId()), MCREvent.EventType.UPDATE);
649     }
650 
651     /**
652      * Retrieves instance of {@link MCRDerivate} with the given {@link MCRObjectID}
653      * 
654      * @param id
655      *            the derivate ID
656      * @exception MCRPersistenceException
657      *                if a persistence problem is occurred
658      */
659     public static MCRDerivate retrieveMCRDerivate(final MCRObjectID id) throws MCRPersistenceException {
660         try {
661             Document xml = MCRXMLMetadataManager.instance().retrieveXML(id);
662             if (xml == null) {
663                 throw new MCRPersistenceException("Could not retrieve xml of derivate: " + id);
664             }
665             return new MCRDerivate(xml);
666         } catch (IOException | JDOMException | SAXException e) {
667             throw new MCRPersistenceException("Could not retrieve xml of derivate: " + id, e);
668         }
669     }
670 
671     /**
672      * Retrieves an instance of {@link MCRObject} with the given {@link MCRObjectID}
673      * 
674      * @param id
675      *                the object ID
676      * @exception MCRPersistenceException
677      *                if a persistence problem is occurred
678      */
679     public static MCRObject retrieveMCRObject(final MCRObjectID id) throws MCRPersistenceException {
680         try {
681             Document xml = MCRXMLMetadataManager.instance().retrieveXML(id);
682             if (xml == null) {
683                 throw new MCRPersistenceException("Could not retrieve xml of object: " + id);
684             }
685             return new MCRObject(xml);
686         } catch (IOException | JDOMException | SAXException e) {
687             throw new MCRPersistenceException("Could not retrieve xml of object: " + id, e);
688         }
689     }
690 
691     /**
692      * Retrieves an instance of {@link MCRObject} or {@link MCRDerivate} depending on {@link MCRObjectID#getTypeId()}
693      * 
694      * @param id
695      *                derivate or object id
696      * @exception MCRPersistenceException
697      *                if a persistence problem is occurred
698      */
699     public static MCRBase retrieve(final MCRObjectID id) throws MCRPersistenceException {
700         if (id.getTypeId().equals("derivate")) {
701             return retrieveMCRDerivate(id);
702         }
703         return retrieveMCRObject(id);
704     }
705 
706     /**
707      * Updates the <code>MCRObject</code> or <code>MCRDerivate</code>.
708      *
709      * @param base the object to update
710      *
711      * @throws MCRPersistenceException
712      *              if a persistence problem is occurred
713      * @throws MCRAccessException
714      *              if write permission is missing or see {@link #create(MCRObject)}
715      */
716     public static void update(final MCRBase base)
717         throws MCRPersistenceException, MCRAccessException {
718         if (base instanceof MCRObject) {
719             MCRMetadataManager.update((MCRObject) base);
720         } else if (base instanceof MCRDerivate) {
721             MCRMetadataManager.update((MCRDerivate) base);
722         } else {
723             throw new IllegalArgumentException("Type is unsupported " + base.getId());
724         }
725     }
726 
727     /**
728      * Updates the derivate or creates it if it does not exist yet.
729      * 
730      * @throws MCRPersistenceException
731      *                if a persistence problem is occurred
732      * @throws MCRAccessException
733      *                if write permission to object or derivate is missing
734      */
735     public static void update(final MCRDerivate mcrDerivate)
736         throws MCRPersistenceException, MCRAccessException {
737         MCRObjectID derivateId = mcrDerivate.getId();
738         // check deletion mark
739         if (MCRMarkManager.instance().isMarkedForDeletion(derivateId)) {
740             return;
741         }
742         if (!MCRMetadataManager.exists(derivateId)) {
743             MCRMetadataManager.create(mcrDerivate);
744             return;
745         }
746         if (!MCRAccessManager.checkDerivateMetadataPermission(derivateId, PERMISSION_WRITE)) {
747             throw MCRAccessException.missingPermission("Update derivate", derivateId.toString(), PERMISSION_WRITE);
748         }
749         Path fileSourceDirectory = null;
750         MCRMetaIFS internals = mcrDerivate.getDerivate().getInternals();
751         if (internals != null && internals.getSourcePath() != null) {
752             fileSourceDirectory = Paths.get(internals.getSourcePath());
753 
754             if (!Files.exists(fileSourceDirectory)) {
755                 LOGGER.warn("{}: the directory {} was not found.", derivateId, fileSourceDirectory);
756                 fileSourceDirectory = null;
757             }
758             //MCR-2645 
759             internals.setSourcePath(null);
760 
761         }
762         // get the old Item
763         MCRDerivate old = MCRMetadataManager.retrieveMCRDerivate(derivateId);
764 
765         // remove the old link to metadata
766         MCRMetaLinkID oldLink = old.getDerivate().getMetaLink();
767         MCRMetaLinkID newLink = mcrDerivate.getDerivate().getMetaLink();
768         MCRObjectID oldMetadataObjectID = oldLink.getXLinkHrefID();
769         MCRObjectID newMetadataObjectID = newLink.getXLinkHrefID();
770         if (!oldMetadataObjectID.equals(newLink.getXLinkHrefID())) {
771             try {
772                 MCRMetadataManager.removeDerivateFromObject(oldMetadataObjectID, derivateId);
773             } catch (final MCRException e) {
774                 LOGGER.warn(e.getMessage(), e);
775             }
776         }
777 
778         // update the derivate
779         mcrDerivate.getService().setDate("createdate", old.getService().getDate("createdate"));
780         if (!mcrDerivate.getService().isFlagTypeSet(MCRObjectService.FLAG_TYPE_CREATEDBY)) {
781             for (String flagCreatedBy : old.getService().getFlags(MCRObjectService.FLAG_TYPE_CREATEDBY)) {
782                 mcrDerivate.getService().addFlag(MCRObjectService.FLAG_TYPE_CREATEDBY, flagCreatedBy);
783             }
784         }
785 
786         MCRMetadataManager.updateMCRDerivateXML(mcrDerivate);
787 
788         // update to IFS
789         if (fileSourceDirectory != null) {
790             MCRPath targetPath = MCRPath.getPath(derivateId.toString(), "/");
791             try {
792                 Files.walkFileTree(fileSourceDirectory, new MCRTreeCopier(fileSourceDirectory, targetPath));
793             } catch (Exception exc) {
794                 throw new MCRPersistenceException(
795                     "Unable to update IFS. Copy failed from " + fileSourceDirectory.toAbsolutePath()
796                         + " to target " + targetPath.toAbsolutePath(),
797                     exc);
798             }
799         }
800 
801         // add the link to metadata
802         final MCRMetaEnrichedLinkID derivateLink = MCRMetaEnrichedLinkIDFactory.getInstance()
803             .getDerivateLink(mcrDerivate);
804         addOrUpdateDerivateToObject(newMetadataObjectID, derivateLink);
805     }
806 
807     /**
808      * Updates the object or creates it if it does not exist yet.
809      *
810      * @throws MCRPersistenceException
811      *                if a persistence problem is occurred
812      * @throws MCRAccessException
813      *                if write permission is missing or see {@link #create(MCRObject)}
814      */
815     public static void update(final MCRObject mcrObject)
816         throws MCRPersistenceException, MCRAccessException {
817         MCRObjectID id = mcrObject.getId();
818         // check deletion mark
819         if (MCRMarkManager.instance().isMarkedForDeletion(id)) {
820             return;
821         }
822         if (!MCRMetadataManager.exists(id)) {
823             MCRMetadataManager.create(mcrObject);
824             return;
825         }
826         if (!MCRAccessManager.checkPermission(id, PERMISSION_WRITE)) {
827             throw MCRAccessException.missingPermission("Update object.", id.toString(), PERMISSION_WRITE);
828         }
829         MCRObject old = MCRMetadataManager.retrieveMCRObject(id);
830         Date diskModifyDate = old.getService().getDate(MCRObjectService.DATE_TYPE_MODIFYDATE);
831         Date updateModifyDate = mcrObject.getService().getDate(MCRObjectService.DATE_TYPE_MODIFYDATE);
832         if (diskModifyDate != null && updateModifyDate != null && updateModifyDate.before(diskModifyDate)) {
833             throw new MCRPersistenceException("The object " + mcrObject.getId() + " was modified(" + diskModifyDate
834                 + ") during the time it was opened in the editor.");
835         }
836 
837         // save the order of derivates and clean the structure
838         final List<String> childOrder = mcrObject.getStructure()
839             .getChildren()
840             .stream()
841             .map(MCRMetaLinkID::getXLinkHref)
842             .collect(Collectors.toList());
843         mcrObject.getStructure().clearChildren();
844 
845         final List<MCRMetaEnrichedLinkID> derivateLinks = mcrObject.getStructure().getDerivates();
846         derivateLinks.clear();
847         old.getStructure().getDerivates()
848             .forEach(mcrObject.getStructure()::addDerivate);
849 
850         // set the parent from the original and this update
851         MCRObjectID oldParentID = old.getStructure().getParentID();
852         MCRObjectID newParentID = mcrObject.getStructure().getParentID();
853 
854         if (oldParentID != null && exists(oldParentID) && (!oldParentID.equals(newParentID))) {
855             // remove child from the old parent
856             if (LOGGER.isDebugEnabled()) {
857                 LOGGER.debug("Parent ID = {}", oldParentID);
858             }
859             final MCRObject parent = MCRMetadataManager.retrieveMCRObject(oldParentID);
860             parent.getStructure().removeChild(id);
861             MCRMetadataManager.fireUpdateEvent(parent);
862         }
863 
864         // set the children from the original -> but with new order
865         List<MCRMetaLinkID> children = old.getStructure().getChildren();
866         children.sort((link1, link2) -> {
867             int i1 = childOrder.indexOf(link1.getXLinkHref());
868             int i2 = childOrder.indexOf(link2.getXLinkHref());
869             return Integer.compare(i1, i2);
870         });
871         mcrObject.getStructure().getChildren().addAll(children);
872 
873         // import all herited matadata from the parent
874         receiveMetadata(mcrObject);
875 
876         // if not imported via cli, createdate remains unchanged
877         if (!mcrObject.isImportMode() || mcrObject.getService().getDate("createdate") == null) {
878             mcrObject.getService().setDate("createdate", old.getService().getDate("createdate"));
879         }
880         if (!mcrObject.isImportMode() && !mcrObject.getService().isFlagTypeSet(MCRObjectService.FLAG_TYPE_CREATEDBY)) {
881             for (String flagCreatedBy : old.getService().getFlags(MCRObjectService.FLAG_TYPE_CREATEDBY)) {
882                 mcrObject.getService().addFlag(MCRObjectService.FLAG_TYPE_CREATEDBY, flagCreatedBy);
883             }
884         }
885 
886         // update this dataset
887         MCRMetadataManager.fireUpdateEvent(mcrObject);
888 
889         // check if the parent was new set and set them
890         if (newParentID != null && !newParentID.equals(oldParentID)) {
891             MCRObject newParent = retrieveMCRObject(newParentID);
892             newParent.getStructure().addChild(new MCRMetaLinkID("child", id, null, mcrObject.getLabel()));
893             MCRMetadataManager.fireUpdateEvent(newParent);
894         }
895 
896         // update all children
897         if (shareableMetadataChanged(mcrObject, old)) {
898             MCRMetadataShareAgent metadataShareAgent = MCRMetadataShareAgentFactory.getAgent(id);
899             metadataShareAgent.distributeMetadata(mcrObject);
900         }
901     }
902 
903     private static boolean shareableMetadataChanged(final MCRObject mcrObject, MCRObject old) {
904         MCRMetadataShareAgent metadataShareAgent = MCRMetadataShareAgentFactory.getAgent(mcrObject.getId());
905         return metadataShareAgent.shareableMetadataChanged(old, mcrObject);
906     }
907 
908     private static void receiveMetadata(final MCRObject recipient) {
909         MCRMetadataShareAgent metadataShareAgent = MCRMetadataShareAgentFactory.getAgent(recipient.getId());
910         metadataShareAgent.receiveMetadata(recipient);
911     }
912 
913     /**
914      * Updates only the XML part of the derivate.
915      * 
916      * @exception MCRPersistenceException
917      *                if a persistence problem is occurred
918      */
919     private static void updateMCRDerivateXML(final MCRDerivate mcrDerivate) throws MCRPersistenceException {
920         if (!mcrDerivate.isImportMode() || mcrDerivate.getService().getDate("modifydate") == null) {
921             mcrDerivate.getService().setDate("modifydate");
922         }
923         fireEvent(mcrDerivate, retrieveMCRDerivate(mcrDerivate.getId()), MCREvent.EventType.UPDATE);
924     }
925 
926     /**
927      * Adds or updates a derivate MCRMetaLinkID to the structure part and updates the object with the ID in the data
928      * store.
929      * 
930      * @param id
931      *            the object ID
932      * @param link
933      *            a link to a derivate as MCRMetaLinkID
934      * @return True if the link is added or updated, false if nothing changed.
935      * @throws MCRPersistenceException
936      *             if a persistence problem is occurred
937      */
938     public static boolean addOrUpdateDerivateToObject(final MCRObjectID id, final MCRMetaEnrichedLinkID link)
939         throws MCRPersistenceException {
940         final MCRObject object = MCRMetadataManager.retrieveMCRObject(id);
941         if (!object.getStructure().addOrUpdateDerivate(link)) {
942             return false;
943         }
944         if (!object.isImportMode()) {
945             object.getService().setDate("modifydate");
946         }
947         MCRMetadataManager.fireUpdateEvent(object);
948         return true;
949     }
950 
951     public static boolean removeDerivateFromObject(final MCRObjectID objectID, final MCRObjectID derivateID)
952         throws MCRPersistenceException {
953         final MCRObject object = MCRMetadataManager.retrieveMCRObject(objectID);
954         if (object.getStructure().removeDerivate(derivateID)) {
955             object.getService().setDate("modifydate");
956             MCRMetadataManager.fireUpdateEvent(object);
957             return true;
958         }
959         return false;
960     }
961 
962     private static void restore(final MCRDerivate mcrDerivate, final MCRObjectID mcrObjectId, final byte[] backup) {
963         try {
964             final MCRObject obj = new MCRObject(backup, false);
965             // If an event handler exception occurred, its crucial to restore the object first
966             // before updating it again. Otherwise the exception could be thrown again and
967             // the object will be in an invalid state (the not existing derivate will be
968             // linked with the object).
969             MCRXMLMetadataManager.instance().update(mcrObjectId, obj.createXML(), new Date());
970 
971             // update and call event handlers
972             MCRMetadataManager.update(obj);
973         } catch (final Exception e1) {
974             LOGGER.warn("Error while restoring {}", mcrObjectId, e1);
975         } finally {
976             // remove derivate
977             fireEvent(mcrDerivate, null, MCREvent.EventType.DELETE);
978         }
979     }
980 
981     private static void fireEvent(MCRBase base, MCRBase oldBase, MCREvent.EventType eventType) {
982         boolean objectEvent = base instanceof MCRObject;
983         MCREvent.ObjectType type = objectEvent ? MCREvent.ObjectType.OBJECT : MCREvent.ObjectType.DERIVATE;
984         final MCREvent evt = new MCREvent(type, eventType);
985         if (objectEvent) {
986             evt.put(MCREvent.OBJECT_KEY, base);
987         } else {
988             evt.put(MCREvent.DERIVATE_KEY, base);
989         }
990         Optional.ofNullable(oldBase)
991             .ifPresent(b -> evt.put(objectEvent ? MCREvent.OBJECT_OLD_KEY : MCREvent.DERIVATE_OLD_KEY, b));
992         if (MCREvent.EventType.DELETE == eventType) {
993             MCREventManager.instance().handleEvent(evt, MCREventManager.BACKWARD);
994         } else {
995             MCREventManager.instance().handleEvent(evt);
996         }
997     }
998 }