1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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
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
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
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 }