1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.mycore.pi.urn.rest;
20
21 import java.io.IOException;
22 import java.nio.file.Files;
23 import java.nio.file.Path;
24 import java.text.MessageFormat;
25 import java.util.ArrayList;
26 import java.util.Arrays;
27 import java.util.Date;
28 import java.util.Iterator;
29 import java.util.LinkedHashMap;
30 import java.util.List;
31 import java.util.Locale;
32 import java.util.Optional;
33 import java.util.function.BiConsumer;
34 import java.util.function.Function;
35 import java.util.function.Predicate;
36 import java.util.function.Supplier;
37 import java.util.regex.Pattern;
38 import java.util.stream.Collectors;
39 import java.util.stream.Stream;
40
41 import org.apache.logging.log4j.LogManager;
42 import org.apache.logging.log4j.Logger;
43 import org.mycore.access.MCRAccessException;
44 import org.mycore.backend.jpa.MCREntityManagerProvider;
45 import org.mycore.common.MCRException;
46 import org.mycore.common.MCRPersistenceException;
47 import org.mycore.datamodel.common.MCRActiveLinkException;
48 import org.mycore.datamodel.metadata.MCRBase;
49 import org.mycore.datamodel.metadata.MCRDerivate;
50 import org.mycore.datamodel.metadata.MCRMetadataManager;
51 import org.mycore.datamodel.metadata.MCRObjectDerivate;
52 import org.mycore.datamodel.metadata.MCRObjectID;
53 import org.mycore.datamodel.metadata.MCRObjectService;
54 import org.mycore.datamodel.niofs.MCRPath;
55 import org.mycore.pi.MCRPIManager;
56 import org.mycore.pi.MCRPIService;
57 import org.mycore.pi.backend.MCRPI;
58 import org.mycore.pi.exceptions.MCRPersistentIdentifierException;
59 import org.mycore.pi.urn.MCRDNBURN;
60 import org.mycore.pi.urn.MCRDNBURNParser;
61
62 import com.google.gson.Gson;
63
64 import jakarta.persistence.EntityTransaction;
65
66
67
68
69
70
71
72
73
74
75
76
77 public class MCRURNGranularRESTService extends MCRPIService<MCRDNBURN> {
78
79 private static final Logger LOGGER = LogManager.getLogger();
80
81 private final Function<MCRDerivate, Stream<MCRPath>> derivateFileStream;
82
83 public MCRURNGranularRESTService() {
84 this(MCRURNGranularRESTService::defaultDerivateFileStream);
85 }
86
87 public MCRURNGranularRESTService(Function<MCRDerivate, Stream<MCRPath>> derivateFileStreamFunc) {
88 super(MCRDNBURN.TYPE);
89 this.derivateFileStream = derivateFileStreamFunc;
90 }
91
92 private static Stream<MCRPath> defaultDerivateFileStream(MCRDerivate derivate) {
93 MCRObjectID derivateId = derivate.getId();
94 Path derivRoot = MCRPath.getPath(derivateId.toString(), "/");
95
96 try {
97 return Files.walk(derivRoot)
98 .map(MCRPath::toMCRPath)
99 .filter(p -> !Files.isDirectory(p))
100 .filter(p -> !p.equals(derivRoot));
101 } catch (IOException e) {
102 LOGGER.error("I/O error while access the starting file of derivate {}!", derivateId, e);
103 } catch (SecurityException s) {
104 LOGGER.error("No access to starting file of derivate {}!", derivateId, s);
105 }
106
107 return Stream.empty();
108 }
109
110 @Override
111 public MCRDNBURN register(MCRBase obj, String filePath, boolean updateObject)
112 throws MCRAccessException, MCRActiveLinkException, MCRPersistentIdentifierException {
113 this.validateRegistration(obj, filePath);
114
115 if (obj instanceof MCRDerivate) {
116 MCRDerivate derivate = (MCRDerivate) obj;
117 return registerURN(derivate, filePath);
118 } else {
119 throw new MCRPersistentIdentifierException("Object " + obj.getId() + " is not a MCRDerivate!");
120 }
121 }
122
123 private MCRDNBURN registerURN(MCRDerivate deriv, String filePath) throws MCRPersistentIdentifierException {
124 MCRObjectID derivID = deriv.getId();
125
126 Function<String, Integer> countCreatedPI = s -> MCRPIManager
127 .getInstance()
128 .getCreatedIdentifiers(derivID, getType(), getServiceID())
129 .size();
130
131 int seed = Optional.of(filePath)
132 .filter(p -> !"".equals(p))
133 .map(countCreatedPI)
134 .map(count -> count + 1)
135 .orElse(1);
136
137 MCRDNBURN derivURN = Optional
138 .ofNullable(deriv.getDerivate())
139 .map(MCRObjectDerivate::getURN)
140 .flatMap(new MCRDNBURNParser()::parse)
141 .orElseGet(() -> createNewURN(deriv));
142
143 String setID = derivID.getNumberAsString();
144 GranularURNGenerator granularURNGen = new GranularURNGenerator(seed, derivURN, setID);
145 Function<MCRPath, Supplier<String>> generateURN = p -> granularURNGen.getURNSupplier();
146
147 LinkedHashMap<Supplier<String>, MCRPath> urnPathMap = derivateFileStream.apply(deriv)
148 .filter(notInIgnoreList().and(matchFile(filePath)))
149 .sorted()
150 .collect(Collectors.toMap(generateURN, p -> p, (m1, m2) -> m1,
151 LinkedHashMap::new));
152
153 if (!"".equals(filePath) && urnPathMap.isEmpty()) {
154 String errMsg = new MessageFormat("File {0} does not exist in {1}.\n", Locale.ROOT)
155 .format(new Object[] { filePath, derivID.toString() })
156 + "Use absolute path of file without owner ID like /abs/path/to/file.\n";
157
158 throw new MCRPersistentIdentifierException(errMsg);
159 }
160
161 urnPathMap.forEach(createFileMetadata(deriv).andThen(persistURN(deriv)));
162
163 try {
164 MCRMetadataManager.update(deriv);
165 } catch (MCRPersistenceException | MCRAccessException e) {
166 LOGGER.error("Error while updating derivate {}", derivID, e);
167 }
168
169 EntityTransaction transaction = MCREntityManagerProvider
170 .getCurrentEntityManager()
171 .getTransaction();
172
173 if (!transaction.isActive()) {
174 transaction.begin();
175 }
176
177 transaction.commit();
178
179 return derivURN;
180 }
181
182 public MCRDNBURN createNewURN(MCRDerivate deriv) {
183 MCRObjectID derivID = deriv.getId();
184
185 try {
186 MCRDNBURN derivURN = getNewIdentifier(deriv, "");
187 deriv.getDerivate().setURN(derivURN.asString());
188
189 persistURNStr(deriv, null).accept(derivURN::asString, "");
190
191 if (Boolean.valueOf(getProperties().getOrDefault("supportDfgViewerURN", "false"))) {
192 String suffix = "dfg";
193 persistURNStr(deriv, null, getServiceID() + "-" + suffix)
194 .accept(() -> derivURN.withNamespaceSuffix(suffix + "-").asString(), "");
195 }
196
197 return derivURN;
198 } catch (MCRPersistentIdentifierException e) {
199 throw new MCRPICreationException("Could not create new URN for " + derivID, e);
200 }
201 }
202
203 private BiConsumer<Supplier<String>, MCRPath> createFileMetadata(MCRDerivate deriv) {
204 return (urnSup, path) -> deriv.getDerivate().getOrCreateFileMetadata(path, urnSup.get());
205 }
206
207 private BiConsumer<Supplier<String>, MCRPath> persistURN(MCRDerivate deriv) {
208 return (urnSup, path) -> persistURNStr(deriv, null).accept(urnSup, path.getOwnerRelativePath());
209 }
210
211 private BiConsumer<Supplier<String>, String> persistURNStr(MCRDerivate deriv, Date registerDate) {
212 return (urnSup, path) -> persistURNStr(deriv, registerDate, getServiceID()).accept(urnSup, path);
213 }
214
215 private BiConsumer<Supplier<String>, String> persistURNStr(MCRDerivate deriv, Date registerDate, String serviceID) {
216 return (urnSup, path) -> {
217 MCRPI mcrpi = new MCRPI(urnSup.get(), getType(), deriv.getId().toString(), path, serviceID,
218 registerDate);
219 MCREntityManagerProvider.getCurrentEntityManager().persist(mcrpi);
220 };
221 }
222
223 private Predicate<MCRPath> matchFile(String ownerRelativPath) {
224 return path -> Optional.of(ownerRelativPath)
225 .filter(""::equals)
226 .map(p -> Boolean.TRUE)
227 .orElseGet(() -> path.getOwnerRelativePath().equals(ownerRelativPath));
228
229 }
230
231 private Predicate<MCRPath> notInIgnoreList() {
232 Supplier<? extends RuntimeException> errorInIgnorList = () -> new RuntimeException(
233 "Error in ignore filename list!");
234
235 return path -> getIgnoreFileList()
236 .stream()
237 .map(Pattern::compile)
238 .map(Pattern::asPredicate)
239 .map(Predicate::negate)
240 .reduce(Predicate::and)
241 .orElseThrow(errorInIgnorList)
242 .test(path.getOwnerRelativePath());
243 }
244
245 private List<String> getIgnoreFileList() {
246 List<String> ignoreFileNamesList = new ArrayList<>();
247 String ignoreFileNames = getProperties().get("IgnoreFileNames");
248 if (ignoreFileNames != null) {
249 ignoreFileNamesList.addAll(Arrays.asList(ignoreFileNames.split(",")));
250 } else {
251 ignoreFileNamesList.add("mets\\.xml");
252 }
253 return ignoreFileNamesList;
254 }
255
256 @Override
257 protected void registerIdentifier(MCRBase obj, String additional, MCRDNBURN urn)
258 throws MCRPersistentIdentifierException {
259
260 }
261
262 @Override
263 protected void delete(MCRDNBURN identifier, MCRBase obj, String additional)
264 throws MCRPersistentIdentifierException {
265 throw new MCRPersistentIdentifierException("Delete is not supported for " + getType());
266 }
267
268 @Override
269 protected void update(MCRDNBURN identifier, MCRBase obj, String additional)
270 throws MCRPersistentIdentifierException {
271
272 LOGGER.info("No update in this implementation");
273 }
274
275 @Override
276 public void updateFlag(MCRObjectID id, String additional, MCRPI mcrpi) {
277 MCRBase obj = MCRMetadataManager.retrieve(id);
278 MCRObjectService service = obj.getService();
279 ArrayList<String> flags = service.getFlags(MCRPIService.PI_FLAG);
280 Gson gson = getGson();
281
282
283 if ("".equals(additional)) {
284 Iterator<String> flagsIter = flags.iterator();
285 while (flagsIter.hasNext()) {
286 String flagStr = flagsIter.next();
287 MCRPI currentPi = gson.fromJson(flagStr, MCRPI.class);
288
289 if ("".equals(currentPi.getAdditional())
290 && currentPi.getIdentifier().equals(mcrpi.getIdentifier())) {
291
292 flagsIter.remove();
293 }
294 }
295
296 addFlagToObject(obj, mcrpi);
297 try {
298 MCRMetadataManager.update(obj);
299 } catch (Exception e) {
300 throw new MCRException("Could not update flags of object " + id, e);
301 }
302 }
303 }
304
305 private static class GranularURNGenerator {
306 private final MCRDNBURN urn;
307
308 private final String setID;
309
310 private int counter;
311
312 private String leadingZeros;
313
314 GranularURNGenerator(int seed, MCRDNBURN derivURN, String setID) {
315 this.counter = seed;
316 this.urn = derivURN;
317 this.setID = setID;
318 }
319
320 public Supplier<String> getURNSupplier() {
321 int currentCount = counter++;
322 return () -> urn.toGranular(setID, getIndex(currentCount)).asString();
323 }
324
325 public String getIndex(int currentCount) {
326 return String.format(Locale.getDefault(), leadingZeros(counter), currentCount);
327 }
328
329 private String leadingZeros(int i) {
330 if (leadingZeros == null) {
331 leadingZeros = "%0" + numDigits(i) + "d";
332 }
333
334 return leadingZeros;
335 }
336
337 private long numDigits(long n) {
338 if (n < 10) {
339 return 1;
340 }
341 return 1 + numDigits(n / 10);
342 }
343 }
344
345 public class MCRPICreationException extends RuntimeException {
346 public MCRPICreationException(String message, Throwable cause) {
347 super(message, cause);
348 }
349 }
350 }