1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.mycore.common;
20
21 import java.io.File;
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.io.PrintWriter;
25 import java.io.Reader;
26 import java.io.StringWriter;
27 import java.lang.reflect.Constructor;
28 import java.lang.reflect.InvocationTargetException;
29 import java.nio.charset.Charset;
30 import java.nio.charset.StandardCharsets;
31 import java.nio.file.FileSystem;
32 import java.nio.file.FileVisitResult;
33 import java.nio.file.Files;
34 import java.nio.file.Path;
35 import java.nio.file.Paths;
36 import java.nio.file.SimpleFileVisitor;
37 import java.nio.file.StandardCopyOption;
38 import java.nio.file.StandardOpenOption;
39 import java.nio.file.attribute.FileTime;
40 import java.security.MessageDigest;
41 import java.security.NoSuchAlgorithmException;
42 import java.text.Normalizer;
43 import java.text.Normalizer.Form;
44 import java.time.Duration;
45 import java.util.ArrayList;
46 import java.util.Collections;
47 import java.util.HashMap;
48 import java.util.Locale;
49 import java.util.Optional;
50 import java.util.Objects;
51 import java.util.Properties;
52 import java.util.concurrent.TimeUnit;
53 import java.util.function.Consumer;
54 import java.util.function.Function;
55 import java.util.stream.Stream;
56
57 import javax.xml.parsers.SAXParser;
58 import javax.xml.parsers.SAXParserFactory;
59
60 import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
61 import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
62 import org.apache.commons.io.IOUtils;
63 import org.apache.logging.log4j.LogManager;
64 import org.apache.logging.log4j.Logger;
65 import org.mycore.common.config.MCRConfigurationException;
66 import org.mycore.common.content.streams.MCRDevNull;
67 import org.mycore.common.content.streams.MCRMD5InputStream;
68 import org.mycore.common.function.MCRThrowableTask;
69 import org.mycore.datamodel.niofs.MCRPathUtils;
70 import org.xml.sax.Attributes;
71 import org.xml.sax.InputSource;
72 import org.xml.sax.helpers.DefaultHandler;
73
74 import jakarta.xml.bind.DatatypeConverter;
75
76
77
78
79
80
81
82
83
84 public class MCRUtils {
85
86 private static final Logger LOGGER = LogManager.getLogger();
87
88
89 private static String SLASH = System.getProperty("file.separator");
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109 public static int readBlocking(InputStream in, byte[] b, int off, int len) throws IOException {
110 int totalBytesRead = 0;
111
112 while (totalBytesRead < len) {
113 int bytesRead = in.read(b, off + totalBytesRead, len - totalBytesRead);
114
115 if (bytesRead < 0) {
116 break;
117 }
118
119 totalBytesRead += bytesRead;
120 }
121
122 return totalBytesRead;
123 }
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143 public static int readBlocking(Reader in, char[] c, int off, int len) throws IOException {
144 int totalCharsRead = 0;
145
146 while (totalCharsRead < len) {
147 int charsRead = in.read(c, off + totalCharsRead, len - totalCharsRead);
148
149 if (charsRead < 0) {
150 break;
151 }
152
153 totalCharsRead += charsRead;
154 }
155
156 return totalCharsRead;
157 }
158
159
160
161
162
163
164
165
166
167
168 public static Path writeTextToFile(String textToWrite, String fileName, Charset cs) throws IOException {
169 Path file = Paths.get(fileName);
170 Files.write(file, Collections.singletonList(textToWrite), cs, StandardOpenOption.CREATE);
171 return file;
172 }
173
174
175
176
177
178
179
180
181 public static ArrayList<String> getAllFileNames(File basedir) {
182 ArrayList<String> out = new ArrayList<>();
183 File[] stage = basedir.listFiles();
184
185 for (File element : stage) {
186 if (element.isFile()) {
187 out.add(element.getName());
188 }
189
190 if (element.isDirectory()) {
191 out.addAll(getAllFileNames(element, element.getName() + SLASH));
192 }
193 }
194
195 return out;
196 }
197
198
199
200
201
202
203
204
205
206
207 public static ArrayList<String> getAllFileNames(File basedir, String path) {
208 ArrayList<String> out = new ArrayList<>();
209 File[] stage = basedir.listFiles();
210
211 for (File element : stage) {
212 if (element.isFile()) {
213 out.add(path + element.getName());
214 }
215
216 if (element.isDirectory()) {
217 out.addAll(getAllFileNames(element, path + element.getName() + SLASH));
218 }
219 }
220
221 return out;
222 }
223
224
225
226
227
228
229
230
231 public static ArrayList<String> getAllDirectoryNames(File basedir) {
232 ArrayList<String> out = new ArrayList<>();
233 File[] stage = basedir.listFiles();
234
235 for (File element : stage) {
236 if (element.isDirectory()) {
237 out.add(element.getName());
238 out.addAll(getAllDirectoryNames(element, element.getName() + SLASH));
239 }
240 }
241
242 return out;
243 }
244
245
246
247
248
249
250
251
252
253
254 public static ArrayList<String> getAllDirectoryNames(File basedir, String path) {
255 ArrayList<String> out = new ArrayList<>();
256 File[] stage = basedir.listFiles();
257
258 for (File element : stage) {
259 if (element.isDirectory()) {
260 out.add(path + element.getName());
261 out.addAll(getAllDirectoryNames(element, path + element.getName() + SLASH));
262 }
263 }
264
265 return out;
266 }
267
268 public static String parseDocumentType(InputStream in) {
269 SAXParser parser = null;
270
271 try {
272 parser = SAXParserFactory.newInstance().newSAXParser();
273 } catch (Exception ex) {
274 String msg = "Could not build a SAX Parser for processing XML input";
275 throw new MCRConfigurationException(msg, ex);
276 }
277
278 final Properties detected = new Properties();
279 final String forcedInterrupt = "mcr.forced.interrupt";
280
281 DefaultHandler handler = new DefaultHandler() {
282 @Override
283 public void startElement(String uri, String localName, String qName, Attributes attributes) {
284 LOGGER.debug("MCRLayoutService detected root element = {}", qName);
285 detected.setProperty("docType", qName);
286 throw new MCRException(forcedInterrupt);
287 }
288 };
289
290 try {
291 parser.parse(new InputSource(in), handler);
292 } catch (Exception ex) {
293 if (!forcedInterrupt.equals(ex.getMessage())) {
294 String msg = "Error while detecting XML document type from input source";
295 throw new MCRException(msg, ex);
296 }
297 }
298
299 String docType = detected.getProperty("docType");
300 int pos = docType.indexOf(':') + 1;
301 if (pos > 0) {
302
303 docType = docType.substring(pos);
304 }
305 return docType;
306
307 }
308
309 public static String asSHA1String(int iterations, byte[] salt, String text) throws NoSuchAlgorithmException {
310 return getHash(iterations, salt, text, "SHA-1");
311 }
312
313 public static String asSHA256String(int iterations, byte[] salt, String text) throws NoSuchAlgorithmException {
314 return getHash(iterations, salt, text, "SHA-256");
315 }
316
317 public static String asMD5String(int iterations, byte[] salt, String text) throws NoSuchAlgorithmException {
318 return getHash(iterations, salt, text, "MD5");
319 }
320
321 public static String asCryptString(String salt, String text) {
322 return MCRCrypt.crypt(salt, text);
323 }
324
325 private static String getHash(int iterations, byte[] salt, String text, String algorithm)
326 throws NoSuchAlgorithmException {
327 MessageDigest digest;
328 if (--iterations < 0) {
329 iterations = 0;
330 }
331 byte[] data;
332 digest = MessageDigest.getInstance(algorithm);
333 text = Normalizer.normalize(text, Form.NFC);
334 if (salt != null) {
335 digest.update(salt);
336 }
337 data = digest.digest(text.getBytes(StandardCharsets.UTF_8));
338 for (int i = 0; i < iterations; i++) {
339 data = digest.digest(data);
340 }
341 return toHexString(data);
342 }
343
344 public static String toHexString(byte[] data) {
345 return DatatypeConverter.printHexBinary(data).toLowerCase(Locale.ROOT);
346 }
347
348
349
350
351 public static String getMD5Sum(InputStream inputStream) throws IOException {
352 MCRMD5InputStream md5InputStream = null;
353 try {
354 md5InputStream = new MCRMD5InputStream(inputStream);
355 IOUtils.copy(md5InputStream, new MCRDevNull());
356 return md5InputStream.getMD5String();
357 } finally {
358 if (md5InputStream != null) {
359 md5InputStream.close();
360 }
361 }
362 }
363
364
365
366
367
368
369
370
371
372
373
374 public static void untar(Path source, Path expandToDirectory) throws IOException {
375 try (TarArchiveInputStream tain = new TarArchiveInputStream(Files.newInputStream(source))) {
376 TarArchiveEntry tarEntry;
377 FileSystem targetFS = expandToDirectory.getFileSystem();
378 HashMap<Path, FileTime> directoryTimes = new HashMap<>();
379 while ((tarEntry = tain.getNextTarEntry()) != null) {
380 Path target = MCRPathUtils.getPath(targetFS, tarEntry.getName());
381 Path absoluteTarget = expandToDirectory.resolve(target).normalize().toAbsolutePath();
382 if (tarEntry.isDirectory()) {
383 Files.createDirectories(expandToDirectory.resolve(absoluteTarget));
384 directoryTimes.put(absoluteTarget, FileTime.fromMillis(tarEntry.getLastModifiedDate().getTime()));
385 } else {
386 if (Files.notExists(absoluteTarget.getParent())) {
387 Files.createDirectories(absoluteTarget.getParent());
388 }
389 Files.copy(tain, absoluteTarget, StandardCopyOption.REPLACE_EXISTING);
390 Files.setLastModifiedTime(absoluteTarget,
391 FileTime.fromMillis(tarEntry.getLastModifiedDate().getTime()));
392 }
393 }
394
395 Files.walkFileTree(expandToDirectory, new SimpleFileVisitor<Path>() {
396 @Override
397 public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
398 Path absolutePath = dir.normalize().toAbsolutePath();
399 FileTime lastModifiedTime = directoryTimes.get(absolutePath);
400 if (lastModifiedTime != null) {
401 Files.setLastModifiedTime(absolutePath, lastModifiedTime);
402 } else {
403 LOGGER.warn("Could not restore last modified time for {} from TAR file {}.", absolutePath,
404 source);
405 }
406 return super.postVisitDirectory(dir, exc);
407 }
408 });
409 }
410 }
411
412 @SafeVarargs
413 public static Exception unwrapExCeption(Exception e, Class<? extends Exception>... classes) {
414 if (classes.length == 0) {
415 return e;
416 }
417 Class<? extends Exception> mainExceptionClass = classes[0];
418 Throwable check = e;
419 for (Class<? extends Exception> instChk : classes) {
420 if (instChk.isInstance(check)) {
421 return (Exception) check;
422 }
423 check = check.getCause();
424 if (check == null) {
425 break;
426 }
427 }
428 @SuppressWarnings("unchecked")
429 Constructor<? extends Exception>[] constructors = (Constructor<? extends Exception>[]) mainExceptionClass
430 .getConstructors();
431 for (Constructor<? extends Exception> c : constructors) {
432 Class<?>[] parameterTypes = c.getParameterTypes();
433 try {
434 if (parameterTypes.length == 0) {
435 Exception exception = c.newInstance((Object[]) null);
436 exception.initCause(e);
437 return exception;
438 }
439 if (parameterTypes.length == 1 && parameterTypes[0].isAssignableFrom(mainExceptionClass)) {
440 return c.newInstance(e);
441 }
442 } catch (InstantiationException | IllegalAccessException | IllegalArgumentException
443 | InvocationTargetException ex) {
444 LOGGER.warn("Exception while initializing exception {}", mainExceptionClass.getCanonicalName(), ex);
445 return e;
446 }
447 }
448 LOGGER.warn("Could not instanciate Exception {}", mainExceptionClass.getCanonicalName());
449 return e;
450 }
451
452
453
454
455
456
457 public static String getSizeFormatted(long bytes) {
458 String sizeUnit;
459 String sizeText;
460 double sizeValue;
461
462 if (bytes >= 1024 * 1024) {
463
464 sizeUnit = "MB";
465 sizeValue = (double) Math.round(bytes / 10485.76) / 100;
466 } else if (bytes >= 5 * 1024) {
467
468 sizeUnit = "KB";
469 sizeValue = (double) Math.round(bytes / 102.4) / 10;
470 } else {
471
472 sizeUnit = "Byte";
473 sizeValue = bytes;
474 }
475
476 sizeText = String.valueOf(sizeValue).replace('.', ',');
477
478 if (sizeText.endsWith(",0")) {
479 sizeText = sizeText.substring(0, sizeText.length() - 2);
480 }
481
482 return sizeText + " " + sizeUnit;
483 }
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502 @SafeVarargs
503 @SuppressWarnings("unchecked")
504 public static <T> int compareParts(T first, T other, Function<T, Comparable>... part) {
505 return Stream.of(part)
506 .mapToInt(f -> f.apply(first).compareTo(f.apply(other)))
507 .filter(i -> i != 0)
508 .findFirst()
509 .orElse(0);
510 }
511
512
513
514
515
516 public static String getStackTraceAsString(Throwable t) {
517 StringWriter sw = new StringWriter();
518 t.printStackTrace(new PrintWriter(sw));
519 return sw.toString();
520 }
521
522
523
524
525
526
527 public static Optional<String> filterTrimmedNotEmpty(String value) {
528 return Optional.ofNullable(value)
529 .map(String::trim)
530 .filter(s -> !s.isEmpty());
531 }
532
533
534
535
536
537
538
539
540
541 public static <T extends Throwable> void measure(TimeUnit unit, Consumer<Long> timeHandler,
542 MCRThrowableTask<T> task) throws T {
543 long time = System.nanoTime();
544 try {
545 task.run();
546 } finally {
547 time = System.nanoTime() - time;
548 timeHandler.accept(unit.convert(time, TimeUnit.NANOSECONDS));
549 }
550 }
551
552
553
554
555
556
557 public static <T extends Throwable> Duration measure(MCRThrowableTask<T> task) throws T {
558 long time = System.nanoTime();
559 task.run();
560 time = System.nanoTime() - time;
561 return Duration.of(time, TimeUnit.NANOSECONDS.toChronoUnit());
562 }
563
564 public static Path safeResolve(Path basePath, Path resolve) {
565 Path absoluteBasePath = Objects.requireNonNull(basePath)
566 .toAbsolutePath();
567 final Path resolved = absoluteBasePath
568 .resolve(Objects.requireNonNull(resolve))
569 .normalize();
570
571 if (resolved.startsWith(absoluteBasePath)) {
572 return resolved;
573 }
574 throw new MCRException("Bad path: " + resolve);
575 }
576
577 public static Path safeResolve(Path basePath, String... resolve) {
578 if (resolve.length == 0) {
579 return basePath;
580 }
581
582 String[] more = Stream.of(resolve).skip(1).toArray(String[]::new);
583 final Path resolvePath = basePath.getFileSystem().getPath(resolve[0], more);
584 return safeResolve(basePath, resolvePath);
585 }
586 }