From fe25fa8048131bc298bb6c4bb4398b00c0ec537c Mon Sep 17 00:00:00 2001 From: Holly Mistlebauer Date: Wed, 11 Sep 2013 10:22:42 -0400 Subject: [PATCH 1/6] Holly updated the test case AdminLoginLogout.html. --- .../acceptance-tests/suites/AdminSecurity/AdminLoginLogout.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utilities/acceptance-tests/suites/AdminSecurity/AdminLoginLogout.html b/utilities/acceptance-tests/suites/AdminSecurity/AdminLoginLogout.html index a1b01699..3b56df96 100644 --- a/utilities/acceptance-tests/suites/AdminSecurity/AdminLoginLogout.html +++ b/utilities/acceptance-tests/suites/AdminSecurity/AdminLoginLogout.html @@ -132,7 +132,7 @@ verifyElementPresent - link=Restrict Logins + link=Restrict logins From ffc968b1ca373c8710acfc96b69b572118c37e06 Mon Sep 17 00:00:00 2001 From: Holly Mistlebauer Date: Wed, 11 Sep 2013 11:36:06 -0400 Subject: [PATCH 2/6] Holly updated 3 test cases. --- .../suites/AdminSecurity/NewAdminCheckPrivileges.html | 6 +++--- .../suites/AdminSecurity/NewAdminDelete.html | 2 +- .../suites/AdminSecurity/NewAdminFirstLogin.html | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/utilities/acceptance-tests/suites/AdminSecurity/NewAdminCheckPrivileges.html b/utilities/acceptance-tests/suites/AdminSecurity/NewAdminCheckPrivileges.html index cb095ca4..355c44ad 100644 --- a/utilities/acceptance-tests/suites/AdminSecurity/NewAdminCheckPrivileges.html +++ b/utilities/acceptance-tests/suites/AdminSecurity/NewAdminCheckPrivileges.html @@ -128,7 +128,7 @@ verifyElementPresent - link=Restrict Logins + link=Restrict logins @@ -329,7 +329,7 @@ verifyTextPresent - Page Management + Page management @@ -504,7 +504,7 @@ verifyTextPresent - Email Address + Email address diff --git a/utilities/acceptance-tests/suites/AdminSecurity/NewAdminDelete.html b/utilities/acceptance-tests/suites/AdminSecurity/NewAdminDelete.html index 89ceda9a..ea6beae1 100644 --- a/utilities/acceptance-tests/suites/AdminSecurity/NewAdminDelete.html +++ b/utilities/acceptance-tests/suites/AdminSecurity/NewAdminDelete.html @@ -124,7 +124,7 @@ verifyTextPresent - Deleted 1 accounts. + Deleted 1 account. diff --git a/utilities/acceptance-tests/suites/AdminSecurity/NewAdminFirstLogin.html b/utilities/acceptance-tests/suites/AdminSecurity/NewAdminFirstLogin.html index 0543f143..8b0c615a 100644 --- a/utilities/acceptance-tests/suites/AdminSecurity/NewAdminFirstLogin.html +++ b/utilities/acceptance-tests/suites/AdminSecurity/NewAdminFirstLogin.html @@ -223,7 +223,7 @@ verifyTextPresent - Please enter a password between 6 and 12 characters in length. + Password must be between 6 and 12 characters. From 331869b52646a7ba4e620086f432549ca00f6247 Mon Sep 17 00:00:00 2001 From: j2blake Date: Thu, 12 Sep 2013 16:46:50 -0400 Subject: [PATCH 3/6] VIVO-281 Make extended linked data a configurable option. --- doc/upgrade-1.6.html | 78 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 72 insertions(+), 6 deletions(-) diff --git a/doc/upgrade-1.6.html b/doc/upgrade-1.6.html index 1e25bdfe..b6ef4ecd 100644 --- a/doc/upgrade-1.6.html +++ b/doc/upgrade-1.6.html @@ -50,6 +50,7 @@
  • Home Page Re-design
  • Auto-loaded RDF files move to the Home directory
  • Support for additional languages
  • +
  • More compact responses to Linked data requests
  • ANYTHING ELSE?
  • @@ -335,8 +336,9 @@ rdf/tbox/filegraph/tbox/vivo-foaf-1.5.owl rdf/tbox/filegraph/tbox/vivo-pws-1.5.owl rdf/tbox/filegraph/tbox/vivo-skos-1.5.owl

    - If you are using a three-tier build process, you will need to add a single line - to the build script so the RDF files will be merged properly across the tiers. So this: + If you are using a three-tier build process, you will need to add two lines + to the build script to accomodate the RDF files, and the language support (see below) + So this:

    <patternset id="appbase.patterns">
     <include name="src/**/*" />
     <include name="lib/**/*" />
    @@ -353,6 +355,7 @@ rdf/tbox/filegraph/tbox/vivo-skos-1.5.owl
    <include name="src/**/*" /> <include name="lib/**/*" /> <include name="rdf/**/*" /> +<include name="languages/**/*" /> <include name="test/**/*" /> <include name="themes/**/*" /> <include name="config/*.properties" /> @@ -387,6 +390,47 @@ rdf/tbox/filegraph/tbox/vivo-skos-1.5.owl Add a new language to VIVO.

    +

    More compact responses to Linked data requests

    +

    + In VIVO 1.6, the response to requests for linked data is changed, to be smaller and faster. +

    +

    + When responding to a request for linked data about an individual, VIVO 1.6 returns: +

      +
    • Data properties of the individual
    • +
    • Object relationships to and from the individual
    • +
    • The RDF types and RDFS labels for any object that directly relates to the individual
    • +
    + This data is filtered by the usual VIVO privacy policies, + so properties such as salary or employee ID number may not be revealed + unless the requester has been properly authenticated. +

    +

    + VIVO releases prior to VIVO 1.6 returned a more complex set of statements, + referred to as "extended linked data": +

      +
    • Data properties of the individual
    • +
    • Object relationships from the individual
    • +
    • All properties of the context nodes (positions, roles, etc.) that are associated with the individual.
    • +
    • Labels of objects that are joined to the individual through context nodes.
    • +
    • Full details of time intervals that are attached to context nodes: start, end, precision.
    • +
    + As above, this data was filtered by the VIVO privacy policies. + Although these additional items were included, + extended linked data was based only on relationships from the individual. + Relationships to the individual were not included. +

    +

    + Extended linked data was costly to produce, in terms of resources, + because it required a recursive search of the data model. + Extended linked data typically contained 50% more information than its non-extended equivalent, + and took more than 10 times as long to produce. +

    +

    + VIVO release 1.6 can be configured to produce extended linked data like previous releases. + However, extended linked data will not be supported in future releases. +

    +

    ANYTHING ELSE?

    @@ -614,7 +658,7 @@ rdf/tbox/filegraph/tbox/vivo-skos-1.5.owl - developer.defeatI18nCache = true + developer.defeatI18nCache false @@ -632,7 +676,7 @@ rdf/tbox/filegraph/tbox/vivo-skos-1.5.owl - developer.insertFreemarkerDelimiters = true + developer.insertFreemarkerDelimiters false @@ -647,7 +691,7 @@ rdf/tbox/filegraph/tbox/vivo-skos-1.5.owl - homePage.geoFocusMaps = enabled + homePage.geoFocusMaps enabled @@ -667,13 +711,35 @@ rdf/tbox/filegraph/tbox/vivo-skos-1.5.owl - MultiViews.profilePageTypes=disabled + MultiViews.profilePageTypes disabled + + + Setting this property causes VIVO 1.6 to produce extended responses to requests + for linked data. This provides compatibility with earlier releases. + The default is false. +
    + Extended linked data is costly, in terms of server resource. Typically, extended + linke data contains 50% more information than its non-extended equivalent, and + takes 10 times as long to produce. +
    + Extended linked data will not be supported in future releases of VIVO. + + + + + serveExtendedLinkedData + + + true + + + From 9dcf9d50e19fe32cb37f98918dddaaf55a243cd4 Mon Sep 17 00:00:00 2001 From: j2blake Date: Fri, 13 Sep 2013 14:29:10 -0400 Subject: [PATCH 4/6] Remove superfluous noise from the log. --- .../templatemodels/searchresult/IndividualSearchResult.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/edu/cornell/mannlib/vitro/webapp/web/templatemodels/searchresult/IndividualSearchResult.java b/src/edu/cornell/mannlib/vitro/webapp/web/templatemodels/searchresult/IndividualSearchResult.java index 780f95a2..782c2316 100644 --- a/src/edu/cornell/mannlib/vitro/webapp/web/templatemodels/searchresult/IndividualSearchResult.java +++ b/src/edu/cornell/mannlib/vitro/webapp/web/templatemodels/searchresult/IndividualSearchResult.java @@ -16,18 +16,18 @@ public class IndividualSearchResult extends BaseIndividualSearchResult { public IndividualSearchResult(Individual individual, VitroRequest vreq) { super(individual, vreq); - log.info("Called Individual Search Result"); + log.debug("Called Individual Search Result"); } /* Access methods for templates */ public String getPreferredTitle() { - log.info("Called get Title"); + log.debug("Called get Title"); return individual.getDataValue(CORE + "preferredTitle"); } public String getEmail() { - log.info("Called get Email"); + log.debug("Called get Email"); return individual.getDataValue(CORE + "email"); } From 5b8309bcbccde6b2369a444201676f9cc1d7d383 Mon Sep 17 00:00:00 2001 From: j2blake Date: Fri, 13 Sep 2013 17:24:59 -0400 Subject: [PATCH 5/6] VIVO-262 Complete the push script. Add more checking to extract_files. --- utilities/releaseScripts/4_extract_files.rb | 25 +++++++++++-- utilities/releaseScripts/8_push_changes.rb | 39 +++++++++++++++++++++ 2 files changed, 62 insertions(+), 2 deletions(-) create mode 100644 utilities/releaseScripts/8_push_changes.rb diff --git a/utilities/releaseScripts/4_extract_files.rb b/utilities/releaseScripts/4_extract_files.rb index 6b480338..d223158f 100644 --- a/utilities/releaseScripts/4_extract_files.rb +++ b/utilities/releaseScripts/4_extract_files.rb @@ -21,7 +21,23 @@ require '_common' # # Get the VIVO files and the Vitro files, and remove the .git directories. # -def export_files(vivo_path, vitro_path, tag, export_dir) +def export_files(vivo_path, vitro_path, tag, branch, export_dir) + Dir.chdir(vivo_path) do |path| + cmds = ["git checkout #{branch}", + "git pull", + ] + cmds.delete_at(1) unless is_remote_branch?(branch) + approve_and_execute(cmds, "in #{path}") + end + + Dir.chdir(vitro_path) do |path| + cmds = ["git checkout #{branch}", + "git pull", + ] + cmds.delete_at(1) unless is_remote_branch?(branch) + approve_and_execute(cmds, "in #{path}") + end + approve_and_execute([ "rm -Rf #{File.expand_path("..", export_dir)}", "mkdir -pv #{export_dir}", @@ -31,6 +47,10 @@ def export_files(vivo_path, vitro_path, tag, export_dir) ]) end +def is_remote_branch?(branch) + ! `git branch --list -a origin/#{branch}`.strip.empty? +end + # # ------------------------------------------------------------------------------------ # Main method @@ -39,6 +59,7 @@ end begin tag = Settings.tag_name + branch = Settings.branch_name vivo_path = Settings.vivo_path vitro_path = Settings.vitro_path export_dir = Settings.export_dir @@ -54,7 +75,7 @@ begin get_permission_and_go(p) do puts "Building export area" - export_files(vivo_path, vitro_path, tag, export_dir) + export_files(vivo_path, vitro_path, tag, branch, export_dir) end rescue BadState puts "#{$!.message} - Aborting." diff --git a/utilities/releaseScripts/8_push_changes.rb b/utilities/releaseScripts/8_push_changes.rb new file mode 100644 index 00000000..3bb46eba --- /dev/null +++ b/utilities/releaseScripts/8_push_changes.rb @@ -0,0 +1,39 @@ +=begin +-------------------------------------------------------------------------------- + +Push any branches, tags, or merges back to GitHub. + +-------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- +=end + +$: << File.dirname(File.expand_path(__FILE__)) +require '_common' + +# +# Merge the maintenance branch to the master branch and create the tag. +# +def push_to_origin(repo_path) + Dir.chdir(repo_path) do |path| + approve_and_execute(["git push --all", "git push --tags"], "in #{path}") + end +end + +# +# ------------------------------------------------------------------------------------ +# Main method +# ------------------------------------------------------------------------------------ +# + +begin + vivo_path = Settings.vivo_path + vitro_path = Settings.vitro_path + + get_permission_and_go("OK to push changes to the origin?") do + puts "Merging tags" + push_to_origin(vivo_path) + push_to_origin(vitro_path) + end +rescue BadState + puts "#{$!.message} - Aborting." +end From ba1c6c7075a3aa6cd13ff8cf3ba5f7c249b3e17d Mon Sep 17 00:00:00 2001 From: hudajkhan Date: Mon, 16 Sep 2013 14:02:47 -0400 Subject: [PATCH 6/6] updates for concept search service, adding LCSH search capability --- .../edit/forms/addAssociatedConcept.ftl | 69 ++- ...dAssociatedConceptVocabSpecificDisplay.ftl | 14 + .../freemarker/edit/forms/css/addConcept.css | 29 + .../freemarker/edit/forms/js/addConcept.js | 109 +++- rdf/abox/filegraph/vocabularySource.n3 | 4 +- .../semservices/service/impl/LCSHService.java | 510 ++++++++++++++++++ .../AddAssociatedConceptGenerator.java | 35 +- .../AddAssociatedConceptsPreprocessor.java | 368 +++++++++---- .../ConceptSearchServiceUtils.java | 5 + themes/wilma/i18n/all.properties | 1 + 10 files changed, 1009 insertions(+), 135 deletions(-) create mode 100644 productMods/templates/freemarker/edit/forms/addAssociatedConceptVocabSpecificDisplay.ftl create mode 100644 src/edu/cornell/mannlib/semservices/service/impl/LCSHService.java diff --git a/productMods/templates/freemarker/edit/forms/addAssociatedConcept.ftl b/productMods/templates/freemarker/edit/forms/addAssociatedConcept.ftl index bacdf43f..0f7a2c1b 100644 --- a/productMods/templates/freemarker/edit/forms/addAssociatedConcept.ftl +++ b/productMods/templates/freemarker/edit/forms/addAssociatedConcept.ftl @@ -1,6 +1,6 @@ <#-- $This file is distributed under the terms of the license in /doc/license.txt$ --> <#import "lib-vivo-form.ftl" as lvf> - +<#include "addAssociatedConceptVocabSpecificDisplay.ftl" > <#assign existingConcepts = editConfiguration.pageData.existingConcepts/> <#assign userDefinedConceptUrl = editConfiguration.pageData.userDefinedConceptUrl/> <#assign sources = editConfiguration.pageData.searchServices/> @@ -35,29 +35,44 @@ -
      +
        + <#if (existingConcepts?size > 0)> +
      • +
        +
        +

        Concept (Type)

        +
        +
        +

        Vocabulary Source

        +
        +
          +
        +
        +
      • + <#list existingConcepts as existingConcept> -
      • - - - - - ${existingConcept.conceptLabel} - <#if existingConcept.vocabURI?has_content && existingConcept.vocabLabel?has_content> - (${existingConcept.vocabLabel}) - +
      • +
        +
        ${existingConcept.conceptLabel} <#if existingConcept.conceptSemanticTypeLabel?has_content> - ${existingConcept.conceptSemanticTypeLabel} + (${existingConcept.conceptSemanticTypeLabel}) - - -  ${i18n().remove_capitalized} - +
        +
        + <#if existingConcept.vocabURI?has_content && existingConcept.vocabLabel?has_content> + ${existingConcept.vocabLabel} + +
        + +
      • diff --git a/productMods/templates/freemarker/edit/forms/addAssociatedConceptVocabSpecificDisplay.ftl b/productMods/templates/freemarker/edit/forms/addAssociatedConceptVocabSpecificDisplay.ftl new file mode 100644 index 00000000..43ab963e --- /dev/null +++ b/productMods/templates/freemarker/edit/forms/addAssociatedConceptVocabSpecificDisplay.ftl @@ -0,0 +1,14 @@ +<#-- $This file is distributed under the terms of the license in /doc/license.txt$ --> + +<#--The original concept javascript is service independent, i.e. all vocabulary service information is returned from the servlet +and the template itself generates the same display for all the services. Right now we would like to show a different label +in the search results based on the service. I am storing that information here and later we can consider how the display +can return to being independent of vocabulary service-specific display options. +These values will be passed to the javascript--> + +<#assign vocabSpecificDisplay = { +"http://link.informatics.stonybrook.edu/umls":"${i18n().label_type}", +"http://aims.fao.org/aos/agrovoc/agrovocScheme":"${i18n().label_altLabels}", +"http://www.eionet.europa.eu/gemet/gemetThesaurus":"${i18n().label_type}", +"http://id.loc.gov/authorities/subjects":"${i18n().label_altLabels}" +}/> diff --git a/productMods/templates/freemarker/edit/forms/css/addConcept.css b/productMods/templates/freemarker/edit/forms/css/addConcept.css index 967571ab..225b86bf 100644 --- a/productMods/templates/freemarker/edit/forms/css/addConcept.css +++ b/productMods/templates/freemarker/edit/forms/css/addConcept.css @@ -1,5 +1,9 @@ /* $This file is distributed under the terms of the license in /doc/license.txt$ */ +.conceptsListContainer { + overflow:hidden; + width:100%; +} .concepts .column { float:left; padding-right:3px; @@ -44,4 +48,29 @@ form#addConceptForm { form#addConceptForm span#createOwnOne{ float:left; margin-top:24px +} + +/*For existing concepts, display will also be tabular with columns*/ + +.existingConcept .row, .conceptHeadings .row { + clear:both; + float:left; +} + +.existingConcept .column , .conceptHeadings .column { + float:left; + padding-right:3px; + clear:none !important; /*Overriding customFor div's clearing*/ +} +/*label and semantic type if it exists*/ +.existingConcept .conceptLabelInfo, .conceptHeadings .conceptLabelInfo { + width:220px; +} + +.existingConcept .conceptVocabSource, .conceptHeadings .conceptVocabSource { + width:220px; +} + +.conceptHeadings .row { + border-bottom: 1px solid #5F6464; } \ No newline at end of file diff --git a/productMods/templates/freemarker/edit/forms/js/addConcept.js b/productMods/templates/freemarker/edit/forms/js/addConcept.js index d650d349..ce9200a6 100644 --- a/productMods/templates/freemarker/edit/forms/js/addConcept.js +++ b/productMods/templates/freemarker/edit/forms/js/addConcept.js @@ -55,12 +55,19 @@ var addConceptForm = { this.externalConceptLabel = $('#conceptLabel'); this.externalConceptSource = $('#conceptSource'); this.externalConceptSemanticTypeLabel = $("#conceptSemanticTypeLabel"); + this.externalConceptBroaderUris = $("#conceptBroaderURI"); + this.externalConceptNarrowerUris = $("#conceptNarrowerURI"); //remove links this.removeConceptLinks = $('a.remove'); this.errors = $('#errors'); this.createOwn1 = $('#createOwnOne'); this.createOwn2 = $('#createOwnTwo'); this.orSpan = $('span.or') + this.loadingIndicator = $("#indicator"); + this.showHideSearchResults = $("#showHideResults"); + //Value we are setting to cut off length of alternate labels string + this.maxNumberAlternateLabels = 4; + this.numberOfMaxInitialSearchResults = 7; }, initPage: function() { @@ -87,6 +94,10 @@ var addConceptForm = { addConceptForm.removeExistingConcept(this); return false; }); + this.showHideSearchResults.find("a#showHideLink").click(function() { + addConceptForm.showHideMultipleSearchResults(this); + return false; + }); }, initForm: function() { // Hide the button that shows the form @@ -99,7 +110,9 @@ var addConceptForm = { //Also clear the search input this.searchTerm.val(""); this.cancel.unbind('click'); - + //make sure results loading indicator is hidden + this.loadingIndicator.addClass("hidden"); + this.showHideSearchResults.hide(); // Show the form this.form.show(); }, @@ -114,6 +127,8 @@ var addConceptForm = { }, clearSearchResults:function() { $('#selectedConcept').empty(); + //Hide the indicator icon if still there + $("#indicator").addClass("hidden"); }, clearErrors:function() { addConceptForm.errors.empty(); @@ -134,6 +149,25 @@ var addConceptForm = { this.hideForm(); this.showFormButtonWrapper.show(); }, + showHideMultipleSearchResults: function(link) { + if($(link).hasClass("showmore")) { + //if clicking and already says show more then need to show the rest of the results + $("li.concepts").show(); //show everything + $(link).html("Show fewer results"); + $(link).removeClass("showmore"); + } else { + //if clicking and does not say show more than need to show less + $("li.concepts").slice(addConceptForm.numberOfMaxInitialSearchResults).hide(); + $(link).html("Show more results"); + $(link).addClass("showmore"); + } + }, + //reset this to default, which is hidden with show more link + resetShowHideMultipleSearchResults: function() { + addConceptForm.showHideSearchResults.hide(); + addConceptForm.showHideSearchResults.find("a#showHideLink").html("Show more results"); + addConceptForm.showHideSearchResults.find("a#showHideLink").addClass("showmore"); + }, submitSearchTerm: function() { //Get value of search term var searchValue = this.searchTerm.val(); @@ -145,7 +179,11 @@ var addConceptForm = { } var vocabSourceValue = checkedVocabSource.val(); var dataServiceUrl = addConceptForm.dataServiceUrl + "?searchTerm=" + encodeURIComponent(searchValue) + "&source=" + encodeURIComponent(vocabSourceValue); - //This should return an object including the concept list or any errors if there are any + //Show the loading icon until the results appear + addConceptForm.loadingIndicator.removeClass("hidden"); + //Hide and reset the show more button + addConceptForm.resetShowHideMultipleSearchResults(); + //This should return an object including the concept list or any errors if there are any $.getJSON(dataServiceUrl, function(results) { var htmlAdd = ""; var vocabUnavailable = "

        " + addConceptForm.vocServiceUnavailable + "

        "; @@ -166,7 +204,7 @@ var addConceptForm = { //For each result, display if(numberTotalMatches > 0) { htmlAdd = "
          "; - htmlAdd+= addConceptForm.addResultsHeader(); + htmlAdd+= addConceptForm.addResultsHeader(vocabSourceValue); //Show best matches first for(i = 0; i < numberBestMatches; i++) { var conceptResult = bestMatchResults[i]; @@ -184,6 +222,8 @@ var addConceptForm = { } if(htmlAdd.length) { + //hide the loading icon again + addConceptForm.loadingIndicator.addClass("hidden"); $('#selectedConcept').html(htmlAdd); if (htmlAdd.indexOf("No search results") >= 0) { addConceptForm.showHiddenElements(hasResults); @@ -191,6 +231,8 @@ var addConceptForm = { else { hasResults = true; addConceptForm.showHiddenElements(hasResults); + //Here, tweak the display based on the number of results + addConceptForm.displayUptoMaxResults(); } } }); @@ -203,9 +245,12 @@ var addConceptForm = { var definedBy = conceptResult.definedBy; var type = conceptResult.type; var uri = conceptResult.uri; + //also adding broader and narrower uris wherever they exist + var broaderUris = conceptResult.broaderURIList; + var narrowerUris = conceptResult.narrowerURIList; //this will be null if there are no alternate labels var altLabels = conceptResult.altLabelList; - return addConceptForm.generateIndividualConceptDisplay(uri, label, altLabels, definition, type, definedBy, isBestMatch); + return addConceptForm.generateIndividualConceptDisplay(uri, label, altLabels, definition, type, definedBy, isBestMatch, broaderUris, narrowerUris); }, //This should now return all best matches in one array and other results in another parseResults:function(resultsArray) { @@ -225,10 +270,19 @@ var addConceptForm = { } return {"bestMatch":bestMatchResults, "alternate":alternateResults}; }, - addResultsHeader:function() { - var htmlAdd = "
        • " + addConceptForm.labelTypeString + " " + addConceptForm.definitionString + "" + addConceptForm.bestMatchString + "
        • "; + addResultsHeader:function(vocabSourceValue) { + var htmlAdd = "
        • " + + addConceptForm.getVocabSpecificColumnLabel(vocabSourceValue) + " " + addConceptForm.definitionString + "" + addConceptForm.bestMatchString + "
        • "; return htmlAdd; }, + //currently just the first column label depends on which service has been utilized + getVocabSpecificColumnLabel: function(vocabSourceValue) { + var columnLabel = addConceptForm.vocabSpecificLabels[vocabSourceValue]; + if(columnLabel == undefined) { + columnLabel = addConceptForm.defaultLabelTypeString; + } + return columnLabel; + }, hideSearchResults:function() { this.selectedConcept.hide(); }, @@ -239,11 +293,14 @@ var addConceptForm = { } var i; var len = checkedElements.length; - var checkedConcept, checkedConceptElement, conceptLabel, conceptSource, conceptSemanticType; + var checkedConcept, checkedConceptElement, conceptLabel, conceptSource, conceptSemanticType, + conceptBroaderUri, conceptNarrowerUri; var conceptNodes = []; var conceptLabels = []; var conceptSources = []; var conceptSemanticTypes = []; + var conceptBroaderUris = []; //each array element can be a string which is comma delimited for multiple uris + var conceptNarrowerUris = [];//same as above checkedElements.each(function() { checkedConceptElement = $(this); @@ -251,22 +308,29 @@ var addConceptForm = { conceptLabel = checkedConceptElement.attr("label"); conceptSource = checkedConceptElement.attr("conceptDefinedBy"); conceptSemanticType = checkedConceptElement.attr("conceptType"); + conceptBroaderUri = checkedConceptElement.attr("broaderUris"); + conceptNarrowerUri = checkedConceptElement.attr("narrowerUris"); conceptNodes.push(checkedConcept); conceptLabels.push(conceptLabel); conceptSources.push(conceptSource); conceptSemanticTypes.push(conceptSemanticType); + conceptBroaderUris.push(conceptBroaderUri); + conceptNarrowerUris.push(conceptNarrowerUri); }); this.externalConceptURI.val(conceptNodes); this.externalConceptLabel.val(conceptLabels); this.externalConceptSource.val(conceptSources); this.externalConceptSemanticTypeLabel.val(conceptSemanticTypes); + this.externalConceptBroaderUris.val(conceptBroaderUris); + this.externalConceptNarrowerUris.val(conceptNarrowerUris); + return true; }, - generateIndividualConceptDisplay: function(cuiURI, label, altLabels, definition, type, definedBy, isBestMatch) { + generateIndividualConceptDisplay: function(cuiURI, label, altLabels, definition, type, definedBy, isBestMatch, broaderUris, narrowerUris) { var htmlAdd = "
        • " + "
          " + "
          " + - addConceptForm.generateIndividualCUIInput(cuiURI, label, type, definedBy) + + addConceptForm.generateIndividualCUIInput(cuiURI, label, type, definedBy, broaderUris, narrowerUris) + addConceptForm.generateIndividualLabelsDisplay(label, altLabels) + addConceptForm.generateIndividualTypeDisplay(type) + "
          " + addConceptForm.generateIndividualDefinitionDisplay(definition) + addConceptForm.generateBestOrAlternate(isBestMatch) + @@ -274,14 +338,23 @@ var addConceptForm = { "
        • "; return htmlAdd; }, - generateIndividualCUIInput:function(cuiURI, label, type, definedBy) { - return ""; + generateIndividualCUIInput:function(cuiURI, label, type, definedBy, broaderUris, narrowerUris) { + return ""; }, //In case there are multiple labels display those generateIndividualLabelsDisplay:function(label, altLabels) { var labelDisplay = label; + var displayAltLabels = altLabels; if(altLabels != null && altLabels.length > 0) { - labelDisplay += "
          [" + altLabels + "]"; + //Certain vocabulary services might return a long list of alternate labels, in which case we will show fewer + //display only upto a certain number of alternate labels and use an ellipsis to signify there + //are additional terms + if(altLabels.length > addConceptForm.maxNumberAlternateLabels) { + displayAltLabels = altLabels.slice(0, addConceptForm.maxNumberAlternateLabels) + ",..."; + } + labelDisplay += "
          (" + displayAltLabels + ")"; } return labelDisplay; }, @@ -307,6 +380,18 @@ var addConceptForm = { } return "
           
          "; }, + //Certain vocabulary services return a great number of results, we would like the ability to show more or less of those results + displayUptoMaxResults:function() { + var numberConcepts = $("li.concepts").length; + if(numberConcepts > addConceptForm.numberOfMaxInitialSearchResults) { + $("li.concepts").slice(addConceptForm.numberOfMaxInitialSearchResults).hide(); + //Hide the link for showing/hiding search results + addConceptForm.showHideSearchResults.show(); + addConceptForm.showHideSearchResults.find("a#showHideLink").html("Show more results"); + addConceptForm.showHideSearchResults.find("a#showHideLink").addClass("showmore"); + } + + }, validateConceptSelection:function(checkedElements) { var numberElements = checkedElements.length; if(numberElements < 1) { diff --git a/rdf/abox/filegraph/vocabularySource.n3 b/rdf/abox/filegraph/vocabularySource.n3 index a111d896..cb51ff84 100644 --- a/rdf/abox/filegraph/vocabularySource.n3 +++ b/rdf/abox/filegraph/vocabularySource.n3 @@ -3,4 +3,6 @@ . "UMLS"^^ . "AGROVOC"^^ . - "GEMET"^^ . \ No newline at end of file + "GEMET"^^ . + . + "LCSH"^^ . diff --git a/src/edu/cornell/mannlib/semservices/service/impl/LCSHService.java b/src/edu/cornell/mannlib/semservices/service/impl/LCSHService.java new file mode 100644 index 00000000..14a59dd1 --- /dev/null +++ b/src/edu/cornell/mannlib/semservices/service/impl/LCSHService.java @@ -0,0 +1,510 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.semservices.service.impl; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.StringWriter; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLEncoder; +import java.rmi.RemoteException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.rpc.ServiceException; + +import net.sf.json.JSONArray; +import net.sf.json.JSONObject; +import net.sf.json.JSONSerializer; + +import org.apache.commons.lang.StringUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.fao.www.webservices.AgrovocWS.ACSWWebService; +import org.fao.www.webservices.AgrovocWS.ACSWWebServiceServiceLocator; +import org.semanticweb.skos.SKOSAnnotation; +import org.semanticweb.skos.SKOSConcept; +import org.semanticweb.skos.SKOSDataFactory; +import org.semanticweb.skos.SKOSDataProperty; +import org.semanticweb.skos.SKOSDataRelationAssertion; +import org.semanticweb.skos.SKOSDataset; +import org.semanticweb.skos.SKOSEntity; +import org.semanticweb.skos.SKOSLiteral; +import org.semanticweb.skos.SKOSObjectRelationAssertion; +import org.semanticweb.skos.SKOSUntypedLiteral; +import org.semanticweb.skos.properties.*; +import org.semanticweb.skosapibinding.SKOSManager; +import org.w3c.dom.Attr; +import org.w3c.dom.Document; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +import com.hp.hpl.jena.query.Query; +import com.hp.hpl.jena.query.QueryExecution; +import com.hp.hpl.jena.query.QueryExecutionFactory; +import com.hp.hpl.jena.query.QueryFactory; +import com.hp.hpl.jena.query.QuerySolution; +import com.hp.hpl.jena.query.ResultSet; +import com.hp.hpl.jena.rdf.model.Literal; +import com.hp.hpl.jena.rdf.model.RDFNode; +import com.hp.hpl.jena.rdf.model.Resource; + +import edu.cornell.mannlib.semservices.bo.Concept; +import edu.cornell.mannlib.semservices.exceptions.ConceptsNotFoundException; +import edu.cornell.mannlib.semservices.service.ExternalConceptService; +import edu.cornell.mannlib.semservices.util.XMLUtils; + +public class LCSHService implements ExternalConceptService { + + protected final Log log = LogFactory.getLog(getClass()); + private final String skosSuffix = ".skos.rdf"; + private final String hostUri = "http://id.loc.gov"; + private java.lang.String LCSHWS_address = hostUri + "/authorities/subjects"; + private final String schemeUri = hostUri + "/authorities/subjects"; + private final String baseUri = hostUri + "/search/"; + private final String ontologyName = "LCSH"; + private final String format = "SKOS"; + private final String lang = "en"; + private final String codeName = "hasCodeAgrovoc"; + private final String searchMode = "Exact Match"; + protected final String dbpedia_endpoint = " http://dbpedia.org/sparql"; + //Property uris used for SKOS + protected final String SKOSNotePropertyURI = "http://www.w3.org/2004/02/skos/core#note"; + protected final String SKOSPrefLabelURI = "http://www.w3.org/2004/02/skos/core#prefLabel"; + protected final String SKOSAltLabelURI = "http://www.w3.org/2008/05/skos-xl#altLabel"; + protected final String SKOSBroaderURI = "http://www.w3.org/2004/02/skos/core#broader"; + protected final String SKOSNarrowerURI = "http://www.w3.org/2004/02/skos/core#narrower"; + protected final String SKOSExactMatchURI = "http://www.w3.org/2004/02/skos/core#exactMatch"; + protected final String SKOSCloseMatchURI = "http://www.w3.org/2004/02/skos/core#closeMatch"; + + @Override + public List getConcepts(String term) throws Exception { + List conceptList = new ArrayList(); + String results = null; + String dataUrl = baseUri + "?q=" + URLEncoder.encode(term, "UTF-8") + + "&q=cs%3Ahttp%3A%2F%2Fid.loc.gov%2Fauthorities%2Fsubjects" + + "&format=XML"; + log.debug("dataURL " + dataUrl); + + try { + + StringWriter sw = new StringWriter(); + URL rss = new URL(dataUrl); + + BufferedReader in = new BufferedReader(new InputStreamReader( + rss.openStream())); + String inputLine; + while ((inputLine = in.readLine()) != null) { + sw.write(inputLine); + } + in.close(); + + results = sw.toString(); + log.debug(results); + } catch (Exception ex) { + log.error("error occurred in servlet", ex); + return null; + } + + if (StringUtils.isEmpty(results)) { + return conceptList; + } + + conceptList = processOutput(results); + + return conceptList; + } + + // Results are in json format (atom) - atom entries need to be extracted + // retrieve the URIs and get the SKOS version of the entry, getting broader + // and narrower terms as applicable as well as any description (skos:note) + // that might exist + private List processOutput(String results) throws Exception { + List conceptList = new ArrayList(); + SKOSManager manager = new SKOSManager(); + // Get uris from the results + // Properties we will be querying for + SKOSDataFactory sdf = manager.getSKOSDataFactory(); + + + List uris = getConceptURIFromXML(results); + String bestMatch = "true"; + int i = 0; + for (String uri : uris) { + if(i > 0) { + bestMatch = "false"; + } + log.debug("-" + uri + "-"); + String conceptUriString = getSKOSURI(uri); + URI conceptURI = null; + try { + conceptURI = new URI(conceptUriString); + } catch (URISyntaxException e) { + log.error("URI syntax exception in trying to get concept uri " + conceptUriString, e); + return conceptList; + } + log.debug("loading concept uri " + conceptUriString); + SKOSDataset dataset = manager.loadDataset(conceptURI); + Set skosConcepts = dataset.getSKOSConcepts(); + log.debug("Number of skos concepts " + skosConcepts.size()); + + for (SKOSConcept skosConcept : skosConcepts) { + + Concept c = this.createConcept(sdf, bestMatch, skosConcept, dataset); + if(c != null) { + conceptList.add(c); + } + + } + i++; + + } + return conceptList; + } + + + + //Will use skos if does not encounter error from skos api, otherwise will use regular XML parsing techniques + public Concept createConcept(SKOSDataFactory skosDataFactory, String bestMatch, SKOSConcept skosConcept, SKOSDataset dataset) { + + Concept concept = new Concept(); + String skosConceptURI = skosConcept.getURI().toString(); + log.debug("SKOSConceptURI is " + skosConceptURI); + // get skos version of uri + + concept.setUri(skosConceptURI); + concept.setConceptId(stripConceptId(skosConceptURI)); + concept.setBestMatch(bestMatch); + concept.setDefinedBy(schemeUri); + concept.setSchemeURI(schemeUri); + concept.setType(""); + + //Get the skos annotations first to see if there is an error triggered, if so try and see if we can instead utilize XML + //For some of the SKOS concepts, a null pointer exception occurs while XML processing still works + //I do not yet know the reasons, hjk54 + try { + Set skosAnnots = skosConcept + .getSKOSAnnotations(dataset); + } catch(NullPointerException ex) { + concept = createConceptUsingXML(concept, bestMatch, skosConcept); + return concept; + } catch(Exception ex) { + log.debug("Error occurred for annotation retrieval for skos concept " + skosConceptURI, ex); + return null; + } + + concept = this.createConceptUsingSKOS(skosDataFactory, concept, skosConcept, dataset); + return concept; + } + + private Concept createConceptUsingSKOS(SKOSDataFactory skosDataFactory, Concept concept, SKOSConcept skosConcept, SKOSDataset dataset) { + + SKOSPrefLabelProperty prefLabelProperty = skosDataFactory.getSKOSPrefLabelProperty(); + SKOSAltLabelProperty altLabelProperty = skosDataFactory.getSKOSAltLabelProperty(); + + try { + List labelLiterals = this.getSKOSLiteralValues(skosConcept + .getSKOSRelatedConstantByProperty(dataset, + prefLabelProperty)); + if(labelLiterals.size() > 0) { + concept.setLabel(labelLiterals.get(0)); + } else { + //This is an error because there should be at least one label returned + log.debug("The number of preferred labels is not greater than zero"); + } + + // get altLabels + List altLabelList = this.getSKOSLiteralValues(skosConcept + .getSKOSRelatedConstantByProperty(dataset, altLabelProperty)); + concept.setAltLabelList(altLabelList); + + // See if we can get a description as well + List notes = this.getSKOSAnnotationValues(skosConcept + .getSKOSAnnotationsByURI(dataset, new URI(this.SKOSNotePropertyURI))); + + concept.setDefinition(StringUtils.join(notes, ",")); + + // get the broader property URI + List broaderURIList = this.getSKOSAnnotationValues(skosConcept + .getSKOSAnnotationsByURI(dataset, new URI(this.SKOSBroaderURI))); + concept.setBroaderURIList(broaderURIList); + + // get the narrower property URI + List narrowerURIList = this.getSKOSAnnotationValues(skosConcept + .getSKOSAnnotationsByURI(dataset, new URI(this.SKOSNarrowerURI))); + concept.setNarrowerURIList(narrowerURIList); + + // exact match + List exactMatchURIList = this.getSKOSAnnotationValues(skosConcept + .getSKOSAnnotationsByURI(dataset, + new URI(this.SKOSExactMatchURI))); + concept.setExactMatchURIList(exactMatchURIList); + + // close match + List closeMatchURIList = this.getSKOSAnnotationValues(skosConcept + .getSKOSAnnotationsByURI(dataset, + new URI(this.SKOSCloseMatchURI))); + concept.setCloseMatchURIList(closeMatchURIList); + log.debug("add concept to list"); + } catch (Exception ex) { + log.debug("Exception occurred for -" + skosConcept.getURI() + + "- " + ex.getMessage(), ex); + return null; + } + return concept; + } + + + private List getSKOSLiteralValues(Set skosLiterals) { + String lang = ""; + List literalValues = new ArrayList(); + for (SKOSLiteral literal : skosLiterals) { + if (!literal.isTyped()) { + // if it has language + SKOSUntypedLiteral untypedLiteral = literal + .getAsSKOSUntypedLiteral(); + if (untypedLiteral.hasLang()) { + lang = untypedLiteral.getLang(); + } else { + lang = ""; + } + } + // log.debug("literal: "+ literal.getLiteral()); + if (lang.equals("en")) { + log.debug("literal value: " + literal.getLiteral()); + literalValues.add(literal.getLiteral()); + } + } + return literalValues; + } + + //For a given set of annotations (for example, for a specific property) + private List getSKOSAnnotationValues(Set skosAnnotations) { + List valuesList = new ArrayList(); + for (SKOSAnnotation annotation : skosAnnotations) { + String value = this.getSKOSAnnotationStringValue(annotation); + valuesList.add(value); + } + return valuesList; + } + + //Get string value for annotation + private String getSKOSAnnotationStringValue(SKOSAnnotation annotation) { + String value = new String(); + if (annotation.isAnnotationByConstant()) { + SKOSLiteral literal = annotation + .getAnnotationValueAsConstant(); + value = literal.getLiteral(); + log.debug("broder uri: " + value); + } else { + // annotation is some resource + SKOSEntity entity = annotation.getAnnotationValue(); + value = entity.getURI().toString(); + } + return value; + } + + //this method relies on the XML of the single SKOS rdf concept in case the SKOS api throws a null pointer exception + private Concept createConceptUsingXML(Concept concept, String bestMatch, + SKOSConcept skosConcept) { + String conceptUriString = skosConcept.getURI().toString() + this.skosSuffix;; + + URL conceptURL = null; + try { + conceptURL = new URL(conceptUriString); + } catch (Exception e) { + log.error("Exception occurred in instantiating URL for " + conceptUriString, e); + //If the url is having trouble, just return null for the concept + return null; + } + log.debug("loading concept uri " + conceptUriString); + + + String results = null; + try { + + StringWriter sw = new StringWriter(); + + + BufferedReader in = new BufferedReader(new InputStreamReader( + conceptURL.openStream())); + String inputLine; + while ((inputLine = in.readLine()) != null) { + sw.write(inputLine); + } + in.close(); + + results = sw.toString(); + log.debug(results); + } catch (Exception ex) { + log.error("Error occurred in getting concept from the URL " + conceptUriString, ex); + return null; + } + + + try { + Document doc = XMLUtils.parse(results); + List labelLiterals = this.getValuesFromXMLNodes(doc, "skos:prefLabel", null); + if(labelLiterals.size() > 0) { + concept.setLabel(labelLiterals.get(0)); + } else { + //This is an error because there should be at least one label returned + log.debug("The number of preferred labels is not greater than zero"); + } + List altLabelList = this.getValuesFromXMLNodes(doc, "skos:altLabel", null); + concept.setAltLabelList(altLabelList); + + List broaderURIList = this.getValuesFromXMLNodes(doc, "skos:broader", "rdf:resource"); + concept.setBroaderURIList(broaderURIList); + List narrowerURIList = this.getValuesFromXMLNodes(doc, "skos:narrower", "rdf:resource"); + concept.setNarrowerURIList(narrowerURIList); + + List exactMatchURIList = this.getValuesFromXMLNodes(doc, "skos:exactMatch", "rdf:resource"); + concept.setExactMatchURIList(exactMatchURIList); + List closeMatchURIList = this.getValuesFromXMLNodes(doc, "skos:closeMatch", "rdf:resource"); + concept.setCloseMatchURIList(closeMatchURIList); + + + } catch (IOException e) { + log.error("error occurred in parsing " + results, e); + } catch (SAXException e) { + log.error("error occurred in parsing " + results, e); + } catch (ParserConfigurationException e) { + log.error("error occurred in parsing " + results, e); + } + + return concept; + + } + + private String getSKOSURI(String uri) { + // Strip .xml at the end and replace with .skos.rdf + String skosURI = uri; + if (uri.endsWith(".xml")) { + skosURI = uri.substring(0, uri.length() - 4); + skosURI += skosSuffix; + } + return hostUri + skosURI; + } + + public List getConceptURISFromJSON(String results) { + List uris = new ArrayList(); + try { + JSONObject json = (JSONObject) JSONSerializer.toJSON(results); + log.debug(json.toString()); + // Get atom entry elements + + } catch (Exception ex) { + log.error("Could not get concepts", ex); + throw ex; + } + return uris; + + } + + protected List getConceptURIFromXML(String rdf) { + List uris = new ArrayList(); + String conceptUri = new String(); + try { + Document doc = XMLUtils.parse(rdf); + NodeList nodes = doc.getElementsByTagName("search:result"); + int len = nodes.getLength(); + int i; + for (i = 0; i < len; i++) { + Node node = nodes.item(i); + NamedNodeMap attrs = node.getAttributes(); + Attr idAttr = (Attr) attrs.getNamedItem("uri"); + conceptUri = idAttr.getTextContent(); + log.debug("concept uri is " + conceptUri); + uris.add(conceptUri); + } + + } catch (IOException e) { + log.error("error occurred in parsing " +rdf, e); + } catch (SAXException e) { + log.error("error occurred in parsing " +rdf, e); + } catch (ParserConfigurationException e) { + log.error("error occurred in parsing " +rdf, e); + + } + return uris; + + } + + public List processResults(String term) throws Exception { + return getConcepts(term); + } + + /** + * @param uri + * @return + */ + protected String stripConceptId(String uri) { + String conceptId = new String(); + int lastslash = uri.lastIndexOf('/'); + conceptId = uri.substring(lastslash + 1, uri.length()); + return conceptId; + } + + /** + * @param str + * @return + */ + protected String extractConceptId(String str) { + try { + return str.substring(1, str.length() - 1); + } catch (Exception ex) { + log.error("Exception occurred in extracting concept id for " + str, ex); + return ""; + } + } + + @Override + public List getConceptsByURIWithSparql(String uri) + throws Exception { + // TODO Auto-generated method stub + return null; + } + + + + + public List getValuesFromXMLNodes(Document doc, String tagName, String attributeName) { + NodeList nodes = doc.getElementsByTagName(tagName); + + return getValuesFromXML(nodes, attributeName); + } + + //Returns list of values based on nodes and whether or not a specific attribute name should be used or just the text content + public List getValuesFromXML(NodeList nodes, String attributeName) { + int len = nodes.getLength(); + int i; + List values = new ArrayList(); + for (i = 0; i < len; i++) { + Node node = nodes.item(i); + if(attributeName != null && !attributeName.isEmpty()) { + NamedNodeMap attrs = node.getAttributes(); + Attr a = (Attr)attrs.getNamedItem(attributeName); + if(a != null) { + values.add(a.getTextContent()); + } + } else { + values.add(node.getTextContent()); + } + } + return values; + } + + + +} diff --git a/src/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/generators/AddAssociatedConceptGenerator.java b/src/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/generators/AddAssociatedConceptGenerator.java index 23f4e1bf..98d38df6 100644 --- a/src/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/generators/AddAssociatedConceptGenerator.java +++ b/src/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/generators/AddAssociatedConceptGenerator.java @@ -59,6 +59,8 @@ public class AddAssociatedConceptGenerator extends VivoBaseGenerator implements //TODO: Set this to a dynamic mechanism private static String VIVOCore = "http://vivoweb.org/ontology/core#"; private static String SKOSConceptType = "http://www.w3.org/2004/02/skos/core#Concept"; + private static String SKOSBroaderURI = "http://www.w3.org/2004/02/skos/core#broader"; + private static String SKOSNarrowerURI = "http://www.w3.org/2004/02/skos/core#narrower"; @Override public EditConfigurationVTwo getEditConfiguration(VitroRequest vreq, HttpSession session) { EditConfigurationVTwo editConfiguration = new EditConfigurationVTwo(); @@ -97,7 +99,8 @@ public class AddAssociatedConceptGenerator extends VivoBaseGenerator implements // Add preprocessors addPreprocessors(editConfiguration, ModelAccess.on(vreq).getJenaOntModel(), - ModelAccess.on(vreq).getOntModelSelector().getTBoxModel()); + ModelAccess.on(vreq).getOntModelSelector().getTBoxModel(), + vreq.getWebappDaoFactory()); // Adding additional data, specifically edit mode addFormSpecificData(editConfiguration, vreq); // One override for basic functionality, changing url pattern @@ -189,7 +192,11 @@ public class AddAssociatedConceptGenerator extends VivoBaseGenerator implements "?conceptNode <" + RDFS.isDefinedBy.getURI() + "> ?conceptSource .", "?conceptNode <" + RDF.type + "> ?conceptSemanticTypeURI ." + "?conceptSemanticTypeURI <" + RDFS.label.getURI() + "> ?conceptSemanticTypeLabel ." + - "?conceptSemanticTypeURI <" + RDFS.subClassOf + "> <" + SKOSConceptType + "> ." + "?conceptSemanticTypeURI <" + RDFS.subClassOf + "> <" + SKOSConceptType + "> .", + "?conceptNode <" + this.SKOSNarrowerURI + "> ?conceptNarrowerURI ." + + "?conceptNarrowerURI <" + this.SKOSBroaderURI + "> ?conceptNode .", + "?conceptNode <" + this.SKOSBroaderURI + "> ?conceptBroaderURI ." + + "?conceptBroaderURI <" + this.SKOSNarrowerURI + "> ?conceptNode ." ); } @@ -254,6 +261,8 @@ public class AddAssociatedConceptGenerator extends VivoBaseGenerator implements urisOnForm.add("conceptNode"); urisOnForm.add("conceptSource"); urisOnForm.add("conceptSemanticTypeURI"); + urisOnForm.add("conceptBroaderURI"); + urisOnForm.add("conceptNarrowerURI"); editConfiguration.setUrisOnform(urisOnForm); //Also need to add the label of the concept literalsOnForm.add("conceptLabel"); @@ -288,8 +297,23 @@ public class AddAssociatedConceptGenerator extends VivoBaseGenerator implements setVocabURIField(editConfiguration, vreq); setConceptSemanticTypeURIField(editConfiguration,vreq); setConceptSemanticTypeLabelField(editConfiguration,vreq); + setConceptBroaderURIField(editConfiguration, vreq); + setConceptNarrowerURIField(editConfiguration, vreq); } + private void setConceptNarrowerURIField( + EditConfigurationVTwo editConfiguration, VitroRequest vreq) { + editConfiguration.addField(new FieldVTwo(). + setName("conceptNarrowerURI")); + } + + private void setConceptBroaderURIField( + EditConfigurationVTwo editConfiguration, VitroRequest vreq) { + editConfiguration.addField(new FieldVTwo(). + setName("conceptBroaderURI")); + + } + //this field will be hidden and include the concept node URI private void setConceptNodeField(EditConfigurationVTwo editConfiguration, VitroRequest vreq) { @@ -335,14 +359,17 @@ public class AddAssociatedConceptGenerator extends VivoBaseGenerator implements //Add preprocessor - private void addPreprocessors(EditConfigurationVTwo editConfiguration, OntModel ontModel, OntModel modelChangeModel) { + private void addPreprocessors(EditConfigurationVTwo editConfiguration, + OntModel ontModel, + OntModel modelChangeModel, + WebappDaoFactory wdf) { //An Edit submission preprocessor for enabling addition of multiple terms for a single search //TODO: Check if this is the appropriate way of getting model //Passing model to check for any URIs that are present editConfiguration.addEditSubmissionPreprocessor( - new AddAssociatedConceptsPreprocessor(editConfiguration, ontModel)); + new AddAssociatedConceptsPreprocessor(editConfiguration, ontModel, wdf)); editConfiguration.addModelChangePreprocessor(new ConceptSemanticTypesPreprocessor( modelChangeModel)); diff --git a/src/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/preprocessors/AddAssociatedConceptsPreprocessor.java b/src/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/preprocessors/AddAssociatedConceptsPreprocessor.java index 1b81dbf1..87deceb4 100644 --- a/src/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/preprocessors/AddAssociatedConceptsPreprocessor.java +++ b/src/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/preprocessors/AddAssociatedConceptsPreprocessor.java @@ -9,6 +9,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; +import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -27,6 +28,8 @@ import com.hp.hpl.jena.vocabulary.RDFS; import com.hp.hpl.jena.vocabulary.XSD; +import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest; +import edu.cornell.mannlib.vitro.webapp.dao.WebappDaoFactory; import edu.cornell.mannlib.vitro.webapp.edit.n3editing.VTwo.BaseEditSubmissionPreprocessorVTwo; import edu.cornell.mannlib.vitro.webapp.edit.n3editing.VTwo.EditConfigurationUtils; import edu.cornell.mannlib.vitro.webapp.edit.n3editing.VTwo.EditConfigurationVTwo; @@ -38,8 +41,8 @@ public class AddAssociatedConceptsPreprocessor extends protected static final Log log = LogFactory .getLog(AddAssociatedConceptsPreprocessor.class.getName()); - //TODO: Check if better way to do this? protected OntModel ontModel = null; + protected WebappDaoFactory wdf = null; // Field names/variables names for n3 - these will have numbers added as // suffix if more than one term private static String conceptNodeBase = "conceptNode"; @@ -47,6 +50,9 @@ public class AddAssociatedConceptsPreprocessor extends private static String labelBase = "conceptLabel"; private static String conceptSemanticTypeLabelBase = "conceptSemanticTypeLabel"; private static String conceptSemanticTypeURIBase = "conceptSemanticTypeURI"; + private static String conceptBroaderURIBase = "conceptBroaderURI"; + private static String conceptNarrowerURIBase = "conceptNarrowerURI"; + //keyed off label variable, specifies which uri variable should be used, useful if same label repeated twice private HashMap labelVarToUriVarHash = null; private HashMap> conceptSemanticTypeURIVarToValueMap = null; @@ -56,15 +62,21 @@ public class AddAssociatedConceptsPreprocessor extends private static String conceptSourceValues = null; private static String conceptSemanticTypeLabelValues = null; private static String conceptSemanticTypeURIValues = null; + private static List conceptBroaderURIValues = null; + private static List conceptNarrowerURIValues = null; private static MultiValueEditSubmission submission = null; + + private static String SKOSBroaderURI = "http://www.w3.org/2004/02/skos/core#broader"; + private static String SKOSNarrowerURI = "http://www.w3.org/2004/02/skos/core#narrower"; // String datatype // Will be editing the edit configuration as well as edit submission here - public AddAssociatedConceptsPreprocessor(EditConfigurationVTwo editConfig, OntModel ontModel) { + public AddAssociatedConceptsPreprocessor(EditConfigurationVTwo editConfig, OntModel ontModel, WebappDaoFactory wadf) { super(editConfig); this.ontModel = ontModel; + this.wdf = wadf; this.labelVarToUriVarHash = new HashMap(); //Saves values of concept type uris this.conceptSemanticTypeURIVarToValueMap = new HashMap>(); @@ -86,6 +98,7 @@ public class AddAssociatedConceptsPreprocessor extends processConceptSemanticValues(); //Also need to see if any broader or narrower uris for the concepts that already exist in the system //and set up the appropriate relationships between this concept and the broader/narrower uri + getExistingConceptRelationships(); if (numberConcepts > 1) { processConceptNodes(numberConcepts); } @@ -96,6 +109,8 @@ public class AddAssociatedConceptsPreprocessor extends } + + //Since we will change the uris and literals from form, we should make copies //of the original values and store them, this will also make iterations //and updates to the submission independent from accessing the values @@ -103,11 +118,104 @@ public class AddAssociatedConceptsPreprocessor extends conceptLabelValues = getConceptLabelValues(); conceptNodeValues = getConceptNodeValues(); conceptSourceValues = getConceptSourceValues(); + conceptBroaderURIValues = getConceptBroaderURIValues(); + conceptNarrowerURIValues = getConceptNarrowerURIValues(); log.debug("concept label values are " + conceptLabelValues); } - // + + //For broader and narrower relationships, we will be + //linking the concept to broader and narrower terms where those terms already + //exist in the system + //This method or approach may change later in which case this method should change + private void getExistingConceptRelationships() { + List existingNarrowerURIs = getExistingNarrowerURIs(conceptNarrowerURIValues); + List existingBroaderURIs = getExistingBroaderURIs(conceptBroaderURIValues); + //Now set the submission values to these, overwriting the original + Map> urisFromForm = submission.getUrisFromForm(); + if(existingNarrowerURIs.size() > 0) { + urisFromForm.put("conceptNarrowerURI", existingNarrowerURIs); + } else { + //The original code for submission wouldn't put in a key if the values were null or size 0 + urisFromForm.remove("conceptNarrowerURI"); + } + if(existingBroaderURIs.size() > 0) { + urisFromForm.put("conceptBroaderURI", existingBroaderURIs); + } else { + urisFromForm.remove("conceptBroaderURI"); + } + } + + //get the broader and narrower uri values that already exist in the system from the ones returned in the search + //and use those to populate relationships between the concept and other concepts already in the system + //We should also make sure to use bidirectional n3 so the graph has both sets of relationships represented + private List getConceptNarrowerURIValues() { + Map> urisFromForm = submission.getUrisFromForm(); + List narrowerURIs = urisFromForm.get("conceptNarrowerURI"); + return narrowerURIs; + } + + private List getConceptBroaderURIValues() { + Map> urisFromForm = submission.getUrisFromForm(); + List broaderURIs = urisFromForm.get("conceptBroaderURI"); + return broaderURIs; + } + + private List getExistingBroaderURIs(List broaderURIs) { + if(broaderURIs == null) { + return new ArrayList(); + } + List existingBroaderURIs = this.getExistingURIs(broaderURIs); + return existingBroaderURIs; + } + + private List getExistingNarrowerURIs(List narrowerURIs) { + if(narrowerURIs == null) + return new ArrayList(); + List existingNarrowerURIs = this.getExistingURIs(narrowerURIs); + return existingNarrowerURIs; + } + + //We need to keep the number of elements the same if there are any entries at all in the original + //So we will use an empty string or null + private List getExistingURIs(List uris) { + //Important to keep the same formatting as original, because a comma delimited string as an element in the array + //refers to a list of uris appropriate for a given concept, where each element in the array corresponds to a different + //concept + List existingURIs = new ArrayList(); + for(String uri:uris) { + if(uri.indexOf(",") != -1) { + List existingURISet = new ArrayList(); + String[] uriSet = uri.split(","); + for(String u: uriSet) { + if(u != null && !u.isEmpty() && this.wdf.hasExistingURI(u)) { + existingURISet.add(u); + } + } + //Now add the comma delimited version back to the array + if(existingURISet.size() > 0) { + existingURIs.add(StringUtils.join(existingURISet, ",")); + } else { + //add empty string to indicate no value here + existingURIs.add(""); + } + } else { + if(uri != null && !uri.isEmpty() && this.wdf.hasExistingURI(uri)) { + existingURIs.add(uri); + } + else + { + existingURIs.add(""); + } + + } + } + return existingURIs; + } + + + //Process the semantic type label and URI values for the concepts private void processConceptSemanticValues() { conceptSemanticTypeLabelValues = getConceptSemanticTypeLabelValues(); conceptSemanticTypeURIValues = getConceptSemanticTypeURIValues(); @@ -183,10 +291,10 @@ public class AddAssociatedConceptsPreprocessor extends addConceptSourceInputs(numberConcepts); addConceptLabelInputs(numberConcepts); //for concept semantic type labels and uris where they exist - //TODO: Make into single method as URIs depend on labels addConceptSemanticTypeLabelAndURIInputs(numberConcepts); - //addConceptSemanticTypeURIInputs(numberConcepts); - + //For broader and narrower uris where they exist (this of course is in the case of multiple broader and narrower uris + addConceptBroaderURIInputs(numberConcepts); + addConceptNarrowerURIInputs(numberConcepts); } private void addConceptNodeInputs(int numberConcepts) { @@ -301,30 +409,53 @@ public class AddAssociatedConceptsPreprocessor extends String[] uriValuesArray = uriVals.toArray(new String[uriVals.size()]); submission.addUriToForm(editConfiguration, uriInputName, uriValuesArray); } - - - //the number of existing values may not match up, or at least existing populated ones - /* - if(conceptSemanticTypeURIs != null && conceptSemanticTypeURIs.length == numberConcepts) { - int i; - for(i = 0; i < numberConcepts; i++) { - int suffix = i + 1; - String conceptInputName = conceptSemanticTypeURIBase + suffix; - String[] uriValues = new String[1]; - uriValues[0] = conceptSemanticTypeURIs[i]; - //Add value for uri to form - //TODO: Check if value is empty in which case don't add to submission - submission.addUriToForm(editConfiguration, conceptInputName, uriValues); - } - } else if(conceptSemanticTypeURIs != null && conceptSemanticTypeURIs.length != numberConcepts){ - log.error("Number of concept nodes did not match the number of concepts to be added"); - } else{ - log.error("Concept nodes returned were null"); - } - - */ } + private void addConceptBroaderURIInputs(int numberConcepts) { + int i; + //Add inputs based on if there are any broader uris to add + //Can't really compare number of existing broader uris to concepts + //as each concept may or may not have a broader uri + if(this.conceptBroaderURIValues.size() > 0 && this.conceptBroaderURIValues.size() <= numberConcepts) { + for(i = 0; i < numberConcepts; i++) { + int suffix = i + 1; + String conceptBroaderURIInputName = conceptBroaderURIBase + suffix; + String broaderURIs = this.conceptBroaderURIValues.get(i); + if(broaderURIs != null && !broaderURIs.isEmpty()) { + String[] broaderURISet = new String[1]; + if(broaderURIs.indexOf(",") != -1) { + broaderURISet = broaderURIs.split(","); + } else { + broaderURISet[0] = broaderURIs; + } + //Add value for uri to form + submission.addUriToForm(editConfiguration, conceptBroaderURIInputName, broaderURISet); + } + } + } + + } + private void addConceptNarrowerURIInputs(int numberConcepts) { + int i; + if(this.conceptNarrowerURIValues.size() > 0 && this.conceptNarrowerURIValues.size() <= numberConcepts) { + for(i = 0; i < numberConcepts; i++) { + int suffix = i + 1; + String conceptNarrowerURIInputName = conceptNarrowerURIBase + suffix; + String narrowerURIs = this.conceptNarrowerURIValues.get(i); + if(narrowerURIs != null && !narrowerURIs.isEmpty()) { + String[] narrowerURISet = new String[1]; + if(narrowerURIs.indexOf(",") != -1) { + narrowerURISet = narrowerURIs.split(","); + } else { + narrowerURISet[0] = narrowerURIs; + } + //Add value for uri to form + submission.addUriToForm(editConfiguration, conceptNarrowerURIInputName, narrowerURISet); + } + } + } + } + //Fields private void addFields(int numberConcepts) { @@ -340,7 +471,8 @@ public class AddAssociatedConceptsPreprocessor extends String source = sourceBase + suffix; String conceptSemanticTypeLabel = conceptSemanticTypeLabelBase + suffix; String conceptSemanticTypeURI = this.getConceptSemanticTypeURIFieldName(conceptSemanticTypeLabel, suffix); - + String conceptBroaderURI = conceptBroaderURIBase + suffix; + String conceptNarrowerURI = conceptNarrowerURIBase + suffix; addConceptNodeField(conceptNode); addLabelField(label); addSourceField(source); @@ -351,9 +483,15 @@ public class AddAssociatedConceptsPreprocessor extends conceptSemanticTypeUris.add(conceptSemanticTypeURI); addConceptSemanticTypeURIField(conceptSemanticTypeURI); } + + //add fields for concept broader and narrower uris + addConceptBroaderURIField(conceptBroaderURI); + addConceptNarrowerURIField(conceptNarrowerURI); } } + + private void addConceptNodeField(String conceptNode) { List validators = new ArrayList(); validators.add("nonempty"); @@ -394,7 +532,18 @@ public class AddAssociatedConceptsPreprocessor extends } } + private void addConceptNarrowerURIField(String conceptNarrowerURI) { + editConfiguration.addField(new FieldVTwo(). + setName(conceptNarrowerURI)); + + } + private void addConceptBroaderURIField(String conceptBroaderURI) { + editConfiguration.addField(new FieldVTwo(). + setName(conceptBroaderURI)); + + } + //original literals on form: label, uris on form: conceptNode and conceptSource //This will overwrite the original values in the edit configuration private void addLiteralsAndUrisOnForm(int numberTerms) { @@ -412,12 +561,16 @@ public class AddAssociatedConceptsPreprocessor extends String conceptSemanticTypeLabel = conceptSemanticTypeLabelBase + suffix; //String conceptSemanticTypeURI = conceptSemanticTypeURIBase + suffix; String conceptSemanticTypeURI = this.getConceptSemanticTypeURIFieldName(conceptSemanticTypeLabel, suffix); + String conceptBroaderURI = conceptBroaderURIBase + suffix; + String conceptNarrowerURI = conceptNarrowerURIBase + suffix; urisOnForm.add(conceptNode); urisOnForm.add(source); if(!conceptSemanticTypeURIs.contains(conceptSemanticTypeURI)) { conceptSemanticTypeURIs.add(conceptSemanticTypeURI); urisOnForm.add(conceptSemanticTypeURI); } + urisOnForm.add(conceptBroaderURI); + urisOnForm.add(conceptNarrowerURI); literalsOnForm.add(label); literalsOnForm.add(conceptSemanticTypeLabel); } @@ -456,10 +609,13 @@ public class AddAssociatedConceptsPreprocessor extends String labelVar = "?" + labelBase; String sourceVar = "?" + sourceBase; String conceptSemanticTypeLabelVar = "?" + conceptSemanticTypeLabelBase; + String conceptBroaderURIVar = "?" + conceptBroaderURIBase; + String conceptNarrowerURIVar = "?" + conceptNarrowerURIBase; String prefixStr = "@prefix core: ."; // First one already included so add new ones here //We already have a label var to uri var setup for (index = 1; index <= numberConcepts; index++) { + //Set up the variables based on which concept node int suffix = index; String node = nodeBase + suffix; String label = labelVar + suffix; @@ -467,7 +623,9 @@ public class AddAssociatedConceptsPreprocessor extends String conceptSemanticTypeLabel = conceptSemanticTypeLabelVar + suffix; //get the URI appropriate for the concept semantic type label var String conceptSemanticTypeURI = getConceptSemanticTypeURIVar(conceptSemanticTypeLabelBase + suffix, suffix); - //onceptSemanticTypeURIVar + suffix; + String conceptBroaderURI = conceptBroaderURIVar + suffix; + String conceptNarrowerURI = conceptNarrowerURIVar + suffix; + //Set up the n3 strings String n3String = prefixStr; n3String += node + " <" + RDFS.label.getURI() + "> " + label + " .\n" + node + " <" + RDFS.isDefinedBy.getURI() + "> " + source + " ."; @@ -475,10 +633,17 @@ public class AddAssociatedConceptsPreprocessor extends n3ConceptTypeString += node + " <" + RDF.type.getURI() + "> " + conceptSemanticTypeURI + " ." + conceptSemanticTypeURI + " <" + RDFS.label.getURI() + "> " + conceptSemanticTypeLabel + " .\n" + conceptSemanticTypeURI + " <" + RDFS.subClassOf.getURI() + "> .\n" ; + //String representing the broader and narrower uri(s) for each of the concepts - these may or may not exist + String n3ConceptBroaderURI = prefixStr + node + " <" + this.SKOSNarrowerURI + "> " + conceptNarrowerURI + " ." + + conceptNarrowerURI + " <" + this.SKOSBroaderURI + "> " + node + " ."; + String n3ConceptNarrowerURI = prefixStr + node + " <" + this.SKOSBroaderURI + "> " + conceptBroaderURI + " ." + + conceptBroaderURI + " <" + this.SKOSNarrowerURI + "> " + node + " ."; n3Optional.add(n3String); //adding separately so their resolution does not depend on each other n3Optional.add(n3ConceptTypeString); + n3Optional.add(n3ConceptBroaderURI); + n3Optional.add(n3ConceptNarrowerURI); } //Already have n3 required so need to add to that @@ -536,86 +701,93 @@ public class AddAssociatedConceptsPreprocessor extends private String getConceptSemanticTypeLabelValues() { Map> literalsFromForm = submission.getLiteralsFromForm(); Map> transformed = EditConfigurationUtils.transformLiteralMap(literalsFromForm); - return (String) getFirstElement(transformed.get("conceptSemanticTypeLabel")); + String label = (String) getFirstElement(transformed.get("conceptSemanticTypeLabel")); + if(label == null) { + label = ""; + } + + return label; } //This will either generate or retrieve URIs for the concept semantic type labels if they exist //We will then update the submission to include this private String getConceptSemanticTypeURIValues() { - String[] conceptSemanticTypeLabels = convertDelimitedStringToArray(conceptSemanticTypeLabelValues); - //keep track of what label values already exist and to which label variables they map - HashMap> labelValueToVarSuffix = new HashMap>(); - int numberLabels = conceptSemanticTypeLabels.length; String pseudoInputString = ""; - - //The rest of this code is really only relevant for multiple values, so we could break out the old code above - //as we don't need to set up hashes etc. if there is only one concept node being added - if(numberLabels == 1) { - String label = conceptSemanticTypeLabels[0]; - String uri = getURIForSemanticTypeLabel(label); - if(uri != "") { - String[] urisToAdd = new String[1]; - urisToAdd[0] = uri; - pseudoInputString = uri; - log.debug("uris to add" + uri); - submission.addUriToForm(this.editConfiguration, "conceptSemanticTypeURI", urisToAdd); - } + if(conceptSemanticTypeLabelValues != null && !conceptSemanticTypeLabelValues.isEmpty()) { + String[] conceptSemanticTypeLabels = convertDelimitedStringToArray(conceptSemanticTypeLabelValues); + //keep track of what label values already exist and to which label variables they map + HashMap> labelValueToVarSuffix = new HashMap>(); + int numberLabels = conceptSemanticTypeLabels.length; - } - //if there is more than one concept node, we may have duplicate semantic types - //which will need to be referred to by the same semantic type uri - else if (numberLabels > 1){ - - for(int i = 0; i < numberLabels; i++) { - int suffix = i + 1; - String label = conceptSemanticTypeLabels[i]; - String labelVar = this.conceptSemanticTypeLabelBase + suffix; - //if label has not already been encountered, create entry for label value - //and list with the label variables that would refer to it - //for unique values, the uri variable will be the same as label - Integer thisSuffix = new Integer(suffix); - if(!labelValueToVarSuffix.containsKey(label)) { - labelValueToVarSuffix.put(label, new ArrayList()); - //Add suffix to list if not already there - labelValueToVarSuffix.get(label).add(thisSuffix); - } else { - //in this case, the label already exists, get the very first element in the list - //and use that as the uri variable - List suffixList = labelValueToVarSuffix.get(label); - if(suffixList != null && suffixList.size() > 0) { - thisSuffix = suffixList.get(0); - } - - } - - //Now add the uri var to the hash mapping label variable to uri variable - String uriVar = this.conceptSemanticTypeURIBase + thisSuffix.intValue(); - this.labelVarToUriVarHash.put(labelVar, uriVar); - - - //Make or retrieve URI for this label - //TODO: Do we create this string with empty inputs ? + //The rest of this code is really only relevant for multiple values, so we could break out the old code above + //as we don't need to set up hashes etc. if there is only one concept node being added + if(numberLabels == 1) { + String label = conceptSemanticTypeLabels[0]; String uri = getURIForSemanticTypeLabel(label); if(uri != "") { - //uri var shouldn't be repeated? - if(!this.conceptSemanticTypeURIVarToValueMap.containsKey(uriVar)) { - this.conceptSemanticTypeURIVarToValueMap.put(uriVar, new ArrayList()); - this.conceptSemanticTypeURIVarToValueMap.get(uriVar).add(uri); - } + String[] urisToAdd = new String[1]; + urisToAdd[0] = uri; + pseudoInputString = uri; + log.debug("uris to add" + uri); + submission.addUriToForm(this.editConfiguration, "conceptSemanticTypeURI", urisToAdd); } - if(i != 0) { - pseudoInputString += ","; - } - pseudoInputString += uri; - + } + //if there is more than one concept node, we may have duplicate semantic types + //which will need to be referred to by the same semantic type uri + else if (numberLabels > 1){ - //Add this string to the uris for the form - String[] urisToAdd = new String[1]; - urisToAdd[0] = pseudoInputString; - log.debug("uris to add" + pseudoInputString); - submission.addUriToForm(this.editConfiguration, "conceptSemanticTypeURI", urisToAdd); - + for(int i = 0; i < numberLabels; i++) { + int suffix = i + 1; + String label = conceptSemanticTypeLabels[i]; + String labelVar = this.conceptSemanticTypeLabelBase + suffix; + //if label has not already been encountered, create entry for label value + //and list with the label variables that would refer to it + //for unique values, the uri variable will be the same as label + Integer thisSuffix = new Integer(suffix); + if(!labelValueToVarSuffix.containsKey(label)) { + labelValueToVarSuffix.put(label, new ArrayList()); + //Add suffix to list if not already there + labelValueToVarSuffix.get(label).add(thisSuffix); + } else { + //in this case, the label already exists, get the very first element in the list + //and use that as the uri variable + List suffixList = labelValueToVarSuffix.get(label); + if(suffixList != null && suffixList.size() > 0) { + thisSuffix = suffixList.get(0); + } + + } + + //Now add the uri var to the hash mapping label variable to uri variable + String uriVar = this.conceptSemanticTypeURIBase + thisSuffix.intValue(); + this.labelVarToUriVarHash.put(labelVar, uriVar); + + + //Make or retrieve URI for this label + //TODO: Do we create this string with empty inputs ? + String uri = getURIForSemanticTypeLabel(label); + if(uri != "") { + //uri var shouldn't be repeated? + if(!this.conceptSemanticTypeURIVarToValueMap.containsKey(uriVar)) { + this.conceptSemanticTypeURIVarToValueMap.put(uriVar, new ArrayList()); + this.conceptSemanticTypeURIVarToValueMap.get(uriVar).add(uri); + } + } + if(i != 0) { + pseudoInputString += ","; + } + pseudoInputString += uri; + + } + + //Add this string to the uris for the form + String[] urisToAdd = new String[1]; + urisToAdd[0] = pseudoInputString; + log.debug("uris to add" + pseudoInputString); + submission.addUriToForm(this.editConfiguration, "conceptSemanticTypeURI", urisToAdd); + + } } return pseudoInputString; } diff --git a/src/edu/cornell/mannlib/vitro/webapp/utils/ConceptSearchService/ConceptSearchServiceUtils.java b/src/edu/cornell/mannlib/vitro/webapp/utils/ConceptSearchService/ConceptSearchServiceUtils.java index d232e875..68705bd3 100644 --- a/src/edu/cornell/mannlib/vitro/webapp/utils/ConceptSearchService/ConceptSearchServiceUtils.java +++ b/src/edu/cornell/mannlib/vitro/webapp/utils/ConceptSearchService/ConceptSearchServiceUtils.java @@ -23,6 +23,7 @@ public class ConceptSearchServiceUtils { private static final String UMLSVocabSource = "http://link.informatics.stonybrook.edu/umls"; private static final String AgrovocVocabSource = "http://aims.fao.org/aos/agrovoc/agrovocScheme"; private static final String GemetVocabSource = "http://www.eionet.europa.eu/gemet/gemetThesaurus"; + private static final String LCSHVocabSource = "http://id.loc.gov/authorities/subjects"; //Get the class that corresponds to the appropriate search public static String getConceptSearchServiceClassName(String searchServiceName) { @@ -41,6 +42,8 @@ public class ConceptSearchServiceUtils { //Commenting out agrovoc for now until implementation is updated map.put(AgrovocVocabSource, new VocabSourceDescription("AGROVOC", AgrovocVocabSource, "http://www.fao.org/agrovoc/", "Agricultural Vocabulary")); map.put(GemetVocabSource, new VocabSourceDescription("GEMET", GemetVocabSource, "http://www.eionet.europa.eu/gemet", "GEneral Multilingual Environmental Thesaurus")); + map.put(LCSHVocabSource, new VocabSourceDescription("LCSH", LCSHVocabSource, "http://id.loc.gov/authorities/subjects/", "Library of Congress Subject Headings")); + return map; } @@ -53,6 +56,8 @@ public class ConceptSearchServiceUtils { map.put(UMLSVocabSource, "edu.cornell.mannlib.semservices.service.impl.UMLSService"); map.put(AgrovocVocabSource, "edu.cornell.mannlib.semservices.service.impl.AgrovocService"); map.put(GemetVocabSource, "edu.cornell.mannlib.semservices.service.impl.GemetService"); + map.put(LCSHVocabSource, "edu.cornell.mannlib.semservices.service.impl.LCSHService"); + return map; } diff --git a/themes/wilma/i18n/all.properties b/themes/wilma/i18n/all.properties index a0f2585f..37d9ac61 100644 --- a/themes/wilma/i18n/all.properties +++ b/themes/wilma/i18n/all.properties @@ -692,6 +692,7 @@ or_add_new_one = or add a new one. vocabulary_service_unavailable = The vocabulary service is unavailable. Please try again later. no_serch_results_found = No search results were found. label_type = Label (Type) +label_altLabels = Label (Alternate Labels) definition_capitalized = Definition best_match = Best Match select_term_from_results = Please select at least one term from the search search results.