Skip to content

Commit

Permalink
UBO-288 publication event handler needs refactoring (#346)
Browse files Browse the repository at this point in the history
* UBO-288 Move instance variable instantiation into constructor

* UBO-288 Moved handling each name and logging matches to separate methods

* UBO-288 Build the chain of user matchers only once

* UBO-288 Moved redundant code to separate method

* UBO-288 Move completely nested "else if" up

* UBO-288 Further refactoring´

* UBO-288 Refactor setting real name

* UBO-288 Further refactoring

* Refactoring of connectModsNameElementWithMCRUser()
  • Loading branch information
fluetze authored Jan 25, 2024
1 parent 7f314d8 commit f73a6e4
Showing 1 changed file with 137 additions and 140 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import org.jdom2.filter.Filters;
import org.jdom2.output.Format;
import org.jdom2.output.XMLOutputter;
import org.jdom2.xpath.XPathExpression;
import org.mycore.common.MCRClassTools;
import org.mycore.common.MCRConstants;
import org.mycore.common.config.MCRConfiguration2;
Expand Down Expand Up @@ -92,6 +93,35 @@ public class PublicationEventHandler extends MCREventHandlerBase {

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;

/** Matcher to lookup a matching local user **/
private MCRUserMatcher localMatcher;

/** A chain of implemented user matchers */
private List<MCRUserMatcher> 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("");
this.localMatcher = new MCRUserMatcherLocal();
this.chainOfUserMatchers = loadMatcherImplementationChain();
this.connectionStrategy = MCRConfiguration2.getString(CONFIG_CONNECTION_STRATEGY).orElse("");
}

private List<MCRUserMatcher> loadMatcherImplementationChain() {
List<MCRUserMatcher> matchers = new ArrayList<>();

Expand All @@ -101,36 +131,16 @@ private List<MCRUserMatcher> loadMatcherImplementationChain() {
for (int i = 0; i < matcherClasses.length; i++) {
String matcherClass = matcherClasses[i];
try {

matchers.add((MCRUserMatcher) MCRClassTools.forName(matcherClass).getDeclaredConstructor().newInstance());
} catch (Exception e) {
e.printStackTrace();
throw new MCRConfigurationException("Property key " + CONFIG_MATCHERS + " not valid.");
throw new MCRConfigurationException("Property key " + CONFIG_MATCHERS + " not valid.", e);
}
}
}
return matchers;
}

private String loadLeadIDName() {
return MCRConfiguration2.getString(CONFIG_LEAD_ID).orElse("");
}

private String loadDefaultRoleConfig() {
return MCRConfiguration2.getString(CONFIG_DEFAULT_ROLE).orElse("submitter");
}

private String loadUnvalidatedRealmConfig() {
return MCRConfiguration2.getString(CONFIG_UNVALIDATED_REALM).get();
}

/**
* Returns the configured connection strategy to "connect" publications to MCRUsers
* @return String, null if no connection strategy has been set
*/
private String loadConnectionStrategyConfig() {
return MCRConfiguration2.getString(CONFIG_CONNECTION_STRATEGY).get();
}

@Override
protected void handleObjectCreated(MCREvent evt, MCRObject obj) {
handlePublication(obj);
Expand All @@ -148,83 +158,71 @@ protected void handleObjectRepaired(MCREvent evt, MCRObject obj) {
}

protected void handlePublication(MCRObject obj) {
// get default role for new users
String defaultRole = loadDefaultRoleConfig();

// get realmID for unvalidated MCRUsers
final String UNVALIDATED_REALM = loadUnvalidatedRealmConfig();

// get all mods:name from persons (authors etc.) of the publication
List<Element> modsNameElements = MCRUserMatcherUtils.getNameElements(obj);

// leadIDName -> if the matched MCRUser has this ID set in the attributes, enrich the publication with it
String leadIDName = loadLeadIDName();

// for every mods:name element, call our configured Implementation(s) of MCRUserMatcher
List<MCRUserMatcher> matchers = loadMatcherImplementationChain();
MCRUserMatcher localMatcher = new MCRUserMatcherLocal();

for(Element modsNameElement : modsNameElements) {

MCRUserMatcherDTO matcherDTO = new MCRUserMatcherDTO(
MCRUserMatcherUtils.createNewMCRUserFromModsNameElement(modsNameElement));

for (MCRUserMatcher matcher : matchers) {
matcherDTO = matcher.matchUser(matcherDTO);
MCRUser mcrUser = matcherDTO.getMCRUser();
if (matcherDTO.wasMatchedOrEnriched()) {
LOGGER.info("Found a match for user: {} of mods:name: {}, with attributes: {}, using " +
"matcher: {}",
mcrUser.getUserName(),
new XMLOutputter(Format.getPrettyFormat()).outputString(modsNameElement),
mcrUser.getAttributes().stream().map(a -> a.getName() + "=" + a.getValue())
.collect(Collectors.joining(" | ")),
matcher.getClass());
}
}
// for every mods:name[@type='person'] (authors etc.) of the publication...
MCRUserMatcherUtils.getNameElements(obj).forEach(modsNameElement -> handleName(modsNameElement));

MCRUserMatcherDTO localMatcherDTO = localMatcher.matchUser(matcherDTO);
if (localMatcherDTO.wasMatchedOrEnriched()) {
MCRUser mcrUserFinal = localMatcherDTO.getMCRUser();
mcrUserFinal.assignRole(defaultRole);
enrichModsNameElementByLeadID(modsNameElement, leadIDName, mcrUserFinal);
connectModsNameElementWithMCRUser(modsNameElement, mcrUserFinal);
MCRUserManager.updateUser(mcrUserFinal);
} else {
if(MCRUserMatcherUtils.checkAffiliation(modsNameElement) &&
(MCRUserMatcherUtils.getNameIdentifiers(modsNameElement).size() > 0)) {
MCRUser affiliatedUser = MCRUserMatcherUtils.createNewMCRUserFromModsNameElement(modsNameElement, UNVALIDATED_REALM);
affiliatedUser.assignRole(defaultRole);
enrichModsNameElementByLeadID(modsNameElement, leadIDName, affiliatedUser);
connectModsNameElementWithMCRUser(modsNameElement, affiliatedUser);
MCRUserManager.updateUser(affiliatedUser);
} else {
Optional<Element> leadId = modsNameElement.getChildren("nameIdentifier", MCRConstants.MODS_NAMESPACE)
.stream()
.filter(element -> leadIDName.equals(element.getAttributeValue("type")))
.findFirst();

if(leadId.isPresent()) {
MCRUser newLocalUser = MCRUserMatcherUtils.createNewMCRUserFromModsNameElement(
modsNameElement, MCRRealmFactory.getLocalRealm().getID());
newLocalUser.setRealName(getRealNameFromNameElement(modsNameElement, newLocalUser));
connectModsNameElementWithMCRUser(modsNameElement, newLocalUser);
MCRUserManager.updateUser(newLocalUser);
}
}
LOGGER.debug("Final document: {}", new XMLOutputter(Format.getPrettyFormat()).outputString(obj.createXML()));
}

private void handleName(Element modsNameElement) {
MCRUser userFromModsName = MCRUserMatcherUtils.createNewMCRUserFromModsNameElement(modsNameElement);
MCRUserMatcherDTO matcherDTO = new MCRUserMatcherDTO(userFromModsName);

// call our configured Implementation(s) of MCRUserMatcher
for (MCRUserMatcher matcher : chainOfUserMatchers) {
matcherDTO = matcher.matchUser(matcherDTO);
if (matcherDTO.wasMatchedOrEnriched()) {
logUserMatch(modsNameElement, matcherDTO, matcher);
}
}

MCRConfiguration2.getBoolean(CONFIG_SKIP_LEAD_ID)
.filter(Boolean::booleanValue)
.ifPresent(trueValue -> {
List<Element> elementsToRemove = modsNameElement.getChildren("nameIdentifier", MCRConstants.MODS_NAMESPACE)
.stream()
.filter(element -> element.getAttributeValue("type").equals(leadIDName))
.collect(Collectors.toList());
elementsToRemove.forEach(modsNameElement::removeContent);
});
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());
newLocalUser.setRealName(buildPersonNameFromMODS(modsNameElement).orElse(newLocalUser.getUserID()));
connectModsNameElementWithMCRUser(modsNameElement, newLocalUser);
MCRUserManager.updateUser(newLocalUser);
}
LOGGER.debug("Final document: {}", new XMLOutputter(Format.getPrettyFormat()).outputString(obj.createXML()));

MCRConfiguration2.getBoolean(CONFIG_SKIP_LEAD_ID)
.filter(Boolean::booleanValue)
.ifPresent(trueValue -> {
List<Element> elementsToRemove = modsNameElement.getChildren("nameIdentifier", MCRConstants.MODS_NAMESPACE)
.stream()
.filter(element -> element.getAttributeValue("type").equals(leadIDName))
.collect(Collectors.toList());
elementsToRemove.forEach(modsNameElement::removeContent);
});
}

private boolean containsLeadID(Element modsNameElement) {
return modsNameElement.getChildren("nameIdentifier", MCRConstants.MODS_NAMESPACE)
.stream().anyMatch(element -> leadIDName.equals(element.getAttributeValue("type")));
}

private void handleUser(Element modsName, MCRUser user) {
enrichModsNameElementByLeadID(modsName, user);
connectModsNameElementWithMCRUser(modsName, user);
user.assignRole(defaultRoleForNewlyCreatedUsers);
MCRUserManager.updateUser(user);
}

private void logUserMatch(Element modsNameElement, MCRUserMatcherDTO matcherDTO, MCRUserMatcher matcher) {
MCRUser mcrUser = matcherDTO.getMCRUser();
LOGGER.info("Found a match for user: {} of mods:name: {}, with attributes: {}, using matcher: {}",
mcrUser.getUserName(),
new XMLOutputter(Format.getPrettyFormat()).outputString(modsNameElement),
mcrUser.getAttributes().stream().map(a -> a.getName() + "=" + a.getValue())
.collect(Collectors.joining(" | ")),
matcher.getClass());
}

/**
Expand All @@ -235,68 +233,67 @@ protected void handlePublication(MCRObject obj) {
* 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 leadID the "lead_id" (as configured in the mycore.properties) in mods format (no prefix "id_")
* @param mcrUser the MCRUser corresponding to the modsNameElement
*/
private void enrichModsNameElementByLeadID(Element modsNameElement, String leadID, MCRUser mcrUser) {
String attributeName = "id_" + leadID;
Optional<MCRUserAttribute> leadIDAttribute = mcrUser.getAttributes().stream()
.filter(a -> a.getName().equals(attributeName) && StringUtils.isNotEmpty(a.getValue())).findFirst();

if (leadIDAttribute.isPresent()) {
String leadIDValue = leadIDAttribute.get().getValue();
if (!MCRUserMatcherUtils.containsNameIdentifierWithType(modsNameElement, leadID)) {
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(), leadID, leadIDValue);
enrichModsNameElementByNameIdentifierElement(modsNameElement, leadID, leadIDValue);
}
"and value: {}", mcrUser.getUserName(), leadIDName, leadIDValue);
addNameIdentifierTo(modsNameElement, leadIDName, leadIDValue);
});
}
}

