From be4d7cf269641afa054b69cae3f41eb86cb9340b Mon Sep 17 00:00:00 2001 From: erodde <155449327+erodde@users.noreply.github.com> Date: Tue, 6 Feb 2024 17:49:54 +0100 Subject: [PATCH 01/15] UBO-295 publication event handler creates redundant users in database (#354) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * UBO-295 Do not ignore existing user(s) if there's more than one * UBO-295 / Improved connection_id handling and user-matching by real name * UBO-295 / simplified testdata * UBO-295 / fixed build errors * UBO-295 / fixed codestyle + role-management in test case * UBO-295 / fixed handling of apostrophes in author names * UBO-295 / Authors with only family name can be matched * UBO-295 / xsl-stylesheet for removal of connection ids added --------- Co-authored-by: Frank Lützenkirchen --- ubo-common/pom.xml | 50 ++++--- .../ubo/matcher/MCRUserMatcherLocal.java | 59 +++++--- .../ubo/matcher/MCRUserMatcherUtils.java | 86 +++++++---- .../publication/PublicationEventHandler.java | 37 ++--- .../main/resources/xsl/removeConnectionId.xsl | 8 ++ .../PublicationEventHandlerTest.java | 133 ++++++++++++++++++ .../junit_mods_00000001.xml | 20 +++ .../junit_mods_00000002.xml | 20 +++ .../junit_mods_00000003.xml | 20 +++ .../junit_mods_00000004.xml | 18 +++ .../src/test/resources/mycore.properties | 6 + 11 files changed, 378 insertions(+), 79 deletions(-) create mode 100644 ubo-common/src/main/resources/xsl/removeConnectionId.xsl create mode 100644 ubo-common/src/test/java/org/mycore/ubo/publication/PublicationEventHandlerTest.java create mode 100644 ubo-common/src/test/resources/PublicationEventHandlerTest/junit_mods_00000001.xml create mode 100644 ubo-common/src/test/resources/PublicationEventHandlerTest/junit_mods_00000002.xml create mode 100644 ubo-common/src/test/resources/PublicationEventHandlerTest/junit_mods_00000003.xml create mode 100644 ubo-common/src/test/resources/PublicationEventHandlerTest/junit_mods_00000004.xml create mode 100644 ubo-common/src/test/resources/mycore.properties diff --git a/ubo-common/pom.xml b/ubo-common/pom.xml index a71f32382..15786fef5 100644 --- a/ubo-common/pom.xml +++ b/ubo-common/pom.xml @@ -133,6 +133,19 @@ + + maven-dependency-plugin + + + analyze + + + jaxen:jaxen + + + + + @@ -162,6 +175,24 @@ jakarta.xml.bind jakarta.xml.bind-api + + jaxen + jaxen + + + jdom + jdom2 + + + xom + xom + + + xml-apis + xml-apis + + + net.sf.saxon Saxon-HE @@ -262,25 +293,6 @@ 2.3.2 runtime - - jaxen - jaxen - runtime - - - jdom - jdom2 - - - xom - xom - - - xml-apis - xml-apis - - - org.apache.logging.log4j log4j-core diff --git a/ubo-common/src/main/java/org/mycore/ubo/matcher/MCRUserMatcherLocal.java b/ubo-common/src/main/java/org/mycore/ubo/matcher/MCRUserMatcherLocal.java index 4d34a1178..718125550 100644 --- a/ubo-common/src/main/java/org/mycore/ubo/matcher/MCRUserMatcherLocal.java +++ b/ubo-common/src/main/java/org/mycore/ubo/matcher/MCRUserMatcherLocal.java @@ -10,6 +10,11 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.jdom2.Element; +import org.mycore.common.MCRException; +import org.mycore.common.xml.MCRNodeBuilder; +import org.mycore.mods.merger.MCRMerger; +import org.mycore.mods.merger.MCRMergerFactory; import org.mycore.user2.MCRUser; import org.mycore.user2.MCRUserAttribute; import org.mycore.user2.MCRUserManager; @@ -17,6 +22,7 @@ /** * Given a MCRUser, match against the local MCRUsers, returning the given User or an existing local one if matched. * If matched, the returned local MCRUsers attributes are enriched by attributes from the given MCRUser. + * The attribute id_connection is considered unique and can not be enriched/added a second time. * * @author Pascal Rost */ @@ -24,28 +30,39 @@ public class MCRUserMatcherLocal implements MCRUserMatcher { private final static Logger LOGGER = LogManager.getLogger(MCRUserMatcherLocal.class); + private final static String CONNECTION_TYPE_NAME = "id_connection"; + + private static final String XPATH_TO_BUILD_MODSNAME = "mods:name[@type='personal']/mods:namePart"; + @Override public MCRUserMatcherDTO matchUser(MCRUserMatcherDTO matcherDTO) { - MCRUser mcrUser = matcherDTO.getMCRUser(); List matchingUsers = new ArrayList<>(getUsersForGivenAttributes(mcrUser.getAttributes())); - if(matchingUsers.size() == 1) { + + MCRMerger nameThatShouldMatch = buildNameMergerFrom(mcrUser); + matchingUsers.removeIf(userToTest -> !buildNameMergerFrom(userToTest).isProbablySameAs(nameThatShouldMatch)); + + if (!matchingUsers.isEmpty()) { MCRUser matchingUser = matchingUsers.get(0); - LOGGER.info("Found local matching user! Matched user: {} and attributes: {} with local user: {} and attributes: {}", - mcrUser.getUserName(), - mcrUser.getAttributes().stream().map(a -> a.getName() + "=" + a.getValue()).collect(Collectors.joining(" | ")), - matchingUser.getUserName(), - matchingUser.getAttributes().stream().map(a -> a.getName() + "=" + a.getValue()).collect(Collectors.joining(" | "))); + LOGGER.info( + "Found local matching user! Matched user: {} and attributes: {} with local user: {} and attributes: {}", + mcrUser.getUserName(), + mcrUser.getAttributes().stream().map(a -> a.getName() + "=" + a.getValue()) + .collect(Collectors.joining(" | ")), + matchingUser.getUserName(), + matchingUser.getAttributes().stream().map(a -> a.getName() + "=" + a.getValue()) + .collect(Collectors.joining(" | "))); - // only add not attributes which are not present - matchingUser.getAttributes() - .addAll(mcrUser.getAttributes().stream() - .filter(Predicate.not(matchingUser.getAttributes()::contains)) - .collect(Collectors.toUnmodifiableList())); + final boolean hasMatchingUserConnectionKey = matchingUser.getUserAttribute(CONNECTION_TYPE_NAME) != null; - mcrUser = matchingUser; - matcherDTO.setMCRUser(mcrUser); + // only add attributes which are not present, don't add duplicate connection attributes + matchingUser.getAttributes().addAll(mcrUser.getAttributes().stream() + .filter(attribute -> !attribute.getName().equals(CONNECTION_TYPE_NAME) || !hasMatchingUserConnectionKey) + .filter(Predicate.not(matchingUser.getAttributes()::contains)) + .toList()); + + matcherDTO.setMCRUser(matchingUser); matcherDTO.setMatchedOrEnriched(true); } return matcherDTO; @@ -53,12 +70,22 @@ public MCRUserMatcherDTO matchUser(MCRUserMatcherDTO matcherDTO) { private Set getUsersForGivenAttributes(SortedSet mcrAttributes) { Set users = new HashSet<>(); - for(MCRUserAttribute mcrAttribute : mcrAttributes) { + for (MCRUserAttribute mcrAttribute : mcrAttributes) { String attributeName = mcrAttribute.getName(); String attributeValue = mcrAttribute.getValue(); - users.addAll(MCRUserManager.getUsers(attributeName, attributeValue).collect(Collectors.toList())); + users.addAll(MCRUserManager.getUsers(attributeName, attributeValue).toList()); } return users; } + private MCRMerger buildNameMergerFrom(MCRUser user) { + try { + Element modsName = new MCRNodeBuilder().buildElement(XPATH_TO_BUILD_MODSNAME, + user.getRealName(), null).getParentElement(); + return MCRMergerFactory.buildFrom(modsName); + } catch (Exception shouldNeverOccur) { + throw new MCRException(shouldNeverOccur); + } + } + } diff --git a/ubo-common/src/main/java/org/mycore/ubo/matcher/MCRUserMatcherUtils.java b/ubo-common/src/main/java/org/mycore/ubo/matcher/MCRUserMatcherUtils.java index 5fcf8ba96..0c76be552 100644 --- a/ubo-common/src/main/java/org/mycore/ubo/matcher/MCRUserMatcherUtils.java +++ b/ubo-common/src/main/java/org/mycore/ubo/matcher/MCRUserMatcherUtils.java @@ -28,22 +28,23 @@ import org.mycore.user2.MCRUser2Constants; import org.mycore.user2.MCRUserAttribute; +import static org.mycore.common.MCRConstants.XPATH_FACTORY; /** * Utility class for everything related to matching users of publications in MODS-format with MCRUsers and other * applications/servers/APIs. - * + *

* The following properties in the mycore.properties are used: - * + *

* # Used to check the affiliation of publication authors * MCR.user2.IdentityManagement.UserCreation.Affiliation=Uni Jena - * + *

* # Mapping from LDAP attribute to real name of user * MCR.user2.LDAP.Mapping.Name=cn - * + *

* # Mapping from LDAP attribute to E-Mail address of user * MCR.user2.LDAP.Mapping.E-Mail=mail - * + *

* # Mapping of any attribute.value combination to group membership of user * # eduPersonScopedAffiliation may be faculty|staff|employee|student|alum|member|affiliate * MCR.user2.LDAP.Mapping.Group.eduPersonScopedAffiliation.staff@uni-duisburg-essen.de=submitter * @@ -66,9 +67,9 @@ public static List getNameElements(MCRObject obj) { public static Map getNameIdentifiers(Element modsNameElement) { Map nameIdentifiers = new HashMap<>(); // TODO: possible use of MultiMap for multiple attributes of the same type List identifiers = modsNameElement.getChildren("nameIdentifier", MODS_NAMESPACE); - for(Element identifierElement : identifiers) { + for (Element identifierElement : identifiers) { String type = identifierElement.getAttributeValue("type"); - if(type!=null){ + if (type != null) { String identifier = identifierElement.getText(); nameIdentifiers.put(type, identifier); } @@ -77,21 +78,43 @@ public static Map getNameIdentifiers(Element modsNameElement) { return nameIdentifiers; } + /** + * Extracts the real name from the given MODS-Element + * @param modsNameElement the given MODS "name"-Element + * @return the real name in the format "family-name, given-name", or only the family name, + * or null if real name could not be determined + */ + public static String getRealName(Element modsNameElement) { + Element givenName = XPATH_FACTORY.compile("mods:namePart[@type='given']", + Filters.element(), null, MODS_NAMESPACE).evaluateFirst(modsNameElement); + + Element familyName = XPATH_FACTORY.compile("mods:namePart[@type='family']", + Filters.element(), null, MODS_NAMESPACE).evaluateFirst(modsNameElement); + + if ((givenName != null) && (familyName != null)) { + return familyName.getText() + ", " + givenName.getText(); + } else if ((familyName != null)) { + return familyName.getText(); + } + return null; + } + public static boolean containsNameIdentifierWithType(Element modsNameElement, String identifierType) { return MCRUserMatcherUtils.getNameIdentifiers(modsNameElement).containsKey(identifierType); } /** * Extend the MCRUsers attributes by the given mods:nameIdentifiers if the user does not already have these IDs - * @param user the MCRUser whose attributes will be enriched + * + * @param user the MCRUser whose attributes will be enriched * @param nameIdentifiers the mods:nameIdentifiers that should be added, if they are not already present */ public static void enrichUserWithGivenNameIdentifiers(MCRUser user, Map nameIdentifiers) { SortedSet userAttributes = user.getAttributes(); - for(Map.Entry nameIdentifier : nameIdentifiers.entrySet()) { + for (Map.Entry nameIdentifier : nameIdentifiers.entrySet()) { String name = mapModsNameIdentifierTypeToMycore(nameIdentifier.getKey()); String value = nameIdentifier.getValue(); - if(user.getUserAttribute(name) == null) { + if (user.getUserAttribute(name) == null) { LOGGER.debug("Enriching user: {} with attribute: {}, value: {}", user.getUserName(), name, value); userAttributes.add(new MCRUserAttribute(name, value)); } @@ -107,6 +130,7 @@ private static String mapModsNameIdentifierTypeToMycore(String nameIdentifierTyp * Given a mods:name Element, create a new transient (not persisted) MCRUser where the mods:namePart and * mods:nameIdentifier child-elements are used for the user name and attributes of the new MCRUser. Does not set a * realm for the new MCRUser (the default one is the configured "local"-realm). + * * @param modsNameElement the mods:name-Element (xml) from which a new transient MCRUser shall be created * @return MCRUser, a transient MCRUser in the realm "local" (as configured) */ @@ -119,41 +143,42 @@ public static MCRUser createNewMCRUserFromModsNameElement(Element modsNameElemen Map nameIdentifiers = MCRUserMatcherUtils.getNameIdentifiers(modsNameElement); MCRUser mcrUser = new MCRUser(userName, realmID); enrichUserWithGivenNameIdentifiers(mcrUser, nameIdentifiers); + mcrUser.setRealName(getRealName(modsNameElement)); return mcrUser; } public static String getAttributesAsURLString(List modsNameElements) { String parameters = ""; - for(Element modsNameElement : modsNameElements) { + for (Element modsNameElement : modsNameElements) { Map parametersMap = getNameIdentifiers(modsNameElement); XPathFactory xFactory = XPathFactory.instance(); XPathExpression givenNameExpr = xFactory.compile("mods:namePart[@type='given']", - Filters.element(), null, MODS_NAMESPACE); + Filters.element(), null, MODS_NAMESPACE); Element givenNameElem = givenNameExpr.evaluateFirst(modsNameElement); XPathExpression familyNameExpr = xFactory.compile("mods:namePart[@type='family']", - Filters.element(), null, MODS_NAMESPACE); + Filters.element(), null, MODS_NAMESPACE); Element familyNameElem = familyNameExpr.evaluateFirst(modsNameElement); - if(familyNameElem != null) { + if (familyNameElem != null) { parametersMap.put("lastName", familyNameElem.getText()); } - if(givenNameElem != null) { + if (givenNameElem != null) { // the following is a compatibility preserving hack, the LSF-Search works with "firstname" WITHOUT CAMELCASE parametersMap.put("firstname", givenNameElem.getText()); // the LDAP-Search works WITH CAMELCASE so at this point we just provide both parameters parametersMap.put("firstName", givenNameElem.getText()); } List singleParameters = new ArrayList<>(); - for(Map.Entry parameter : parametersMap.entrySet()) { + for (Map.Entry parameter : parametersMap.entrySet()) { String encodedValue = null; try { encodedValue = URLEncoder.encode(parameter.getValue(), "UTF-8"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } - if(!parameter.getKey().isEmpty()){ + if (!parameter.getKey().isEmpty()) { singleParameters.add(parameter.getKey() + '=' + encodedValue); } } @@ -166,8 +191,9 @@ public static String getAttributesAsURLString(List modsNameElements) { } public static boolean checkAffiliation(Element modsNameElement) { - String affiliation = MCRConfiguration2.getString("MCR.user2.IdentityManagement.UserCreation.Affiliation").orElse(null); - if(affiliation == null) { + String affiliation + = MCRConfiguration2.getString("MCR.user2.IdentityManagement.UserCreation.Affiliation").orElse(null); + if (affiliation == null) { return false; } @@ -178,11 +204,11 @@ public static boolean checkAffiliation(Element modsNameElement) { XPathFactory xFactory = XPathFactory.instance(); XPathExpression affiliationExpr = xFactory.compile("mods:affiliation", - Filters.element(), null, MODS_NAMESPACE); + Filters.element(), null, MODS_NAMESPACE); Element affiliationElem = affiliationExpr.evaluateFirst(modsNameElement); - if(affiliationElem != null) { + if (affiliationElem != null) { String modsNameAffiliation = affiliationElem.getText(); - if(modsNameAffiliation.contains(affiliation)) { + if (modsNameAffiliation.contains(affiliation)) { affiliated = true; } } @@ -192,11 +218,12 @@ public static boolean checkAffiliation(Element modsNameElement) { /** * Method to set the static MCRUser-Attributes (RealName and Email) - * @param mcrUser the MCRUser whose static Attributes should be set + * + * @param mcrUser the MCRUser whose static Attributes should be set * @param ldapUser the ldapUser whose Attributes are used to fill the static attributes of the MCRUser */ public static void setStaticMCRUserAttributes(MCRUser mcrUser, LDAPObject ldapUser) { - for(Map.Entry attributeEntry : ldapUser.getAttributes().entries()) { + for (Map.Entry attributeEntry : ldapUser.getAttributes().entries()) { String attributeID = attributeEntry.getKey(); String attributeValue = attributeEntry.getValue(); setUserRealName(mcrUser, attributeID, attributeValue); @@ -221,7 +248,9 @@ private static void setUserRealName(MCRUser user, String attributeID, String att } } - /** Formats a user name into "lastname, firstname" syntax. */ + /** + * Formats a user name into "lastname, firstname" syntax. + */ private static String formatName(String name) { name = name.replaceAll("\\s+", " ").trim(); if (name.contains(",")) { @@ -240,13 +269,14 @@ private static String formatName(String name) { * # Mapping of any attribute.value combination to group membership of user * # eduPersonScopedAffiliation may be faculty|staff|employee|student|alum|member|affiliate * MCR.user2.LDAP.Mapping.Group.eduPersonScopedAffiliation.staff@uni-duisburg-essen.de=submitter * - * @param mcrUser the MCRUser that shall be added to the configured groups/roles if the corresponding - * LDAP-Attributes exist + * + * @param mcrUser the MCRUser that shall be added to the configured groups/roles if the corresponding + * LDAP-Attributes exist * @param ldapUser the LDAPObject/User from which the LDAP-Attributes should be used to map against the configured * groups/roles for the MCRUser */ public static void addMCRUserToDynamicGroups(MCRUser mcrUser, LDAPObject ldapUser) { - for(Map.Entry attributeEntry : ldapUser.getAttributes().entries()) { + for (Map.Entry attributeEntry : ldapUser.getAttributes().entries()) { String attributeID = attributeEntry.getKey(); String attributeValue = attributeEntry.getValue(); addToGroup(mcrUser, attributeID, attributeValue); diff --git a/ubo-common/src/main/java/org/mycore/ubo/publication/PublicationEventHandler.java b/ubo-common/src/main/java/org/mycore/ubo/publication/PublicationEventHandler.java index 160a31390..6e4eb824a 100644 --- a/ubo-common/src/main/java/org/mycore/ubo/publication/PublicationEventHandler.java +++ b/ubo-common/src/main/java/org/mycore/ubo/publication/PublicationEventHandler.java @@ -87,7 +87,8 @@ public class PublicationEventHandler extends MCREventHandlerBase { private final static String CONFIG_LEAD_ID = "MCR.user2.matching.lead_id"; private final static String CONFIG_CONNECTION_STRATEGY = "MCR.user2.matching.publication.connection.strategy"; private final static String CONFIG_DEFAULT_ROLE = "MCR.user2.IdentityManagement.UserCreation.DefaultRole"; - private final static String CONFIG_UNVALIDATED_REALM = "MCR.user2.IdentityManagement.UserCreation.Unvalidated.Realm"; + private final static String CONFIG_UNVALIDATED_REALM + = "MCR.user2.IdentityManagement.UserCreation.Unvalidated.Realm"; private final static String CONFIG_SKIP_LEAD_ID = "MCR.user2.matching.lead_id.skip"; @@ -95,7 +96,7 @@ public class PublicationEventHandler extends MCREventHandlerBase { /** The default Role that is assigned to newly created users **/ private String defaultRoleForNewlyCreatedUsers; - + /** The ID of the realm for newly created unvalidated MCRUsers **/ private String unvalidatedRealmID; @@ -107,13 +108,13 @@ public class PublicationEventHandler extends MCREventHandlerBase { /** A chain of implemented user matchers */ private List chainOfUserMatchers; - + /** The configured connection strategy to "connect" publications to MCRUsers */ private String connectionStrategy; public PublicationEventHandler() { super(); - + this.defaultRoleForNewlyCreatedUsers = MCRConfiguration2.getString(CONFIG_DEFAULT_ROLE).orElse("submitter"); this.unvalidatedRealmID = MCRConfiguration2.getString(CONFIG_UNVALIDATED_REALM).get(); this.leadIDName = MCRConfiguration2.getString(CONFIG_LEAD_ID).orElse(""); @@ -126,13 +127,14 @@ private List loadMatcherImplementationChain() { List matchers = new ArrayList<>(); Optional matcherConfig = MCRConfiguration2.getString(CONFIG_MATCHERS); - if(matcherConfig.isPresent()) { + if (matcherConfig.isPresent()) { String[] matcherClasses = matcherConfig.get().split(","); for (int i = 0; i < matcherClasses.length; i++) { String matcherClass = matcherClasses[i]; try { - - matchers.add((MCRUserMatcher) MCRClassTools.forName(matcherClass).getDeclaredConstructor().newInstance()); + + matchers.add( + (MCRUserMatcher) MCRClassTools.forName(matcherClass).getDeclaredConstructor().newInstance()); } catch (Exception e) { throw new MCRConfigurationException("Property key " + CONFIG_MATCHERS + " not valid.", e); } @@ -165,7 +167,7 @@ protected void handlePublication(MCRObject obj) { } private void handleName(Element modsNameElement) { - MCRUser userFromModsName = MCRUserMatcherUtils.createNewMCRUserFromModsNameElement(modsNameElement); + MCRUser userFromModsName = MCRUserMatcherUtils.createNewMCRUserFromModsNameElement(modsNameElement); MCRUserMatcherDTO matcherDTO = new MCRUserMatcherDTO(userFromModsName); // call our configured Implementation(s) of MCRUserMatcher @@ -195,7 +197,8 @@ private void handleName(Element modsNameElement) { MCRConfiguration2.getBoolean(CONFIG_SKIP_LEAD_ID) .filter(Boolean::booleanValue) .ifPresent(trueValue -> { - List elementsToRemove = modsNameElement.getChildren("nameIdentifier", MCRConstants.MODS_NAMESPACE) + List elementsToRemove + = modsNameElement.getChildren("nameIdentifier", MCRConstants.MODS_NAMESPACE) .stream() .filter(element -> element.getAttributeValue("type").equals(leadIDName)) .collect(Collectors.toList()); @@ -231,7 +234,7 @@ private void logUserMatch(Element modsNameElement, MCRUserMatcherDTO matcherDTO, * mycore.properties and given as parameter "leadID". * A new mods:nameIdentifier-element with type "lead_id" and its value will only be created if no other * mods:nameIdentifier-element with the same ID/type exists as a sub-element of the given modsNameElement. - + * @param modsNameElement the mods:name-element which will be enriched * @param mcrUser the MCRUser corresponding to the modsNameElement */ @@ -254,13 +257,13 @@ private Optional getLeadIDAttributeFromUser(MCRUser mcrUser) { } private void connectModsNameElementWithMCRUser(Element modsNameElement, MCRUser mcrUser) { - if("uuid".equals(connectionStrategy)) { + if ("uuid".equals(connectionStrategy)) { String connectionID = getOrAddConnectionID(mcrUser); // if not already present, persist connection in mods:name - nameIdentifier-Element String connectionIDType = CONNECTION_TYPE_NAME.replace("id_", ""); - if(!MCRUserMatcherUtils.containsNameIdentifierWithType(modsNameElement, connectionIDType)) { + if (!MCRUserMatcherUtils.containsNameIdentifierWithType(modsNameElement, connectionIDType)) { LOGGER.info("Connecting publication with MCRUser: {}, via nameIdentifier of type: {} " + - "and value: {}", mcrUser.getUserName(), connectionIDType, connectionID); + "and value: {}", mcrUser.getUserName(), connectionIDType, connectionID); addNameIdentifierTo(modsNameElement, connectionIDType, connectionID); } } @@ -269,7 +272,7 @@ private void connectModsNameElementWithMCRUser(Element modsNameElement, MCRUser private String getOrAddConnectionID(MCRUser mcrUser) { // check if MCRUser already has a "connection" UUID String uuid = mcrUser.getUserAttribute(CONNECTION_TYPE_NAME); - if(uuid == null) { + if (uuid == null) { // create new UUID and persist it for mcrUser uuid = UUID.randomUUID().toString(); mcrUser.getAttributes().add(new MCRUserAttribute(CONNECTION_TYPE_NAME, uuid)); @@ -290,8 +293,10 @@ protected Optional buildPersonNameFromMODS(Element nameElement) { Element givenName = XPATH_TO_GET_GIVEN_NAME.evaluateFirst(nameElement); Element familyName = XPATH_TO_GET_FAMILY_NAME.evaluateFirst(nameElement); - if ( (givenName != null) && (familyName != null)) { - return Optional.of( familyName.getText() + ", " + givenName.getText() ); + if ((givenName != null) && (familyName != null)) { + return Optional.of(familyName.getText() + ", " + givenName.getText()); + } else if (familyName != null) { + return Optional.of(familyName.getText()); } else { return Optional.empty(); } diff --git a/ubo-common/src/main/resources/xsl/removeConnectionId.xsl b/ubo-common/src/main/resources/xsl/removeConnectionId.xsl new file mode 100644 index 000000000..4ce8035d9 --- /dev/null +++ b/ubo-common/src/main/resources/xsl/removeConnectionId.xsl @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/ubo-common/src/test/java/org/mycore/ubo/publication/PublicationEventHandlerTest.java b/ubo-common/src/test/java/org/mycore/ubo/publication/PublicationEventHandlerTest.java new file mode 100644 index 000000000..83238d7fe --- /dev/null +++ b/ubo-common/src/test/java/org/mycore/ubo/publication/PublicationEventHandlerTest.java @@ -0,0 +1,133 @@ +package org.mycore.ubo.publication; + +import org.jdom2.Document; +import org.jdom2.JDOMException; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.mycore.access.MCRAccessException; +import org.mycore.common.MCRStoreTestCase; +import org.mycore.common.content.MCRURLContent; +import org.mycore.common.events.MCREvent; +import org.mycore.datamodel.metadata.MCRMetadataManager; +import org.mycore.datamodel.metadata.MCRObject; +import org.mycore.datamodel.metadata.MCRObjectMetadataTest; +import org.mycore.user2.*; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; + +import java.io.IOException; +import java.net.URL; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.SortedSet; + +import static org.junit.Assert.fail; + +public class PublicationEventHandlerTest extends MCRStoreTestCase { + + @Before + public void before() { + MCRRoleManager.addRole(new MCRRole("admin", new HashSet<>())); + MCRRoleManager.addRole(new MCRRole("submitter", new HashSet<>())); + } + + /** + * Tests the behavior of {@link PublicationEventHandler#handleObjectRepaired(MCREvent, MCRObject)}, + * especially the case of incorrectly matched {@link MCRUserAttribute attributes} and + * non-unique correlation-IDS. See UBO-295. + * @throws SAXParseException in case of error + * @throws MCRAccessException in case of error + */ + @Test + public void testHandleObjectRepaired() throws SAXException, MCRAccessException, IOException, JDOMException { + // Arrange + URL url1 = MCRObjectMetadataTest.class.getResource("/PublicationEventHandlerTest/junit_mods_00000001.xml"); + Document doc1 = new MCRURLContent(url1).asXML(); + MCRObject obj1 = new MCRObject(doc1); + + URL url2 = MCRObjectMetadataTest.class.getResource("/PublicationEventHandlerTest/junit_mods_00000002.xml"); + Document doc2 = new MCRURLContent(url2).asXML(); + MCRObject obj2 = new MCRObject(doc2); + + URL url3 = MCRObjectMetadataTest.class.getResource("/PublicationEventHandlerTest/junit_mods_00000003.xml"); + Document doc3 = new MCRURLContent(url3).asXML(); + MCRObject obj3 = new MCRObject(doc3); + + MCRMetadataManager.create(obj1); + MCRMetadataManager.create(obj2); + MCRMetadataManager.create(obj3); + + // Act + // tries to add second Connection-ID + MCRMetadataManager.fireRepairEvent(obj2); + + // Assert + List users = MCRUserManager.listUsers(null, null, null, null); + Assert.assertEquals(2, users.size()); + + SortedSet attributesUserMueller = users.get(0).getRealName().contains("Müller") + ? users.get(0).getAttributes() : users.get(1).getAttributes(); + + SortedSet attributesUserMeyer = users.get(0).getRealName().contains("Meyer") + ? users.get(0).getAttributes() : users.get(1).getAttributes(); + + Assert.assertEquals(4, attributesUserMueller.size()); + assertSingleAttribute(attributesUserMueller, "id_connection"); + MCRUserAttribute lsf = assertSingleAttribute(attributesUserMueller, "id_lsf"); + Assert.assertEquals(lsf.getValue(), "11111"); + Assert.assertEquals(2, attributesUserMueller.stream() + .filter(attr -> attr.getName().equals("id_scopus")).toList().size()); + + Assert.assertEquals(3, attributesUserMeyer.size()); + + assertSingleAttribute(attributesUserMeyer, "id_connection"); + + lsf = assertSingleAttribute(attributesUserMeyer, "id_lsf"); + Assert.assertEquals(lsf.getValue(), "12345"); + + MCRUserAttribute scopus = assertSingleAttribute(attributesUserMeyer, "id_scopus"); + Assert.assertEquals(scopus.getValue(), "1112222444"); + } + + /** + * Tests if author names with only the namePart-field "family" are correctly processed + * by the {@link PublicationEventHandler#handleObjectRepaired(MCREvent, MCRObject) repair-event} + * @throws SAXParseException in case of error + * @throws MCRAccessException in case of error + */ + @Test + public void testHandleObjectRepairedOnlyFamilyName() + throws SAXException, MCRAccessException, IOException, JDOMException { + URL url = MCRObjectMetadataTest.class.getResource("/PublicationEventHandlerTest/junit_mods_00000004.xml"); + Document doc = new MCRURLContent(url).asXML(); + MCRObject obj = new MCRObject(doc); + + MCRMetadataManager.create(obj); + MCRMetadataManager.fireRepairEvent(obj); + + List users = MCRUserManager.listUsers(null, null, null, null); + Assert.assertEquals(1, users.size()); + Assert.assertEquals("O'Reilly", users.get(0).getRealName()); + } + + /** + * Helper-method to assert that only a single entry of {@link MCRUserAttribute} + * with a specific key is contained in the given Set. + * Fails, when more than one key is found. Throws a {@link java.util.NoSuchElementException} + * when no attribute with the given key is found. + * @param attributes the given Set of attributes + * @param attrName the name of the key that should only be contained once in the set + * @return the single identified attribute + */ + private MCRUserAttribute assertSingleAttribute(Set attributes, String attrName) { + return attributes.stream() + .filter(attr -> attr.getName().equals(attrName)) + .reduce((a, b) -> { + fail("multiple IDs of type " + attrName + ": " + a.getValue() + ", " + b.getValue()); + return null; + }).get(); + } +} diff --git a/ubo-common/src/test/resources/PublicationEventHandlerTest/junit_mods_00000001.xml b/ubo-common/src/test/resources/PublicationEventHandlerTest/junit_mods_00000001.xml new file mode 100644 index 000000000..fff366d51 --- /dev/null +++ b/ubo-common/src/test/resources/PublicationEventHandlerTest/junit_mods_00000001.xml @@ -0,0 +1,20 @@ + + + + + + + + This is a test-publication + + + Müller + Lisa + 11111 + 1112222333 + + + + + + \ No newline at end of file diff --git a/ubo-common/src/test/resources/PublicationEventHandlerTest/junit_mods_00000002.xml b/ubo-common/src/test/resources/PublicationEventHandlerTest/junit_mods_00000002.xml new file mode 100644 index 000000000..ca66af699 --- /dev/null +++ b/ubo-common/src/test/resources/PublicationEventHandlerTest/junit_mods_00000002.xml @@ -0,0 +1,20 @@ + + + + + + + + This is also a test-publication + + + Meyer + Lena + 12345 + 1112222444 + + + + + + \ No newline at end of file diff --git a/ubo-common/src/test/resources/PublicationEventHandlerTest/junit_mods_00000003.xml b/ubo-common/src/test/resources/PublicationEventHandlerTest/junit_mods_00000003.xml new file mode 100644 index 000000000..d3ad49239 --- /dev/null +++ b/ubo-common/src/test/resources/PublicationEventHandlerTest/junit_mods_00000003.xml @@ -0,0 +1,20 @@ + + + + + + + + This is also a test-publication + + + Müller + Lisa + 11111 + 1112222444 + + + + + + \ No newline at end of file diff --git a/ubo-common/src/test/resources/PublicationEventHandlerTest/junit_mods_00000004.xml b/ubo-common/src/test/resources/PublicationEventHandlerTest/junit_mods_00000004.xml new file mode 100644 index 000000000..a12e10623 --- /dev/null +++ b/ubo-common/src/test/resources/PublicationEventHandlerTest/junit_mods_00000004.xml @@ -0,0 +1,18 @@ + + + + + + + + This is also a test-publication + + + O'Reilly + 22222 + + + + + + \ No newline at end of file diff --git a/ubo-common/src/test/resources/mycore.properties b/ubo-common/src/test/resources/mycore.properties new file mode 100644 index 000000000..9e11ea1ce --- /dev/null +++ b/ubo-common/src/test/resources/mycore.properties @@ -0,0 +1,6 @@ +MCR.user2.IdentityManagement.UserCreation.Unvalidated.Realm=local +MCR.user2.matching.chain=org.mycore.ubo.matcher.MCRUserMatcherDummy +MCR.user2.matching.lead_id=lsf +MCR.user2.matching.publication.connection.strategy=uuid +MCR.EventHandler.MCRObject.019.Class=org.mycore.ubo.publication.PublicationEventHandler +MCR.Access.Class=org.mycore.access.MCRAccessBaseImpl From f7f3e421790af73a1107f9dfae47250fbf9e6d9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Frank=20L=C3=BCtzenkirchen?= Date: Wed, 7 Feb 2024 08:52:27 +0100 Subject: [PATCH 02/15] UBO-301 Removed code to handle affiliation and to write lead ID back --- .../publication/PublicationEventHandler.java | 53 ------------------- 1 file changed, 53 deletions(-) diff --git a/ubo-common/src/main/java/org/mycore/ubo/publication/PublicationEventHandler.java b/ubo-common/src/main/java/org/mycore/ubo/publication/PublicationEventHandler.java index 6e4eb824a..0b0089c99 100644 --- a/ubo-common/src/main/java/org/mycore/ubo/publication/PublicationEventHandler.java +++ b/ubo-common/src/main/java/org/mycore/ubo/publication/PublicationEventHandler.java @@ -1,6 +1,5 @@ package org.mycore.ubo.publication; -import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.jdom2.Element; @@ -51,21 +50,11 @@ * * 4. Persist all new MCRUsers ONLY if they where matched/enriched in 2.1. or in 3. * - * 5. Extend the mods:name -> mods:nameIdentifier element of the publication with the configured "lead-ID" if it is - * not present but available in the matched MCRUsers attributes. - * - * 6. If no MCRUser was created because there was neither Match found nor attributes enriched (2.1. or 3.), check each - * person in the publication for affiliation. If an affiliation is found, create a new MCRUser in a special realm and - * persist it. - * * The following properties in the mycore.properties are used: * * # Default Role that is assigned to newly created users * MCR.user2.IdentityManagement.UserCreation.DefaultRole=submitter * - * # Realm of unvalidated MCRUsers - * MCR.user2.IdentityManagement.UserCreation.Unvalidated.Realm=unvalidated - * * MCR.user2.matching.chain (Multiple implementations separated by ",") * Example: * MCR.user2.matching.chain=org.mycore.ubo.matcher.MCRUserMatcherLDAP,org.mycore.ubo.matcher.MCRUserMatcherDummy @@ -87,19 +76,12 @@ public class PublicationEventHandler extends MCREventHandlerBase { private final static String CONFIG_LEAD_ID = "MCR.user2.matching.lead_id"; private final static String CONFIG_CONNECTION_STRATEGY = "MCR.user2.matching.publication.connection.strategy"; private final static String CONFIG_DEFAULT_ROLE = "MCR.user2.IdentityManagement.UserCreation.DefaultRole"; - private final static String CONFIG_UNVALIDATED_REALM - = "MCR.user2.IdentityManagement.UserCreation.Unvalidated.Realm"; - private final static String CONFIG_SKIP_LEAD_ID = "MCR.user2.matching.lead_id.skip"; - private final static String CONNECTION_TYPE_NAME = "id_connection"; /** The default Role that is assigned to newly created users **/ private String defaultRoleForNewlyCreatedUsers; - /** The ID of the realm for newly created unvalidated MCRUsers **/ - private String unvalidatedRealmID; - /** If the matched MCRUser has this ID set in its attributes, enrich the publication with it */ private String leadIDName; @@ -116,7 +98,6 @@ public PublicationEventHandler() { super(); this.defaultRoleForNewlyCreatedUsers = MCRConfiguration2.getString(CONFIG_DEFAULT_ROLE).orElse("submitter"); - this.unvalidatedRealmID = MCRConfiguration2.getString(CONFIG_UNVALIDATED_REALM).get(); this.leadIDName = MCRConfiguration2.getString(CONFIG_LEAD_ID).orElse(""); this.localMatcher = new MCRUserMatcherLocal(); this.chainOfUserMatchers = loadMatcherImplementationChain(); @@ -181,11 +162,6 @@ private void handleName(Element modsNameElement) { MCRUserMatcherDTO localMatcherDTO = localMatcher.matchUser(matcherDTO); if (localMatcherDTO.wasMatchedOrEnriched()) { handleUser(modsNameElement, localMatcherDTO.getMCRUser()); - } else if (MCRUserMatcherUtils.checkAffiliation(modsNameElement) && - (!MCRUserMatcherUtils.getNameIdentifiers(modsNameElement).isEmpty())) { - MCRUser affiliatedUser - = MCRUserMatcherUtils.createNewMCRUserFromModsNameElement(modsNameElement, unvalidatedRealmID); - handleUser(modsNameElement, affiliatedUser); } else if (containsLeadID(modsNameElement)) { MCRUser newLocalUser = MCRUserMatcherUtils.createNewMCRUserFromModsNameElement( modsNameElement, MCRRealmFactory.getLocalRealm().getID()); @@ -212,7 +188,6 @@ private boolean containsLeadID(Element modsNameElement) { } private void handleUser(Element modsName, MCRUser user) { - enrichModsNameElementByLeadID(modsName, user); connectModsNameElementWithMCRUser(modsName, user); user.assignRole(defaultRoleForNewlyCreatedUsers); MCRUserManager.updateUser(user); @@ -228,34 +203,6 @@ private void logUserMatch(Element modsNameElement, MCRUserMatcherDTO matcherDTO, matcher.getClass()); } - /** - * Enriches the mods:name-element that corresponds to the given MCRUser with a mods:nameIdentifier-element if the - * given MCRUser has an attribute with the name of the so called "lead_id" that is configured in the - * mycore.properties and given as parameter "leadID". - * A new mods:nameIdentifier-element with type "lead_id" and its value will only be created if no other - * mods:nameIdentifier-element with the same ID/type exists as a sub-element of the given modsNameElement. - - * @param modsNameElement the mods:name-element which will be enriched - * @param mcrUser the MCRUser corresponding to the modsNameElement - */ - private void enrichModsNameElementByLeadID(Element modsNameElement, MCRUser mcrUser) { - if (!MCRUserMatcherUtils.containsNameIdentifierWithType(modsNameElement, leadIDName)) { - getLeadIDAttributeFromUser(mcrUser).ifPresent(leadIDAttribute -> { - String leadIDValue = leadIDAttribute.getValue(); - LOGGER.info("Enriched publication for MCRUser: {}, with nameIdentifier of type: {} (lead_id) " + - "and value: {}", mcrUser.getUserName(), leadIDName, leadIDValue); - addNameIdentifierTo(modsNameElement, leadIDName, leadIDValue); - }); - } - } - - private Optional getLeadIDAttributeFromUser(MCRUser mcrUser) { - String attributeName = "id_" + leadIDName; - return mcrUser.getAttributes().stream() - .filter(a -> a.getName().equals(attributeName)) - .filter(a -> StringUtils.isNotEmpty(a.getValue())).findFirst(); - } - private void connectModsNameElementWithMCRUser(Element modsNameElement, MCRUser mcrUser) { if ("uuid".equals(connectionStrategy)) { String connectionID = getOrAddConnectionID(mcrUser); From 1575dd14d217c70c07fa704322790402059949db Mon Sep 17 00:00:00 2001 From: Possommi Date: Thu, 15 Feb 2024 13:39:06 +0100 Subject: [PATCH 03/15] UBO-306 Upgraded pica2mods to version 2.9-SNAPSHOT (#360) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 256ff54ca..d45343ebf 100644 --- a/pom.xml +++ b/pom.xml @@ -55,7 +55,7 @@ 3.5.1 2022.06.3-SNAPSHOT v16.0.0 - 2.8-SNAPSHOT + 2.9-SNAPSHOT scope,groupId,artifactId https://gist.githubusercontent.com/yagee-de/dfd3698c1b49173dbf251f74eb6a9297/raw/406460c088ff3cb6354e4ae6b40535e6f841607d/mycore_sort.xml true From 1c95aa4463873405ed44c4c50cf0b2722e8e97f2 Mon Sep 17 00:00:00 2001 From: Possommi Date: Fri, 16 Feb 2024 09:30:17 +0100 Subject: [PATCH 04/15] UBO-304 FSU040THUL-2567 Index place field, copy place field to all field, copy publisher to all field (#358) --- .../config/ubo-common/solr/main/solr-schema.json | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/ubo-common/src/main/resources/config/ubo-common/solr/main/solr-schema.json b/ubo-common/src/main/resources/config/ubo-common/solr/main/solr-schema.json index 4ee9f4951..734005f6e 100644 --- a/ubo-common/src/main/resources/config/ubo-common/solr/main/solr-schema.json +++ b/ubo-common/src/main/resources/config/ubo-common/solr/main/solr-schema.json @@ -479,7 +479,7 @@ "name": "place", "type": "ubo_text", "multiValued": true, - "indexed": false, + "indexed": true, "stored": true } }, @@ -743,6 +743,18 @@ "dest": "all" } }, + { + "add-copy-field": { + "source": "place", + "dest": "all" + } + }, + { + "add-copy-field": { + "source": "publisher", + "dest": "all" + } + }, { "add-copy-field": { "source": "publisher", From 275cb9ef25314c05ed8760691b11d39be0df409a Mon Sep 17 00:00:00 2001 From: Possommi Date: Mon, 19 Feb 2024 10:42:27 +0100 Subject: [PATCH 05/15] UBO-305 Allow to configure solr request handlers in response-facets.xsl (#359) * UBO-305 Added stylesheet response-get-handler.xsl * UBO-305 Set variable solrRequestHandler in response-get-handler.xsl --- ubo-common/src/main/resources/xsl/response-facets.xsl | 8 ++++++-- .../src/main/resources/xsl/response-get-handler.xsl | 9 +++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 ubo-common/src/main/resources/xsl/response-get-handler.xsl diff --git a/ubo-common/src/main/resources/xsl/response-facets.xsl b/ubo-common/src/main/resources/xsl/response-facets.xsl index 535ac03ab..e45706fe1 100644 --- a/ubo-common/src/main/resources/xsl/response-facets.xsl +++ b/ubo-common/src/main/resources/xsl/response-facets.xsl @@ -10,6 +10,8 @@ xmlns:mcrxml="xalan://org.mycore.common.xml.MCRXMLFunctions" exclude-result-prefixes="xsl xalan i18n encoder mcrxml str"> + + @@ -39,7 +41,8 @@ - select? + + @@ -145,7 +148,8 @@ - select? + + diff --git a/ubo-common/src/main/resources/xsl/response-get-handler.xsl b/ubo-common/src/main/resources/xsl/response-get-handler.xsl new file mode 100644 index 000000000..d4a06acc9 --- /dev/null +++ b/ubo-common/src/main/resources/xsl/response-get-handler.xsl @@ -0,0 +1,9 @@ + + + + + + select? + + + From 5e9d5d8a40914b56a886d497b0ea539cd974b9b6 Mon Sep 17 00:00:00 2001 From: Kerstin Ponten Date: Tue, 20 Feb 2024 10:56:49 +0100 Subject: [PATCH 06/15] UBO-218 Added workaround for a hidden submit button --- .../main/resources/META-INF/resources/import-search.xed | 8 +++++++- .../src/main/resources/META-INF/resources/search.xed | 2 ++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/ubo-common/src/main/resources/META-INF/resources/import-search.xed b/ubo-common/src/main/resources/META-INF/resources/import-search.xed index e69f303f0..92b08706f 100644 --- a/ubo-common/src/main/resources/META-INF/resources/import-search.xed +++ b/ubo-common/src/main/resources/META-INF/resources/import-search.xed @@ -111,5 +111,11 @@ - + + + + + + diff --git a/ubo-common/src/main/resources/META-INF/resources/search.xed b/ubo-common/src/main/resources/META-INF/resources/search.xed index a5c18baa5..ac959bf89 100644 --- a/ubo-common/src/main/resources/META-INF/resources/search.xed +++ b/ubo-common/src/main/resources/META-INF/resources/search.xed @@ -7,6 +7,8 @@

+ + From c249fd62a870d1f42bf837940da2a6674093845f Mon Sep 17 00:00:00 2001 From: Kerstin Ponten Date: Thu, 22 Feb 2024 13:12:55 +0100 Subject: [PATCH 07/15] Added missing xed:target --- .../src/main/resources/META-INF/resources/import-search.xed | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ubo-common/src/main/resources/META-INF/resources/import-search.xed b/ubo-common/src/main/resources/META-INF/resources/import-search.xed index 92b08706f..ebd30981a 100644 --- a/ubo-common/src/main/resources/META-INF/resources/import-search.xed +++ b/ubo-common/src/main/resources/META-INF/resources/import-search.xed @@ -115,7 +115,7 @@ - + From 172980259bd2f1a17e6cfacdc4b0700cf790a1ca Mon Sep 17 00:00:00 2001 From: Silvio Hermann Date: Fri, 16 Feb 2024 09:39:48 +0100 Subject: [PATCH 08/15] UBO-307 FSU040THUL-2605 Added text-wrap class to .personalName elements --- ubo-common/src/main/resources/xsl/mods-display.xsl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ubo-common/src/main/resources/xsl/mods-display.xsl b/ubo-common/src/main/resources/xsl/mods-display.xsl index cc16fe1d9..e60bb882b 100644 --- a/ubo-common/src/main/resources/xsl/mods-display.xsl +++ b/ubo-common/src/main/resources/xsl/mods-display.xsl @@ -517,10 +517,10 @@ - + - + From 90a191513bb66f656b00885a96f470db2566e3ec Mon Sep 17 00:00:00 2001 From: kponten <42465821+kponten@users.noreply.github.com> Date: Wed, 13 Mar 2024 09:35:38 +0100 Subject: [PATCH 09/15] Fixed Web of Science link (#363) --- .../src/main/resources/config/ubo-common/mycore.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ubo-common/src/main/resources/config/ubo-common/mycore.properties b/ubo-common/src/main/resources/config/ubo-common/mycore.properties index f5ff9140b..f7c284990 100644 --- a/ubo-common/src/main/resources/config/ubo-common/mycore.properties +++ b/ubo-common/src/main/resources/config/ubo-common/mycore.properties @@ -658,7 +658,7 @@ MCR.MODS.EnrichmentResolver.DataSource.Scopus.scopus.URI=xslStyle:import/scopus2 # # ###################################################################### -UBO.WebOfScience.Link=http://ws.isiknowledge.com/cps/openurl/service?url_ver=Z39.88-2004&rft_id=info:ut/ +UBO.WebOfScience.Link=https://www.webofscience.com/wos/woscc/full-record/WOS: ###################################################################### # # From 9255497274452fe02e84c23f26e0e9c8f2e0f400 Mon Sep 17 00:00:00 2001 From: Possommi Date: Wed, 13 Mar 2024 14:26:11 +0100 Subject: [PATCH 10/15] UBO-310 FSU040THUL-2479 Import via list and newPublication.xed produce different mods:identifier[@type='uri'] elements (#364) * UBO-310 FSU040THUL-2479 Set encoding to UTF-8 in mods-preprocessor.xsl * UBO-310 FSU040THUL-2479 Generate https gbv uris in mods-postprocessor.xsl * UBO-310 FSU040THUL-2479 Test for protocol in mods-preprocessor.xsl * UBO-310 FSU040THUL-2479 Added migration command 'migrate http uris matching {0} to https in {1}' --- .../java/org/mycore/ubo/DozBibCommands.java | 43 ++++++++++++++++++- .../main/resources/xsl/mods-postprocessor.xsl | 2 +- .../main/resources/xsl/mods-preprocessor.xsl | 9 ++-- 3 files changed, 48 insertions(+), 6 deletions(-) diff --git a/ubo-common/src/main/java/org/mycore/ubo/DozBibCommands.java b/ubo-common/src/main/java/org/mycore/ubo/DozBibCommands.java index d9657720e..76a668503 100644 --- a/ubo-common/src/main/java/org/mycore/ubo/DozBibCommands.java +++ b/ubo-common/src/main/java/org/mycore/ubo/DozBibCommands.java @@ -29,8 +29,10 @@ import org.jdom2.Content; import org.jdom2.Document; import org.jdom2.Element; +import org.jdom2.filter.Filters; import org.jdom2.input.SAXBuilder; import org.jdom2.input.sax.XMLReaders; +import org.mycore.access.MCRAccessException; import org.mycore.common.MCRConstants; import org.mycore.common.MCRException; import org.mycore.common.config.MCRConfiguration2; @@ -51,6 +53,9 @@ import org.mycore.mods.MCRMODSWrapper; import org.mycore.ubo.importer.scopus.ScopusInitialImporter; +import static org.mycore.common.MCRConstants.MODS_NAMESPACE; +import static org.mycore.common.MCRConstants.XPATH_FACTORY; + public class DozBibCommands extends MCRAbstractCommands { private static final Logger LOGGER = LogManager.getLogger(DozBibCommands.class); @@ -85,8 +90,14 @@ public DozBibCommands() { "org.mycore.ubo.importer.scopus.ScopusInitialImporter.initialImport String int", "Queries all affiliation IDs and imports all documents {0} = afid {1} = start point should be 0")); addCommand(new MCRCommand(ScopusInitialImporter.IMPORT_SINGLE_COMMAND, - "org.mycore.ubo.importer.scopus.ScopusInitialImporter.doImport String", - "{0] = ID of object")); + "org.mycore.ubo.importer.scopus.ScopusInitialImporter.doImport String", + "{0] = ID of object")); + addCommand( + new MCRCommand("migrate http uris matching {0} to https in {1}", + "org.mycore.ubo.DozBibCommands.migrateURItoHttps String String", + "Migrates http protocol of uris to https if they match the pattern given in {0} " + + "(xpath will be '//mods:identifier[@type = 'uri'][contains(text(), {0})]'). " + + "The mycore object id must be provided in {1}")); } /** Exports all entries as MODS dump to a zipped xml file in the given directory */ @@ -221,4 +232,32 @@ public static void importMODSCollection(String fileName) throws Exception { MCRMetadataManager.create(obj); } } + + public static void migrateURItoHttps(String uriContains, String id) { + MCRObjectID mcrObjectID = MCRObjectID.getInstance(id); + if (!MCRMetadataManager.exists(mcrObjectID)) { + LOGGER.error("{} does not exist", id); + return; + } + + Document xml = MCRMetadataManager.retrieveMCRObject(mcrObjectID).createXML(); + List elements = XPATH_FACTORY.compile( + "//mods:identifier[@type = 'uri'][contains(text(), '" + uriContains + "')]", + Filters.element(), null, MODS_NAMESPACE).evaluate(xml); + + if (elements.isEmpty()) { + return; + } + + elements.forEach(element -> { + String uri = element.getText(); + element.setText(uri.replace("http:", "https:")); + }); + + try { + MCRMetadataManager.update(new MCRObject(xml)); + } catch (MCRAccessException e) { + LOGGER.error("Could not replace URI protocol in ", id); + } + } } diff --git a/ubo-common/src/main/resources/xsl/mods-postprocessor.xsl b/ubo-common/src/main/resources/xsl/mods-postprocessor.xsl index 1519d831f..132e0dfa0 100644 --- a/ubo-common/src/main/resources/xsl/mods-postprocessor.xsl +++ b/ubo-common/src/main/resources/xsl/mods-postprocessor.xsl @@ -80,7 +80,7 @@ - + diff --git a/ubo-common/src/main/resources/xsl/mods-preprocessor.xsl b/ubo-common/src/main/resources/xsl/mods-preprocessor.xsl index 1260571a6..8f12ae7a2 100644 --- a/ubo-common/src/main/resources/xsl/mods-preprocessor.xsl +++ b/ubo-common/src/main/resources/xsl/mods-preprocessor.xsl @@ -1,4 +1,4 @@ - + @@ -85,8 +85,11 @@ - - + + + + + From 04d3a6e1518b8537b59d5d005d02479bb10c00dd Mon Sep 17 00:00:00 2001 From: Possommi Date: Thu, 21 Mar 2024 09:28:52 +0100 Subject: [PATCH 11/15] UBO-311 FSU040-THUL-2889 The query parts in the links of the origin badges are not quoted properly (#366) * UBO-311 FSU040THUL-2889 Quote value of origin field * UBO-311 FSU040THUL-2889 Provide onclick action for subjects derived from origin.xml and document type * UBO-311 FSU040THUL-2889 Fixed blank --- ubo-common/src/main/resources/xsl/mods-display.xsl | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/ubo-common/src/main/resources/xsl/mods-display.xsl b/ubo-common/src/main/resources/xsl/mods-display.xsl index e60bb882b..5759291d0 100644 --- a/ubo-common/src/main/resources/xsl/mods-display.xsl +++ b/ubo-common/src/main/resources/xsl/mods-display.xsl @@ -52,8 +52,9 @@ - - + + in @@ -65,7 +66,7 @@ + onclick="location.assign('{$WebApplicationBaseURL}servlets/solr/select?sort=modified+desc&q={encoder:encode(concat($fq, '+subject:"', substring-after(@valueURI,'#'),'"'))}')"> @@ -77,7 +78,7 @@ + onclick="location.assign('{$WebApplicationBaseURL}servlets/solr/select?sort=modified+desc&q={encoder:encode(concat($fq, '+origin:"', substring-after(@valueURI,'#'), '"'))}')"> @@ -98,7 +99,9 @@ - + From 7e6d07c41fd182bf42dafe4bbdf22f7a46d11907 Mon Sep 17 00:00:00 2001 From: Possommi Date: Thu, 21 Mar 2024 10:21:24 +0100 Subject: [PATCH 12/15] UBO-312 Read request handler from variable in response.xsl (#367) --- ubo-common/src/main/resources/xsl/response-facets.xsl | 2 -- ubo-common/src/main/resources/xsl/response.xsl | 3 ++- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/ubo-common/src/main/resources/xsl/response-facets.xsl b/ubo-common/src/main/resources/xsl/response-facets.xsl index e45706fe1..f44257aa4 100644 --- a/ubo-common/src/main/resources/xsl/response-facets.xsl +++ b/ubo-common/src/main/resources/xsl/response-facets.xsl @@ -10,8 +10,6 @@ xmlns:mcrxml="xalan://org.mycore.common.xml.MCRXMLFunctions" exclude-result-prefixes="xsl xalan i18n encoder mcrxml str"> - - diff --git a/ubo-common/src/main/resources/xsl/response.xsl b/ubo-common/src/main/resources/xsl/response.xsl index 70b75666d..afe6083f7 100644 --- a/ubo-common/src/main/resources/xsl/response.xsl +++ b/ubo-common/src/main/resources/xsl/response.xsl @@ -16,6 +16,7 @@ exclude-result-prefixes="xsl xalan i18n mods mcr mcrxml encoder str basket"> + @@ -174,7 +175,7 @@ - select? + From 2ecac135be896c37bcb8779ae476849377d8eecd Mon Sep 17 00:00:00 2001 From: Possommi Date: Mon, 25 Mar 2024 16:10:45 +0100 Subject: [PATCH 13/15] UBO-313 Index accessrights and peerreviewed. Added i18n 'facets.facet.accessrights'. Added fallback for generation of title of facet if facet is a classification (#368) --- .../config/ubo-common/messages_de.properties | 1 + .../config/ubo-common/messages_en.properties | 1 + .../ubo-common/solr/main/solr-schema.json | 16 +++++++++++++++ .../main/resources/xsl/response-facets.xsl | 20 +++++++++++++++++-- .../src/main/resources/xsl/ubo-solr.xsl | 14 +++++++++++++ 5 files changed, 50 insertions(+), 2 deletions(-) diff --git a/ubo-common/src/main/resources/config/ubo-common/messages_de.properties b/ubo-common/src/main/resources/config/ubo-common/messages_de.properties index b34712b7f..6f4800bd0 100644 --- a/ubo-common/src/main/resources/config/ubo-common/messages_de.properties +++ b/ubo-common/src/main/resources/config/ubo-common/messages_de.properties @@ -412,6 +412,7 @@ error.stackTrace = Stack trace error.title = Fehler {0} error.type = Fehlertyp +facets.facet.accessrights = Zugangsrechte (nach KDSF) facets.facet.created = eingetragen am facets.facet.genre = Publikationstyp facets.facet.host_title = Ver\u00F6ffentlicht in diff --git a/ubo-common/src/main/resources/config/ubo-common/messages_en.properties b/ubo-common/src/main/resources/config/ubo-common/messages_en.properties index d478e7d13..a74f5371b 100644 --- a/ubo-common/src/main/resources/config/ubo-common/messages_en.properties +++ b/ubo-common/src/main/resources/config/ubo-common/messages_en.properties @@ -410,6 +410,7 @@ error.stackTrace = Stack trace error.title = Error {0} error.type = Type of error +facets.facet.accessrights = Access Rights (KDSF) facets.facet.created = entered on facets.facet.genre = Publication type facets.facet.host_title = Published in diff --git a/ubo-common/src/main/resources/config/ubo-common/solr/main/solr-schema.json b/ubo-common/src/main/resources/config/ubo-common/solr/main/solr-schema.json index 734005f6e..47777bfc1 100644 --- a/ubo-common/src/main/resources/config/ubo-common/solr/main/solr-schema.json +++ b/ubo-common/src/main/resources/config/ubo-common/solr/main/solr-schema.json @@ -581,6 +581,22 @@ "stored": true } }, + { + "add-field": { + "name": "accessrights", + "type": "ubo_id", + "indexed": true, + "stored": true + } + }, + { + "add-field": { + "name": "peerreviewed", + "type": "ubo_id", + "indexed": true, + "stored": true + } + }, { "add-field": { "name": "tag", diff --git a/ubo-common/src/main/resources/xsl/response-facets.xsl b/ubo-common/src/main/resources/xsl/response-facets.xsl index f44257aa4..38612f3da 100644 --- a/ubo-common/src/main/resources/xsl/response-facets.xsl +++ b/ubo-common/src/main/resources/xsl/response-facets.xsl @@ -114,7 +114,20 @@
-

+ +

+ + + + + + + + + + + +

    @@ -240,7 +253,10 @@ - + + + + diff --git a/ubo-common/src/main/resources/xsl/ubo-solr.xsl b/ubo-common/src/main/resources/xsl/ubo-solr.xsl index 0fc5b07df..1ae3db954 100644 --- a/ubo-common/src/main/resources/xsl/ubo-solr.xsl +++ b/ubo-common/src/main/resources/xsl/ubo-solr.xsl @@ -44,6 +44,8 @@ + + @@ -311,6 +313,18 @@ + + + + + + + + + + + + From 2a111946aa1f8d5b0dac158aa5324c02c08f9610 Mon Sep 17 00:00:00 2001 From: Silvio Hermann Date: Tue, 26 Mar 2024 08:56:40 +0100 Subject: [PATCH 14/15] UBO-314 Index mediaType --- .../config/ubo-common/solr/main/solr-schema.json | 8 ++++++++ ubo-common/src/main/resources/xsl/mods-display.xsl | 1 + ubo-common/src/main/resources/xsl/response-facets.xsl | 3 +++ ubo-common/src/main/resources/xsl/ubo-solr.xsl | 7 +++++++ 4 files changed, 19 insertions(+) diff --git a/ubo-common/src/main/resources/config/ubo-common/solr/main/solr-schema.json b/ubo-common/src/main/resources/config/ubo-common/solr/main/solr-schema.json index 47777bfc1..302ce330a 100644 --- a/ubo-common/src/main/resources/config/ubo-common/solr/main/solr-schema.json +++ b/ubo-common/src/main/resources/config/ubo-common/solr/main/solr-schema.json @@ -597,6 +597,14 @@ "stored": true } }, + { + "add-field": { + "name": "mediaType", + "type": "ubo_id", + "indexed": true, + "stored": true + } + }, { "add-field": { "name": "tag", diff --git a/ubo-common/src/main/resources/xsl/mods-display.xsl b/ubo-common/src/main/resources/xsl/mods-display.xsl index 5759291d0..074434db6 100644 --- a/ubo-common/src/main/resources/xsl/mods-display.xsl +++ b/ubo-common/src/main/resources/xsl/mods-display.xsl @@ -42,6 +42,7 @@ + diff --git a/ubo-common/src/main/resources/xsl/response-facets.xsl b/ubo-common/src/main/resources/xsl/response-facets.xsl index 38612f3da..f41ddaa5f 100644 --- a/ubo-common/src/main/resources/xsl/response-facets.xsl +++ b/ubo-common/src/main/resources/xsl/response-facets.xsl @@ -257,6 +257,9 @@ + + + diff --git a/ubo-common/src/main/resources/xsl/ubo-solr.xsl b/ubo-common/src/main/resources/xsl/ubo-solr.xsl index 1ae3db954..0e0a37022 100644 --- a/ubo-common/src/main/resources/xsl/ubo-solr.xsl +++ b/ubo-common/src/main/resources/xsl/ubo-solr.xsl @@ -46,6 +46,7 @@ + @@ -325,6 +326,12 @@ + + + + + + From e066e9c900c33797e3e4eefff93ce10dfea0ecf8 Mon Sep 17 00:00:00 2001 From: Silvio Hermann Date: Tue, 26 Mar 2024 09:37:01 +0100 Subject: [PATCH 15/15] UBO-315 Made search filter base template configurable by introducing property MCR.user2.LDAP.searchFilter.base=(&(objectClass=eduPerson)(|%s)) --- .../org/mycore/ubo/matcher/MCRUserMatcherLDAP.java | 14 ++++++++++---- .../resources/config/ubo-common/mycore.properties | 3 +++ 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/ubo-common/src/main/java/org/mycore/ubo/matcher/MCRUserMatcherLDAP.java b/ubo-common/src/main/java/org/mycore/ubo/matcher/MCRUserMatcherLDAP.java index dff84c480..7da028559 100644 --- a/ubo-common/src/main/java/org/mycore/ubo/matcher/MCRUserMatcherLDAP.java +++ b/ubo-common/src/main/java/org/mycore/ubo/matcher/MCRUserMatcherLDAP.java @@ -100,6 +100,15 @@ public class MCRUserMatcherLDAP implements MCRUserMatcher { private String realm; + /** + * The default filter applied to LDAP searches + * + * TODO: take into consideration the member-status of the (email?) of the LDAP-users + * */ + protected final String SEARCH_FILTER_TEMPLATE = MCRConfiguration2 + .getString("MCR.user2.LDAP.searchFilter.base") + .orElse("(&(objectClass=eduPerson)(|%s))"); + public MCRUserMatcherLDAP() { loadLDAPMappingConfiguration(); orcid_resolver = MCRConfiguration2.getString(CONFIG_ORCID_NORMALIZATION_RESOLVER).orElse(""); @@ -492,9 +501,6 @@ public List getLDAPUsersByGivenLDAPAttributes(Multimap ldapAttribute * @return A LDAP-searchfilter of the form (&(objectClass=eduPerson)(|(%a1=%v1)(%a2=%v2)...(%aN=%vN))) */ private String createLDAPSearchFilter(Multimap ldapAttributes, String innerTemplate) { - // TODO: take into consideration the member-status of the (email?) of the LDAP-users - String searchFilterBaseTemplate = "(&(objectClass=eduPerson)(|%s))"; - StringBuilder searchFilterInner = new StringBuilder(); for (Map.Entry> ldapAttribute : ldapAttributes.asMap().entrySet()) { String attributeName = ldapAttribute.getKey(); @@ -521,7 +527,7 @@ private String createLDAPSearchFilter(Multimap ldapAttributes, S searchFilterInner.append(")"); } } - return String.format(Locale.ROOT, searchFilterBaseTemplate, searchFilterInner); + return String.format(Locale.ROOT, SEARCH_FILTER_TEMPLATE, searchFilterInner); } /** diff --git a/ubo-common/src/main/resources/config/ubo-common/mycore.properties b/ubo-common/src/main/resources/config/ubo-common/mycore.properties index f7c284990..b80bf9388 100644 --- a/ubo-common/src/main/resources/config/ubo-common/mycore.properties +++ b/ubo-common/src/main/resources/config/ubo-common/mycore.properties @@ -220,6 +220,9 @@ MCR.user2.LDAP.Mapping.explicit=id_orcid:eduPersonOrcid # eduPersonScopedAffiliation may be faculty|staff|employee|student|alum|member|affiliate # MCR.user2.LDAP.Mapping.Group.eduPersonScopedAffiliation.staff@uni-duisburg-essen.de=submitter +# The default filter applied to LDAP searches +MCR.user2.LDAP.searchFilter.base=(&(objectClass=eduPerson)(|%s)) + # only author and editor are considered as result in the PersonalList UBO.Search.PersonalList.Roles=aut.top,cre.top,tch.top,pht.top,prg.top,edt.top UBO.Search.PersonalList.Ids=%MCR.user2.matching.lead_id%,dhsbid