diff --git a/mycore-base/src/main/java/org/mycore/common/MCRDefaultHTTPClient.java b/mycore-base/src/main/java/org/mycore/common/MCRDefaultHTTPClient.java
new file mode 100644
index 0000000000..1973e0f114
--- /dev/null
+++ b/mycore-base/src/main/java/org/mycore/common/MCRDefaultHTTPClient.java
@@ -0,0 +1,119 @@
+/*
+ * This file is part of *** M y C o R e ***
+ * See http://www.mycore.de/ for details.
+ *
+ * MyCoRe is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * MyCoRe is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MyCoRe. If not, see .
+ */
+
+package org.mycore.common;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+
+import org.apache.http.client.cache.HttpCacheContext;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.cache.CacheConfig;
+import org.apache.http.impl.client.cache.CachingHttpClients;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.mycore.common.config.annotation.MCRProperty;
+import org.mycore.common.content.MCRContent;
+import org.mycore.common.content.MCRStreamContent;
+import org.mycore.common.events.MCRShutdownHandler;
+import org.mycore.services.http.MCRHttpUtils;
+
+public class MCRDefaultHTTPClient implements MCRHTTPClient {
+ private static Logger logger = LogManager.getLogger();
+
+ private long maxObjectSize;
+
+ private int maxCacheEntries;
+
+ private int requestTimeout;
+
+ private CloseableHttpClient restClient;
+
+ public MCRDefaultHTTPClient() {
+ CacheConfig cacheConfig = CacheConfig.custom()
+ .setMaxObjectSize(maxObjectSize)
+ .setMaxCacheEntries(maxCacheEntries)
+ .build();
+ RequestConfig requestConfig = RequestConfig.custom()
+ .setConnectTimeout(requestTimeout)
+ .setSocketTimeout(requestTimeout)
+ .build();
+ this.restClient = CachingHttpClients.custom()
+ .setCacheConfig(cacheConfig)
+ .setDefaultRequestConfig(requestConfig)
+ .setUserAgent(MCRHttpUtils.getHttpUserAgent())
+ .useSystemProperties()
+ .build();
+ MCRShutdownHandler.getInstance().addCloseable(this::close);
+ }
+
+ @MCRProperty(name = "MaxObjectSize")
+ public void setMaxObjectSize(String size) {
+ this.maxObjectSize = Long.parseLong(size);
+ }
+
+ @MCRProperty(name = "MaxCacheEntries")
+ public void setMaxCacheEntries(String size) {
+ this.maxCacheEntries = Integer.parseInt(size);
+ }
+
+ @MCRProperty(name = "RequestTimeout")
+ public void setRequestTimeout(String size) {
+ this.requestTimeout = Integer.parseInt(size);
+ }
+
+ public void close() {
+ try {
+ restClient.close();
+ } catch (IOException e) {
+ logger.warn("Exception while closing http client.", e);
+ }
+ }
+
+ @Override
+ public MCRContent get(URI hrefURI) throws IOException {
+ HttpCacheContext context = HttpCacheContext.create();
+ HttpGet get = new HttpGet(hrefURI);
+ MCRContent retContent = null;
+ try (CloseableHttpResponse response = restClient.execute(get, context);
+ InputStream content = response.getEntity().getContent();) {
+ logger.debug("http query: {}", hrefURI);
+ logger.debug("http resp status: {}", response.getStatusLine());
+ logger.debug(() -> getCacheDebugMsg(hrefURI, context));
+ retContent = (new MCRStreamContent(content)).getReusableCopy();
+ } finally {
+ get.reset();
+ }
+ return retContent;
+ }
+
+ private String getCacheDebugMsg(URI hrefURI, HttpCacheContext context) {
+ return hrefURI.toASCIIString() + ": " +
+ switch (context.getCacheResponseStatus()) {
+ case CACHE_HIT -> "A response was generated from the cache with no requests sent upstream";
+ case CACHE_MODULE_RESPONSE -> "The response was generated directly by the caching module";
+ case CACHE_MISS -> "The response came from an upstream server";
+ case VALIDATED -> "The response was generated from the cache after validating the entry "
+ + "with the origin server";
+ };
+ }
+}
diff --git a/mycore-base/src/main/java/org/mycore/common/MCRHTTPClient.java b/mycore-base/src/main/java/org/mycore/common/MCRHTTPClient.java
new file mode 100644
index 0000000000..2b20326d58
--- /dev/null
+++ b/mycore-base/src/main/java/org/mycore/common/MCRHTTPClient.java
@@ -0,0 +1,30 @@
+/*
+ * This file is part of *** M y C o R e ***
+ * See http://www.mycore.de/ for details.
+ *
+ * MyCoRe is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * MyCoRe is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MyCoRe. If not, see .
+ */
+
+package org.mycore.common;
+
+import java.io.IOException;
+import java.net.URI;
+
+import org.mycore.common.content.MCRContent;
+
+public interface MCRHTTPClient {
+ MCRContent get(URI hrefURI) throws IOException;
+
+ void close();
+}
diff --git a/mycore-base/src/main/java/org/mycore/common/xml/MCRURIResolver.java b/mycore-base/src/main/java/org/mycore/common/xml/MCRURIResolver.java
index bba946b247..4e074d8d7e 100644
--- a/mycore-base/src/main/java/org/mycore/common/xml/MCRURIResolver.java
+++ b/mycore-base/src/main/java/org/mycore/common/xml/MCRURIResolver.java
@@ -58,14 +58,7 @@
import javax.xml.xpath.XPathExpressionException;
import org.apache.commons.lang3.StringUtils;
-import org.apache.http.client.cache.HttpCacheContext;
-import org.apache.http.client.config.RequestConfig;
-import org.apache.http.client.methods.CloseableHttpResponse;
-import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.utils.URLEncodedUtils;
-import org.apache.http.impl.client.CloseableHttpClient;
-import org.apache.http.impl.client.cache.CacheConfig;
-import org.apache.http.impl.client.cache.CachingHttpClients;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.Marker;
@@ -82,6 +75,7 @@
import org.mycore.common.MCRCoreVersion;
import org.mycore.common.MCRDeveloperTools;
import org.mycore.common.MCRException;
+import org.mycore.common.MCRHTTPClient;
import org.mycore.common.MCRSessionMgr;
import org.mycore.common.MCRUsageException;
import org.mycore.common.MCRUserInformation;
@@ -95,7 +89,6 @@
import org.mycore.common.content.transformer.MCRContentTransformer;
import org.mycore.common.content.transformer.MCRParameterizedTransformer;
import org.mycore.common.content.transformer.MCRXSLTransformer;
-import org.mycore.common.events.MCRShutdownHandler;
import org.mycore.common.xsl.MCRLazyStreamSource;
import org.mycore.common.xsl.MCRParameterCollector;
import org.mycore.datamodel.classifications2.MCRCategory;
@@ -114,7 +107,6 @@
import org.mycore.datamodel.niofs.MCRPath;
import org.mycore.datamodel.niofs.MCRPathXML;
import org.mycore.frontend.MCRLayoutUtilities;
-import org.mycore.services.http.MCRHttpUtils;
import org.mycore.services.i18n.MCRTranslation;
import org.mycore.tools.MCRObjectFactory;
import org.xml.sax.InputSource;
@@ -140,6 +132,8 @@ public final class MCRURIResolver implements URIResolver {
private static final String CONFIG_PREFIX = "MCR.URIResolver.";
+ private static final String HTTP_CLIENT_CLASS = "MCR.HTTPClient.Class";
+
private static final Marker UNIQUE_MARKER = MarkerManager.getMarker("tryResolveXML");
private static Map SUPPORTED_SCHEMES;
@@ -539,77 +533,23 @@ public Source resolve(String href, String base) throws TransformerException {
}
private static class MCRRESTResolver implements URIResolver {
- private static final long MAX_OBJECT_SIZE = MCRConfiguration2.getLong(CONFIG_PREFIX + "REST.MaxObjectSize")
- .orElse(128 * 1024L);
-
- private static final int MAX_CACHE_ENTRIES = MCRConfiguration2.getInt(CONFIG_PREFIX + "REST.MaxCacheEntries")
- .orElse(1000);
-
- private static final int REQUEST_TIMEOUT = MCRConfiguration2.getInt(CONFIG_PREFIX + "REST.RequestTimeout")
- .orElse(30000);
-
- private CloseableHttpClient restClient;
-
- private org.apache.logging.log4j.Logger logger;
+ private MCRHTTPClient client;
MCRRESTResolver() {
- CacheConfig cacheConfig = CacheConfig.custom()
- .setMaxObjectSize(MAX_OBJECT_SIZE)
- .setMaxCacheEntries(MAX_CACHE_ENTRIES)
- .build();
- RequestConfig requestConfig = RequestConfig.custom()
- .setConnectTimeout(REQUEST_TIMEOUT)
- .setSocketTimeout(REQUEST_TIMEOUT)
- .build();
- this.restClient = CachingHttpClients.custom()
- .setCacheConfig(cacheConfig)
- .setDefaultRequestConfig(requestConfig)
- .setUserAgent(MCRHttpUtils.getHttpUserAgent())
- .useSystemProperties()
- .build();
- MCRShutdownHandler.getInstance().addCloseable(this::close);
- this.logger = LogManager.getLogger();
- }
-
- public void close() {
- try {
- restClient.close();
- } catch (IOException e) {
- LogManager.getLogger().warn("Exception while closing http client.", e);
- }
+ this.client = (MCRHTTPClient) MCRConfiguration2.getInstanceOf(HTTP_CLIENT_CLASS).get();
}
@Override
public Source resolve(String href, String base) throws TransformerException {
URI hrefURI = MCRURIResolver.resolveURI(href, base);
try {
- HttpCacheContext context = HttpCacheContext.create();
- HttpGet get = new HttpGet(hrefURI);
- try (CloseableHttpResponse response = restClient.execute(get, context);
- InputStream content = response.getEntity().getContent()) {
- logger.debug(() -> getCacheDebugMsg(hrefURI, context));
- final Source source = new MCRStreamContent(content).getReusableCopy().getSource();
- source.setSystemId(hrefURI.toASCIIString());
- return source;
- } finally {
- get.reset();
- }
+ final Source source = client.get(hrefURI).getSource();
+ source.setSystemId(hrefURI.toASCIIString());
+ return source;
} catch (IOException e) {
throw new TransformerException(e);
}
}
-
- private String getCacheDebugMsg(URI hrefURI, HttpCacheContext context) {
- return hrefURI.toASCIIString() + ": " +
- switch (context.getCacheResponseStatus()) {
- case CACHE_HIT -> "A response was generated from the cache with no requests sent upstream";
- case CACHE_MODULE_RESPONSE -> "The response was generated directly by the caching module";
- case CACHE_MISS -> "The response came from an upstream server";
- case VALIDATED -> "The response was generated from the cache after validating the entry "
- + "with the origin server";
- };
- }
-
}
private static class MCRObjectResolver implements URIResolver {
diff --git a/mycore-base/src/main/java/org/mycore/common/xml/MCRXMLFunctions.java b/mycore-base/src/main/java/org/mycore/common/xml/MCRXMLFunctions.java
index 715cf74cec..93942a8080 100644
--- a/mycore-base/src/main/java/org/mycore/common/xml/MCRXMLFunctions.java
+++ b/mycore-base/src/main/java/org/mycore/common/xml/MCRXMLFunctions.java
@@ -25,9 +25,11 @@
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLEncoder;
+import java.nio.file.FileStore;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.text.Normalizer;
@@ -842,7 +844,7 @@ public static boolean isHtml(final String s) {
}
/**
- * Strippes HTML tags from string.
+ * Stripes HTML tags from string.
*
* @param s string to strip HTML tags of
* @return the plain text without tags
@@ -856,6 +858,20 @@ public static String stripHtml(final String s) {
return StringEscapeUtils.unescapeHtml4(res.toString()).replaceAll(TAG_SELF_CLOSING, "");
}
+ /**
+ * Returns approximately the usable space in the MCR.datadir in bytes as in {@link FileStore#getUsableSpace()}.
+ *
+ * @return approximately the usable space in the MCR.datadir
+ * @throws IOException
+ * */
+ public static long getUsableSpace() throws IOException{
+ Path dataDir = Paths.get(MCRConfiguration2.getStringOrThrow("MCR.datadir"));
+ dataDir = dataDir.toRealPath();
+ FileStore fs = Files.getFileStore(dataDir);
+
+ return fs.getUsableSpace();
+ }
+
/**
* Converts a string to valid NCName.
*
diff --git a/mycore-base/src/main/java/org/mycore/datamodel/common/MCRFileBaseCacheObjectIDGenerator.java b/mycore-base/src/main/java/org/mycore/datamodel/common/MCRFileBaseCacheObjectIDGenerator.java
new file mode 100644
index 0000000000..4a52c89d61
--- /dev/null
+++ b/mycore-base/src/main/java/org/mycore/datamodel/common/MCRFileBaseCacheObjectIDGenerator.java
@@ -0,0 +1,214 @@
+/*
+ * This file is part of *** M y C o R e ***
+ * See http://www.mycore.de/ for details.
+ *
+ * MyCoRe is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * MyCoRe is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MyCoRe. If not, see .
+ */
+
+package org.mycore.datamodel.common;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.mycore.common.MCRException;
+import org.mycore.common.MCRUtils;
+import org.mycore.common.config.MCRConfiguration2;
+import org.mycore.datamodel.metadata.MCRObjectID;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.nio.channels.FileLock;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardOpenOption;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+/**
+ * This class generates object ids based on a file based cache. The cache is used to store the last generated id for a
+ * given base id. The cache file is located in the data directory of MyCoRe and is named "id_cache" and contains one
+ * file for each base id. The file contains the last generated id as a string.
+ */
+public class MCRFileBaseCacheObjectIDGenerator implements MCRObjectIDGenerator {
+
+ private static final Logger LOGGER = LogManager.getLogger();
+
+ static ConcurrentHashMap locks = new ConcurrentHashMap<>();
+
+ private static Path getCacheFilePath(String baseId) {
+
+ Path dataDir = getDataDirPath();
+
+ Path idCachePath = dataDir.resolve("id_cache");
+ if (!Files.exists(idCachePath)) {
+ synchronized (MCRFileBaseCacheObjectIDGenerator.class) {
+ if (!Files.exists(idCachePath)) {
+ try {
+ Files.createDirectory(idCachePath);
+ } catch (IOException e) {
+ throw new MCRException(
+ "Could not create " + idCachePath.toAbsolutePath() + " directory", e);
+ }
+ }
+ }
+ }
+
+ Path cacheFile = MCRUtils.safeResolve(idCachePath, baseId);
+ if (!Files.exists(cacheFile)) {
+ synchronized (MCRFileBaseCacheObjectIDGenerator.class) {
+ if (!Files.exists(cacheFile)) {
+ try {
+ Files.createFile(cacheFile);
+ } catch (IOException e) {
+ throw new MCRException("Could not create " + cacheFile.toAbsolutePath(), e);
+ }
+ }
+ }
+ }
+ return cacheFile;
+ }
+
+ static Path getDataDirPath() {
+ Path path = Paths.get(MCRConfiguration2.getStringOrThrow("MCR.datadir"));
+ if (Files.exists(path) && !Files.isDirectory(path)) {
+ throw new MCRException("Data directory does not exist or is not a directory: " + path);
+ }
+ return path;
+ }
+
+ private static void writeNewID(MCRObjectID nextID, ByteBuffer buffer, FileChannel channel, Path cacheFile)
+ throws IOException {
+ buffer.clear();
+ channel.position(0);
+ byte[] idAsBytes = nextID.toString().getBytes(StandardCharsets.UTF_8);
+ buffer.put(idAsBytes);
+ buffer.flip();
+ int written = channel.write(buffer);
+ if (written != idAsBytes.length) {
+ throw new MCRException("Could not write new ID to " + cacheFile.toAbsolutePath());
+ }
+ }
+
+ /**
+ * Set the next free id for the given baseId. Should only be used for migration purposes and the caller has to make
+ * sure that the cache file is not used by another process.
+ * @param baseId the base id
+ * @param next the next free id to be returned by getNextFreeId
+ */
+ public void setNextFreeId(String baseId, int next) {
+ Path cacheFile = getCacheFilePath(baseId);
+
+ int idLengthInBytes = MCRObjectID.formatID(baseId, 1).getBytes(StandardCharsets.UTF_8).length;
+ try (
+ FileChannel channel = FileChannel.open(cacheFile, StandardOpenOption.WRITE,
+ StandardOpenOption.SYNC, StandardOpenOption.CREATE);){
+ ByteBuffer buffer = ByteBuffer.allocate(idLengthInBytes);
+ channel.position(0);
+ writeNewID(MCRObjectID.getInstance(MCRObjectID.formatID(baseId, next-1)), buffer, channel, cacheFile);
+ } catch (FileNotFoundException e) {
+ throw new MCRException("Could not create " + cacheFile.toAbsolutePath(), e);
+ } catch (IOException e) {
+ throw new MCRException("Could not open " + cacheFile.toAbsolutePath(), e);
+ }
+ }
+
+ @Override
+ public MCRObjectID getNextFreeId(String baseId, int maxInWorkflow) {
+ Path cacheFile = getCacheFilePath(baseId);
+
+ MCRObjectID nextID;
+
+ ReentrantReadWriteLock lock = locks.computeIfAbsent(baseId, k -> new ReentrantReadWriteLock());
+ ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock();
+
+ try {
+ writeLock.lock();
+ try (
+ FileChannel channel = FileChannel.open(cacheFile, StandardOpenOption.READ, StandardOpenOption.WRITE,
+ StandardOpenOption.SYNC);
+ FileLock fileLock = channel.lock()) {
+
+ int idLengthInBytes = MCRObjectID.formatID(baseId, 1).getBytes(StandardCharsets.UTF_8).length;
+ ByteBuffer buffer = ByteBuffer.allocate(idLengthInBytes);
+ buffer.clear();
+ channel.position(0);
+ int bytesRead = channel.read(buffer);
+ if (bytesRead <= 0) {
+ LOGGER.info("No ID found in " + cacheFile.toAbsolutePath());
+ // empty file -> new currentID is 1
+ nextID = MCRObjectID.getInstance(MCRObjectID.formatID(baseId, maxInWorkflow + 1));
+ writeNewID(nextID, buffer, channel, cacheFile);
+ } else if (bytesRead == idLengthInBytes) {
+ buffer.flip();
+ MCRObjectID objectID = readObjectIDFromBuffer(idLengthInBytes, buffer);
+ int lastID = objectID.getNumberAsInteger();
+ nextID = MCRObjectID.getInstance(MCRObjectID.formatID(baseId, lastID + maxInWorkflow + 1));
+ writeNewID(nextID, buffer, channel, cacheFile);
+ } else {
+ throw new MCRException("Content has different id length " + cacheFile.toAbsolutePath());
+ }
+ } catch (FileNotFoundException e) {
+ throw new MCRException("Could not create " + cacheFile.toAbsolutePath(), e);
+ } catch (IOException e) {
+ throw new MCRException("Could not open " + cacheFile.toAbsolutePath(), e);
+ }
+ } finally {
+ writeLock.unlock();
+ }
+
+ return nextID;
+ }
+
+ private static MCRObjectID readObjectIDFromBuffer(int idLengthBytes, ByteBuffer buffer) {
+ byte[] idBytes = new byte[idLengthBytes];
+ buffer.get(idBytes);
+ String lastIDString = new String(idBytes, StandardCharsets.UTF_8);
+ return MCRObjectID.getInstance(lastIDString);
+ }
+
+ @Override
+ public MCRObjectID getLastID(String baseId) {
+ Path cacheFilePath = getCacheFilePath(baseId);
+ ReentrantReadWriteLock lock = locks.computeIfAbsent(baseId, k -> new ReentrantReadWriteLock());
+ ReentrantReadWriteLock.ReadLock readLock = lock.readLock();
+ try {
+ readLock.lock();
+ int idLengthInBytes = MCRObjectID.formatID(baseId, 1).getBytes(StandardCharsets.UTF_8).length;
+
+ try (FileChannel channel = FileChannel.open(cacheFilePath)) {
+ ByteBuffer buffer = ByteBuffer.allocate(idLengthInBytes);
+ buffer.clear();
+ channel.position(0);
+ int bytesRead = channel.read(buffer);
+ if (bytesRead == -1) {
+ // empty file -> no ID found
+ return null;
+ } else if (bytesRead == idLengthInBytes) {
+ buffer.flip();
+ return readObjectIDFromBuffer(idLengthInBytes, buffer);
+ } else {
+ throw new MCRException("Content has different id length " + cacheFilePath.toAbsolutePath());
+ }
+ } catch (IOException e) {
+ throw new MCRException("Could not open " + cacheFilePath.toAbsolutePath(), e);
+ }
+ } finally {
+ readLock.unlock();
+ }
+ }
+
+}
diff --git a/mycore-base/src/main/java/org/mycore/frontend/cli/MCRObjectCommands.java b/mycore-base/src/main/java/org/mycore/frontend/cli/MCRObjectCommands.java
index 0eea4718a2..35f6e304fd 100644
--- a/mycore-base/src/main/java/org/mycore/frontend/cli/MCRObjectCommands.java
+++ b/mycore-base/src/main/java/org/mycore/frontend/cli/MCRObjectCommands.java
@@ -84,6 +84,7 @@
import org.mycore.common.xsl.MCRErrorListener;
import org.mycore.datamodel.common.MCRAbstractMetadataVersion;
import org.mycore.datamodel.common.MCRActiveLinkException;
+import org.mycore.datamodel.common.MCRFileBaseCacheObjectIDGenerator;
import org.mycore.datamodel.common.MCRLinkTableManager;
import org.mycore.datamodel.common.MCRXMLMetadataManager;
import org.mycore.datamodel.metadata.MCRBase;
@@ -1386,6 +1387,20 @@ public static void repairSharedMetadata(String id) throws MCRAccessException {
MCRMetadataManager.repairSharedMetadata(obj);
}
+ @MCRCommand(
+ syntax = "create object id cache",
+ help = "Creates a cache for all object ids in the configuration directory.",
+ order = 175)
+ public static void createObjectIDCache() {
+ MCRXMLMetadataManager metadataManager = MCRXMLMetadataManager.instance();
+ metadataManager.getObjectBaseIds().forEach(id -> {
+ LOGGER.info("Creating cache for base {}", id);
+ int highestStoredID = metadataManager.getHighestStoredID(id);
+ MCRFileBaseCacheObjectIDGenerator gen = new MCRFileBaseCacheObjectIDGenerator();
+ gen.setNextFreeId(id, highestStoredID+1);
+ });
+ }
+
/**
* The method start the repair of the metadata search for a given MCRObjectID as String.
*
diff --git a/mycore-base/src/main/java/org/mycore/tools/MyCoReWebPageProvider.java b/mycore-base/src/main/java/org/mycore/tools/MyCoReWebPageProvider.java
index 30efd0b096..f925511ef9 100644
--- a/mycore-base/src/main/java/org/mycore/tools/MyCoReWebPageProvider.java
+++ b/mycore-base/src/main/java/org/mycore/tools/MyCoReWebPageProvider.java
@@ -88,7 +88,7 @@ public class MyCoReWebPageProvider {
public static final String TIME_FORMAT = "HH:mm";
- private Document xml;
+ private final Document xml;
public MyCoReWebPageProvider() {
this.xml = new Document();
@@ -148,11 +148,14 @@ public Element addSection(String title, List contentList, String lang)
if (lang != null) {
section.setAttribute(XML_LANG, lang, Namespace.XML_NAMESPACE);
}
- if (title != null && !title.equals("")) {
+ if (title != null && !title.isEmpty()) {
section.setAttribute(XML_TITLE, title);
}
for (Content content : contentList) {
- if (content instanceof Text) {
+ if (content instanceof Text text) {
+ if (text.getTextTrim().isEmpty()) {
+ continue;
+ }
// MyCoReWebPage.xsl ignores single text content -> wrap it in a p
section.addContent(new Element("p").addContent(content));
} else {
diff --git a/mycore-base/src/main/resources/config/deprecated.properties b/mycore-base/src/main/resources/config/deprecated.properties
index 604c0fdac8..403e1e3d41 100644
--- a/mycore-base/src/main/resources/config/deprecated.properties
+++ b/mycore-base/src/main/resources/config/deprecated.properties
@@ -5,3 +5,7 @@ MCR.Filter.UserAgent=MCR.Filter.UserAgent.BotPattern
MCR.Jersey.resource.packages=MCR.Jersey.Resource.Packages
MCR.URN.Enabled.Objects=Your.Own.Property.see.MCR-1583
+
+MCR.URIResolver.MaxObjectSize=MCR.HTTPClient.MaxObjectSize
+MCR.URIResolver.MaxCacheEntries=MCR.HTTPClient.MaxCacheEntries
+MCR.URIResolver.RequestTimeout=MCR.HTTPClient.RequestTimeout
diff --git a/mycore-base/src/main/resources/config/mycore.properties b/mycore-base/src/main/resources/config/mycore.properties
index e7731b4106..c98b5b0d46 100644
--- a/mycore-base/src/main/resources/config/mycore.properties
+++ b/mycore-base/src/main/resources/config/mycore.properties
@@ -73,6 +73,12 @@ MCR.Filter.UserAgent.AcceptInvalid=false
MCR.URIResolver.CachingResolver.Capacity=100
MCR.URIResolver.CachingResolver.MaxAge=3600000
+
+# The HTTP Client in use (currently only by MCR.URIResolver.MCRRESTResolver)
+ MCR.HTTPClient.Class=org.mycore.common.MCRDefaultHTTPClient
+ MCR.HTTPClient.MaxObjectSize=131072
+ MCR.HTTPClient.MaxCacheEntries=1000
+ MCR.HTTPClient.RequestTimeout=30000
##############################################################################
# Classes for the commandline interface
diff --git a/mycore-base/src/test/java/org/mycore/datamodel/common/MCRFileBaseCacheObjectIDGeneratorTest.java b/mycore-base/src/test/java/org/mycore/datamodel/common/MCRFileBaseCacheObjectIDGeneratorTest.java
new file mode 100644
index 0000000000..d18c5e0d60
--- /dev/null
+++ b/mycore-base/src/test/java/org/mycore/datamodel/common/MCRFileBaseCacheObjectIDGeneratorTest.java
@@ -0,0 +1,76 @@
+/*
+ * This file is part of *** M y C o R e ***
+ * See http://www.mycore.de/ for details.
+ *
+ * MyCoRe is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * MyCoRe is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MyCoRe. If not, see .
+ */
+
+package org.mycore.datamodel.common;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.junit.Test;
+import org.mycore.common.MCRTestCase;
+import org.mycore.datamodel.metadata.MCRObjectID;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.stream.IntStream;
+
+import static org.junit.Assert.assertEquals;
+
+public class MCRFileBaseCacheObjectIDGeneratorTest extends MCRTestCase {
+
+ public static final int GENERATOR_COUNT = 10;
+ public static final int TEST_IDS = 100;
+
+ private static final Logger LOGGER = LogManager.getLogger();
+
+ @Test
+ public void getNextFreeId() throws IOException {
+ Files.createDirectories(MCRFileBaseCacheObjectIDGenerator.getDataDirPath());
+
+ var generatorList = new ArrayList();
+ for (int i = 0; i < GENERATOR_COUNT; i++) {
+ generatorList.add(new MCRFileBaseCacheObjectIDGenerator());
+ }
+
+ // need thread safe list of generated ids
+ var generatedIds = Collections.synchronizedList(new ArrayList());
+ IntStream.range(0, TEST_IDS)
+ .parallel()
+ .forEach(i -> {
+ LOGGER.info("Generating ID {}", i);
+ var generator = generatorList.get(i % GENERATOR_COUNT);
+ MCRObjectID id = generator.getNextFreeId("junit", "test");
+ generatedIds.add(id);
+ });
+
+
+ // check if all ids are unique
+ assertEquals(TEST_IDS, generatedIds.size());
+ assertEquals(TEST_IDS, generatedIds.stream().distinct().count());
+
+ // check if there is no space in the ids
+ var sortedIds = new ArrayList<>(generatedIds);
+ Collections.sort(sortedIds);
+ for (int i = 0; i < sortedIds.size() - 1; i++) {
+ assertEquals(i+1, sortedIds.get(i).getNumberAsInteger());
+ }
+
+ }
+
+}
diff --git a/mycore-iview2/src/main/resources/components/iview2/config/mycore.properties b/mycore-iview2/src/main/resources/components/iview2/config/mycore.properties
index f3ceb61f56..2ffe6d9f1d 100644
--- a/mycore-iview2/src/main/resources/components/iview2/config/mycore.properties
+++ b/mycore-iview2/src/main/resources/components/iview2/config/mycore.properties
@@ -20,6 +20,7 @@ MCR.Module-iview2.MaxResetCount=3
MCR.CLI.Classes.Internal=%MCR.CLI.Classes.Internal%,org.mycore.iview2.frontend.MCRIView2Commands
MCR.URIResolver.ModuleResolver.iview2=org.mycore.iview2.services.MCRIview2URIResolver
MCR.URIResolver.xslIncludes.components=%MCR.URIResolver.xslIncludes.components%,mcr-module-startIview2.xsl
+MCR.URIResolver.xslIncludes.components-3=%MCR.URIResolver.xslIncludes.components-3%,mcr-module-startIview2.xsl
MCR.URIResolver.xslIncludes.solrResponse=%MCR.URIResolver.xslIncludes.solrResponse%,iview2-solrresponse.xsl
MCR.URIResolver.xslIncludes.functions=%MCR.URIResolver.xslIncludes.functions%,functions/iview2.xsl
diff --git a/mycore-mets/src/main/java/org/mycore/mets/model/MCRMETSDefaultGenerator.java b/mycore-mets/src/main/java/org/mycore/mets/model/MCRMETSDefaultGenerator.java
index df01e8dc66..c045f9c617 100644
--- a/mycore-mets/src/main/java/org/mycore/mets/model/MCRMETSDefaultGenerator.java
+++ b/mycore-mets/src/main/java/org/mycore/mets/model/MCRMETSDefaultGenerator.java
@@ -31,6 +31,7 @@
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
+import java.util.concurrent.atomic.AtomicInteger;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -132,7 +133,8 @@ private Mets createMets() throws IOException {
StructLink structLink = new StructLink();
// create internal structure
- structureMets(getDerivatePath(), getIgnorePaths(), fileSec, physicalDiv, logicalDiv, structLink, 0);
+ structureMets(getDerivatePath(), getIgnorePaths(), fileSec, physicalDiv, logicalDiv, structLink,
+ new AtomicInteger(0));
hrefIdMap.clear();
// add to mets
@@ -147,8 +149,7 @@ private Mets createMets() throws IOException {
}
private void structureMets(MCRPath dir, Set ignoreNodes, FileSec fileSec, PhysicalDiv physicalDiv,
- LogicalDiv logicalDiv, StructLink structLink, int logOrder) throws IOException {
- int lOrder = logOrder;
+ LogicalDiv logicalDiv, StructLink structLink, AtomicInteger logCounter) throws IOException {
SortedMap files = new TreeMap<>();
SortedMap directories = new TreeMap<>();
@@ -160,11 +161,12 @@ private void structureMets(MCRPath dir, Set ignoreNodes, FileSec fileSe
for (Map.Entry directory : directories.entrySet()) {
String dirName = directory.getKey().getFileName().toString();
if (isInExcludedRootFolder(directory.getKey())) {
- structureMets(directory.getKey(), ignoreNodes, fileSec, physicalDiv, logicalDiv, structLink, lOrder);
+ structureMets(directory.getKey(), ignoreNodes, fileSec, physicalDiv, logicalDiv, structLink,
+ logCounter);
} else {
- LogicalDiv section = new LogicalDiv("log_" + ++lOrder, "section", dirName);
+ LogicalDiv section = new LogicalDiv("log_" + logCounter.incrementAndGet(), "section", dirName);
logicalDiv.add(section);
- structureMets(directory.getKey(), ignoreNodes, fileSec, physicalDiv, section, structLink, lOrder);
+ structureMets(directory.getKey(), ignoreNodes, fileSec, physicalDiv, section, structLink, logCounter);
}
}
}
diff --git a/mycore-restapi/src/main/java/org/mycore/restapi/MCRCORSResponseFilter.java b/mycore-restapi/src/main/java/org/mycore/restapi/MCRCORSResponseFilter.java
index 49317f5806..bdf83745ca 100644
--- a/mycore-restapi/src/main/java/org/mycore/restapi/MCRCORSResponseFilter.java
+++ b/mycore-restapi/src/main/java/org/mycore/restapi/MCRCORSResponseFilter.java
@@ -59,16 +59,21 @@ public class MCRCORSResponseFilter implements ContainerResponseFilter {
private static final String ACCESS_CONTROL_REQUEST_HEADERS = "Access-Control-Request-Headers";
+ private static final String ACCESS_CONTROL_REQUEST_METHOD = "Access-Control-Request-Method";
+
private static final String ACCESS_CONTROL_ALLOW_HEADERS = "Access-Control-Allow-Headers";
private static final String ACCESS_CONTROL_MAX_AGE = "Access-Control-Max-Age";
+
@Context
ResourceInfo resourceInfo;
private static boolean handlePreFlight(ContainerRequestContext requestContext,
MultivaluedMap responseHeaders) {
- if (!requestContext.getMethod().equals(HttpMethod.OPTIONS)) {
+ if (!requestContext.getMethod().equals(HttpMethod.OPTIONS)
+ || requestContext.getHeaderString(ACCESS_CONTROL_REQUEST_METHOD) == null) {
+ //required for CORS-preflight request
return false;
}
//allow all methods
@@ -106,6 +111,10 @@ public void filter(ContainerRequestContext requestContext, ContainerResponseCont
if (!handlePreFlight(requestContext, responseHeaders)) {
//not a CORS preflight request
ArrayList exposedHeaders = new ArrayList<>();
+ //MCR-3041 expose all header starting with X-
+ responseHeaders.keySet().stream()
+ .filter(name -> name.startsWith("x-") || name.startsWith("X-"))
+ .forEach(exposedHeaders::add);
if (authenticatedRequest && responseHeaders.getFirst(HttpHeaders.AUTHORIZATION) != null) {
exposedHeaders.add(HttpHeaders.AUTHORIZATION);
}
diff --git a/mycore-restapi/src/main/java/org/mycore/restapi/MCRSessionFilter.java b/mycore-restapi/src/main/java/org/mycore/restapi/MCRSessionFilter.java
index 3dade395d2..1d93a82839 100644
--- a/mycore-restapi/src/main/java/org/mycore/restapi/MCRSessionFilter.java
+++ b/mycore-restapi/src/main/java/org/mycore/restapi/MCRSessionFilter.java
@@ -250,8 +250,8 @@ public void filter(ContainerRequestContext requestContext, ContainerResponseCont
try {
MCRSessionMgr.unlock();
MCRSession currentSession = MCRSessionMgr.getCurrentSession();
- if (responseContext.getStatus() == Response.Status.FORBIDDEN.getStatusCode() && currentSession
- .getUserInformation().getUserID().equals(MCRSystemUserInformation.getGuestInstance().getUserID())) {
+ if (responseContext.getStatus() == Response.Status.FORBIDDEN.getStatusCode()
+ && isUnAuthorized(requestContext)) {
LOGGER.debug("Guest detected, change response from FORBIDDEN to UNAUTHORIZED.");
responseContext.setStatus(Response.Status.UNAUTHORIZED.getStatusCode());
responseContext.getHeaders().putSingle(HttpHeaders.WWW_AUTHENTICATE,
@@ -285,6 +285,10 @@ public void close() throws IOException {
}
}
+ private static boolean isUnAuthorized(ContainerRequestContext requestContext) {
+ return requestContext.getHeaderString(HttpHeaders.AUTHORIZATION) == null;
+ }
+
//returns true for Ajax-Requests or requests for embedded images
private static boolean doNotWWWAuthenticate(ContainerRequestContext requestContext) {
return !"ServiceWorker".equals(requestContext.getHeaderString("X-Requested-With")) &&
diff --git a/mycore-restapi/src/main/java/org/mycore/restapi/converter/MCRObjectIDParamConverterProvider.java b/mycore-restapi/src/main/java/org/mycore/restapi/converter/MCRObjectIDParamConverterProvider.java
index a449ad2830..0d65a1bc71 100644
--- a/mycore-restapi/src/main/java/org/mycore/restapi/converter/MCRObjectIDParamConverterProvider.java
+++ b/mycore-restapi/src/main/java/org/mycore/restapi/converter/MCRObjectIDParamConverterProvider.java
@@ -23,9 +23,9 @@
import org.mycore.common.MCRException;
import org.mycore.datamodel.metadata.MCRObjectID;
+import org.mycore.restapi.v2.MCRErrorResponse;
import jakarta.ws.rs.BadRequestException;
-import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.ext.ParamConverter;
import jakarta.ws.rs.ext.ParamConverterProvider;
import jakarta.ws.rs.ext.Provider;
@@ -50,9 +50,12 @@ public T fromString(String value) {
try {
return rawType.cast(MCRObjectID.getInstance(value));
} catch (MCRException e) {
- throw new BadRequestException(Response.status(CODE_INVALID)
- .entity(MSG_INVALID)
- .build());
+ throw MCRErrorResponse.fromStatus(CODE_INVALID)
+ .withErrorCode("MCROBJECTID_INVALID")
+ .withMessage(MSG_INVALID)
+ .withDetail(e.getMessage())
+ .withCause(e)
+ .toException();
}
}
diff --git a/mycore-restapi/src/main/java/org/mycore/restapi/v2/MCRErrorResponse.java b/mycore-restapi/src/main/java/org/mycore/restapi/v2/MCRErrorResponse.java
index c083db5678..347df5a689 100644
--- a/mycore-restapi/src/main/java/org/mycore/restapi/v2/MCRErrorResponse.java
+++ b/mycore-restapi/src/main/java/org/mycore/restapi/v2/MCRErrorResponse.java
@@ -83,6 +83,11 @@ public WebApplicationException toException() {
WebApplicationException e;
Response.Status s = Response.Status.fromStatusCode(status);
final Response response = Response.status(s)
+ .header("X-Error-Code", getErrorCode())
+ .header("X-Error-Message", getMessage())
+ .header("X-Error-Time", new Date(getTimestamp().toEpochMilli())) //no HeaderDelegate for Instant
+ .header("X-Error-Detail", getDetail())
+ .header("X-Error-UUID", getUuid())
.entity(this)
.build();
//s maybe null
diff --git a/mycore-restapi/src/main/java/org/mycore/restapi/v2/MCRRestAuthorizationFilter.java b/mycore-restapi/src/main/java/org/mycore/restapi/v2/MCRRestAuthorizationFilter.java
index 6dcafe6e95..0da65d14f0 100644
--- a/mycore-restapi/src/main/java/org/mycore/restapi/v2/MCRRestAuthorizationFilter.java
+++ b/mycore-restapi/src/main/java/org/mycore/restapi/v2/MCRRestAuthorizationFilter.java
@@ -27,8 +27,10 @@
import org.apache.logging.log4j.LogManager;
import org.mycore.access.MCRAccessManager;
import org.mycore.access.mcrimpl.MCRAccessControlSystem;
+import org.mycore.datamodel.metadata.MCRObjectID;
import org.mycore.frontend.jersey.access.MCRRequestScopeACL;
import org.mycore.restapi.converter.MCRDetailLevel;
+import org.mycore.restapi.converter.MCRObjectIDParamConverterProvider;
import org.mycore.restapi.v2.access.MCRRestAPIACLPermission;
import org.mycore.restapi.v2.access.MCRRestAccessManager;
import org.mycore.restapi.v2.annotation.MCRRestRequiredPermission;
@@ -44,6 +46,7 @@
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.MultivaluedMap;
import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.ext.ParamConverter;
@Priority(Priorities.AUTHORIZATION)
public class MCRRestAuthorizationFilter implements ContainerRequestFilter {
@@ -56,6 +59,9 @@ public class MCRRestAuthorizationFilter implements ContainerRequestFilter {
public static final String PARAM_DER_PATH = "path";
+ public static final ParamConverter OBJECT_ID_PARAM_CONVERTER = new MCRObjectIDParamConverterProvider()
+ .getConverter(MCRObjectID.class, null, null);
+
@Context
ResourceInfo resourceInfo;
@@ -88,7 +94,9 @@ private void checkBaseAccess(ContainerRequestContext requestContext, MCRRestAPIA
Optional checkable = Optional.ofNullable(derId)
.filter(d -> path != null) //only check for derId if path is given
.map(Optional::of)
- .orElseGet(() -> Optional.ofNullable(objectId));
+ .orElseGet(() -> Optional.ofNullable(objectId))
+ .map(OBJECT_ID_PARAM_CONVERTER::fromString) //MCR-3041 check for Bad Request
+ .map(MCRObjectID::toString);
checkable.ifPresent(id -> LogManager.getLogger().info("Checking " + permission + " access on " + id));
MCRRequestScopeACL aclProvider = MCRRequestScopeACL.getInstance(requestContext);
boolean allowed = checkable
diff --git a/mycore-restapi/src/main/java/org/mycore/restapi/v2/MCRRestDerivateContents.java b/mycore-restapi/src/main/java/org/mycore/restapi/v2/MCRRestDerivateContents.java
index ac887cad42..580d04f552 100644
--- a/mycore-restapi/src/main/java/org/mycore/restapi/v2/MCRRestDerivateContents.java
+++ b/mycore-restapi/src/main/java/org/mycore/restapi/v2/MCRRestDerivateContents.java
@@ -289,6 +289,7 @@ private static byte[] getMD5Digest(String md5sum) {
@Header(name = "Last-Modified", description = "last modified date of file"),
}))
public Response getFileOrDirectoryMetadata() {
+ MCRRestDerivates.validateDerivateRelation(mcrId, derid);
MCRPath mcrPath = getPath();
MCRFileAttributes fileAttributes;
try {
@@ -329,6 +330,7 @@ public Response getFileOrDirectoryMetadata() {
summary = "List directory contents or serves file given by {path} in derivate",
tags = MCRRestUtils.TAG_MYCORE_FILE)
public Response getFileOrDirectory(@Context UriInfo uriInfo, @Context HttpHeaders requestHeader) {
+ MCRRestDerivates.validateDerivateRelation(mcrId, derid);
LogManager.getLogger().info("{}:{}", derid, path);
MCRPath mcrPath = MCRPath.getPath(derid.toString(), path);
MCRFileAttributes fileAttributes;
@@ -384,6 +386,7 @@ public Response getFileOrDirectory(@Context UriInfo uriInfo, @Context HttpHeader
},
tags = MCRRestUtils.TAG_MYCORE_FILE)
public Response createFileOrDirectory(InputStream contents) {
+ MCRRestDerivates.validateDerivateRelation(mcrId, derid);
MCRPath mcrPath = MCRPath.getPath(derid.toString(), path);
if (mcrPath.getNameCount() > 1) {
MCRPath parentDirectory = mcrPath.getParent();
@@ -429,6 +432,7 @@ public Response createFileOrDirectory(InputStream contents) {
tags = MCRRestUtils.TAG_MYCORE_FILE)
@MCRRequireTransaction
public Response deleteFileOrDirectory() {
+ MCRRestDerivates.validateDerivateRelation(mcrId, derid);
MCRPath mcrPath = getPath();
try {
if (Files.exists(mcrPath) && Files.isDirectory(mcrPath)) {
diff --git a/mycore-restapi/src/main/java/org/mycore/restapi/v2/MCRRestObjects.java b/mycore-restapi/src/main/java/org/mycore/restapi/v2/MCRRestObjects.java
index 6acd70bb9a..b4bd6c4573 100644
--- a/mycore-restapi/src/main/java/org/mycore/restapi/v2/MCRRestObjects.java
+++ b/mycore-restapi/src/main/java/org/mycore/restapi/v2/MCRRestObjects.java
@@ -400,9 +400,14 @@ public Response createObject(String xml) {
tags = MCRRestUtils.TAG_MYCORE_OBJECT)
public Response getObject(@Parameter(example = "mir_mods_00004711") @PathParam(PARAM_MCRID) MCRObjectID id)
throws IOException {
- long modified = MCRXMLMetadataManager.instance().getLastModified(id);
- if (modified < 0) {
- throw new NotFoundException("MCRObject " + id + " not found");
+ long modified;
+ try {
+ modified = MCRXMLMetadataManager.instance().getLastModified(id);
+ } catch (IOException io) {
+ throw MCRErrorResponse.fromStatus(Response.Status.NOT_FOUND.getStatusCode())
+ .withErrorCode(MCRErrorCodeConstants.MCROBJECT_NOT_FOUND)
+ .withMessage("MCRObject " + id + " not found")
+ .toException();
}
Date lastModified = new Date(modified);
Optional cachedResponse = MCRRestUtils.getCachedResponse(request, lastModified);
diff --git a/mycore-solr/src/main/resources/xslt/solr/response/response.xsl b/mycore-solr/src/main/resources/xslt/solr/response/response.xsl
index ce03b24bda..0efe94892b 100644
--- a/mycore-solr/src/main/resources/xslt/solr/response/response.xsl
+++ b/mycore-solr/src/main/resources/xslt/solr/response/response.xsl
@@ -6,7 +6,7 @@
xmlns:mcri18n="http://www.mycore.de/xslt/i18n"
exclude-result-prefixes="mcri18n">
&html-output;
-
+
diff --git a/mycore-viewer/src/main/resources/xslt/IViewConfig-js.xsl b/mycore-viewer/src/main/resources/xslt/IViewConfig-js.xsl
new file mode 100644
index 0000000000..63492690ab
--- /dev/null
+++ b/mycore-viewer/src/main/resources/xslt/IViewConfig-js.xsl
@@ -0,0 +1,116 @@
+
+
+
+
+
+
+
+ (function () {
+ window["viewerLoader"] = window["viewerLoader"] || (function () {
+ let executeOnReady = [];
+ let notLoadedCount = 0;
+ let scriptToLoad = [];
+
+ let loader = {
+ loadedStyles: function () {
+ let existingCss = [];
+ for (let i = 0; i < document.styleSheets.length; i++) {
+ let css = document.styleSheets[i];
+ let src = css.href;
+ if (src != null) {
+ existingCss.push(src);
+ }
+ }
+ return existingCss;
+ },
+ addConstructorExecution: function (fn) {
+ executeOnReady.push(fn);
+ },
+ getLoadedScripts: function () {
+ let existingScripts = [];
+
+ for (let i = 0; i < document.scripts.length; i++) {
+ let script = document.scripts[i];
+ let href = script.src;
+ if (href != null) {
+ existingScripts.push(href);
+ }
+ }
+ return existingScripts;
+ },
+ addRequiredScripts: function (scripts, mobile) {
+ if(!mobile && !loader.isBootstrapPresent()){
+ notLoadedCount++;
+ interval = window.setInterval(function(){
+ if(loader.isBootstrapPresent()){
+ notLoadedCount--;
+ window.clearInterval(interval);
+ loader.excecuteOnReadyFn();
+ }
+ }, 50);
+ }
+ scripts.filter(function (script) {
+ return loader.getLoadedScripts().indexOf(script) === -1;
+ }).forEach(function (scriptSrc) {
+ let script = document.createElement('script');
+ script.async = false;
+ notLoadedCount++;
+
+ script.onload = function() {
+ notLoadedCount--;
+ loader.excecuteOnReadyFn();
+ };
+ script.async = false;
+ script.src = scriptSrc;
+ document.head.appendChild(script);
+ });
+
+
+ },
+ excecuteOnReadyFn: function(){
+ if(notLoadedCount==0){
+ let current;
+ while((current=executeOnReady.pop()) != null){
+ current();
+ }
+ }
+ },
+ isBootstrapPresent: function(){
+ return typeof $ !='undefined' &&
+ typeof $.fn !='undefined' &&
+ typeof $.fn.tooltip !='undefined' &&
+ typeof $.fn.tooltip.Constructor!='undefined' &&
+ typeof $.fn.tooltip.Constructor.VERSION!='undefined';
+ },
+ addRequiredCss: function (styles) {
+ styles.filter(function (s) {
+ return loader.loadedStyles().indexOf(s) === -1;
+ }).forEach(function (style) {
+ let link = document.createElement('link');
+ link.rel = 'stylesheet';
+ link.type = 'text/css';
+ link.href = style;
+ link.media = 'all';
+ document.head.appendChild(link);
+ });
+ }
+ };
+
+ return loader;
+ })();
+
+// viewer dependency loader
+ let configuration =;
+
+ viewerLoader.addRequiredCss(configuration.resources.css);
+ viewerLoader.addRequiredScripts(configuration.resources.script, configuration.properties.mobile);
+ viewerLoader.addConstructorExecution(function(){
+ let container = jQuery("[data-viewer='"+configuration.properties.derivate+":"+CSS.escape(configuration.properties.filePath)+"']");
+ new mycore.viewer.MyCoReViewer(container, configuration.properties);
+ });
+
+})
+();
+
+
+
diff --git a/mycore-webtools/src/main/java/org/mycore/webtools/upload/MCRDefaultUploadHandler.java b/mycore-webtools/src/main/java/org/mycore/webtools/upload/MCRDefaultUploadHandler.java
index f75da6e081..f4269a8576 100644
--- a/mycore-webtools/src/main/java/org/mycore/webtools/upload/MCRDefaultUploadHandler.java
+++ b/mycore-webtools/src/main/java/org/mycore/webtools/upload/MCRDefaultUploadHandler.java
@@ -33,6 +33,7 @@
import org.mycore.access.MCRAccessManager;
import org.mycore.common.MCRException;
import org.mycore.common.MCRPersistenceException;
+import org.mycore.common.MCRUtils;
import org.mycore.common.config.MCRConfiguration2;
import org.mycore.datamodel.metadata.MCRDerivate;
import org.mycore.datamodel.metadata.MCRMetaClassification;
@@ -72,6 +73,12 @@ public class MCRDefaultUploadHandler implements MCRUploadHandler {
public static final String CLASSIFICATIONS_PARAMETER_NAME = "classifications";
private static final Logger LOGGER = LogManager.getLogger();
+ public static final String UNIQUE_OBJECT_TRANSLATION_KEY = "component.webtools.upload.invalid.parameter.unique";
+ public static final String INVALID_OBJECT_TRANSLATION_KEY = "component.webtools.upload.invalid.parameter.object";
+ public static final String OBJECT_DOES_NOT_EXIST_TRANSLATION_KEY
+ = "component.webtools.upload.invalid.parameter.object.not.exist";
+ public static final String INVALID_FILE_NAME_TRANSLATION_KEY = "component.webtools.upload.invalid.fileName";
+ public static final String INVALID_FILE_SIZE_TRANSLATION_KEY = "component.webtools.upload.invalid.fileSize";
public static void setDefaultMainFile(MCRDerivate derivate) {
MCRPath path = MCRPath.getPath(derivate.getId().toString(), "/");
@@ -139,17 +146,40 @@ public URI commit(MCRFileUploadBucket bucket) throws MCRUploadServerException {
return null; // We donĀ“t want to redirect to the derivate, so we return null
}
+ private static void checkPermissions(MCRObjectID oid) throws MCRInvalidUploadParameterException,
+ MCRUploadForbiddenException {
+ if (!MCRMetadataManager.exists(oid)) {
+ throw new MCRInvalidUploadParameterException(OBJ_OR_DERIVATE_ID_PARAMETER_NAME, oid.toString(),
+ OBJECT_DOES_NOT_EXIST_TRANSLATION_KEY, true);
+ }
+
+ if (!oid.getTypeId().equals("derivate")) {
+ try {
+ String formattedNewDerivateIDString = MCRObjectID.formatID(oid.getProjectId(), "derivate", 0);
+ MCRObjectID newDerivateId = MCRObjectID.getInstance(formattedNewDerivateIDString);
+ MCRMetadataManager.checkCreatePrivilege(newDerivateId);
+ } catch (MCRAccessException e) {
+ throw new MCRUploadForbiddenException();
+ }
+ }
+
+ if (!MCRAccessManager.checkPermission(oid, MCRAccessManager.PERMISSION_WRITE)) {
+ throw new MCRUploadForbiddenException();
+ }
+ }
+
@Override
public void validateFileMetadata(String name, long size) throws MCRInvalidFileException {
try {
MCRUploadHelper.checkPathName(name);
} catch (MCRException e) {
- throw new MCRInvalidFileException(name, e.getMessage());
+ throw new MCRInvalidFileException(name, INVALID_FILE_NAME_TRANSLATION_KEY, true);
}
long maxSize = MCRConfiguration2.getOrThrow("MCR.FileUpload.MaxSize", Long::parseLong);
if (size > maxSize) {
- throw new MCRInvalidFileException(name, "Maximum allowed size is " + maxSize + ", size is " + size);
+ throw new MCRInvalidFileException(name, INVALID_FILE_SIZE_TRANSLATION_KEY, true,
+ MCRUtils.getSizeFormatted(size), MCRUtils.getSizeFormatted(maxSize));
}
}
@@ -163,13 +193,13 @@ public String begin(Map> parameters)
List oidList = parameters.get(OBJ_OR_DERIVATE_ID_PARAMETER_NAME);
if (oidList.size() != 1) {
throw new MCRInvalidUploadParameterException(OBJ_OR_DERIVATE_ID_PARAMETER_NAME, String.join(",", oidList),
- "There must be exactly one object or derivate id");
+ UNIQUE_OBJECT_TRANSLATION_KEY, true);
}
String oidString = oidList.get(0);
if (!MCRObjectID.isValid(oidString)) {
throw new MCRInvalidUploadParameterException(OBJ_OR_DERIVATE_ID_PARAMETER_NAME, oidString,
- "Invalid object or derivate id given");
+ INVALID_OBJECT_TRANSLATION_KEY, true);
}
MCRObjectID oid = MCRObjectID.getInstance(oidString);
@@ -179,28 +209,6 @@ public String begin(Map> parameters)
return UUID.randomUUID().toString();
}
- private static void checkPermissions(MCRObjectID oid) throws MCRInvalidUploadParameterException,
- MCRUploadForbiddenException {
- if (!MCRMetadataManager.exists(oid)) {
- throw new MCRInvalidUploadParameterException(OBJ_OR_DERIVATE_ID_PARAMETER_NAME, oid.toString(),
- "does not exist!");
- }
-
- if (!oid.getTypeId().equals("derivate")) {
- try {
- String formattedNewDerivateIDString = MCRObjectID.formatID(oid.getProjectId(), "derivate", 0);
- MCRObjectID newDerivateId = MCRObjectID.getInstance(formattedNewDerivateIDString);
- MCRMetadataManager.checkCreatePrivilege(newDerivateId);
- } catch (MCRAccessException e) {
- throw new MCRUploadForbiddenException("No rights to create derivate for " + oid + "!");
- }
- }
-
- if (!MCRAccessManager.checkPermission(oid, MCRAccessManager.PERMISSION_WRITE)) {
- throw new MCRUploadForbiddenException("No write access to " + oid);
- }
- }
-
/**
* returns the object id from the parameters under the key {@link #OBJ_OR_DERIVATE_ID_PARAMETER_NAME}
* @param parameters the parameters
diff --git a/mycore-webtools/src/main/java/org/mycore/webtools/upload/exception/MCRInvalidFileException.java b/mycore-webtools/src/main/java/org/mycore/webtools/upload/exception/MCRInvalidFileException.java
index d0eab7d0d3..0d96358dc3 100644
--- a/mycore-webtools/src/main/java/org/mycore/webtools/upload/exception/MCRInvalidFileException.java
+++ b/mycore-webtools/src/main/java/org/mycore/webtools/upload/exception/MCRInvalidFileException.java
@@ -18,6 +18,8 @@
package org.mycore.webtools.upload.exception;
+import org.mycore.services.i18n.MCRTranslation;
+
/**
* Should be thrown if the file is not valid. E.g. the size is too big or the file name contains invalid characters.
*/
@@ -29,8 +31,20 @@ public class MCRInvalidFileException extends MCRUploadException {
private final String reason;
+ public MCRInvalidFileException(String fileName) {
+ super("component.webtools.upload.invalid.file.noReason", fileName);
+ this.fileName = fileName;
+ this.reason = null;
+ }
+
public MCRInvalidFileException(String fileName, String reason) {
- super("component.webtools.upload.invalid.file", fileName, reason);
+ this(fileName, reason, false);
+ }
+
+ public MCRInvalidFileException(String fileName, String reason, boolean translateReason,
+ Object... translationParams) {
+ super("component.webtools.upload.invalid.file", fileName,
+ translateReason ? MCRTranslation.translate(reason, translationParams) : reason);
this.fileName = fileName;
this.reason = reason;
}
diff --git a/mycore-webtools/src/main/java/org/mycore/webtools/upload/exception/MCRInvalidUploadParameterException.java b/mycore-webtools/src/main/java/org/mycore/webtools/upload/exception/MCRInvalidUploadParameterException.java
index 7f13cd5c73..a25e4723fe 100644
--- a/mycore-webtools/src/main/java/org/mycore/webtools/upload/exception/MCRInvalidUploadParameterException.java
+++ b/mycore-webtools/src/main/java/org/mycore/webtools/upload/exception/MCRInvalidUploadParameterException.java
@@ -18,6 +18,8 @@
package org.mycore.webtools.upload.exception;
+import org.mycore.services.i18n.MCRTranslation;
+
/**
* Should be thrown if a parameter required by an upload handler is not valid. E.g. a classification does not exist.
*/
@@ -32,7 +34,13 @@ public class MCRInvalidUploadParameterException extends MCRUploadException {
private final String badValue;
public MCRInvalidUploadParameterException(String parameterName, String badValue, String wrongReason) {
- super("component.webtools.upload.invalid.parameter", parameterName, badValue, wrongReason);
+ this(parameterName, badValue, wrongReason, false);
+ }
+
+ public MCRInvalidUploadParameterException(String parameterName, String badValue, String wrongReason,
+ boolean translateReason) {
+ super("component.webtools.upload.invalid.parameter", parameterName, badValue,
+ translateReason ? MCRTranslation.translate(wrongReason) : wrongReason);
this.parameterName = parameterName;
this.wrongReason = wrongReason;
this.badValue = badValue;
diff --git a/mycore-webtools/src/main/java/org/mycore/webtools/upload/exception/MCRUploadForbiddenException.java b/mycore-webtools/src/main/java/org/mycore/webtools/upload/exception/MCRUploadForbiddenException.java
index 3de69a9bf6..e465738d41 100644
--- a/mycore-webtools/src/main/java/org/mycore/webtools/upload/exception/MCRUploadForbiddenException.java
+++ b/mycore-webtools/src/main/java/org/mycore/webtools/upload/exception/MCRUploadForbiddenException.java
@@ -22,14 +22,14 @@ public class MCRUploadForbiddenException extends MCRUploadException {
private static final long serialVersionUID = 1L;
- private final String reason;
+
public MCRUploadForbiddenException(String reason) {
super("component.webtools.upload.forbidden", reason);
- this.reason = reason;
}
- public String getReason() {
- return reason;
+ public MCRUploadForbiddenException() {
+ super("component.webtools.upload.forbidden.noReason");
}
+
}
diff --git a/mycore-webtools/src/main/resources/components/webtools/config/messages_de.properties b/mycore-webtools/src/main/resources/components/webtools/config/messages_de.properties
index 054b337aa8..333cd20e7f 100644
--- a/mycore-webtools/src/main/resources/components/webtools/config/messages_de.properties
+++ b/mycore-webtools/src/main/resources/components/webtools/config/messages_de.properties
@@ -16,13 +16,19 @@ component.webtools.upload.title = Datei-Upload
component.webtools.upload.processing = Das Hochladen der Dateien wird auf dem Server abgeschlossen. Schlie\u00DFen Sie dieses Fenster nicht!
component.webtools.upload.validating = Die Datei-Namen und -Gr\u00F6\u00DFen werden \u00FCberpr\u00FCft, bevor die Dateien hochgeladen werden. Bitte warten...
component.webtools.upload.error = Beim Hochladen ist ein Fehler aufgetreten:
-component.webtools.upload.invalid.fileSize = Die Datei {0} ist zu gro\u00DF: {1} / {2}
+component.webtools.upload.invalid.fileSize = Die Datei ist zu gro\u00DF: {0} / {1}
+component.webtools.upload.invalid.fileName = Der Datei-Name enth\u00E4lt ung\u00FCltige Zeichen.
component.webtools.upload.invalid.file = Die Datei {0} ist nicht valide: {1}
+component.webtools.upload.invalid.file.noReason = Die Datei {0} ist nicht valide.
component.webtools.upload.invalid.parameter = Der Parameter {0} mit dem wert {1} ist nicht valide: {2}
component.webtools.upload.invalid.parameter.missing = Der Parameter {0} fehlt.
+component.webtools.upload.invalid.parameter.unique = Es darf nur ein Parameter "object" existieren.
+component.webtools.upload.invalid.parameter.object = Der Parameter "object" ist keine g\u00FCltige MyCoRe-Objekt-ID.
+component.webtools.upload.invalid.parameter.object.not.exist = Das angegebene MyCoRe-Objekt existiert nicht.
component.webtools.upload.forbidden = Das Hochladen ist nicht erlaubt: {0}
+component.webtools.upload.forbidden.noReason = Das Hochladen ist nicht erlaubt.
component.webtools.upload.temp.delete.failed = Das L\u00F6schen des tempor\u00E4ren Verzeichnisses ist fehlgeschlagen.
-component.webtools.upload.temp.create.failed = Das Erstellen eines tempor\u00E4ren Ordner ist gescheitert!
+component.webtools.upload.temp.create.failed = Das Erstellen des tempor\u00E4ren Verzeichnisses ist gescheitert!
component.webtools.msie.learnMore=Mehr erfahren
diff --git a/mycore-webtools/src/main/resources/components/webtools/config/messages_en.properties b/mycore-webtools/src/main/resources/components/webtools/config/messages_en.properties
index 86018f2629..52542c80c9 100644
--- a/mycore-webtools/src/main/resources/components/webtools/config/messages_en.properties
+++ b/mycore-webtools/src/main/resources/components/webtools/config/messages_en.properties
@@ -16,11 +16,17 @@ component.webtools.upload.title = File Upload
component.webtools.upload.processing = The file upload is processed on the server. Please do not close the browser window.
component.webtools.upload.validating = The file names and sizes are validated on the server. Please wait...
component.webtools.upload.error = Something went wrong with the Upload:
-component.webtools.upload.invalid.fileSize = The File {0} is to Big: {1} / {2}
+component.webtools.upload.invalid.fileSize = The File is to Big: {0} / {1}
+component.webtools.upload.invalid.fileName = The name of the file contains invalid characters.
component.webtools.upload.invalid.file = The File {0} is not valid: {1}
+component.webtools.upload.invalid.file.noReason = The File {0} is not valid.
component.webtools.upload.invalid.parameter = The Parameter {0} is not valid: {1}
component.webtools.upload.invalid.parameter.missing = The Parameter {0} is missing
+component.webtools.upload.invalid.parameter.unique = There must be only one parameter "object".
+component.webtools.upload.invalid.parameter.object = The Parameter "object" is not a valid MyCoRe-Object-ID.
+component.webtools.upload.invalid.parameter.object.not.exist = The given MyCoRe-Object does not exist.
component.webtools.upload.forbidden = The Upload is not allowed: {0}
+component.webtools.upload.forbidden.noReason = The Upload is not allowed.
component.webtools.upload.temp.delete.failed = The temporary folder could not be deleted!
component.webtools.upload.temp.create.failed = The temporary folder could not be created!
diff --git a/mycore-webtools/src/main/ts/upload/src/FileTransferQueue.ts b/mycore-webtools/src/main/ts/upload/src/FileTransferQueue.ts
index 3513032770..94bb20b419 100644
--- a/mycore-webtools/src/main/ts/upload/src/FileTransferQueue.ts
+++ b/mycore-webtools/src/main/ts/upload/src/FileTransferQueue.ts
@@ -103,6 +103,7 @@ export class FileTransferQueue {
private uploadIDCount: {} = {};
private _validating: boolean;
private validatingHandlerList:Array<(validating: boolean) => void > = [];
+ private validationHandlerList: Array<(message: string) => void> = [];
constructor() {
}
@@ -192,6 +193,14 @@ export class FileTransferQueue {
return this._validating;
}
+ public pushValidationError(message: string) {
+ this.validationHandlerList.forEach(handler => handler(message));
+ }
+
+ public addValidationHandler(handler: (message: string) => void) {
+ this.validationHandlerList.push(handler);
+ }
+
public addCompleteHandler(handler: FileTransferHandler) {
this.completeHandlerList.push(handler);
}
diff --git a/mycore-webtools/src/main/ts/upload/src/TransferSession.ts b/mycore-webtools/src/main/ts/upload/src/TransferSession.ts
index 0527c2bb93..e262ba7165 100644
--- a/mycore-webtools/src/main/ts/upload/src/TransferSession.ts
+++ b/mycore-webtools/src/main/ts/upload/src/TransferSession.ts
@@ -66,21 +66,21 @@ export class TransferSession {
this.request.open('PUT', Utils.getUploadSettings().webAppBaseURL + "rsc/files/upload/begin" + uploadHandlerParameter + parameters, true);
this.request.onreadystatechange = (result) => {
- if (this.request.readyState === 4 && this.request.status === 200) {
- this._bucketID = this.request.responseText;
- this._started = true;
- if (completionHandler) {
- completionHandler();
+ if (this.request.readyState === 4) {
+ if(this.request.status === 200) {
+ this._bucketID = this.request.responseText;
+ this._started = true;
+ if (completionHandler) {
+ completionHandler();
+ }
+ } else {
+ if (errorHandler) {
+ errorHandler(this.request.responseText);
+ }
}
}
};
- this.request.onerror = (evt) => {
- if (errorHandler) {
- errorHandler(this.request.responseText);
- }
- };
-
this.request.send();
}
diff --git a/mycore-webtools/src/main/ts/upload/src/UploadTarget.ts b/mycore-webtools/src/main/ts/upload/src/UploadTarget.ts
index 7e07d86a7d..8e73c221cc 100644
--- a/mycore-webtools/src/main/ts/upload/src/UploadTarget.ts
+++ b/mycore-webtools/src/main/ts/upload/src/UploadTarget.ts
@@ -87,25 +87,29 @@ export class UploadTarget {
}
-
+ fileInput.multiple = true;
fileInput.setAttribute("type", "file");
fileInput.addEventListener('change', async () => {
- let transferSession = FileTransferQueue.getQueue().registerTransferSession(this.uploadHandler,
+ const queue = FileTransferQueue.getQueue();
+ let transferSession = queue.registerTransferSession(this.uploadHandler,
this.attributes);
+ queue.setValidating(true);
+ const validFiles: File[] = [];
for (let i = 0; i < fileInput.files.length; i++) {
let file = fileInput.files.item(i);
- FileTransferQueue.getQueue().setValidating(true);
const validation = await this.validateTraverse(file);
- FileTransferQueue.getQueue().setValidating(false);
if (!validation.test) {
- // TODO: show nice in GUI
- alert(validation.reason);
+ queue.setValidating(false);
+ queue.pushValidationError(validation.reason);
return true;
}
- const fileTransfer = new FileTransfer(file, this.target, transferSession, []);
- FileTransferQueue.getQueue()
- FileTransferQueue.getQueue().add(fileTransfer);
+ validFiles.push(file);
}
+ queue.setValidating(false);
+ validFiles.forEach((file) => {
+ const fileTransfer = new FileTransfer(file, this.target, transferSession, []);
+ queue.add(fileTransfer);
+ });
});
if (!testing) {
fileInput.click();
@@ -123,20 +127,38 @@ export class UploadTarget {
const webkitEntries: any[] = [];
for (let i = 0; i < items.length; i++) {
- webkitEntries.push(items[i].webkitGetAsEntry());
+ const item = items[i];
+ if (item.kind !== "file") {
+ console.warn("Item is not a file", item);
+ continue;
+ }
+ let result: FileSystemEntry | null = null;
+ if ("webkitGetAsEntry" in item) {
+ result = item.webkitGetAsEntry();
+ } else if ("getAsEntry" in item) {
+ // webkitGetAsEntry will be renamed to getAsEntry in the future
+ result = (item as any).getAsEntry();
+ }
+ if (result != null) {
+ webkitEntries.push(result);
+ }
+ }
+ if (webkitEntries.length == 0) {
+ return false;
}
+ FileTransferQueue.getQueue().setValidating(true);
for (let i = 0; i < webkitEntries.length; i++) {
const file = webkitEntries[i];
- FileTransferQueue.getQueue().setValidating(true);
const validation = await this.validateTraverse(file);
- FileTransferQueue.getQueue().setValidating(false);
if (!validation.test) {
- // TODO: show nice in GUI
- alert(validation.reason);
- return true;
+ FileTransferQueue.getQueue().setValidating(false);
+ FileTransferQueue.getQueue().pushValidationError(validation.reason);
+ return false;
}
}
+ FileTransferQueue.getQueue().setValidating(false);
+
for (let i = 0; i < webkitEntries.length; i++) {
const file = webkitEntries[i];
this.traverse(ts, file);
@@ -146,9 +168,17 @@ export class UploadTarget {
return true;
}
- private async validateTraverse(fileEntry: any): Promise<{ test: boolean, fileEntry: any, reason: string | null }> {
- if (fileEntry.isDirectory) {
- let children = await this.readEntries(fileEntry);
+ private async validateTraverse(fileEntry: File | FileSystemEntry): Promise<{
+ test: boolean,
+ fileEntry: any,
+ reason: string | null
+ }> {
+ if(fileEntry == null) {
+ console.error("File entry is null", fileEntry);
+ return {test: false, fileEntry: fileEntry, reason: "File entry is null"};
+ }
+ if ("isDirectory" in fileEntry && fileEntry.isDirectory) {
+ let children = await this.readEntries(fileEntry as FileSystemDirectoryEntry);
const promises: Array> = [];
for (let childIndex in children) {
const child = children[childIndex];
@@ -160,12 +190,12 @@ export class UploadTarget {
const failed = results.find((result) => !result.test);
return failed || {test: true, fileEntry, reason: null};
} else {
- const validation = await this.validateFile(fileEntry);
+ const validation = await this.validateFile(fileEntry as FileSystemFileEntry | File);
return {test: validation.valid, fileEntry, reason: validation.reason};
}
}
- private async readEntries(fileEntry: any): Promise {
+ private async readEntries(fileEntry: FileSystemDirectoryEntry): Promise {
const reader = fileEntry.createReader();
return new Promise((accept, reject) => {
@@ -187,7 +217,7 @@ export class UploadTarget {
* @param fileEntry the entry which contains the name and size
* @private
*/
- private async validateFile(fileEntry: File|FileSystemEntry): Promise<{ valid: boolean, reason: string | null }> {
+ private async validateFile(fileEntry: File | FileSystemEntry): Promise<{ valid: boolean, reason: string | null }> {
const size = await this.getEntrySize(fileEntry);
return new Promise((accept, reject) => {
const isFileSystemEntry = 'fullPath' in fileEntry;
@@ -223,13 +253,19 @@ export class UploadTarget {
* @param fileEntry the file entry
* @private
*/
- private async getEntrySize(fileEntry: any): Promise {
+ private async getEntrySize(fileEntry: File | FileSystemEntry): Promise {
return new Promise((accept, reject) => {
- if("getMetadata" in fileEntry){
- fileEntry.getMetadata((metadata) => {
- accept(metadata.size);
- }, (err) => reject(err));
+ if("file" in fileEntry) {
+ (fileEntry as FileSystemFileEntry ).file((file) => {
+ accept(file.size);
+ }, (error) => {
+ console.error(["Error while reading file size", fileEntry, error]);
+ accept(-1);
+ });
+ } else if("size" in fileEntry) {
+ accept(fileEntry.size);
} else {
+ console.error(["Error while reading file size", fileEntry]);
accept(-1);
}
});
diff --git a/mycore-webtools/src/main/ts/upload/src/upload-gui.ts b/mycore-webtools/src/main/ts/upload/src/upload-gui.ts
index 33386d51d1..38cad7e129 100644
--- a/mycore-webtools/src/main/ts/upload/src/upload-gui.ts
+++ b/mycore-webtools/src/main/ts/upload/src/upload-gui.ts
@@ -110,6 +110,10 @@ export class FileTransferGUI {
this.handleSessionBeginError(session, message);
});
+ queue.addValidationHandler((ft) => {
+ this.showError(ft);
+ });
+
window.setInterval(() => {
this.transferProgressMap.forEach((progress) => {
this.setTransferProgress(this.getEntry(progress.transfer), progress.loaded, progress.total);
@@ -273,7 +277,7 @@ export class FileTransferGUI {
}
}
- private showError(message: string) {
+ public showError(message: string) {
const error = this._uploadBox.querySelector(".mcr-commit-error");
if (error.classList.contains("d-none")) {
error.classList.remove("d-none");
@@ -334,7 +338,7 @@ class FileTransferGUITemplates {