private void enrichModsNameElementByNameIdentifierElement(Element modsNameElement,
String attributeType, String attributeValue) {
Element nameIdentifier = new Element("nameIdentifier", MODS_NAMESPACE)
.setAttribute("type", attributeType)
.setText(attributeValue);
modsNameElement.addContent(nameIdentifier);
private Optional<MCRUserAttribute> getLeadIDAttributeFromUser(MCRUser mcrUser) {
String attributeName = "id_" + leadIDName;
return mcrUser.getAttributes().stream()
.filter(a -> a.getName().equals(attributeName))
.filter(a -> StringUtils.isNotEmpty(a.getValue())).findFirst();
}

/**
*
* @param modsNameElement
* @param mcrUser
*/
private void connectModsNameElementWithMCRUser(Element modsNameElement, MCRUser mcrUser) {
String connectionStrategy = loadConnectionStrategyConfig();
if(StringUtils.isNotEmpty(connectionStrategy) && connectionStrategy.equals("uuid")) {
// check if MCRUser already has a "connection" UUID
String uuid = mcrUser.getUserAttribute(CONNECTION_TYPE_NAME);
String modsTypeName = CONNECTION_TYPE_NAME.replace("id_", "");
if(uuid == null) {
// create new UUID and persist it for mcrUser
uuid = UUID.randomUUID().toString();
mcrUser.getAttributes().add(new MCRUserAttribute(CONNECTION_TYPE_NAME, uuid));
}
if("uuid".equals(connectionStrategy)) {
String connectionID = getOrAddConnectionID(mcrUser);
// if not already present, persist connection in mods:name - nameIdentifier-Element
if(!MCRUserMatcherUtils.containsNameIdentifierWithType(modsNameElement, modsTypeName)) {
String connectionIDType = CONNECTION_TYPE_NAME.replace("id_", "");
if(!MCRUserMatcherUtils.containsNameIdentifierWithType(modsNameElement, connectionIDType)) {
LOGGER.info("Connecting publication with MCRUser: {}, via nameIdentifier of type: {} " +
"and value: {}", mcrUser.getUserName(), modsTypeName, uuid);
enrichModsNameElementByNameIdentifierElement(modsNameElement, modsTypeName, uuid);
"and value: {}", mcrUser.getUserName(), connectionIDType, connectionID);
addNameIdentifierTo(modsNameElement, connectionIDType, connectionID);
}
}
}

protected String getRealNameFromNameElement(Element nameElement, MCRUser mcrUser){
Element givenName = XPATH_FACTORY.compile("mods:namePart[@type='given']",
Filters.element(), null, MODS_NAMESPACE).evaluateFirst(nameElement);
private String getOrAddConnectionID(MCRUser mcrUser) {
// check if MCRUser already has a "connection" UUID
String uuid = mcrUser.getUserAttribute(CONNECTION_TYPE_NAME);
if(uuid == null) {
// create new UUID and persist it for mcrUser
uuid = UUID.randomUUID().toString();
mcrUser.getAttributes().add(new MCRUserAttribute(CONNECTION_TYPE_NAME, uuid));
}
return uuid;
}

private void addNameIdentifierTo(Element modsName, String type, String value) {
modsName.addContent(new Element("nameIdentifier", MODS_NAMESPACE).setAttribute("type", type).setText(value));
}

Element familyName = XPATH_FACTORY.compile("mods:namePart[@type='family']",
Filters.element(), null, MODS_NAMESPACE).evaluateFirst(nameElement);
private final static XPathExpression<Element> XPATH_TO_GET_GIVEN_NAME
= XPATH_FACTORY.compile("mods:namePart[@type='given']", Filters.element(), null, MODS_NAMESPACE);
private final static XPathExpression<Element> XPATH_TO_GET_FAMILY_NAME
= XPATH_FACTORY.compile("mods:namePart[@type='family']", Filters.element(), null, MODS_NAMESPACE);

if((givenName != null) && (familyName != null)) {
return familyName.getText() + ", " + givenName.getText();
}
protected Optional<String> buildPersonNameFromMODS(Element nameElement) {
Element givenName = XPATH_TO_GET_GIVEN_NAME.evaluateFirst(nameElement);
Element familyName = XPATH_TO_GET_FAMILY_NAME.evaluateFirst(nameElement);

return mcrUser.getUserID();
if ( (givenName != null) && (familyName != null)) {
return Optional.of( familyName.getText() + ", " + givenName.getText() );
} else {
return Optional.empty();
}
}
}

0 comments on commit f73a6e4

Please sign in to comment.