diff --git a/mycore-base/src/main/java/org/mycore/common/content/MCRContent.java b/mycore-base/src/main/java/org/mycore/common/content/MCRContent.java index 083e5ecb4d..e5212c3b10 100644 --- a/mycore-base/src/main/java/org/mycore/common/content/MCRContent.java +++ b/mycore-base/src/main/java/org/mycore/common/content/MCRContent.java @@ -33,11 +33,18 @@ import java.nio.channels.ReadableByteChannel; import java.nio.file.AtomicMoveNotSupportedException; import java.nio.file.CopyOption; +import java.nio.file.FileStore; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.nio.file.StandardOpenOption; +import java.nio.file.attribute.AclEntry; +import java.nio.file.attribute.AclFileAttributeView; +import java.nio.file.attribute.PosixFileAttributeView; +import java.nio.file.attribute.PosixFilePermission; import java.util.Base64; +import java.util.List; +import java.util.Set; import javax.xml.transform.Source; @@ -245,7 +252,33 @@ public void sendSafelyTo(Path target) throws IOException { if (!Files.isWritable(dir)) { throw new IOException("Target directory is not writable: " + dir); } + + // store current permissions of target file + // usually: ACLs for Windows and POSIX permissions for UNIX + List aclEntries = null; + Set posixPermissions = null; + FileStore fileStore = Files.getFileStore(target); + if (fileStore.supportsFileAttributeView(AclFileAttributeView.class)) { + aclEntries = Files.getFileAttributeView(target, AclFileAttributeView.class).getAcl(); + } + if (fileStore.supportsFileAttributeView(PosixFileAttributeView.class)) { + posixPermissions = Files.getPosixFilePermissions(target); + } + if (LOGGER.isWarnEnabled() && (aclEntries == null && posixPermissions == null)) { + LOGGER.warn("No supported file permissions (ACLs, POSIX) were found when recrating {} with new content;" + + " new version of file will have default file permissions", target::toAbsolutePath); + } + Path tmp = Files.createTempFile(dir, target.getFileName().toString(), ".tmp"); + + // apply stored permissions to tmp file; will be preserved during the move operation below + if (aclEntries != null) { + Files.getFileAttributeView(tmp, AclFileAttributeView.class).setAcl(aclEntries); + } + if (posixPermissions != null) { + Files.setPosixFilePermissions(tmp, posixPermissions); + } + try { final int chunkSize = 128 * 1024; try (InputStream in = getInputStream(); @@ -272,6 +305,7 @@ public void sendSafelyTo(Path target) throws IOException { LOGGER.error("Failed to delete temporary file at {}", tmp, e); } } + } /** diff --git a/mycore-orcid2/src/main/java/org/mycore/orcid2/v3/work/MCRORCIDWorkEventHandlerImpl.java b/mycore-orcid2/src/main/java/org/mycore/orcid2/v3/work/MCRORCIDWorkEventHandlerImpl.java index b2ce29d2f3..198e0dfe8c 100644 --- a/mycore-orcid2/src/main/java/org/mycore/orcid2/v3/work/MCRORCIDWorkEventHandlerImpl.java +++ b/mycore-orcid2/src/main/java/org/mycore/orcid2/v3/work/MCRORCIDWorkEventHandlerImpl.java @@ -18,6 +18,7 @@ package org.mycore.orcid2.v3.work; +import java.util.List; import java.util.Set; import org.mycore.common.content.MCRJDOMContent; @@ -73,4 +74,12 @@ protected Set findMatchingORCIDs(Set identifiers) { protected Work transformObject(MCRJDOMContent object) { return MCRORCIDWorkTransformerHelper.transformContent(object); } + + @Override + protected List listRelatedOrcidIdentifiers(Work work) { + return work.getWorkContributors().getContributor().stream().filter(c -> c.getContributorOrcid() != null) + .filter(c -> c.getContributorAttributes() != null) + .filter(c -> c.getContributorAttributes().getContributorRole() != null) + .map(c -> c.getContributorOrcid().getPath()).toList(); + } } diff --git a/mycore-orcid2/src/main/java/org/mycore/orcid2/work/MCRORCIDWorkEventHandler.java b/mycore-orcid2/src/main/java/org/mycore/orcid2/work/MCRORCIDWorkEventHandler.java index 8f14aed27c..0f1eaff611 100644 --- a/mycore-orcid2/src/main/java/org/mycore/orcid2/work/MCRORCIDWorkEventHandler.java +++ b/mycore-orcid2/src/main/java/org/mycore/orcid2/work/MCRORCIDWorkEventHandler.java @@ -148,13 +148,14 @@ private void handlePublication(MCRObject object) { final Map toPublish = new HashMap<>(userOrcidPairFromFlag); toPublish.putAll(userOrcidPairFromObject); toPublish.keySet().removeAll(toDelete.keySet()); + final T work = transformObject(new MCRJDOMContent(filteredObject.createXML())); + toPublish.keySet().removeAll(listRelatedOrcidIdentifiers(work)); if (toDelete.isEmpty() && toPublish.isEmpty()) { LOGGER.info("Nothing to delete or publish. Skipping {}...", objectID); tryCollectAndSaveExternalPutCodes(filteredObject); return; } try { - final T work = transformObject(new MCRJDOMContent(filteredObject.createXML())); final Set identifiers = listTrustedIdentifiers(work); if (!toDelete.isEmpty()) { deleteWorks(toDelete, identifiers, flagContent); @@ -376,7 +377,7 @@ private List listTrustedNameIdentifiers(Element nameElement) { /** * Lists trusted identifiers as Set of MCRIdentifier. - * + * * @param work the Work * @return Set of MCRIdentifier */ @@ -384,7 +385,7 @@ private List listTrustedNameIdentifiers(Element nameElement) { /** * Lists matching ORCID iDs based on search via MCRIdentifier - * + * * @param identifiers the MCRIdentifiers * @return Set of ORCID iDs as String * @throws MCRORCIDException if request fails @@ -393,7 +394,7 @@ private List listTrustedNameIdentifiers(Element nameElement) { /** * Removes Work in ORCID profile and updates MCRORCIDPutCodeInfo. - * + * * @param workInfo the MCRORCIDPutCodeInfo * @param orcid the ORCID iD * @param credential the MCRORCIDCredential @@ -405,7 +406,7 @@ private List listTrustedNameIdentifiers(Element nameElement) { /** * Updates Work in ORCID profile. - * + * * @param putCode the put code * @param work the Work * @param orcid the ORCID iD @@ -418,7 +419,7 @@ private List listTrustedNameIdentifiers(Element nameElement) { /** * Creates Work in ORCID profile and updates MCRORCIDPutCodeInfo. - * + * * @param work the Work * @param workInfo the MCRORCIDPutCodeInfo * @param orcid the ORCID iD @@ -431,7 +432,7 @@ abstract protected void createWork(T work, MCRORCIDPutCodeInfo workInfo, String /** * Updates work info based on MCRIdentifier. - * + * * @param identifiers the MCRIdentifier * @param workInfo the MCRORCIDPutCodeInfo * @param orcid the ORCID iD @@ -443,7 +444,7 @@ abstract protected void updateWorkInfo(Set identifiers, MCRORCIDP /** * Updates work info based on MCRIdentifier. - * + * * @param identifiers the MCRIdentifier * @param workInfo the MCRORCIDPutCodeInfo * @param orcid the ORCID iD @@ -453,10 +454,18 @@ abstract protected void updateWorkInfo(Set identifiers, MCRORCIDP /** * Transforms MCRObject as MCRJDOMContent to Work. - * + * * @param object the MCRObject * @return the Work */ @SuppressWarnings("TypeParameterUnusedInFormals") abstract protected T transformObject(MCRJDOMContent object); + + /** + * Returns all ORCID iDs of related persons for work. + * + * @param work the work + * @return list over ORCID iD elements + */ + abstract protected List listRelatedOrcidIdentifiers(T work); } diff --git a/mycore-pi/src/main/java/org/mycore/pi/urn/MCRURNUtils.java b/mycore-pi/src/main/java/org/mycore/pi/urn/MCRURNUtils.java index 13b830be50..e904cd2f85 100644 --- a/mycore-pi/src/main/java/org/mycore/pi/urn/MCRURNUtils.java +++ b/mycore-pi/src/main/java/org/mycore/pi/urn/MCRURNUtils.java @@ -19,9 +19,8 @@ package org.mycore.pi.urn; import java.text.ParseException; -import java.text.SimpleDateFormat; +import java.time.Instant; import java.util.Date; -import java.util.Locale; import java.util.Optional; import org.apache.logging.log4j.LogManager; @@ -53,11 +52,14 @@ public static Date getDNBRegisterDate(String identifier) throws ParseException { .map(JsonElement::getAsString) .orElse(null); + return parseDNBRegisterDate(date); + } + + static Date parseDNBRegisterDate(String date) { if (date == null) { return null; } - - return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX", Locale.GERMAN).parse(date); + return Date.from(Instant.parse(date)); } } diff --git a/mycore-pi/src/test/java/org/mycore/pi/urn/MCRURNUtilsTest.java b/mycore-pi/src/test/java/org/mycore/pi/urn/MCRURNUtilsTest.java new file mode 100644 index 0000000000..4af8c0235f --- /dev/null +++ b/mycore-pi/src/test/java/org/mycore/pi/urn/MCRURNUtilsTest.java @@ -0,0 +1,59 @@ +/* + * 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.pi.urn; + +import java.time.Instant; +import java.time.format.DateTimeParseException; +import java.util.Date; + +import org.junit.Assert; +import org.junit.Test; + +public class MCRURNUtilsTest { + + private static final String[] VALID_DATES = { + "2025-11-07T15:00:21.000Z", + "2025-11-07T15:00:21Z", + "2025-11-07T15:00:21.1Z", + "2025-11-07T15:00:21-12:00", + "2025-11-07T15:00:21+01:00", + "2025-11-07T15:00:21.123456Z", + }; + + @Test + public void parseValidDates() { + for (String date : VALID_DATES) { + Date result = MCRURNUtils.parseDNBRegisterDate(date); + Assert.assertNotNull("should parse: " + date, result); + Assert.assertEquals("parsed date should match Instant.parse for: " + date, + Date.from(Instant.parse(date)), result); + } + } + + @Test + public void parseNullReturnsNull() { + Assert.assertNull(MCRURNUtils.parseDNBRegisterDate(null)); + } + + @Test(expected = DateTimeParseException.class) + public void parseInvalidThrows() { + MCRURNUtils.parseDNBRegisterDate("invalidDate"); + } + +} diff --git a/mycore-user2/src/main/resources/xsl/users-subselect.xsl b/mycore-user2/src/main/resources/xsl/users-subselect.xsl index 8ba4a40787..ef81f889ad 100644 --- a/mycore-user2/src/main/resources/xsl/users-subselect.xsl +++ b/mycore-user2/src/main/resources/xsl/users-subselect.xsl @@ -21,6 +21,7 @@ + diff --git a/mycore-user2/src/main/resources/xslt/users-subselect.xsl b/mycore-user2/src/main/resources/xslt/users-subselect.xsl index a8c855a1bb..47829914fd 100644 --- a/mycore-user2/src/main/resources/xslt/users-subselect.xsl +++ b/mycore-user2/src/main/resources/xslt/users-subselect.xsl @@ -15,6 +15,7 @@ +