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.history;
20  
21  import java.io.IOException;
22  import java.time.Instant;
23  import java.util.ArrayList;
24  import java.util.Collections;
25  import java.util.List;
26  import java.util.Optional;
27  import java.util.concurrent.ExecutionException;
28  import java.util.concurrent.ExecutorService;
29  import java.util.concurrent.Executors;
30  import java.util.concurrent.TimeUnit;
31  import java.util.concurrent.atomic.AtomicInteger;
32  import java.util.stream.Collectors;
33  import java.util.stream.IntStream;
34  import java.util.stream.Stream;
35  
36  import org.apache.logging.log4j.LogManager;
37  import org.jdom2.JDOMException;
38  import org.mycore.access.MCRAccessManager;
39  import org.mycore.backend.jpa.MCREntityManagerProvider;
40  import org.mycore.common.MCRSession;
41  import org.mycore.common.MCRSessionMgr;
42  import org.mycore.common.MCRSystemUserInformation;
43  import org.mycore.common.MCRUsageException;
44  import org.mycore.datamodel.common.MCRAbstractMetadataVersion;
45  import org.mycore.datamodel.common.MCRCreatorCache;
46  import org.mycore.datamodel.common.MCRXMLMetadataManager;
47  import org.mycore.datamodel.ifs2.MCRMetadataVersion;
48  import org.mycore.datamodel.metadata.MCRDerivate;
49  import org.mycore.datamodel.metadata.MCRMetadataManager;
50  import org.mycore.datamodel.metadata.MCRObject;
51  import org.mycore.datamodel.metadata.MCRObjectID;
52  import org.mycore.frontend.cli.annotation.MCRCommand;
53  import org.mycore.frontend.cli.annotation.MCRCommandGroup;
54  import org.mycore.util.concurrent.MCRTransactionableCallable;
55  import org.xml.sax.SAXException;
56  
57  import jakarta.persistence.EntityManager;
58  import jakarta.persistence.criteria.CriteriaBuilder;
59  import jakarta.persistence.criteria.CriteriaDelete;
60  import jakarta.persistence.criteria.Root;
61  
62  /**
63   * @author Thomas Scheffler (yagee)
64   *
65   */
66  @MCRCommandGroup(name = "Metadata history")
67  public class MCRMetadataHistoryCommands {
68  
69      @MCRCommand(syntax = "clear metadata history of base {0}",
70          help = "clears metadata history of all objects with base id {0}")
71      public static void clearHistory(String baseId) {
72          EntityManager em = MCREntityManagerProvider.getCurrentEntityManager();
73          CriteriaBuilder cb = em.getCriteriaBuilder();
74          CriteriaDelete<MCRMetaHistoryItem> delete = cb.createCriteriaDelete(MCRMetaHistoryItem.class);
75          Root<MCRMetaHistoryItem> item = delete.from(MCRMetaHistoryItem.class);
76          int rowsDeleted = em.createQuery(
77              delete.where(
78                  cb.like(item.get(MCRMetaHistoryItem_.id).as(String.class), baseId + "_".replace("_", "$_") + '%', '$')))
79              .executeUpdate();
80          LogManager.getLogger().info("Deleted {} items in history of {}", rowsDeleted, baseId);
81      }
82  
83      @MCRCommand(syntax = "clear metadata history of id {0}",
84          help = "clears metadata history of object/derivate with id {0}")
85      public static void clearSingleHistory(String mcrId) {
86          EntityManager em = MCREntityManagerProvider.getCurrentEntityManager();
87          CriteriaBuilder cb = em.getCriteriaBuilder();
88          CriteriaDelete<MCRMetaHistoryItem> delete = cb.createCriteriaDelete(MCRMetaHistoryItem.class);
89          Root<MCRMetaHistoryItem> item = delete.from(MCRMetaHistoryItem.class);
90          int rowsDeleted = em.createQuery(
91              delete.where(cb.equal(item.get(MCRMetaHistoryItem_.id).as(String.class), mcrId)))
92              .executeUpdate();
93          LogManager.getLogger().info("Deleted {} items in history of {}", rowsDeleted, mcrId);
94      }
95  
96      @MCRCommand(syntax = "clear metadata history completely", help = "clears metadata history completely")
97      public static List<String> clearHistory() {
98          return MCRXMLMetadataManager.instance()
99              .getObjectBaseIds()
100             .stream()
101             .map(s -> "clear metadata history of base " + s)
102             .collect(Collectors.toList());
103     }
104 
105     @MCRCommand(syntax = "build metadata history completely", help = "build metadata history completely")
106     public static List<String> buildHistory() {
107         return MCRXMLMetadataManager.instance()
108             .getObjectBaseIds()
109             .stream()
110             .map(s -> "build metadata history of base " + s)
111             .collect(Collectors.toList());
112     }
113 
114     @MCRCommand(syntax = "build metadata history of base {0}",
115         help = "build metadata history of all objects with base id {0}")
116     public static List<String> buildHistory(String baseId) {
117         MCRXMLMetadataManager mm = MCRXMLMetadataManager.instance();
118         mm.verifyStore(baseId);
119         ExecutorService executorService = Executors.newWorkStealingPool();
120         MCRSession currentSession = MCRSessionMgr.getCurrentSession();
121         String[] idParts = MCRObjectID.getIDParts(baseId);
122         if (idParts.length != 2) {
123             throw new MCRUsageException("Valid base ID required!");
124         }
125         int maxId = mm.getHighestStoredID(idParts[0], idParts[1]);
126         AtomicInteger completed = new AtomicInteger(maxId);
127         IntStream.rangeClosed(1, maxId)
128             .parallel()
129             .mapToObj(i -> MCRObjectID.formatID(baseId, i))
130             .map(MCRObjectID::getInstance)
131             .map(id -> new MCRTransactionableCallable<>(Executors.callable(() -> {
132                 EntityManager em = MCREntityManagerProvider.getCurrentEntityManager();
133                 getHistoryItems(id).sequential().forEach(em::persist);
134                 completed.decrementAndGet();
135             }), currentSession))
136             .forEach(executorService::submit);
137         executorService.shutdown();
138         boolean waitToFinish = true;
139         while (!executorService.isTerminated() && waitToFinish) {
140             LogManager.getLogger().info("Waiting for history of {} objects/derivates.", completed.get());
141             try {
142                 executorService.awaitTermination(10, TimeUnit.SECONDS);
143             } catch (InterruptedException e) {
144                 waitToFinish = false;
145             }
146         }
147         return Collections.emptyList();
148     }
149 
150     @MCRCommand(syntax = "build metadata history of id {0}",
151         help = "build metadata history of object/derivate with id {0}")
152     public static void buildSingleHistory(String mcrId) {
153         MCRObjectID objId = MCRObjectID.getInstance(mcrId);
154         EntityManager em = MCREntityManagerProvider.getCurrentEntityManager();
155         getHistoryItems(objId).sequential().forEach(em::persist);
156     }
157 
158     private static Stream<MCRMetaHistoryItem> getHistoryItems(MCRObjectID objId) {
159         return objId.getTypeId().equals("derivate") ? buildDerivateHistory(objId)
160             : buildObjectHistory(objId);
161     }
162 
163     private static Stream<MCRMetaHistoryItem> buildDerivateHistory(MCRObjectID derId) {
164         try {
165             List<? extends MCRAbstractMetadataVersion<?>> versions = MCRXMLMetadataManager.instance()
166                 .listRevisions(derId);
167             if (versions == null || versions.isEmpty()) {
168                 return buildSimpleDerivateHistory(derId);
169             } else {
170                 return buildDerivateHistory(derId, versions);
171             }
172         } catch (IOException e) {
173             LogManager.getLogger().error("Error while getting history of {}", derId);
174             return Stream.empty();
175         }
176     }
177 
178     private static Stream<MCRMetaHistoryItem> buildObjectHistory(MCRObjectID objId) {
179         try {
180             List<? extends MCRAbstractMetadataVersion<?>> versions = MCRXMLMetadataManager.instance()
181                 .listRevisions(objId);
182             if (versions == null || versions.isEmpty()) {
183                 return buildSimpleObjectHistory(objId);
184             } else {
185                 return buildObjectHistory(objId, versions);
186             }
187         } catch (IOException e) {
188             LogManager.getLogger().error("Error while getting history of {}", objId);
189             return Stream.empty();
190         }
191     }
192 
193     private static Stream<MCRMetaHistoryItem> buildSimpleDerivateHistory(MCRObjectID derId) throws IOException {
194         LogManager.getLogger().debug("Store of {} has no old revisions. History rebuild is limited", derId);
195         if (MCRMetadataManager.exists(derId)) {
196             MCRDerivate der = MCRMetadataManager.retrieveMCRDerivate(derId);
197             Instant lastModified = Instant
198                 .ofEpochMilli(MCRXMLMetadataManager.instance().getLastModified(derId));
199             String creator;
200             try {
201                 creator = MCRCreatorCache.getCreator(der.getId());
202             } catch (ExecutionException e) {
203                 LogManager.getLogger().warn("Error while getting creator of {}", derId, e);
204                 creator = null;
205             }
206             String user = Optional.ofNullable(creator)
207                 .orElseGet(() -> MCRSystemUserInformation.getSystemUserInstance().getUserID());
208             MCRMetaHistoryItem create = create(derId,
209                 user,
210                 lastModified);
211             boolean objectIsHidden = !MCRAccessManager.checkDerivateDisplayPermission(derId.toString());
212             if (objectIsHidden) {
213                 return Stream.of(create, delete(derId, user, lastModified.plusMillis(1)));
214             }
215             return Stream.of(create);
216         } else {
217             return Stream.of(delete(derId, null, Instant.now()));
218         }
219     }
220 
221     private static Stream<MCRMetaHistoryItem> buildSimpleObjectHistory(MCRObjectID objId) throws IOException {
222         LogManager.getLogger().debug("Store of {} has no old revisions. History rebuild is limited", objId);
223         if (MCRMetadataManager.exists(objId)) {
224             MCRObject obj = MCRMetadataManager.retrieveMCRObject(objId);
225             Instant lastModified = Instant
226                 .ofEpochMilli(MCRXMLMetadataManager.instance().getLastModified(objId));
227             String creator;
228             try {
229                 creator = MCRCreatorCache.getCreator(obj.getId());
230             } catch (ExecutionException e) {
231                 LogManager.getLogger().warn("Error while getting creator of {}", objId, e);
232                 creator = null;
233             }
234             String user = Optional.ofNullable(creator)
235                 .orElseGet(() -> MCRSystemUserInformation.getSystemUserInstance().getUserID());
236             MCRMetaHistoryItem create = create(objId, user, lastModified);
237             boolean objectIsHidden = MCRMetadataHistoryManager.objectIsHidden(obj);
238             if (objectIsHidden) {
239                 return Stream.of(create, delete(objId, user, lastModified.plusMillis(1)));
240             }
241             return Stream.of(create);
242         } else {
243             return Stream.of(delete(objId, null, Instant.now()));
244         }
245     }
246 
247     private static Stream<MCRMetaHistoryItem> buildDerivateHistory(MCRObjectID derId,
248         List<? extends MCRAbstractMetadataVersion<?>> versions)
249         throws IOException {
250         boolean exist = false;
251         LogManager.getLogger().debug("Complete history rebuild of {} should be possible", derId);
252         ArrayList<MCRMetaHistoryItem> items = new ArrayList<>(100);
253         for (MCRAbstractMetadataVersion<?> version : versions) {
254             String user = version.getUser();
255             Instant revDate = version.getDate().toInstant();
256             if (version.getType() == MCRMetadataVersion.DELETED) {
257                 if (exist) {
258                     items.add(delete(derId, user, revDate));
259                     exist = false;
260                 }
261             } else {
262                 //created or updated
263                 int timeOffset = 0;
264                 if (version.getType() == MCRMetadataVersion.CREATED && !exist) {
265                     items.add(create(derId, user, revDate));
266                     timeOffset = 1;
267                     exist = true;
268                 }
269                 try {
270                     MCRDerivate derivate = new MCRDerivate(version.retrieve().asXML());
271                     boolean derivateIsHidden = !MCRAccessManager
272                         .checkDerivateDisplayPermission(derivate.getId().toString());
273                     if (derivateIsHidden && exist) {
274                         items.add(delete(derId, user, revDate.plusMillis(timeOffset)));
275                         exist = false;
276                     } else if (!derivateIsHidden && !exist) {
277                         items.add(create(derId, user,
278                             revDate.plusMillis(timeOffset)));
279                         exist = true;
280                     }
281                 } catch (JDOMException | SAXException e) {
282                     LogManager.getLogger()
283                         .error("Error while reading revision {} of {}", version.getRevision(), derId, e);
284                 }
285             }
286         }
287         return items.stream();
288     }
289 
290     private static Stream<MCRMetaHistoryItem> buildObjectHistory(MCRObjectID objId,
291         List<? extends MCRAbstractMetadataVersion<?>> versions)
292         throws IOException {
293         boolean exist = false;
294         LogManager.getLogger().debug("Complete history rebuild of {} should be possible", objId);
295         ArrayList<MCRMetaHistoryItem> items = new ArrayList<>(100);
296         for (MCRAbstractMetadataVersion<?> version : versions) {
297             String user = version.getUser();
298             Instant revDate = version.getDate().toInstant();
299             if (version.getType() == MCRMetadataVersion.DELETED) {
300                 if (exist) {
301                     items.add(delete(objId, user, revDate));
302                     exist = false;
303                 }
304             } else {
305                 //created or updated
306                 int timeOffset = 0;
307                 if (version.getType() == MCRMetadataVersion.CREATED && !exist) {
308                     items.add(create(objId, user, revDate));
309                     timeOffset = 1;
310                     exist = true;
311                 }
312                 try {
313                     MCRObject obj = new MCRObject(version.retrieve().asXML());
314                     boolean objectIsHidden = MCRMetadataHistoryManager.objectIsHidden(obj);
315                     if (objectIsHidden && exist) {
316                         items.add(delete(objId, user, revDate.plusMillis(timeOffset)));
317                         exist = false;
318                     } else if (!objectIsHidden && !exist) {
319                         items.add(create(objId, user, revDate.plusMillis(timeOffset)));
320                         exist = true;
321                     }
322                 } catch (JDOMException | SAXException e) {
323                     LogManager.getLogger()
324                         .error("Error while reading revision {} of {}", version.getRevision(), objId, e);
325                 }
326             }
327         }
328         return items.stream();
329     }
330 
331     private static MCRMetaHistoryItem create(MCRObjectID mcrid, String author, Instant instant) {
332         return newHistoryItem(mcrid, author, instant, MCRMetadataHistoryEventType.Create);
333     }
334 
335     private static MCRMetaHistoryItem delete(MCRObjectID mcrid, String author, Instant instant) {
336         return newHistoryItem(mcrid, author, instant, MCRMetadataHistoryEventType.Delete);
337     }
338 
339     private static MCRMetaHistoryItem newHistoryItem(MCRObjectID mcrid, String author, Instant instant,
340         MCRMetadataHistoryEventType eventType) {
341         MCRMetaHistoryItem item = new MCRMetaHistoryItem();
342         item.setId(mcrid);
343         item.setTime(instant);
344         item.setUserID(author);
345         item.setEventType(eventType);
346         LogManager.getLogger().debug(() -> item);
347         return item;
348     }
349 
350 }