diff --git a/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java b/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java index 444332df97d2..4e30559e1c92 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java @@ -8,6 +8,7 @@ package org.dspace.content.authority; import java.io.File; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -65,14 +66,17 @@ public class DSpaceControlledVocabulary extends SelfNamedPlugin implements Hiera protected static String labelTemplate = "//node[@label = '%s']"; protected static String idParentTemplate = "//node[@id = '%s']/parent::isComposedBy/parent::node"; protected static String rootTemplate = "/node"; + protected static String idAttribute = "id"; + protected static String labelAttribute = "label"; protected static String pluginNames[] = null; - protected String vocabularyName = null; protected InputSource vocabulary = null; protected Boolean suggestHierarchy = false; protected Boolean storeHierarchy = true; protected String hierarchyDelimiter = "::"; protected Integer preloadLevel = 1; + protected String valueAttribute = labelAttribute; + protected String valueTemplate = labelTemplate; public DSpaceControlledVocabulary() { super(); @@ -115,7 +119,7 @@ public boolean accept(File dir, String name) { } } - protected void init() { + protected void init(String locale) { if (vocabulary == null) { ConfigurationService config = DSpaceServicesFactory.getInstance().getConfigurationService(); @@ -125,13 +129,25 @@ protected void init() { File.separator + "controlled-vocabularies" + File.separator; String configurationPrefix = "vocabulary.plugin." + vocabularyName; storeHierarchy = config.getBooleanProperty(configurationPrefix + ".hierarchy.store", storeHierarchy); + boolean storeIDs = config.getBooleanProperty(configurationPrefix + ".storeIDs", false); suggestHierarchy = config.getBooleanProperty(configurationPrefix + ".hierarchy.suggest", suggestHierarchy); preloadLevel = config.getIntProperty(configurationPrefix + ".hierarchy.preloadLevel", preloadLevel); String configuredDelimiter = config.getProperty(configurationPrefix + ".delimiter"); if (configuredDelimiter != null) { hierarchyDelimiter = configuredDelimiter.replaceAll("(^\"|\"$)", ""); } + if (storeIDs) { + valueAttribute = idAttribute; + valueTemplate = idTemplate; + } + String filename = vocabulariesPath + vocabularyName + ".xml"; + if (StringUtils.isNotEmpty(locale)) { + String localizedFilename = vocabulariesPath + vocabularyName + "_" + locale + ".xml"; + if (Paths.get(localizedFilename).toFile().exists()) { + filename = localizedFilename; + } + } log.info("Loading " + filename); vocabulary = new InputSource(filename); } @@ -144,9 +160,9 @@ protected String buildString(Node node) { return (""); } else { String parentValue = buildString(node.getParentNode()); - Node currentLabel = node.getAttributes().getNamedItem("label"); - if (currentLabel != null) { - String currentValue = currentLabel.getNodeValue(); + Node currentNodeValue = node.getAttributes().getNamedItem(valueAttribute); + if (currentNodeValue != null) { + String currentValue = currentNodeValue.getNodeValue(); if (parentValue.equals("")) { return currentValue; } else { @@ -160,12 +176,13 @@ protected String buildString(Node node) { @Override public Choices getMatches(String text, int start, int limit, String locale) { - init(); + init(locale); log.debug("Getting matches for '" + text + "'"); String xpathExpression = ""; String[] textHierarchy = text.split(hierarchyDelimiter, -1); for (int i = 0; i < textHierarchy.length; i++) { - xpathExpression += String.format(xpathTemplate, textHierarchy[i].replaceAll("'", "'").toLowerCase()); + xpathExpression += + String.format(xpathTemplate, textHierarchy[i].replaceAll("'", "'").toLowerCase()); } XPath xpath = XPathFactory.newInstance().newXPath(); int total = 0; @@ -184,12 +201,13 @@ public Choices getMatches(String text, int start, int limit, String locale) { @Override public Choices getBestMatch(String text, String locale) { - init(); + init(locale); log.debug("Getting best matches for '" + text + "'"); String xpathExpression = ""; String[] textHierarchy = text.split(hierarchyDelimiter, -1); for (int i = 0; i < textHierarchy.length; i++) { - xpathExpression += String.format(labelTemplate, textHierarchy[i].replaceAll("'", "'")); + xpathExpression += + String.format(valueTemplate, textHierarchy[i].replaceAll("'", "'")); } XPath xpath = XPathFactory.newInstance().newXPath(); List choices = new ArrayList(); @@ -205,19 +223,19 @@ public Choices getBestMatch(String text, String locale) { @Override public String getLabel(String key, String locale) { - return getNodeLabel(key, this.suggestHierarchy); + return getNodeValue(key, locale, this.suggestHierarchy); } @Override public String getValue(String key, String locale) { - return getNodeLabel(key, this.storeHierarchy); + return getNodeValue(key, locale, this.storeHierarchy); } @Override public Choice getChoice(String authKey, String locale) { Node node; try { - node = getNode(authKey); + node = getNode(authKey, locale); } catch (XPathExpressionException e) { return null; } @@ -226,27 +244,27 @@ public Choice getChoice(String authKey, String locale) { @Override public boolean isHierarchical() { - init(); + init(null); return true; } @Override public Choices getTopChoices(String authorityName, int start, int limit, String locale) { - init(); + init(locale); String xpathExpression = rootTemplate; return getChoicesByXpath(xpathExpression, start, limit); } @Override public Choices getChoicesByParent(String authorityName, String parentId, int start, int limit, String locale) { - init(); + init(locale); String xpathExpression = String.format(idTemplate, parentId); return getChoicesByXpath(xpathExpression, start, limit); } @Override public Choice getParentChoice(String authorityName, String childId, String locale) { - init(); + init(locale); try { String xpathExpression = String.format(idParentTemplate, childId); Choice choice = createChoiceFromNode(getNodeFromXPath(xpathExpression)); @@ -259,7 +277,7 @@ public Choice getParentChoice(String authorityName, String childId, String local @Override public Integer getPreloadLevel() { - init(); + init(null); return preloadLevel; } @@ -270,8 +288,8 @@ private boolean isRootElement(Node node) { return false; } - private Node getNode(String key) throws XPathExpressionException { - init(); + private Node getNode(String key, String locale) throws XPathExpressionException { + init(locale); String xpathExpression = String.format(idTemplate, key); Node node = getNodeFromXPath(xpathExpression); return node; @@ -319,16 +337,16 @@ private Map addOtherInformation(String parentCurr, String noteCu return extras; } - private String getNodeLabel(String key, boolean useHierarchy) { + private String getNodeValue(String key, String locale, boolean useHierarchy) { try { - Node node = getNode(key); + Node node = getNode(key, locale); if (Objects.isNull(node)) { return null; } if (useHierarchy) { return this.buildString(node); } else { - return node.getAttributes().getNamedItem("label").getNodeValue(); + return node.getAttributes().getNamedItem(valueAttribute).getNodeValue(); } } catch (XPathExpressionException e) { return (""); @@ -349,7 +367,7 @@ private String getValue(Node node) { if (this.storeHierarchy) { return hierarchy; } else { - return node.getAttributes().getNamedItem("label").getNodeValue(); + return node.getAttributes().getNamedItem(valueAttribute).getNodeValue(); } } diff --git a/dspace-api/src/test/data/dspaceFolder/config/controlled-vocabularies/countries.xml b/dspace-api/src/test/data/dspaceFolder/config/controlled-vocabularies/countries.xml new file mode 100644 index 000000000000..078e8bfa3865 --- /dev/null +++ b/dspace-api/src/test/data/dspaceFolder/config/controlled-vocabularies/countries.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/dspace-api/src/test/data/dspaceFolder/config/controlled-vocabularies/countries_de.xml b/dspace-api/src/test/data/dspaceFolder/config/controlled-vocabularies/countries_de.xml new file mode 100644 index 000000000000..b4bbf0b1e03c --- /dev/null +++ b/dspace-api/src/test/data/dspaceFolder/config/controlled-vocabularies/countries_de.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/dspace-api/src/test/data/dspaceFolder/config/local.cfg b/dspace-api/src/test/data/dspaceFolder/config/local.cfg index b44f319a35f6..a85c1114f876 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/local.cfg +++ b/dspace-api/src/test/data/dspaceFolder/config/local.cfg @@ -175,6 +175,9 @@ authority.controlled.dspace.object.owner = true webui.browse.link.1 = author:dc.contributor.* webui.browse.link.2 = subject:dc.subject.* +# Configuration required for testing the controlled vocabulary functionality, which is configured using properties +vocabulary.plugin.countries.hierarchy.store=false +vocabulary.plugin.countries.storeIDs=true # Enable duplicate detection for tests duplicate.enable = true diff --git a/dspace-api/src/test/java/org/dspace/content/authority/DSpaceControlledVocabularyTest.java b/dspace-api/src/test/java/org/dspace/content/authority/DSpaceControlledVocabularyTest.java index 255b070e5eac..43bd20cc1553 100644 --- a/dspace-api/src/test/java/org/dspace/content/authority/DSpaceControlledVocabularyTest.java +++ b/dspace-api/src/test/java/org/dspace/content/authority/DSpaceControlledVocabularyTest.java @@ -89,6 +89,145 @@ public void testGetMatches() throws IOException, ClassNotFoundException { assertEquals("north 40", result.values[0].value); } + /** + * Test of getMatches method of class + * DSpaceControlledVocabulary using a localized controlled vocabulary with no locale (fallback to default) + * @throws java.lang.ClassNotFoundException passed through. + */ + @Test + public void testGetMatchesNoLocale() throws ClassNotFoundException { + final String PLUGIN_INTERFACE = "org.dspace.content.authority.ChoiceAuthority"; + + String idValue = "DZA"; + String labelPart = "Alge"; + int start = 0; + int limit = 10; + // This "countries" Controlled Vocab is included in TestEnvironment data + // (under /src/test/data/dspaceFolder/) and it should be auto-loaded + // by test configs in /src/test/data/dspaceFolder/config/local.cfg + DSpaceControlledVocabulary instance = (DSpaceControlledVocabulary) + CoreServiceFactory.getInstance().getPluginService().getNamedPlugin(Class.forName(PLUGIN_INTERFACE), + "countries"); + assertNotNull(instance); + Choices result = instance.getMatches(labelPart, start, limit, null); + assertEquals(idValue, result.values[0].value); + assertEquals("Algeria", result.values[0].label); + } + + /** + * Test of getBestMatch method of class + * DSpaceControlledVocabulary using a localized controlled vocabulary with no locale (fallback to default) + * @throws java.lang.ClassNotFoundException passed through. + */ + @Test + public void testGetBestMatchIdValueNoLocale() throws ClassNotFoundException { + final String PLUGIN_INTERFACE = "org.dspace.content.authority.ChoiceAuthority"; + + String idValue = "DZA"; + // This "countries" Controlled Vocab is included in TestEnvironment data + // (under /src/test/data/dspaceFolder/) and it should be auto-loaded + // by test configs in /src/test/data/dspaceFolder/config/local.cfg + DSpaceControlledVocabulary instance = (DSpaceControlledVocabulary) + CoreServiceFactory.getInstance().getPluginService().getNamedPlugin(Class.forName(PLUGIN_INTERFACE), + "countries"); + assertNotNull(instance); + Choices result = instance.getBestMatch(idValue, null); + assertEquals(idValue, result.values[0].value); + assertEquals("Algeria", result.values[0].label); + } + + /** + * Test of getMatches method of class + * DSpaceControlledVocabulary using a localized controlled vocabulary with valid locale parameter (localized + * label returned) + */ + @Test + public void testGetMatchesGermanLocale() throws ClassNotFoundException { + final String PLUGIN_INTERFACE = "org.dspace.content.authority.ChoiceAuthority"; + + String idValue = "DZA"; + String labelPart = "Alge"; + int start = 0; + int limit = 10; + // This "countries" Controlled Vocab is included in TestEnvironment data + // (under /src/test/data/dspaceFolder/) and it should be auto-loaded + // by test configs in /src/test/data/dspaceFolder/config/local.cfg + DSpaceControlledVocabulary instance = (DSpaceControlledVocabulary) + CoreServiceFactory.getInstance().getPluginService().getNamedPlugin(Class.forName(PLUGIN_INTERFACE), + "countries"); + assertNotNull(instance); + Choices result = instance.getMatches(labelPart, start, limit, "de"); + assertEquals(idValue, result.values[0].value); + assertEquals("Algerien", result.values[0].label); + } + + /** + * Test of getBestMatch method of class + * DSpaceControlledVocabulary using a localized controlled vocabulary with valid locale parameter (localized + * label returned) + */ + @Test + public void testGetBestMatchIdValueGermanLocale() throws ClassNotFoundException { + final String PLUGIN_INTERFACE = "org.dspace.content.authority.ChoiceAuthority"; + + String idValue = "DZA"; + // This "countries" Controlled Vocab is included in TestEnvironment data + // (under /src/test/data/dspaceFolder/) and it should be auto-loaded + // by test configs in /src/test/data/dspaceFolder/config/local.cfg + DSpaceControlledVocabulary instance = (DSpaceControlledVocabulary) + CoreServiceFactory.getInstance().getPluginService().getNamedPlugin(Class.forName(PLUGIN_INTERFACE), + "countries"); + assertNotNull(instance); + Choices result = instance.getBestMatch(idValue, "de"); + assertEquals(idValue, result.values[0].value); + assertEquals("Algerien", result.values[0].label); + } + + /** + * Test of getChoice method of class + * DSpaceControlledVocabulary using a localized controlled vocabulary with no locale (fallback to default) + * @throws java.lang.ClassNotFoundException passed through. + */ + @Test + public void testGetChoiceNoLocale() throws ClassNotFoundException { + final String PLUGIN_INTERFACE = "org.dspace.content.authority.ChoiceAuthority"; + + String idValue = "DZA"; + // This "countries" Controlled Vocab is included in TestEnvironment data + // (under /src/test/data/dspaceFolder/) and it should be auto-loaded + // by test configs in /src/test/data/dspaceFolder/config/local.cfg + DSpaceControlledVocabulary instance = (DSpaceControlledVocabulary) + CoreServiceFactory.getInstance().getPluginService().getNamedPlugin(Class.forName(PLUGIN_INTERFACE), + "countries"); + assertNotNull(instance); + Choice result = instance.getChoice(idValue, null); + assertEquals(idValue, result.value); + assertEquals("Algeria", result.label); + } + + /** + * Test of getChoice method of class + * DSpaceControlledVocabulary using a localized controlled vocabulary with valid locale parameter (localized + * label returned) + * @throws java.lang.ClassNotFoundException passed through. + */ + @Test + public void testGetChoiceGermanLocale() throws ClassNotFoundException { + final String PLUGIN_INTERFACE = "org.dspace.content.authority.ChoiceAuthority"; + + String idValue = "DZA"; + // This "countries" Controlled Vocab is included in TestEnvironment data + // (under /src/test/data/dspaceFolder/) and it should be auto-loaded + // by test configs in /src/test/data/dspaceFolder/config/local.cfg + DSpaceControlledVocabulary instance = (DSpaceControlledVocabulary) + CoreServiceFactory.getInstance().getPluginService().getNamedPlugin(Class.forName(PLUGIN_INTERFACE), + "countries"); + assertNotNull(instance); + Choice result = instance.getChoice(idValue, "de"); + assertEquals(idValue, result.value); + assertEquals("Algerien", result.label); + } + /** * Test of getBestMatch method, of class DSpaceControlledVocabulary. */