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 new file mode 100644 index 0000000000..886fe52ce0 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java @@ -0,0 +1,248 @@ +/* + * DSpaceControlledVocabulary.java + * + * Version: $Revision: $ + * + * Date: $Date: $ + * + * Copyright (c) 2002-2005, Hewlett-Packard Company and Massachusetts + * Institute of Technology. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of the Hewlett-Packard Company nor the name of the + * Massachusetts Institute of Technology nor the names of their + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR + * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + */ + +package org.dspace.content.authority; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.io.File; + +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathExpressionException; +import javax.xml.xpath.XPathFactory; + +import org.xml.sax.InputSource; + +import org.apache.log4j.Logger; + +import org.dspace.core.ConfigurationManager; +import org.dspace.core.SelfNamedPlugin; + +/** + * ChoiceAuthority source that reads the JSPUI-style hierarchical vocabularies + * from ${dspace.dir}/config/controlled-vocabularies/*.xml and turns them into + * autocompleting authorities. + * + * Configuration: + * This MUST be configured as a self-named plugin, e.g.: + * plugin.selfnamed.org.dspace.content.authority.ChoiceAuthority = \ + * org.dspace.content.authority.DSpaceControlledVocabulary + * + * It AUTOMATICALLY configures a plugin instance for each XML file in the + * controlled vocabularies directory. The name of the plugin is the basename + * of the file; e.g., "${dspace.dir}/config/controlled-vocabularies/nsi.xml" + * would generate a plugin called "nsi". + * + * Each configured plugin comes with three configuration options: + * vocabulary.plugin._plugin_.hierarchy.store = # Store entire hierarchy along with selected value. Default: TRUE + * vocabulary.plugin._plugin_.hierarchy.suggest = # Display entire hierarchy in the suggestion list. Default: TRUE + * vocabulary.plugin._plugin_.delimiter = "" # Delimiter to use when building hierarchy strings. Default: "::" + * + * + * @author Michael B. Klein + * + */ + +public class DSpaceControlledVocabulary extends SelfNamedPlugin implements ChoiceAuthority +{ + + private static Logger log = Logger.getLogger(DSpaceControlledVocabulary.class); + private static String xpathTemplate = "//node[contains(translate(@label,'ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz'),'%s')]"; + private static String idTemplate = "//node[@id = '%s']"; + private static String pluginNames[] = null; + + private String vocabularyName = null; + private InputSource vocabulary = null; + private Boolean suggestHierarchy = true; + private Boolean storeHierarchy = true; + private String hierarchyDelimiter = "::"; + + public DSpaceControlledVocabulary() + { + super(); + } + + public static String[] getPluginNames() + { + if (pluginNames == null) + { + class xmlFilter implements java.io.FilenameFilter + { + public boolean accept(File dir, String name) + { + return name.endsWith(".xml"); + } + } + String vocabulariesPath = ConfigurationManager.getProperty("dspace.dir") + "/config/controlled-vocabularies/"; + String[] xmlFiles = (new File(vocabulariesPath)).list(new xmlFilter()); + List names = new ArrayList(); + for (String filename : xmlFiles) + { + names.add((new File(filename)).getName().replace(".xml","")); + } + pluginNames = names.toArray(new String[names.size()]); + log.info("Got plugin names = "+Arrays.deepToString(pluginNames)); + } + return pluginNames; + } + + private void init() + { + if (vocabulary == null) + { + log.info("Initializing " + this.getClass().getName()); + vocabularyName = this.getPluginInstanceName(); + String vocabulariesPath = ConfigurationManager.getProperty("dspace.dir") + "/config/controlled-vocabularies/"; + String configurationPrefix = "vocabulary.plugin." + vocabularyName; + storeHierarchy = ConfigurationManager.getBooleanProperty(configurationPrefix + ".hierarchy.store", storeHierarchy); + suggestHierarchy = ConfigurationManager.getBooleanProperty(configurationPrefix + ".hierarchy.suggest", suggestHierarchy); + String configuredDelimiter = ConfigurationManager.getProperty(configurationPrefix + ".delimiter"); + if (configuredDelimiter != null) + { + hierarchyDelimiter = configuredDelimiter.replaceAll("(^\"|\"$)",""); + } + String filename = vocabulariesPath + vocabularyName + ".xml"; + log.info("Loading " + filename); + vocabulary = new InputSource(filename); + } + } + + private String buildString(Node node) + { + if (node.getNodeType() == Node.DOCUMENT_NODE) + { + return(""); + } + else + { + String parentValue = buildString(node.getParentNode()); + Node currentLabel = node.getAttributes().getNamedItem("label"); + if (currentLabel != null) + { + String currentValue = currentLabel.getNodeValue(); + if (parentValue.equals("")) + { + return currentValue; + } + else + { + return(parentValue + this.hierarchyDelimiter + currentValue); + } + } + else + { + return(parentValue); + } + } + } + + public Choices getMatches(String text, int collection, int start, int limit, String locale) + { + init(); + log.debug("Getting matches for '" + text + "'"); + String xpathExpression = String.format(xpathTemplate, text.replaceAll("'", "'").toLowerCase()); + XPath xpath = XPathFactory.newInstance().newXPath(); + Choice[] choices; + try { + NodeList results = (NodeList)xpath.evaluate(xpathExpression, vocabulary, XPathConstants.NODESET); + String[] authorities = new String[results.getLength()]; + String[] values = new String[results.getLength()]; + String[] labels = new String[results.getLength()]; + for (int i=0; i 0) + { + for (int i=0; i # default: true +# vocabulary.plugin._plugin_.hierarchy.suggest = # default: true +# vocabulary.plugin._plugin_.delimiter = "" # default: "::" +## +## An example using "srsc" can be found later in this section + #plugin.selfnamed.org.dspace.content.authority.ChoiceAuthority = \ -# org.dspace.content.authority.DCInputAuthority +# org.dspace.content.authority.DCInputAuthority, \ +# org.dspace.content.authority.DSpaceControlledVocabulary ## configure LC Names plugin #lcname.url = http://alcme.oclc.org/srw/search/lcnaf @@ -1497,6 +1510,13 @@ authority.minconfidence = ambiguous ## keyword from the same set as for the default "authority.minconfidence" #authority.minconfidence.dc.contributor.author = accepted +## demo: subject code autocomplete, using srsc as authority +## (DSpaceControlledVocabulary plugin must be enabled) +#choices.plugin.dc.subject = srsc +#choices.presentation.dc.subject = select +#vocabulary.plugin.srsc.hierarchy.store = true +#vocabulary.plugin.srsc.hierarchy.suggest = true +#vocabulary.plugin.srsc.delimiter = "::" ## Demo: publisher name lookup through SHERPA/RoMEO: #choices.plugin.dc.publisher = SRPublisher @@ -1508,12 +1528,12 @@ authority.minconfidence = ambiguous #authority.controlled.dc.title.alternative = true ## demo: use choice authority (without authority-control) to restrict dc.type on EditItemMetadata page -# choices.plugin.dc.type = common_types -# choices.presentation.dc.type = select +#choices.plugin.dc.type = common_types +#choices.presentation.dc.type = select ## demo: same idea for dc.language.iso -# choices.plugin.dc.language.iso = common_iso_languages -# choices.presentation.dc.language.iso = select +#choices.plugin.dc.language.iso = common_iso_languages +#choices.presentation.dc.language.iso = select # Change number of choices shown in the select in Choices lookup popup #xmlui.lookup.select.size = 12