Merge branch 'develop' of https://github.com/vivo-project/VIVO into develop
This commit is contained in:
commit
8a73bd5ae9
37 changed files with 2054 additions and 10 deletions
|
@ -198,6 +198,31 @@ VitroConnection.DataSource.validationQuery = SELECT 1
|
|||
# languages.selectableLocales = en_US, es_GO
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# ORCID INTEGRATION
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# orcid.clientId = 0000-0000-0000-000X
|
||||
# orcid.clientPassword = 00000000-0000-0000-0000-000000000000
|
||||
# orcid.webappBaseUrl = http://localhost:8080/vivo
|
||||
# orcid.messageVersion = 1.0.23
|
||||
# orcid.externalIdCommonName = VIVO Cornell Identifier
|
||||
|
||||
# ---- Setup for the sandbox ----
|
||||
|
||||
# orcid.publicApiBaseUrl = http://pub.sandbox-1.orcid.org/v1.1
|
||||
# orcid.authorizedApiBaseUrl = http://api.sandbox-1.orcid.org/v1.1
|
||||
# orcid.oauthAuthorizeUrl = http://sandbox-1.orcid.org/oauth/authorize
|
||||
# orcid.oauthTokenUrl = http://api.sandbox-1.orcid.org/oauth/token
|
||||
|
||||
# ---- or for the mockorcid webapp ----
|
||||
|
||||
# orcid.publicApiBaseUrl = http://localhost:8080/mockorcid/mock/
|
||||
# orcid.authorizedApiBaseUrl = http://localhost:8080/mockorcid/mock/
|
||||
# orcid.oauthAuthorizeUrl = http://localhost:8080/mockorcid/mock/oauth/authorize
|
||||
# orcid.oauthTokenUrl = http://localhost:8080/mockorcid/mock/oauth/token
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# OTHER OPTIONS
|
||||
# -----------------------------------------------------------------------------
|
||||
|
|
|
@ -40,3 +40,6 @@ productMods/js/jquery_plugins/jquery.truncator.js
|
|||
|
||||
# Part of the OpenSocial integration - should this really be under the Apache license?
|
||||
themes/wilma/css/openSocial/gadgets.css
|
||||
|
||||
# JQuery for the Selenium test helper app.
|
||||
utilities/acceptance-tests/testApp/js/jquery.js
|
|
@ -191,3 +191,7 @@ xsdlib
|
|||
None
|
||||
----
|
||||
Trippi
|
||||
|
||||
ORCID API client
|
||||
----------------
|
||||
Written as part of the ORCID A&I grant. Source is available at https://github.com/j2blake/orcid-api-client
|
BIN
lib/orcid-api-client-0.1.jar
Normal file
BIN
lib/orcid-api-client-0.1.jar
Normal file
Binary file not shown.
|
@ -85,5 +85,7 @@ org.apache.commons.fileupload.servlet.FileCleanerCleanup
|
|||
# and the PermissionRegistry must already be set up.
|
||||
edu.cornell.mannlib.vitro.webapp.dao.jena.VClassGroupCache$Setup
|
||||
|
||||
edu.cornell.mannlib.vivo.orcid.OrcidContextSetup
|
||||
|
||||
# This should be near the end, because it will issue a warning if the connection to Solr times out.
|
||||
edu.cornell.mannlib.vitro.webapp.servlet.setup.SolrSmokeTest
|
||||
|
|
|
@ -1400,6 +1400,14 @@
|
|||
<url-pattern>/searchService/*</url-pattern>
|
||||
</servlet-mapping>
|
||||
|
||||
<servlet>
|
||||
<servlet-name>OrcidIntegrationController</servlet-name>
|
||||
<servlet-class>edu.cornell.mannlib.vivo.orcid.controller.OrcidIntegrationController</servlet-class>
|
||||
</servlet>
|
||||
<servlet-mapping>
|
||||
<servlet-name>OrcidIntegrationController</servlet-name>
|
||||
<url-pattern>/orcid/*</url-pattern>
|
||||
</servlet-mapping>
|
||||
|
||||
<!-- ==================== tag libraries ============================== -->
|
||||
<jsp-config>
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
PREFIX afn: <http://jena.hpl.hp.com/ARQ/function#>
|
||||
PREFIX foaf: <http://xmlns.com/foaf/0.1/>
|
||||
PREFIX vitro: <http://vitro.mannlib.cornell.edu/ns/vitro/0.7#>
|
||||
PREFIX vcard: <http://www.w3.org/2006/vcard/ns#>
|
||||
|
||||
SELECT DISTINCT <collated> ?subclass </collated>
|
||||
?authorship
|
||||
|
@ -27,6 +28,17 @@
|
|||
?subclass rdfs:subClassOf foaf:Agent
|
||||
}
|
||||
</collated>
|
||||
}
|
||||
OPTIONAL { ?authorship core:relates ?author .
|
||||
?author a vcard:Kind .
|
||||
?author rdfs:label ?authorName
|
||||
<collated>
|
||||
OPTIONAL { ?authorship core:relates ?author .
|
||||
?author a vcard:Kind .
|
||||
?author vitro:mostSpecificType ?subclass .
|
||||
?subclass rdfs:subClassOf vcard:Kind
|
||||
}
|
||||
</collated>
|
||||
}
|
||||
<critical-data-required>
|
||||
FILTER ( bound(?author) )
|
||||
|
@ -74,5 +86,45 @@
|
|||
}
|
||||
</query-construct>
|
||||
|
||||
<query-construct>
|
||||
PREFIX core: <http://vivoweb.org/ontology/core#>
|
||||
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
|
||||
PREFIX vitro: <http://vitro.mannlib.cornell.edu/ns/vitro/0.7#>
|
||||
PREFIX vcard: <http://www.w3.org/2006/vcard/ns#>
|
||||
CONSTRUCT {
|
||||
?subject ?property ?authorship .
|
||||
?authorship a core:Authorship .
|
||||
?authorship ?authorshipProperty ?authorshipValue .
|
||||
?authorship core:relates ?author .
|
||||
?author a vcard:Kind .
|
||||
?author rdfs:label ?authorName .
|
||||
?author vitro:mostSpecificType ?subclass .
|
||||
?subclass rdfs:subClassOf vcard:Kind
|
||||
} WHERE {
|
||||
{
|
||||
?subject ?property ?authorship .
|
||||
?authorship a core:Authorship
|
||||
} UNION {
|
||||
?subject ?property ?authorship .
|
||||
?authorship a core:Authorship .
|
||||
?authorship ?authorshipProperty ?authorshipValue
|
||||
} UNION {
|
||||
?subject ?property ?authorship .
|
||||
?authorship a core:Authorship .
|
||||
?authorship core:relates ?author .
|
||||
?author a vcard:Kind .
|
||||
?author rdfs:label ?authorName
|
||||
} UNION {
|
||||
?subject ?property ?authorship .
|
||||
?authorship a core:Authorship .
|
||||
?authorship core:relates ?author .
|
||||
?author a vcard:Kind .
|
||||
?author rdfs:label ?authorName .
|
||||
?author vitro:mostSpecificType ?subclass .
|
||||
?subclass rdfs:subClassOf vcard:Kind
|
||||
}
|
||||
}
|
||||
</query-construct>
|
||||
|
||||
<template>propStatement-informationResourceInAuthorship.ftl</template>
|
||||
</list-view-config>
|
||||
|
|
|
@ -87,7 +87,7 @@ $(document).ready(function(){
|
|||
});
|
||||
var viewMore = "<ul id='viewMoreFac'><li><a href='"
|
||||
+ urlsBase
|
||||
+ "/people/%23http://vivoweb.org/ontology/core%23FacultyMember' alt='"
|
||||
+ "/people#http://vivoweb.org/ontology/core#FacultyMember' alt='"
|
||||
+ i18nStrings.viewAllFaculty + "'>"
|
||||
+ i18nStrings.viewAllString + "</a></li?</ul>";
|
||||
$('div#research-faculty-mbrs').append(viewMore);
|
||||
|
@ -155,7 +155,7 @@ $(document).ready(function(){
|
|||
html += "</ul><ul style='list-style:none'>"
|
||||
+ "<li style='font-size:0.9em;text-align:right;padding: 6px 16px 0 0'><a href='"
|
||||
+ urlsBase
|
||||
+ "/organizations/%23http://vivoweb.org/ontology/core%23AcademicDepartment' alt='"
|
||||
+ "/organizations#http://vivoweb.org/ontology/core#AcademicDepartment' alt='"
|
||||
+ i18nStrings.viewAllDepartments + "'>"
|
||||
+ i18nStrings.viewAllString + "</a></li></ul>";
|
||||
}
|
||||
|
|
148
productMods/templates/freemarker/body/orcid/orcidConfirm.ftl
Normal file
148
productMods/templates/freemarker/body/orcid/orcidConfirm.ftl
Normal file
|
@ -0,0 +1,148 @@
|
|||
<#-- $This file is distributed under the terms of the license in /doc/license.txt$ -->
|
||||
|
||||
<#--
|
||||
The body map contains the orcidInfo structure, which is set up like this:
|
||||
|
||||
orcidInfo
|
||||
progress - a string set to one of these values: START, DENIED_AUTHENTICATE,
|
||||
FAILED_AUTHENTICATE, GOT_PROFILE, ID_ALREADY_PRESENT, DENIED_ID,
|
||||
FAILED_ID, ADDED_ID
|
||||
individualUri - the URI of the person
|
||||
profilePage - the URL of the individual's profile page
|
||||
orcid - the confirmed ORCID (just xxxx-xxxx-xxxx-xxxx),
|
||||
or the empty string.
|
||||
orcidUri - the confirmed ORCID (full URI), or the empty string.
|
||||
externalIds - empty if we haven't read their profile. Otherwise, a sequence
|
||||
of maps, one for each external ID in their profile. These
|
||||
might include SCOPUS ID, etc. Each map looks like this:
|
||||
commonName - e.g., "VIVO Cornell"
|
||||
reference - e.g., their VIVO localname
|
||||
uri - e.g., their VIVO URI
|
||||
hasVivoId - true, if we have read the profile and they already have
|
||||
their VIVO URI as an external ID. False otherwise.
|
||||
existingOrcids - A sequence of the ORCIDs (full URI) that we already associate
|
||||
with this individual.
|
||||
progressUrl - The URL to go to, that will continue this process. If the
|
||||
process is complete or has failed, this is empty.
|
||||
|
||||
-->
|
||||
|
||||
<style TYPE="text/css">
|
||||
#orcid-offer .step {
|
||||
background-color: #F7F7F4;
|
||||
border: 1px solid #cdcdcd;
|
||||
border-radius: 10px;
|
||||
padding: 0 1em 1em;
|
||||
margin: 12px
|
||||
}
|
||||
|
||||
#orcid-offer .links {
|
||||
text-align: left;
|
||||
margin-left: 12px;
|
||||
}
|
||||
|
||||
#orcid-offer ul {
|
||||
list-style: disc inside;
|
||||
line-height: 28px;
|
||||
}
|
||||
|
||||
#orcid-offer ul.inner {
|
||||
list-style: none;
|
||||
padding-left: 8px;
|
||||
}
|
||||
|
||||
#orcid-offer li {
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
#orcid-offer .dimmed {
|
||||
opacity:0.35;
|
||||
filter:alpha(opacity=35);
|
||||
}
|
||||
span.completed {
|
||||
color: #9a9a9a;
|
||||
font-size: .8em;
|
||||
}
|
||||
</style>
|
||||
|
||||
<#assign orcidTextOne = "add an" />
|
||||
<#assign orcidTextTwo = "Adding" />
|
||||
<#if (orcidInfo.existingOrcids?size > 0) >
|
||||
<#assign orcidTextOne = "confirm your" />
|
||||
<#assign orcidTextTwo = "Confirming" />
|
||||
</#if>
|
||||
<#assign step2dimmed = (["START", "FAILED_AUTHENTICATE", "DENIED_AUTHENTICATE"]?seq_contains(orcidInfo.progress))?string("dimmed", "") />
|
||||
<#assign continueAppears = (["START", "GOT_PROFILE"]?seq_contains(orcidInfo.progress))/>
|
||||
|
||||
<div>
|
||||
|
||||
<section id="orcid-offer" role="region">
|
||||
<h2>Do you want to ${orcidTextOne} ORCID iD?</h2>
|
||||
|
||||
<div class="step">
|
||||
<#if "START" == orcidInfo.progress>
|
||||
<h2>Step 1: ${orcidTextTwo} your ORCID iD</h2>
|
||||
<ul>
|
||||
<li>VIVO redirects you to ORCID's web site.</li>
|
||||
<li>You log in to your ORCID account.
|
||||
<ul class="inner"><li>If you don't have an account, you can create one.</li></ul>
|
||||
</li>
|
||||
<li>You tell ORCID that VIVO may read your ORCID record. (one-time permission)</li>
|
||||
<li>VIVO reads your ORCID record.</li>
|
||||
<li>VIVO notes that your ORCID iD is confirmed.</li>
|
||||
</ul>
|
||||
<#elseif "DENIED_AUTHENTICATE" == orcidInfo.progress>
|
||||
<h2>Step 1: ${orcidTextTwo} your ORCID iD</h2>
|
||||
<p>You denied VIVO's request to read your ORCID record.</p>
|
||||
<p>Confirmation can't continue.</p>
|
||||
<#elseif "FAILED_AUTHENTICATE" == orcidInfo.progress>
|
||||
<h2>Step 1: ${orcidTextTwo} your ORCID iD</h2>
|
||||
<p>VIVO failed to read your ORCID record.</p>
|
||||
<p>Confirmation can't continue.</p>
|
||||
<#else>
|
||||
<h2>Step 1: ${orcidTextTwo} your ORCID iD <span class="completed">(step completed)</span></h2>
|
||||
<p>Your ORCID iD is confirmed as ${orcidInfo.orcid}</p>
|
||||
<p><a href="${orcidInfo.orcidUri}" target="_blank">View your ORCID record.</a></p>
|
||||
</#if>
|
||||
</div>
|
||||
|
||||
<div class="step ${step2dimmed}">
|
||||
<#if "ID_ALREADY_PRESENT" == orcidInfo.progress>
|
||||
<h2>Step 2 (recommended): Linking your ORCID record to VIVO <span class="completed">(step completed)</span></h2>
|
||||
<p>Your ORCID record already includes a link to VIVO.</p>
|
||||
<#elseif "DENIED_ID" == orcidInfo.progress>
|
||||
<h2>Step 2 (recommended): Linking your ORCID record to VIVO</h2>
|
||||
<p>You denied VIVO's request to add an External ID to your ORCID record.</p>
|
||||
<p>Linking can't continue.</p>
|
||||
<#elseif "FAILED_ID" == orcidInfo.progress>
|
||||
<h2>Step 2 (recommended): Linking your ORCID record to VIVO</h2>
|
||||
<p>VIVO failed to add an External ID to your ORCID record.</p>
|
||||
<p>Linking can't continue.</p>
|
||||
<#elseif "ADDED_ID" == orcidInfo.progress>
|
||||
<h2>Step 2 (recommended): Linking your ORCID record to VIVO <span class="completed">(step completed)</span></h2>
|
||||
<p>Your ORCID record is linked to VIVO</p>
|
||||
<p><a href="${orcidInfo.orcidUri}" target="_blank">View your ORCID record.</a></p>
|
||||
<#else>
|
||||
<h2>Step 2 (recommended): Linking your ORCID record to VIVO</h2>
|
||||
<ul>
|
||||
<li>VIVO redirects you to ORCID's web site</li>
|
||||
<li>You tell ORCID that VIVO may add an "external ID" to your ORCID record. (one-time permission)</li>
|
||||
<li>VIVO adds the external ID.</li>
|
||||
</ul>
|
||||
</#if>
|
||||
</div>
|
||||
|
||||
<div class=links>
|
||||
<form method="GET" action="${orcidInfo.progressUrl}">
|
||||
<p>
|
||||
<#if continueAppears>
|
||||
<input type="submit" name="submit" value="Continue <#if "START" == orcidInfo.progress>Step 1<#else>Step 2</#if>" class="submit"/>
|
||||
or
|
||||
</#if>
|
||||
<a class="cancel" href="${orcidInfo.profilePage}">Return to your VIVO profile page</a>
|
||||
</p>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
|
@ -11,15 +11,27 @@
|
|||
<h2 id="facultyResearchAreas" class="mainPropGroup">
|
||||
${headingText}
|
||||
</h2>
|
||||
<#assign numberRows = researchAreaResults?size/>
|
||||
<ul id="individual-hasResearchArea" role="list">
|
||||
<#assign totalLength = 0 >
|
||||
<#assign moreDisplayed = false>
|
||||
<#list researchAreaResults as resultRow>
|
||||
<#if ( totalLength > 380 ) && !moreDisplayed >
|
||||
<li id="raMoreContainer" style="border:none">(...<a id="raMore" href="javascript:">more</a>)</li>
|
||||
<li class="raLinkMore" style="display:none">
|
||||
<#assign moreDisplayed = true>
|
||||
<#elseif ( totalLength > 380 ) && moreDisplayed >
|
||||
<li class="raLinkMore" style="display:none">
|
||||
<#else>
|
||||
<li class="raLink">
|
||||
<a class="raLink" href="${urls.base}/${urlForDetailsPage}?orgURI=${individual.uri?url}&raURI=${resultRow["ra"]?url}" title="${i18n().research_area}">
|
||||
</#if>
|
||||
<a class="raLink" href="${urls.base}/deptResearchAreas?deptURI=${individual.uri}&raURI=${resultRow["ra"]}">
|
||||
${resultRow["raLabel"]}
|
||||
</a>
|
||||
</li>
|
||||
<#assign totalLength = totalLength + resultRow["raLabel"]?length >
|
||||
</#list>
|
||||
<#if ( totalLength > 380 ) ><li id="raLessContainer" style="display:none">(<a id="raLess" href="javascript:">less</a>)</li></#if>
|
||||
</ul>
|
||||
</#if>
|
||||
<script>
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
<#-- $This file is distributed under the terms of the license in /doc/license.txt$ -->
|
||||
|
||||
<#--
|
||||
If authorized to confirm ORCID IDs, add the function that will replace the add link.
|
||||
The OrcidIdDataGetter is attached to this template; it sets the orcidInfo structure,
|
||||
which looks like this:
|
||||
|
||||
orcidInfo = map {
|
||||
authorizedToConfirm: boolean
|
||||
orcidUrl: link to the orcid controller
|
||||
orcids: map of String to boolean [
|
||||
orcid: String (full URI)
|
||||
confirmed: boolean
|
||||
]
|
||||
}
|
||||
-->
|
||||
<#assign confirmThis = "" />
|
||||
<#if orcidInfo??>
|
||||
|
||||
<#list orcidInfo.orcids?keys as key>
|
||||
<#if "no" == orcidInfo.orcids[key]?string("yes","no") >
|
||||
<#assign confirmThis = "Confirm the ID" />
|
||||
</#if>
|
||||
</#list>
|
||||
|
||||
<#if orcidInfo.authorizedToConfirm>
|
||||
<script>
|
||||
$(document).ready(function(){
|
||||
$('#orcidId a.add-orcidId').replaceWith("<a class='add-orcidId' style='padding-left:20px' href='${orcidInfo.orcidUrl}'><#if orcidInfo.orcids?size == 0>Add an iD<#else>${confirmThis}</#if></a> ");
|
||||
});
|
||||
</script>
|
||||
</#if>
|
||||
</#if>
|
||||
|
|
@ -9,6 +9,13 @@
|
|||
|
||||
<#macro showStatement statement>
|
||||
<a href="${statement.value!}" title="ORCID iD" target="_blank">${statement.value!"ORCID iD not found"}</a>
|
||||
<#if orcidInfo??>
|
||||
<#if (orcidInfo.orcids[statement.value])!false>
|
||||
<span style="color:#FF7700">(confirmed)</span>
|
||||
<#else>
|
||||
<span style="color:#FF7700">(pending confirmation)</span>
|
||||
</#if>
|
||||
</#if>
|
||||
</#macro>
|
||||
|
||||
|
||||
|
|
2
rdf/abox/filegraph/validation.n3
Normal file
2
rdf/abox/filegraph/validation.n3
Normal file
|
@ -0,0 +1,2 @@
|
|||
<http://orcid.org/1234-1231-1231-3333>
|
||||
<http://vivoweb.org/ontology/core#validatedOrcidId> <http://vivo.mydomain.edu/individual/n4571> .
|
14
rdf/display/everytime/orcidInterfaceDataGetters.n3
Normal file
14
rdf/display/everytime/orcidInterfaceDataGetters.n3
Normal file
|
@ -0,0 +1,14 @@
|
|||
# $This file is distributed under the terms of the license in /doc/license.txt$
|
||||
|
||||
@prefix foaf: <http://xmlns.com/foaf/0.1/> .
|
||||
@prefix display: <http://vitro.mannlib.cornell.edu/ontologies/display/1.1#> .
|
||||
|
||||
#
|
||||
# datagetter to fetch ORCID info for the person.
|
||||
#
|
||||
|
||||
<freemarker:individual-orcidInterface.ftl> display:hasDataGetter display:orcidIdDataGetter .
|
||||
|
||||
display:orcidIdDataGetter
|
||||
a <java:edu.cornell.mannlib.vivo.orcid.OrcidIdDataGetter> .
|
||||
|
10
rdf/tbox/filegraph/orcid-interface.n3
Normal file
10
rdf/tbox/filegraph/orcid-interface.n3
Normal file
|
@ -0,0 +1,10 @@
|
|||
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
|
||||
@prefix owl: <http://www.w3.org/2002/07/owl#> .
|
||||
@prefix core: <http://vivoweb.org/ontology/core#> .
|
||||
@prefix foaf: <http://xmlns.com/foaf/0.1/> .
|
||||
|
||||
core:confirmedOrcidId
|
||||
a owl:ObjectProperty ;
|
||||
rdfs:label "Orcid ID confirmation"@en-US ;
|
||||
rdfs:comment "Indicates that the Orcid ID has been confirmed by this Person" ;
|
||||
rdfs:range foaf:Person .
|
|
@ -17,9 +17,9 @@ public class SiteAdminController extends BaseSiteAdminController {
|
|||
private static final Log log = LogFactory.getLog(SiteAdminController.class);
|
||||
|
||||
@Override
|
||||
protected Map<String, String> getSiteMaintenanceUrls(VitroRequest vreq) {
|
||||
protected Map<String, Object> getSiteMaintenanceUrls(VitroRequest vreq) {
|
||||
|
||||
Map<String, String> urls = super.getSiteMaintenanceUrls(vreq);
|
||||
Map<String, Object> urls = super.getSiteMaintenanceUrls(vreq);
|
||||
|
||||
if (PolicyHelper.isAuthorizedForActions(vreq, ToolsRequestHandler.REQUIRED_ACTIONS)) {
|
||||
urls.put("rebuildVisCache", UrlBuilder.getUrl("/vis/tools"));
|
||||
|
|
107
src/edu/cornell/mannlib/vivo/orcid/OrcidContextSetup.java
Normal file
107
src/edu/cornell/mannlib/vivo/orcid/OrcidContextSetup.java
Normal file
|
@ -0,0 +1,107 @@
|
|||
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
|
||||
|
||||
package edu.cornell.mannlib.vivo.orcid;
|
||||
|
||||
import static edu.cornell.mannlib.orcidclient.context.OrcidClientContext.Setting.AUTHORIZED_API_BASE_URL;
|
||||
import static edu.cornell.mannlib.orcidclient.context.OrcidClientContext.Setting.CALLBACK_PATH;
|
||||
import static edu.cornell.mannlib.orcidclient.context.OrcidClientContext.Setting.CLIENT_ID;
|
||||
import static edu.cornell.mannlib.orcidclient.context.OrcidClientContext.Setting.CLIENT_SECRET;
|
||||
import static edu.cornell.mannlib.orcidclient.context.OrcidClientContext.Setting.MESSAGE_VERSION;
|
||||
import static edu.cornell.mannlib.orcidclient.context.OrcidClientContext.Setting.OAUTH_AUTHORIZE_URL;
|
||||
import static edu.cornell.mannlib.orcidclient.context.OrcidClientContext.Setting.OAUTH_TOKEN_URL;
|
||||
import static edu.cornell.mannlib.orcidclient.context.OrcidClientContext.Setting.PUBLIC_API_BASE_URL;
|
||||
import static edu.cornell.mannlib.orcidclient.context.OrcidClientContext.Setting.WEBAPP_BASE_URL;
|
||||
import static edu.cornell.mannlib.vivo.orcid.controller.OrcidIntegrationController.DEFAULT_EXTERNAL_ID_COMMON_NAME;
|
||||
import static edu.cornell.mannlib.vivo.orcid.controller.OrcidIntegrationController.PROPERTY_EXTERNAL_ID_COMMON_NAME;
|
||||
|
||||
import java.util.EnumMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.ServletContextEvent;
|
||||
import javax.servlet.ServletContextListener;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import edu.cornell.mannlib.orcidclient.OrcidClientException;
|
||||
import edu.cornell.mannlib.orcidclient.context.OrcidClientContext;
|
||||
import edu.cornell.mannlib.orcidclient.context.OrcidClientContext.Setting;
|
||||
import edu.cornell.mannlib.vitro.webapp.config.ConfigurationProperties;
|
||||
import edu.cornell.mannlib.vitro.webapp.startup.StartupStatus;
|
||||
|
||||
/**
|
||||
* Setup for the ORCID interface.
|
||||
*
|
||||
* Note that the property for CLIENT_SECRET is "orcid.clientPassword". Since it
|
||||
* ends in "password", it will not be displayed on the ShowConfiguration page.
|
||||
*
|
||||
* The CALLBACK_PATH is hardcoded. It is relative to the WEBAPP_BASE_URL, so it
|
||||
* won't change.
|
||||
*/
|
||||
public class OrcidContextSetup implements ServletContextListener {
|
||||
private static final Log log = LogFactory.getLog(OrcidContextSetup.class);
|
||||
|
||||
@Override
|
||||
public void contextInitialized(ServletContextEvent sce) {
|
||||
ServletContext ctx = sce.getServletContext();
|
||||
ConfigurationProperties props = ConfigurationProperties.getBean(ctx);
|
||||
StartupStatus ss = StartupStatus.getBean(ctx);
|
||||
|
||||
if (props.getProperty("orcid.clientId", "").isEmpty()) {
|
||||
ss.info(this, "ORCID Integration is not configured.");
|
||||
return;
|
||||
}
|
||||
|
||||
initializeOrcidClientContext(props, ss);
|
||||
|
||||
checkForCommonNameProperty(props, ss);
|
||||
}
|
||||
|
||||
private void initializeOrcidClientContext(ConfigurationProperties props,
|
||||
StartupStatus ss) {
|
||||
try {
|
||||
Map<Setting, String> settings = new EnumMap<>(Setting.class);
|
||||
settings.put(CLIENT_ID, props.getProperty("orcid.clientId"));
|
||||
settings.put(CLIENT_SECRET,
|
||||
props.getProperty("orcid.clientPassword"));
|
||||
settings.put(PUBLIC_API_BASE_URL,
|
||||
props.getProperty("orcid.publicApiBaseUrl"));
|
||||
settings.put(AUTHORIZED_API_BASE_URL,
|
||||
props.getProperty("orcid.authorizedApiBaseUrl"));
|
||||
settings.put(OAUTH_AUTHORIZE_URL,
|
||||
props.getProperty("orcid.oauthAuthorizeUrl"));
|
||||
settings.put(OAUTH_TOKEN_URL,
|
||||
props.getProperty("orcid.oauthTokenUrl"));
|
||||
settings.put(MESSAGE_VERSION,
|
||||
props.getProperty("orcid.messageVersion"));
|
||||
settings.put(WEBAPP_BASE_URL,
|
||||
props.getProperty("orcid.webappBaseUrl"));
|
||||
settings.put(CALLBACK_PATH, "orcid/callback");
|
||||
|
||||
OrcidClientContext.initialize(settings);
|
||||
ss.info(this, "Context is: " + OrcidClientContext.getInstance());
|
||||
|
||||
} catch (OrcidClientException e) {
|
||||
ss.warning(this, "Failed to initialize OrcidClientContent", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkForCommonNameProperty(ConfigurationProperties props,
|
||||
StartupStatus ss) {
|
||||
if (StringUtils.isBlank(props
|
||||
.getProperty(PROPERTY_EXTERNAL_ID_COMMON_NAME))) {
|
||||
ss.warning(this, "'" + PROPERTY_EXTERNAL_ID_COMMON_NAME
|
||||
+ "' is not set. " + "Using default value of '"
|
||||
+ DEFAULT_EXTERNAL_ID_COMMON_NAME + "'");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void contextDestroyed(ServletContextEvent sce) {
|
||||
// Nothing to tear down.
|
||||
}
|
||||
|
||||
}
|
217
src/edu/cornell/mannlib/vivo/orcid/OrcidIdDataGetter.java
Normal file
217
src/edu/cornell/mannlib/vivo/orcid/OrcidIdDataGetter.java
Normal file
|
@ -0,0 +1,217 @@
|
|||
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
|
||||
|
||||
package edu.cornell.mannlib.vivo.orcid;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import com.hp.hpl.jena.query.QuerySolution;
|
||||
import com.hp.hpl.jena.query.ResultSet;
|
||||
import com.hp.hpl.jena.rdf.model.RDFNode;
|
||||
import com.hp.hpl.jena.rdf.model.Resource;
|
||||
|
||||
import edu.cornell.mannlib.vitro.webapp.auth.identifier.IdentifierBundle;
|
||||
import edu.cornell.mannlib.vitro.webapp.auth.identifier.RequestIdentifiers;
|
||||
import edu.cornell.mannlib.vitro.webapp.auth.identifier.common.HasAssociatedIndividual;
|
||||
import edu.cornell.mannlib.vitro.webapp.auth.identifier.common.HasProfile;
|
||||
import edu.cornell.mannlib.vitro.webapp.auth.identifier.common.HasProxyEditingRights;
|
||||
import edu.cornell.mannlib.vitro.webapp.auth.identifier.common.IsRootUser;
|
||||
import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest;
|
||||
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.UrlBuilder;
|
||||
import edu.cornell.mannlib.vitro.webapp.utils.SparqlQueryRunner;
|
||||
import edu.cornell.mannlib.vitro.webapp.utils.SparqlQueryRunner.QueryParser;
|
||||
import edu.cornell.mannlib.vitro.webapp.utils.dataGetter.DataGetter;
|
||||
import edu.cornell.mannlib.vivo.orcid.controller.OrcidIntegrationController;
|
||||
|
||||
/**
|
||||
* This data getter should be assigned to the template that renders the list
|
||||
* view for ORCID IDs.
|
||||
*
|
||||
* Find out whether the user is authorized to confirm the ORCID IDs on this
|
||||
* page. Find the list of ORCID IDs, and whether each has already been
|
||||
* confirmed.
|
||||
*
|
||||
* The information is stored in the values map like this:
|
||||
*
|
||||
* <pre>
|
||||
* orcidInfo = map {
|
||||
* authorizedToConfirm: boolean
|
||||
* orcids: map of String to boolean [
|
||||
* orcid: String
|
||||
* confirm: boolean
|
||||
* ]
|
||||
* }
|
||||
* </pre>
|
||||
*/
|
||||
public class OrcidIdDataGetter implements DataGetter {
|
||||
private static final Log log = LogFactory.getLog(OrcidIdDataGetter.class);
|
||||
|
||||
private static final Map<String, Object> EMPTY_RESULT = Collections
|
||||
.emptyMap();
|
||||
public static final String ORCID_ID = "http://vivoweb.org/ontology/core#orcidId";
|
||||
public static final String ORCID_IS_CONFIRMED = "http://vivoweb.org/ontology/core#confirmedOrcidId";
|
||||
private static final String QUERY_TEMPLATE = "SELECT ?orcid ?confirmed \n"
|
||||
+ "WHERE { \n" //
|
||||
+ " <%s> <%s> ?orcid . \n" //
|
||||
+ " OPTIONAL { \n" //
|
||||
+ " ?orcid <%s> ?confirmed . \n" //
|
||||
+ " } \n" //
|
||||
+ "}\n";
|
||||
|
||||
private final VitroRequest vreq;
|
||||
|
||||
public OrcidIdDataGetter(VitroRequest vreq) {
|
||||
this.vreq = vreq;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getData(Map<String, Object> valueMap) {
|
||||
try {
|
||||
String individualUri = findIndividualUri(valueMap);
|
||||
if (individualUri == null) {
|
||||
return EMPTY_RESULT;
|
||||
}
|
||||
|
||||
boolean isAuthorizedToConfirm = figureIsAuthorizedtoConfirm(individualUri);
|
||||
List<OrcidInfo> orcids = runSparqlQuery(individualUri);
|
||||
return buildMap(isAuthorizedToConfirm, orcids, individualUri);
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to get orcID information", e);
|
||||
return EMPTY_RESULT;
|
||||
}
|
||||
}
|
||||
|
||||
private String findIndividualUri(Map<String, Object> valueMap) {
|
||||
try {
|
||||
String uri = (String) valueMap.get("individualURI");
|
||||
|
||||
if (uri == null) {
|
||||
log.warn("valueMap has no individualURI. Keys are: "
|
||||
+ valueMap.keySet());
|
||||
return null;
|
||||
} else {
|
||||
return uri;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.debug("has a problem finding the individualURI", e);
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* You are authorized to confirm an orcId only if you are a self-editor or
|
||||
* root.
|
||||
*/
|
||||
private boolean figureIsAuthorizedtoConfirm(String individualUri) {
|
||||
IdentifierBundle ids = RequestIdentifiers.getIdBundleForRequest(vreq);
|
||||
boolean isSelfEditor = HasProfile.getProfileUris(ids).contains(
|
||||
individualUri);
|
||||
boolean isProxyEditor = HasProxyEditingRights.getProxiedPageUris(ids)
|
||||
.contains(individualUri);
|
||||
boolean isRoot = IsRootUser.isRootUser(ids);
|
||||
return isRoot || isProxyEditor || isSelfEditor;
|
||||
}
|
||||
|
||||
private List<OrcidInfo> runSparqlQuery(String individualUri) {
|
||||
String queryStr = String.format(QUERY_TEMPLATE, individualUri,
|
||||
ORCID_ID, ORCID_IS_CONFIRMED);
|
||||
SparqlQueryRunner runner = new SparqlQueryRunner(vreq.getJenaOntModel());
|
||||
return runner.executeSelect(new OrcidResultParser(), queryStr);
|
||||
}
|
||||
|
||||
private Map<String, Object> buildMap(boolean isAuthorizedToConfirm,
|
||||
List<OrcidInfo> orcids, String individualUri) {
|
||||
Map<String, Boolean> confirmationMap = new HashMap<>();
|
||||
for (OrcidInfo oInfo : orcids) {
|
||||
confirmationMap.put(oInfo.getOrcid(), oInfo.isConfirmed());
|
||||
}
|
||||
|
||||
Map<String, Object> orcidInfoMap = new HashMap<>();
|
||||
orcidInfoMap.put("authorizedToConfirm", isAuthorizedToConfirm);
|
||||
orcidInfoMap.put("orcidUrl", UrlBuilder.getUrl(
|
||||
OrcidIntegrationController.PATH_DEFAULT, "individualUri",
|
||||
individualUri));
|
||||
orcidInfoMap.put("orcids", confirmationMap);
|
||||
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("orcidInfo", orcidInfoMap);
|
||||
|
||||
log.debug("Returning these values:" + map);
|
||||
return map;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Helper classes
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Parse the results of the SPARQL query.
|
||||
*/
|
||||
private static class OrcidResultParser extends QueryParser<List<OrcidInfo>> {
|
||||
@Override
|
||||
protected List<OrcidInfo> defaultValue() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<OrcidInfo> parseResults(String queryStr,
|
||||
ResultSet results) {
|
||||
List<OrcidInfo> orcids = new ArrayList<>();
|
||||
|
||||
while (results.hasNext()) {
|
||||
try {
|
||||
QuerySolution solution = results.next();
|
||||
Resource orcid = solution.getResource("orcid");
|
||||
RDFNode cNode = solution.get("confirmed");
|
||||
log.debug("Result is orcid=" + orcid + ", confirmed="
|
||||
+ cNode);
|
||||
|
||||
if (orcid != null && orcid.isURIResource()) {
|
||||
boolean confirmed = (cNode != null);
|
||||
orcids.add(new OrcidInfo(orcid.getURI(), confirmed));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to parse the query result: " + queryStr, e);
|
||||
}
|
||||
}
|
||||
|
||||
return orcids;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A bean to hold info for each ORCID.
|
||||
*/
|
||||
static class OrcidInfo {
|
||||
private final String orcid;
|
||||
private final boolean confirmed;
|
||||
|
||||
public OrcidInfo(String orcid, boolean confirmed) {
|
||||
this.orcid = orcid;
|
||||
this.confirmed = confirmed;
|
||||
}
|
||||
|
||||
public String getOrcid() {
|
||||
return orcid;
|
||||
}
|
||||
|
||||
public boolean isConfirmed() {
|
||||
return confirmed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "OrcidInfo[orcid=" + orcid + ", confirmed=" + confirmed
|
||||
+ "]";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,124 @@
|
|||
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
|
||||
|
||||
package edu.cornell.mannlib.vivo.orcid.controller;
|
||||
|
||||
import static edu.cornell.mannlib.vitro.webapp.dao.VitroVocabulary.OWL_THING;
|
||||
import static edu.cornell.mannlib.vitro.webapp.dao.VitroVocabulary.RDF_TYPE;
|
||||
import static edu.cornell.mannlib.vivo.orcid.OrcidIdDataGetter.ORCID_ID;
|
||||
import static edu.cornell.mannlib.vivo.orcid.OrcidIdDataGetter.ORCID_IS_CONFIRMED;
|
||||
import static edu.cornell.mannlib.vivo.orcid.controller.OrcidIntegrationController.TEMPLATE_CONFIRM;
|
||||
import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import edu.cornell.mannlib.orcidclient.auth.AuthorizationManager;
|
||||
import edu.cornell.mannlib.orcidclient.context.OrcidClientContext;
|
||||
import edu.cornell.mannlib.orcidclient.orcidmessage.OrcidMessage;
|
||||
import edu.cornell.mannlib.vedit.beans.LoginStatusBean;
|
||||
import edu.cornell.mannlib.vitro.webapp.beans.Individual;
|
||||
import edu.cornell.mannlib.vitro.webapp.beans.ObjectPropertyStatement;
|
||||
import edu.cornell.mannlib.vitro.webapp.beans.ObjectPropertyStatementImpl;
|
||||
import edu.cornell.mannlib.vitro.webapp.beans.UserAccount;
|
||||
import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest;
|
||||
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.ResponseValues;
|
||||
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.TemplateResponseValues;
|
||||
import edu.cornell.mannlib.vitro.webapp.dao.IndividualDao;
|
||||
import edu.cornell.mannlib.vitro.webapp.dao.ObjectPropertyStatementDao;
|
||||
import edu.cornell.mannlib.vivo.orcid.controller.OrcidConfirmationState.Progress;
|
||||
|
||||
/**
|
||||
* Some utility methods for the handlers.
|
||||
*/
|
||||
public abstract class OrcidAbstractHandler {
|
||||
private static final Log log = LogFactory
|
||||
.getLog(OrcidAbstractHandler.class);
|
||||
|
||||
protected final VitroRequest vreq;
|
||||
protected final OrcidClientContext occ;
|
||||
protected final AuthorizationManager auth;
|
||||
protected final OrcidConfirmationState state;
|
||||
protected final UserAccount currentUser;
|
||||
|
||||
protected OrcidAbstractHandler(VitroRequest vreq) {
|
||||
this.vreq = vreq;
|
||||
this.occ = OrcidClientContext.getInstance();
|
||||
this.auth = this.occ.getAuthorizationManager(vreq);
|
||||
this.state = OrcidConfirmationState.fetch(vreq);
|
||||
this.currentUser = LoginStatusBean.getCurrentUser(vreq);
|
||||
}
|
||||
|
||||
protected Individual findIndividual() {
|
||||
String uri = state.getIndividualUri();
|
||||
try {
|
||||
IndividualDao iDao = vreq.getWebappDaoFactory().getIndividualDao();
|
||||
Individual individual = iDao.getIndividualByURI(uri);
|
||||
if (individual == null) {
|
||||
throw new IllegalStateException("Individual URI not valid: '"
|
||||
+ uri + "'");
|
||||
}
|
||||
return individual;
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("Individual URI not valid: '" + uri
|
||||
+ "'");
|
||||
}
|
||||
}
|
||||
|
||||
protected void recordConfirmation() {
|
||||
String individualUri = state.getIndividualUri();
|
||||
String orcidUri = state.getOrcidUri();
|
||||
log.debug("Recording confirmation of ORCID '" + orcidUri + "' on '"
|
||||
+ individualUri + "'");
|
||||
ObjectPropertyStatement ops1 = new ObjectPropertyStatementImpl(
|
||||
individualUri, ORCID_ID, orcidUri);
|
||||
ObjectPropertyStatement ops2 = new ObjectPropertyStatementImpl(
|
||||
orcidUri, RDF_TYPE, OWL_THING);
|
||||
ObjectPropertyStatement ops3 = new ObjectPropertyStatementImpl(
|
||||
orcidUri, ORCID_IS_CONFIRMED, individualUri);
|
||||
|
||||
ObjectPropertyStatementDao opsd = vreq.getWebappDaoFactory()
|
||||
.getObjectPropertyStatementDao();
|
||||
opsd.insertNewObjectPropertyStatement(ops1);
|
||||
opsd.insertNewObjectPropertyStatement(ops2);
|
||||
opsd.insertNewObjectPropertyStatement(ops3);
|
||||
}
|
||||
|
||||
protected String cornellNetId() {
|
||||
if (currentUser == null) {
|
||||
return null;
|
||||
}
|
||||
String externalId = currentUser.getExternalAuthId();
|
||||
if (externalId == null) {
|
||||
return null;
|
||||
}
|
||||
if (externalId.trim().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return externalId;
|
||||
}
|
||||
|
||||
protected ResponseValues show500InternalServerError(String message) {
|
||||
log.error("Problem with ORCID request: " + message);
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("title", "500 Internal Server Error");
|
||||
map.put("errorMessage", message);
|
||||
return new TemplateResponseValues("error-titled.ftl", map,
|
||||
SC_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
protected ResponseValues showConfirmationPage(Progress p,
|
||||
OrcidMessage... messages) {
|
||||
state.progress(p, messages);
|
||||
return showConfirmationPage();
|
||||
}
|
||||
|
||||
protected ResponseValues showConfirmationPage() {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("orcidInfo", state.toMap());
|
||||
return new TemplateResponseValues(TEMPLATE_CONFIRM, map);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
|
||||
|
||||
package edu.cornell.mannlib.vivo.orcid.controller;
|
||||
|
||||
import static edu.cornell.mannlib.orcidclient.actions.ApiAction.ADD_EXTERNAL_ID;
|
||||
import static edu.cornell.mannlib.orcidclient.orcidmessage.Visibility.PUBLIC;
|
||||
import static edu.cornell.mannlib.vivo.orcid.controller.OrcidConfirmationState.Progress.ADDED_ID;
|
||||
import static edu.cornell.mannlib.vivo.orcid.controller.OrcidConfirmationState.Progress.DENIED_ID;
|
||||
import static edu.cornell.mannlib.vivo.orcid.controller.OrcidConfirmationState.Progress.FAILED_ID;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import edu.cornell.mannlib.orcidclient.OrcidClientException;
|
||||
import edu.cornell.mannlib.orcidclient.actions.AddExternalIdAction;
|
||||
import edu.cornell.mannlib.orcidclient.auth.AuthorizationStatus;
|
||||
import edu.cornell.mannlib.orcidclient.beans.ExternalId;
|
||||
import edu.cornell.mannlib.orcidclient.orcidmessage.OrcidMessage;
|
||||
import edu.cornell.mannlib.vitro.webapp.beans.Individual;
|
||||
import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest;
|
||||
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.ResponseValues;
|
||||
|
||||
/**
|
||||
* We should now be logged in to ORCID and authorized to add an external ID.
|
||||
*/
|
||||
public class OrcidAddExternalIdHandler extends OrcidAbstractHandler {
|
||||
private static final Log log = LogFactory
|
||||
.getLog(OrcidAddExternalIdHandler.class);
|
||||
|
||||
private AuthorizationStatus status;
|
||||
private OrcidMessage profile;
|
||||
|
||||
protected OrcidAddExternalIdHandler(VitroRequest vreq) {
|
||||
super(vreq);
|
||||
}
|
||||
|
||||
public ResponseValues exec() throws OrcidClientException {
|
||||
status = auth.getAuthorizationStatus(ADD_EXTERNAL_ID);
|
||||
if (status.isSuccess()) {
|
||||
addVivoId();
|
||||
return showConfirmationPage(ADDED_ID, profile);
|
||||
} else if (status.isDenied()) {
|
||||
return showConfirmationPage(DENIED_ID);
|
||||
} else {
|
||||
return showConfirmationPage(FAILED_ID);
|
||||
}
|
||||
}
|
||||
|
||||
private void addVivoId() throws OrcidClientException {
|
||||
Individual individual = findIndividual();
|
||||
ExternalId externalId = new ExternalId().setCommonName("VIVO Cornell")
|
||||
.setReference(individual.getLocalName())
|
||||
.setUrl(individual.getURI()).setVisibility(PUBLIC);
|
||||
|
||||
log.debug("Adding external VIVO ID");
|
||||
profile = new AddExternalIdAction().execute(externalId,
|
||||
status.getAccessToken());
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
|
||||
|
||||
package edu.cornell.mannlib.vivo.orcid.controller;
|
||||
|
||||
import static edu.cornell.mannlib.orcidclient.actions.ApiAction.AUTHENTICATE;
|
||||
import static edu.cornell.mannlib.vivo.orcid.controller.OrcidConfirmationState.Progress.DENIED_AUTHENTICATE;
|
||||
import static edu.cornell.mannlib.vivo.orcid.controller.OrcidConfirmationState.Progress.FAILED_AUTHENTICATE;
|
||||
import static edu.cornell.mannlib.vivo.orcid.controller.OrcidIntegrationController.PATH_READ_PROFILE;
|
||||
|
||||
import java.net.URISyntaxException;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import edu.cornell.mannlib.orcidclient.OrcidClientException;
|
||||
import edu.cornell.mannlib.orcidclient.auth.AuthorizationStatus;
|
||||
import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest;
|
||||
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.RedirectResponseValues;
|
||||
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.ResponseValues;
|
||||
|
||||
/**
|
||||
* We offered the confirmation screen, and they decided to go ahead. Get
|
||||
* authorization to authenticate them.
|
||||
*
|
||||
* We can't assume that they haven't been here before, so they might already
|
||||
* have authorized, or denied authorization.
|
||||
*/
|
||||
public class OrcidAuthAuthenticateHandler extends OrcidAbstractHandler {
|
||||
private static final Log log = LogFactory
|
||||
.getLog(OrcidAuthAuthenticateHandler.class);
|
||||
|
||||
private AuthorizationStatus status;
|
||||
|
||||
public OrcidAuthAuthenticateHandler(VitroRequest vreq) {
|
||||
super(vreq);
|
||||
}
|
||||
|
||||
public ResponseValues exec() throws URISyntaxException,
|
||||
OrcidClientException {
|
||||
status = auth.getAuthorizationStatus(AUTHENTICATE);
|
||||
if (status.isNone()) {
|
||||
return seekAuthorizationForAuthenticate();
|
||||
} else if (status.isSuccess()) {
|
||||
return redirectToReadProfile();
|
||||
} else if (status.isDenied()) {
|
||||
return showConfirmationPage(DENIED_AUTHENTICATE);
|
||||
} else {
|
||||
return showConfirmationPage(FAILED_AUTHENTICATE);
|
||||
}
|
||||
}
|
||||
|
||||
private ResponseValues seekAuthorizationForAuthenticate()
|
||||
throws OrcidClientException, URISyntaxException {
|
||||
log.debug("Seeking authorization to authenticate.");
|
||||
String returnUrl = occ.resolvePathWithWebapp(PATH_READ_PROFILE);
|
||||
String seekUrl = auth.seekAuthorization(AUTHENTICATE, returnUrl);
|
||||
return new RedirectResponseValues(seekUrl);
|
||||
}
|
||||
|
||||
private ResponseValues redirectToReadProfile() throws URISyntaxException {
|
||||
log.debug("Already authorized to authenticate.");
|
||||
return new RedirectResponseValues(
|
||||
occ.resolvePathWithWebapp(PATH_READ_PROFILE));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
|
||||
|
||||
package edu.cornell.mannlib.vivo.orcid.controller;
|
||||
|
||||
import static edu.cornell.mannlib.orcidclient.actions.ApiAction.ADD_EXTERNAL_ID;
|
||||
import static edu.cornell.mannlib.vivo.orcid.controller.OrcidConfirmationState.Progress.DENIED_ID;
|
||||
import static edu.cornell.mannlib.vivo.orcid.controller.OrcidConfirmationState.Progress.FAILED_ID;
|
||||
import static edu.cornell.mannlib.vivo.orcid.controller.OrcidIntegrationController.PATH_ADD_EXTERNAL_ID;
|
||||
|
||||
import java.net.URISyntaxException;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import edu.cornell.mannlib.orcidclient.OrcidClientException;
|
||||
import edu.cornell.mannlib.orcidclient.auth.AuthorizationStatus;
|
||||
import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest;
|
||||
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.RedirectResponseValues;
|
||||
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.ResponseValues;
|
||||
|
||||
/**
|
||||
* We offered to add external IDs and they decided to go ahead. Get
|
||||
* authorization.
|
||||
*
|
||||
* We can't assume that they haven't been here before, so they might already
|
||||
* have authorized, or denied authorization.
|
||||
*/
|
||||
public class OrcidAuthExternalIdsHandler extends OrcidAbstractHandler {
|
||||
private static final Log log = LogFactory
|
||||
.getLog(OrcidAuthExternalIdsHandler.class);
|
||||
|
||||
private AuthorizationStatus status;
|
||||
|
||||
public OrcidAuthExternalIdsHandler(VitroRequest vreq) {
|
||||
super(vreq);
|
||||
}
|
||||
|
||||
public ResponseValues exec() throws URISyntaxException,
|
||||
OrcidClientException {
|
||||
status = auth.getAuthorizationStatus(ADD_EXTERNAL_ID);
|
||||
if (status.isNone()) {
|
||||
return seekAuthorizationForExternalId();
|
||||
} else if (status.isSuccess()) {
|
||||
return redirectToAddExternalId();
|
||||
} else if (status.isDenied()) {
|
||||
return showConfirmationPage(DENIED_ID);
|
||||
} else {
|
||||
return showConfirmationPage(FAILED_ID);
|
||||
}
|
||||
}
|
||||
|
||||
private ResponseValues seekAuthorizationForExternalId()
|
||||
throws OrcidClientException, URISyntaxException {
|
||||
log.debug("Seeking authorization to add external ID");
|
||||
String returnUrl = occ.resolvePathWithWebapp(PATH_ADD_EXTERNAL_ID);
|
||||
String seekUrl = auth.seekAuthorization(ADD_EXTERNAL_ID, returnUrl);
|
||||
return new RedirectResponseValues(seekUrl);
|
||||
}
|
||||
|
||||
private ResponseValues redirectToAddExternalId() throws URISyntaxException {
|
||||
log.debug("Already authorized to add external ID.");
|
||||
return new RedirectResponseValues(
|
||||
occ.resolvePathWithWebapp(PATH_ADD_EXTERNAL_ID));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
|
||||
|
||||
package edu.cornell.mannlib.vivo.orcid.controller;
|
||||
|
||||
import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import edu.cornell.mannlib.orcidclient.OrcidClientException;
|
||||
import edu.cornell.mannlib.orcidclient.auth.AuthorizationManager;
|
||||
import edu.cornell.mannlib.orcidclient.auth.AuthorizationStatus;
|
||||
import edu.cornell.mannlib.orcidclient.context.OrcidClientContext;
|
||||
|
||||
/**
|
||||
* Handle the callbacks during the OAuth dance.
|
||||
*
|
||||
* This is not like other handlers. It is created and invoked from doGet(), not
|
||||
* from processRequest().
|
||||
*/
|
||||
public class OrcidCallbackHandler {
|
||||
private static final Log log = LogFactory
|
||||
.getLog(OrcidCallbackHandler.class);
|
||||
|
||||
private final HttpServletRequest req;
|
||||
private final HttpServletResponse resp;
|
||||
|
||||
public OrcidCallbackHandler(HttpServletRequest req, HttpServletResponse resp) {
|
||||
this.req = req;
|
||||
this.resp = resp;
|
||||
}
|
||||
|
||||
public void exec() throws IOException {
|
||||
OrcidClientContext occ = OrcidClientContext.getInstance();
|
||||
AuthorizationManager authManager = occ.getAuthorizationManager(req);
|
||||
try {
|
||||
AuthorizationStatus auth = authManager
|
||||
.processAuthorizationResponse();
|
||||
if (auth.isSuccess()) {
|
||||
resp.sendRedirect(auth.getSuccessUrl());
|
||||
} else {
|
||||
resp.sendRedirect(auth.getFailureUrl());
|
||||
}
|
||||
} catch (OrcidClientException e) {
|
||||
log.error("Invalid authorization response", e);
|
||||
resp.sendError(SC_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,250 @@
|
|||
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
|
||||
|
||||
package edu.cornell.mannlib.vivo.orcid.controller;
|
||||
|
||||
import static edu.cornell.mannlib.vivo.orcid.controller.OrcidConfirmationState.Progress.START;
|
||||
import static edu.cornell.mannlib.vivo.orcid.controller.OrcidIntegrationController.PATH_AUTH_EXTERNAL_ID;
|
||||
import static edu.cornell.mannlib.vivo.orcid.controller.OrcidIntegrationController.PATH_AUTH_AUTHENTICATE;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpSession;
|
||||
import javax.xml.bind.JAXBElement;
|
||||
import javax.xml.namespace.QName;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import edu.cornell.mannlib.orcidclient.orcidmessage.ExternalIdentifier;
|
||||
import edu.cornell.mannlib.orcidclient.orcidmessage.ExternalIdentifiers;
|
||||
import edu.cornell.mannlib.orcidclient.orcidmessage.OrcidBio;
|
||||
import edu.cornell.mannlib.orcidclient.orcidmessage.OrcidId;
|
||||
import edu.cornell.mannlib.orcidclient.orcidmessage.OrcidMessage;
|
||||
import edu.cornell.mannlib.orcidclient.orcidmessage.OrcidProfile;
|
||||
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.UrlBuilder;
|
||||
|
||||
/**
|
||||
* Keep track of where we are in the Orcid confirmation process; what has been
|
||||
* requested, and what has been returned.
|
||||
*/
|
||||
class OrcidConfirmationState {
|
||||
private static final Log log = LogFactory
|
||||
.getLog(OrcidConfirmationState.class);
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// The factory
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
private static final String ATTRIBUTE_NAME = OrcidConfirmationState.class
|
||||
.getName();
|
||||
|
||||
static OrcidConfirmationState fetch(HttpServletRequest req) {
|
||||
HttpSession session = req.getSession();
|
||||
Object o = session.getAttribute(ATTRIBUTE_NAME);
|
||||
if (o instanceof OrcidConfirmationState) {
|
||||
return (OrcidConfirmationState) o;
|
||||
} else {
|
||||
OrcidConfirmationState ocs = new OrcidConfirmationState();
|
||||
session.setAttribute(ATTRIBUTE_NAME, ocs);
|
||||
return ocs;
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// The instance
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
public enum Progress {
|
||||
START, DENIED_AUTHENTICATE, FAILED_AUTHENTICATE, GOT_PROFILE, ID_ALREADY_PRESENT, DENIED_ID, FAILED_ID, ADDED_ID
|
||||
}
|
||||
|
||||
private static final Set<Progress> requiresMessage = EnumSet.of(
|
||||
Progress.GOT_PROFILE, Progress.ADDED_ID);
|
||||
|
||||
private Progress progress;
|
||||
private String individualUri;
|
||||
private Set<String> existingOrcids;
|
||||
private OrcidMessage profile;
|
||||
private String profilePageUrl;
|
||||
|
||||
public void reset(String uri, String profileUrl) {
|
||||
progress = START;
|
||||
individualUri = uri;
|
||||
existingOrcids = Collections.emptySet();
|
||||
profile = null;
|
||||
profilePageUrl = profileUrl;
|
||||
}
|
||||
|
||||
public void setExistingOrcids(Set<String> existing) {
|
||||
existingOrcids = new HashSet<>(existing);
|
||||
}
|
||||
|
||||
public void progress(Progress p, OrcidMessage... messages) {
|
||||
progress = p;
|
||||
|
||||
if (requiresMessage.contains(p)) {
|
||||
if (messages.length != 1) {
|
||||
throw new IllegalStateException("Progress to " + p
|
||||
+ " requires an OrcidMessage");
|
||||
}
|
||||
profile = messages[0];
|
||||
} else {
|
||||
if (messages.length != 0) {
|
||||
throw new IllegalStateException("Progress to " + p
|
||||
+ " does not accept an OrcidMessage");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Convenience methods for extracting information from the profile.
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
public String getProgress() {
|
||||
return progress.toString();
|
||||
}
|
||||
|
||||
public String getProgressUrl() {
|
||||
switch (progress) {
|
||||
case START:
|
||||
return UrlBuilder.getUrl(PATH_AUTH_AUTHENTICATE);
|
||||
case GOT_PROFILE:
|
||||
return UrlBuilder.getUrl(PATH_AUTH_EXTERNAL_ID);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public String getIndividualUri() {
|
||||
return individualUri;
|
||||
}
|
||||
|
||||
public String getProfilePageUrl() {
|
||||
return profilePageUrl;
|
||||
}
|
||||
|
||||
public String getOrcid() {
|
||||
return getElementFromOrcidIdentifier("path");
|
||||
|
||||
}
|
||||
|
||||
public String getOrcidUri() {
|
||||
return getElementFromOrcidIdentifier("uri");
|
||||
}
|
||||
|
||||
public ExternalIdentifier getVivoId() {
|
||||
for (ExternalIdentifier id : getExternalIds()) {
|
||||
if (individualUri.equals(id.getExternalIdUrl().getValue())) {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public List<ExternalIdentifier> getExternalIds() {
|
||||
OrcidProfile orcidProfile = getOrcidProfile();
|
||||
if (orcidProfile == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
OrcidBio bio = orcidProfile.getOrcidBio();
|
||||
if (bio == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
ExternalIdentifiers identifiers = bio.getExternalIdentifiers();
|
||||
if (identifiers == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<ExternalIdentifier> list = identifiers.getExternalIdentifier();
|
||||
if (list == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
private String getElementFromOrcidIdentifier(String elementName) {
|
||||
OrcidProfile orcidProfile = getOrcidProfile();
|
||||
if (orcidProfile == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
OrcidId id = orcidProfile.getOrcidIdentifier();
|
||||
if (id == null) {
|
||||
log.warn("There is no ORCID Identifier in the profile.");
|
||||
return "";
|
||||
}
|
||||
|
||||
List<JAXBElement<String>> idElements = id.getContent();
|
||||
if (idElements != null) {
|
||||
for (JAXBElement<String> idElement : idElements) {
|
||||
QName name = idElement.getName();
|
||||
if (name != null && elementName.equals(name.getLocalPart())) {
|
||||
String value = idElement.getValue();
|
||||
if (value != null) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
log.warn("Didn't find the element '' in the ORCID Identifier: " + idElements);
|
||||
return "";
|
||||
}
|
||||
|
||||
private OrcidProfile getOrcidProfile() {
|
||||
if (profile == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
OrcidProfile orcidProfile = profile.getOrcidProfile();
|
||||
if (orcidProfile == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return orcidProfile;
|
||||
}
|
||||
|
||||
public Map<String, Object> toMap() {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("progress", progress.toString());
|
||||
map.put("individualUri", individualUri);
|
||||
map.put("profilePage", profilePageUrl);
|
||||
map.put("orcid", getOrcid());
|
||||
map.put("orcidUri", getOrcidUri());
|
||||
map.put("hasVivoId", getVivoId() == null);
|
||||
map.put("externalIds", formatExternalIds());
|
||||
map.put("existingOrcids", existingOrcids);
|
||||
|
||||
String progressUrl = getProgressUrl();
|
||||
if (progressUrl == null) {
|
||||
map.put("progressUrl", "");
|
||||
} else {
|
||||
map.put("progressUrl", progressUrl);
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
private List<Map<String, String>> formatExternalIds() {
|
||||
List<Map<String, String>> list = new ArrayList<>();
|
||||
for (ExternalIdentifier id : getExternalIds()) {
|
||||
Map<String, String> map = new HashMap<>();
|
||||
map.put("commonName", id.getExternalIdCommonName().getContent());
|
||||
map.put("reference", id.getExternalIdReference().getContent());
|
||||
map.put("uri", id.getExternalIdUrl().getValue());
|
||||
list.add(map);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,126 @@
|
|||
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
|
||||
|
||||
package edu.cornell.mannlib.vivo.orcid.controller;
|
||||
|
||||
import static edu.cornell.mannlib.orcidclient.actions.ApiAction.ADD_EXTERNAL_ID;
|
||||
import static edu.cornell.mannlib.orcidclient.actions.ApiAction.READ_PROFILE;
|
||||
import static edu.cornell.mannlib.vivo.orcid.OrcidIdDataGetter.ORCID_ID;
|
||||
import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import edu.cornell.mannlib.vedit.beans.LoginStatusBean;
|
||||
import edu.cornell.mannlib.vitro.webapp.auth.identifier.IdentifierBundle;
|
||||
import edu.cornell.mannlib.vitro.webapp.auth.identifier.RequestIdentifiers;
|
||||
import edu.cornell.mannlib.vitro.webapp.auth.identifier.common.HasProfile;
|
||||
import edu.cornell.mannlib.vitro.webapp.beans.Individual;
|
||||
import edu.cornell.mannlib.vitro.webapp.beans.ObjectPropertyStatement;
|
||||
import edu.cornell.mannlib.vitro.webapp.beans.UserAccount;
|
||||
import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest;
|
||||
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.UrlBuilder;
|
||||
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.NotAuthorizedResponseValues;
|
||||
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.ResponseValues;
|
||||
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.TemplateResponseValues;
|
||||
|
||||
/**
|
||||
* A request came from the "Confirm" button on the individual profile. Get a
|
||||
* fresh state object, clear the AuthorizationCache and show the confirmation
|
||||
* page.
|
||||
*/
|
||||
public class OrcidDefaultHandler extends OrcidAbstractHandler {
|
||||
private static final Log log = LogFactory.getLog(OrcidDefaultHandler.class);
|
||||
|
||||
private Individual individual;
|
||||
private final Set<String> existingOrcids = new HashSet<>();
|
||||
|
||||
public OrcidDefaultHandler(VitroRequest vreq) {
|
||||
super(vreq);
|
||||
}
|
||||
|
||||
public ResponseValues exec() {
|
||||
try {
|
||||
initializeState();
|
||||
initializeAuthorizationCache();
|
||||
} catch (Exception e) {
|
||||
log.error("No proper individual URI on the request", e);
|
||||
return show400BadRequest(e);
|
||||
}
|
||||
|
||||
if (!isAuthorized()) {
|
||||
return showNotAuthorized();
|
||||
}
|
||||
|
||||
return showConfirmationPage();
|
||||
}
|
||||
|
||||
private void initializeState() {
|
||||
String uri = vreq.getParameter("individualUri");
|
||||
if (uri == null) {
|
||||
throw new IllegalStateException(
|
||||
"No 'individualUri' parameter on request.");
|
||||
}
|
||||
|
||||
String profilePage = UrlBuilder.getIndividualProfileUrl(uri, vreq);
|
||||
state.reset(uri, profilePage);
|
||||
|
||||
individual = findIndividual();
|
||||
locateExistingOrcids();
|
||||
state.setExistingOrcids(existingOrcids);
|
||||
}
|
||||
|
||||
private void locateExistingOrcids() {
|
||||
if (individual == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<ObjectPropertyStatement> opss = individual
|
||||
.getObjectPropertyStatements(ORCID_ID);
|
||||
if (opss == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (ObjectPropertyStatement ops : opss) {
|
||||
existingOrcids.add(ops.getObjectURI());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void initializeAuthorizationCache() {
|
||||
auth.clearStatus(READ_PROFILE);
|
||||
auth.clearStatus(ADD_EXTERNAL_ID);
|
||||
}
|
||||
|
||||
private ResponseValues show400BadRequest(Exception e) {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("title", "400 Bad Request");
|
||||
map.put("errorMessage", e.getMessage());
|
||||
return new TemplateResponseValues("error-titled.ftl", map,
|
||||
SC_BAD_REQUEST);
|
||||
}
|
||||
|
||||
private boolean isAuthorized() {
|
||||
// Only a self-editor is authorized.
|
||||
IdentifierBundle ids = RequestIdentifiers.getIdBundleForRequest(vreq);
|
||||
Collection<String> profileUris = HasProfile.getProfileUris(ids);
|
||||
log.debug("Authorized? individualUri=" + state.getIndividualUri()
|
||||
+ ", profileUris=" + profileUris);
|
||||
return profileUris.contains(state.getIndividualUri());
|
||||
}
|
||||
|
||||
private ResponseValues showNotAuthorized() {
|
||||
UserAccount user = LoginStatusBean.getCurrentUser(vreq);
|
||||
String userName = (user == null) ? "ANONYMOUS" : user.getEmailAddress();
|
||||
return new NotAuthorizedResponseValues(userName
|
||||
+ "is not authorized for ORCID operations on '" + individual
|
||||
+ "'");
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
|
||||
|
||||
package edu.cornell.mannlib.vivo.orcid.controller;
|
||||
|
||||
import edu.cornell.mannlib.orcidclient.OrcidClientException;
|
||||
|
||||
/**
|
||||
* The OrcidConfirmationState is not as we expected. Probably deserves a 500
|
||||
* error.
|
||||
*/
|
||||
public class OrcidIllegalStateException extends OrcidClientException {
|
||||
public OrcidIllegalStateException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public OrcidIllegalStateException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,140 @@
|
|||
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
|
||||
|
||||
package edu.cornell.mannlib.vivo.orcid.controller;
|
||||
|
||||
import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
|
||||
import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import edu.cornell.mannlib.orcidclient.context.OrcidClientContext;
|
||||
import edu.cornell.mannlib.orcidclient.context.OrcidClientContext.Setting;
|
||||
import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.AuthorizationRequest;
|
||||
import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest;
|
||||
import edu.cornell.mannlib.vitro.webapp.controller.authenticate.LogoutRedirector;
|
||||
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.FreemarkerHttpServlet;
|
||||
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.ExceptionResponseValues;
|
||||
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.ResponseValues;
|
||||
|
||||
/**
|
||||
* New workflow:
|
||||
*
|
||||
* <pre>
|
||||
* Default: clear status for both readProfile and addExternalIDs
|
||||
* show intro screen orcidOffer.ftl
|
||||
* The click "do it", goes to /getProfileAuth
|
||||
* Or "return to profile"
|
||||
* /getProfileAuth: If already authorized, redirect to /readProfile
|
||||
* Else, do the dance, ending with /readProfile callback
|
||||
* Denied? show orcidDenied.ftl
|
||||
* Failed? show orcidFailed.ftl
|
||||
* /readProfile: read the profile, store in status
|
||||
* figure external ID options, show orcidOfferIds.ftl
|
||||
* If they click "do it", goes /authExternalIds
|
||||
* If they click "nah", return to profile
|
||||
* /authExternalIds: if already authorized, redirect to /addExternalIds
|
||||
* Else, do the dance, ending with /addExternalIds callback
|
||||
* /addExternalIds add one or both IDs, store new profile in status
|
||||
* show orcidSuccess.ftl with "return to profile" and "view profile" links.
|
||||
* </pre>
|
||||
*/
|
||||
public class OrcidIntegrationController extends FreemarkerHttpServlet {
|
||||
private static final Log log = LogFactory
|
||||
.getLog(OrcidIntegrationController.class);
|
||||
|
||||
private final static String PATHINFO_CALLBACK = "/callback";
|
||||
private final static String PATHINFO_AUTH_AUTHENTICATE = "/getAuthticateAuth";
|
||||
private final static String PATHINFO_READ_PROFILE = "/readProfile";
|
||||
private final static String PATHINFO_AUTH_EXTERNAL_ID = "/authExternalId";
|
||||
private final static String PATHINFO_ADD_EXTERNAL_ID = "/addExternalId";
|
||||
|
||||
public final static String PATH_DEFAULT = "orcid";
|
||||
|
||||
final static String PATH_AUTH_AUTHENTICATE = path(PATHINFO_AUTH_AUTHENTICATE);
|
||||
final static String PATH_READ_PROFILE = path(PATHINFO_READ_PROFILE);
|
||||
final static String PATH_AUTH_EXTERNAL_ID = path(PATHINFO_AUTH_EXTERNAL_ID);
|
||||
final static String PATH_ADD_EXTERNAL_ID = path(PATHINFO_ADD_EXTERNAL_ID);
|
||||
|
||||
static String path(String pathInfo) {
|
||||
return PATH_DEFAULT + pathInfo;
|
||||
}
|
||||
|
||||
final static String TEMPLATE_CONFIRM = "orcidConfirm.ftl";
|
||||
|
||||
public static final String PROPERTY_EXTERNAL_ID_COMMON_NAME = "orcid.externalIdCommonName";
|
||||
public static final String DEFAULT_EXTERNAL_ID_COMMON_NAME = "VIVO Identifier";
|
||||
|
||||
/**
|
||||
* Get in before FreemarkerHttpServlet for special handling.
|
||||
*/
|
||||
@Override
|
||||
public void doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws IOException, ServletException {
|
||||
if (!isOrcidConfigured()) {
|
||||
show404NotFound(resp);
|
||||
}
|
||||
if (PATHINFO_CALLBACK.equals(req.getPathInfo())) {
|
||||
new OrcidCallbackHandler(req, resp).exec();
|
||||
} else {
|
||||
super.doGet(req, resp);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We return AUTHORIZED here, but we want the LogoutRedirector to know that
|
||||
* the user should not remain on this page after logging out.
|
||||
*/
|
||||
@Override
|
||||
protected AuthorizationRequest requiredActions(VitroRequest vreq) {
|
||||
LogoutRedirector.recordRestrictedPageUri(vreq);
|
||||
return AuthorizationRequest.AUTHORIZED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Look at the path info and delegate to a handler.
|
||||
*/
|
||||
@Override
|
||||
protected ResponseValues processRequest(VitroRequest vreq) throws Exception {
|
||||
try {
|
||||
String pathInfo = vreq.getPathInfo();
|
||||
log.debug("Path info: " + pathInfo);
|
||||
if (PATHINFO_AUTH_AUTHENTICATE.equals(pathInfo)) {
|
||||
return new OrcidAuthAuthenticateHandler(vreq).exec();
|
||||
} else if (PATHINFO_READ_PROFILE.equals(pathInfo)) {
|
||||
return new OrcidReadProfileHandler(vreq).exec();
|
||||
} else if (PATHINFO_AUTH_EXTERNAL_ID.equals(pathInfo)) {
|
||||
return new OrcidAuthExternalIdsHandler(vreq).exec();
|
||||
} else if (PATHINFO_ADD_EXTERNAL_ID.equals(pathInfo)) {
|
||||
return new OrcidAddExternalIdHandler(vreq).exec();
|
||||
} else {
|
||||
return new OrcidDefaultHandler(vreq).exec();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return new ExceptionResponseValues(e, SC_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If the ORCID interface is configured, it should not throw an exception
|
||||
* when asked for the value of a setting.
|
||||
*/
|
||||
private boolean isOrcidConfigured() {
|
||||
try {
|
||||
OrcidClientContext.getInstance().getSetting(Setting.CLIENT_ID);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void show404NotFound(HttpServletResponse resp) throws IOException {
|
||||
resp.sendError(SC_NOT_FOUND);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
|
||||
|
||||
package edu.cornell.mannlib.vivo.orcid.controller;
|
||||
|
||||
import static edu.cornell.mannlib.orcidclient.actions.ApiAction.AUTHENTICATE;
|
||||
import static edu.cornell.mannlib.vivo.orcid.controller.OrcidConfirmationState.Progress.DENIED_AUTHENTICATE;
|
||||
import static edu.cornell.mannlib.vivo.orcid.controller.OrcidConfirmationState.Progress.FAILED_AUTHENTICATE;
|
||||
import static edu.cornell.mannlib.vivo.orcid.controller.OrcidConfirmationState.Progress.GOT_PROFILE;
|
||||
import static edu.cornell.mannlib.vivo.orcid.controller.OrcidConfirmationState.Progress.ID_ALREADY_PRESENT;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import edu.cornell.mannlib.orcidclient.OrcidClientException;
|
||||
import edu.cornell.mannlib.orcidclient.actions.ReadPublicBioAction;
|
||||
import edu.cornell.mannlib.orcidclient.auth.AuthorizationStatus;
|
||||
import edu.cornell.mannlib.orcidclient.orcidmessage.OrcidMessage;
|
||||
import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest;
|
||||
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.ResponseValues;
|
||||
|
||||
/**
|
||||
* We should now know the user's ORCID, so read the user's public ORCID profile.
|
||||
*/
|
||||
public class OrcidReadProfileHandler extends OrcidAbstractHandler {
|
||||
private static final Log log = LogFactory
|
||||
.getLog(OrcidReadProfileHandler.class);
|
||||
|
||||
private AuthorizationStatus status;
|
||||
private OrcidMessage profile;
|
||||
|
||||
protected OrcidReadProfileHandler(VitroRequest vreq) {
|
||||
super(vreq);
|
||||
}
|
||||
|
||||
public ResponseValues exec() throws OrcidClientException {
|
||||
status = auth.getAuthorizationStatus(AUTHENTICATE);
|
||||
if (status.isSuccess()) {
|
||||
readProfile();
|
||||
state.progress(GOT_PROFILE, profile);
|
||||
|
||||
recordConfirmation();
|
||||
|
||||
if (state.getVivoId() != null) {
|
||||
state.progress(ID_ALREADY_PRESENT);
|
||||
}
|
||||
|
||||
return showConfirmationPage();
|
||||
} else if (status.isDenied()) {
|
||||
return showConfirmationPage(DENIED_AUTHENTICATE);
|
||||
} else {
|
||||
return showConfirmationPage(FAILED_AUTHENTICATE);
|
||||
}
|
||||
}
|
||||
|
||||
private void readProfile() throws OrcidClientException {
|
||||
profile = new ReadPublicBioAction().execute(status.getAccessToken()
|
||||
.getOrcid());
|
||||
log.debug("Read profile");
|
||||
}
|
||||
|
||||
}
|
|
@ -21,6 +21,10 @@
|
|||
<#assign languageCount = 1>
|
||||
</#if>
|
||||
<#assign visRequestingTemplate = "foaf-person-wilma">
|
||||
|
||||
<#--add the VIVO-ORCID interface -->
|
||||
<#include "individual-orcidInterface.ftl">
|
||||
|
||||
<section id="individual-intro" class="vcard person" role="region">
|
||||
|
||||
<section id="share-contact" role="region">
|
||||
|
|
65
utilities/acceptance-tests/testApp/TestListRDF.html
Normal file
65
utilities/acceptance-tests/testApp/TestListRDF.html
Normal file
|
@ -0,0 +1,65 @@
|
|||
<!-- $This file is distributed under the terms of the license in /doc/license.txt$ -->
|
||||
|
||||
<script src="js/jquery.js"></script>
|
||||
|
||||
<script>
|
||||
function TestListRdf() {
|
||||
self = this;
|
||||
|
||||
this.setup = setupButtons;
|
||||
|
||||
function setupButtons() {
|
||||
document.getElementById("submit_button").onclick = function() {
|
||||
requestWithAcceptHeader(
|
||||
$("#acceptHeader").val(),
|
||||
$("#vclass").val());
|
||||
}
|
||||
}
|
||||
|
||||
function requestWithAcceptHeader(mimetype, vclass) {
|
||||
var parms = {
|
||||
type: "GET",
|
||||
url: "/vivo/listrdf",
|
||||
headers: {Accept: mimetype},
|
||||
data: {vclass: vclass},
|
||||
dataType: "text",
|
||||
complete: displayResult
|
||||
};
|
||||
$.ajax(parms);
|
||||
}
|
||||
|
||||
function displayResult(xhr, status) {
|
||||
$("#responseCode").text(xhr.status);
|
||||
$("#mimeType").text(xhr.getResponseHeader("Content-Type"));
|
||||
$("#responseText").text(xhr.responseText);
|
||||
}
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
new TestListRdf().setup();
|
||||
});
|
||||
</script>
|
||||
|
||||
<h1>Test the List RDF API</h1>
|
||||
|
||||
<h3>Request data</h3>
|
||||
<table>
|
||||
<tr>
|
||||
<td>Accept header</td>
|
||||
<td><input type="text" size="40" id="acceptHeader"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>VClass URI</td>
|
||||
<td><input type="text" size="80" id="vclass"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><input type="submit" value="submit the request" id="submit_button"></td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h2>Response data</h2>
|
||||
<div>Response code is <b><span id="responseCode">000</span></b></div>
|
||||
<div>MIME type is <b><span id="mimeType">No type</span></b></div>
|
||||
<div>Text is:</div>
|
||||
<div><pre id="responseText" style="font-size:small; font-family:monospace">No text</pre></div>
|
|
@ -3,7 +3,7 @@
|
|||
<script src="js/jquery.js"></script>
|
||||
|
||||
<script>
|
||||
function TestLOD() {
|
||||
function TestSparql() {
|
||||
self = this;
|
||||
|
||||
this.setup = setupButtons;
|
||||
|
@ -38,11 +38,11 @@ function TestLOD() {
|
|||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
new TestLOD().setup();
|
||||
new TestSparql().setup();
|
||||
});
|
||||
</script>
|
||||
|
||||
<h1>Test the Linked Open Data requests</h1>
|
||||
<h1>Test the SPARQL Query API</h1>
|
||||
|
||||
<h3>Request data</h3>
|
||||
<table>
|
||||
|
|
BIN
utilities/orcid/mockorcid.war
Normal file
BIN
utilities/orcid/mockorcid.war
Normal file
Binary file not shown.
16
utilities/performance-measurement/README.txt
Normal file
16
utilities/performance-measurement/README.txt
Normal file
|
@ -0,0 +1,16 @@
|
|||
Ruby scripts for analyzing the SPARQL queries that were written to the log by RDFServiceLogger.
|
||||
|
||||
Run VIVO, enable logging of SPARQL queries, through the Developer panel, load the page of interest.
|
||||
Then, run the script to summarize the results.
|
||||
|
||||
Run it like this:
|
||||
./scan_query_times.rb partitions /Users/jeb228/Development/Performance/Weill/tomcat/logs/vivo.all.log overlap unmatched
|
||||
where:
|
||||
"partitions" is the file that defines how the timings will be broken down. Check the "partitions"
|
||||
file in this directory for an example
|
||||
"......vivo.all.log" is the VIVO log file to be analyzed.
|
||||
"overlap" is the file where the queries are written to, if they match more than one criterion.
|
||||
"unmatched" is the file where queries are written if they match no criteria.
|
||||
|
||||
Read the comments in scan_query_times.rb for more information.
|
||||
|
114
utilities/performance-measurement/partition.rb
Normal file
114
utilities/performance-measurement/partition.rb
Normal file
|
@ -0,0 +1,114 @@
|
|||
class Criterion
|
||||
attr_accessor :expression
|
||||
attr_accessor :count
|
||||
attr_accessor :total_time
|
||||
def initialize(expr)
|
||||
@expression = Regexp.new(expr, Regexp::MULTILINE)
|
||||
@count = 0
|
||||
@total_time = 0.0
|
||||
end
|
||||
|
||||
def add(log_record)
|
||||
@count += 1
|
||||
@total_time += log_record.time
|
||||
end
|
||||
|
||||
def to_s()
|
||||
"#{self.class}: count=#{@count}, total_time=#{format("%.3f", @total_time)}, expression='#{@expression}'"
|
||||
end
|
||||
end
|
||||
|
||||
class QueryCriterion < Criterion
|
||||
def test(log_record)
|
||||
if expression.match(log_record.query)
|
||||
add(log_record)
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class StackCriterion < Criterion
|
||||
def test(log_record)
|
||||
if expression.match(log_record.stack.join())
|
||||
add(log_record)
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class WritingCriterion < Criterion
|
||||
def initialize(expr, filename)
|
||||
super(expr)
|
||||
@file = File.new(filename, "w")
|
||||
end
|
||||
|
||||
def add(log_record)
|
||||
super
|
||||
@file.write("#{@expression}\n\n--------------------------\n\n")
|
||||
@file.write(log_record.to_s)
|
||||
@file.write("\n--------------------------\n\n")
|
||||
end
|
||||
end
|
||||
|
||||
class UnmatchedCriterion < WritingCriterion
|
||||
def initialize(filename)
|
||||
super("UNMATCHED", filename)
|
||||
end
|
||||
end
|
||||
|
||||
class OverlapCriterion < WritingCriterion
|
||||
def initialize(filename)
|
||||
super("OVERLAP", filename)
|
||||
end
|
||||
end
|
||||
|
||||
class Partition
|
||||
# ------------------------------------------------------------------------------------
|
||||
private
|
||||
# ------------------------------------------------------------------------------------
|
||||
#
|
||||
def parse_partition_file(partition_file)
|
||||
@criteria = []
|
||||
File.open(partition_file).each() do |line|
|
||||
next if line.strip().empty? || line.start_with?('#')
|
||||
if line.start_with?("QUERY ")
|
||||
@criteria << QueryCriterion.new(line.slice(6..-1).strip)
|
||||
elsif line.start_with?("STACK ")
|
||||
@criteria << StackCriterion.new(line.slice(6..-1).strip)
|
||||
else
|
||||
raise "Invalid line in partition file: '#{line}'"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def match_against_criteria(log_record)
|
||||
matches = 0
|
||||
@criteria.each do |c|
|
||||
matches += c.test(log_record) ? 1 : 0
|
||||
end
|
||||
if matches == 0
|
||||
@unmatched.add(log_record)
|
||||
elsif matches > 1
|
||||
@overlap.add(log_record)
|
||||
end
|
||||
end
|
||||
|
||||
# ------------------------------------------------------------------------------------
|
||||
public
|
||||
# ------------------------------------------------------------------------------------
|
||||
|
||||
def initialize(partition_file, overlap_file, unmatched_file)
|
||||
parse_partition_file(partition_file)
|
||||
@overlap = OverlapCriterion.new(overlap_file)
|
||||
@unmatched = UnmatchedCriterion.new(unmatched_file)
|
||||
end
|
||||
|
||||
def accumulate(log_record)
|
||||
match_against_criteria(log_record)
|
||||
end
|
||||
|
||||
def report
|
||||
puts "#{@criteria.join("\n")}\n#{@overlap}\n#{@unmatched}"
|
||||
end
|
||||
|
||||
end
|
10
utilities/performance-measurement/partitions
Normal file
10
utilities/performance-measurement/partitions
Normal file
|
@ -0,0 +1,10 @@
|
|||
STACK AjaxVisualizationController
|
||||
#STACK IndividualRequestAnalysisContextImpl
|
||||
#STACK IndividualResponseBuilder
|
||||
# STACK BaseIndividualTemplateModel
|
||||
#STACK \WIndividualTemplateModel
|
||||
#STACK FileServingServlet
|
||||
#QUERY authorshipValue
|
||||
#QUERY infoResourceValue
|
||||
#QUERY editorObj
|
||||
#QUERY IAO_0000030
|
106
utilities/performance-measurement/scan_query_times.rb
Executable file
106
utilities/performance-measurement/scan_query_times.rb
Executable file
|
@ -0,0 +1,106 @@
|
|||
#!/usr/bin/ruby
|
||||
=begin
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
A utility that reads query times from a VIVO log file, and sums them based on a
|
||||
list of partitioning expressions.
|
||||
|
||||
1) The file of partitioning expressions is parsed and stored.
|
||||
2) The log file is scanned for records from the RDFServiceLogger, which are
|
||||
accumulated in the program storage.
|
||||
3) The partitioning expressions are tested against each log record, and matches
|
||||
are recorded.
|
||||
4) For each partioning expression, report:
|
||||
a) the expression
|
||||
b) how many queries matched it
|
||||
c) the total time spent in those queries
|
||||
5) As errors, report:
|
||||
a) how many queries (and how much time) matched no expression. Dump a few of
|
||||
them to the error file.
|
||||
b) how many queries (and how much time) matched multiple expressions. Dump a
|
||||
few of them to the error file.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
Command line: scan_query_times.rb [partition_file] [log_file] [overlap_file] [unmatched_file]
|
||||
If the wrong number of arguments, abort.
|
||||
If either file does not exist, abort.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
Partitioning expressions: parse the lines in the file
|
||||
If a line is empty, ignore it.
|
||||
If a line begins with a #, ignore it.
|
||||
If a line begins with the word "STACK ", then the remainder of the line
|
||||
(trimmed) is a regular expression to be matched against the stack trace of
|
||||
each log record.
|
||||
If a line begins with the word "QUERY ", then the remainder of the line
|
||||
(trimmed) is a regular expression to be matched against the query text of
|
||||
each log record.
|
||||
Otherwise, abort.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
A log record begins like this:
|
||||
2014-04-29 14:26:00,798 INFO [RDFServiceLogger] 0.001 sparqlAskQuery [ASK {...
|
||||
|
||||
The timestamp is ignored.
|
||||
The log leve is ignored.
|
||||
The logging class must be "RDFServiceLogger"
|
||||
The time is recorded.
|
||||
The method name is ignored.
|
||||
The remainder of the text on that line (trimmed) is recorded as the query.
|
||||
Any subsequent lines are the stack trace.
|
||||
|
||||
The end of the log record is a line that does not begin with whitespace.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
=end
|
||||
|
||||
$: << File.dirname(File.expand_path(__FILE__))
|
||||
|
||||
require 'partition'
|
||||
require 'scanner'
|
||||
|
||||
class UsageError < StandardError; end
|
||||
|
||||
#
|
||||
# Parse the arguments and complain if they don't make sense.
|
||||
#
|
||||
def parse_args(args)
|
||||
raise UsageError, "usage is: scan_query_times.rb <partition_file> <log_file> <overlap_file> <unmatched_file>" unless (args.length == 4)
|
||||
|
||||
partition_file = args[0]
|
||||
raise UsageError, "File '#{partition_file}' does not exist." unless File.exist?(partition_file)
|
||||
log_file = args[1]
|
||||
raise UsageError, "File '#{log_file}' does not exist." unless File.exist?(log_file)
|
||||
overlap_file = args[2]
|
||||
raise UsageError, "File '#{overlap_file}' does not exist." unless File.exist?(overlap_file)
|
||||
unmatched_file = args[3]
|
||||
raise UsageError, "File '#{unmatched_file}' does not exist." unless File.exist?(unmatched_file)
|
||||
|
||||
return partition_file, log_file, overlap_file, unmatched_file
|
||||
end
|
||||
|
||||
#
|
||||
#
|
||||
# ------------------------------------------------------------------------------------
|
||||
# Standalone calling.
|
||||
#
|
||||
# Do this if this program was called from the command line. That is, if the command
|
||||
# expands to the path of this file.
|
||||
# ------------------------------------------------------------------------------------
|
||||
#
|
||||
|
||||
if File.expand_path($0) == File.expand_path(__FILE__)
|
||||
begin
|
||||
partition_file, log_file, overlap_file, unmatched_file = parse_args(ARGV)
|
||||
partition = Partition.new(partition_file, overlap_file, unmatched_file)
|
||||
scanner = Scanner.new(partition)
|
||||
scanner.process(log_file)
|
||||
partition.report
|
||||
rescue UsageError => e
|
||||
puts "\n----------------\nUsage error\n----------------\n\n#{e}\n\n----------------\n\n"
|
||||
exit
|
||||
end
|
||||
end
|
116
utilities/performance-measurement/scanner.rb
Normal file
116
utilities/performance-measurement/scanner.rb
Normal file
|
@ -0,0 +1,116 @@
|
|||
class LogRecord
|
||||
attr_accessor :time
|
||||
attr_accessor :method
|
||||
attr_accessor :query
|
||||
attr_accessor :stack
|
||||
def to_s
|
||||
"LogRecord: time=#{@time}\n query: #{@query}\n stack - #{@stack.size} lines:\n #{@stack.join(" ")}"
|
||||
end
|
||||
end
|
||||
|
||||
class LogFileParser
|
||||
# ------------------------------------------------------------------------------------
|
||||
private
|
||||
# ------------------------------------------------------------------------------------
|
||||
#
|
||||
def get_line()
|
||||
if @line_buffer
|
||||
line = @line_buffer
|
||||
@line_buffer = nil
|
||||
return line
|
||||
elsif @file.eof?
|
||||
return nil
|
||||
else
|
||||
@lines += 1
|
||||
return @file.readline
|
||||
end
|
||||
end
|
||||
|
||||
def unget_line(line)
|
||||
@line_buffer = line
|
||||
end
|
||||
|
||||
def parse_first_line(line)
|
||||
match = /\[RDFServiceLogger\]\s+([0-9.]+).*\[(\w*, )?(.+)\]\s*$/.match(line)
|
||||
return nil unless match
|
||||
|
||||
log_record = LogRecord.new
|
||||
log_record.time = match[1].to_f
|
||||
log_record.query = match[3].strip
|
||||
log_record.stack = []
|
||||
return log_record
|
||||
end
|
||||
|
||||
def is_stack_line(line)
|
||||
return false unless line
|
||||
return line.start_with?(' ')
|
||||
end
|
||||
|
||||
def next_record()
|
||||
while true
|
||||
first_line = get_line
|
||||
return nil unless first_line
|
||||
log_record = parse_first_line(first_line)
|
||||
break if log_record
|
||||
end
|
||||
|
||||
while true
|
||||
stack_line = get_line
|
||||
if is_stack_line(stack_line)
|
||||
log_record.stack << stack_line
|
||||
else
|
||||
unget_line(stack_line)
|
||||
return log_record
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# ------------------------------------------------------------------------------------
|
||||
public
|
||||
|
||||
# ------------------------------------------------------------------------------------
|
||||
#
|
||||
def parse_records()
|
||||
@file = File.new(@log_file)
|
||||
begin
|
||||
while true
|
||||
record = next_record()
|
||||
break unless record
|
||||
yield record
|
||||
end
|
||||
ensure
|
||||
@file.close
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(log_file)
|
||||
@log_file = log_file
|
||||
@line_buffer = nil
|
||||
@lines = 0
|
||||
end
|
||||
|
||||
def report()
|
||||
puts "LogFileParser: read #{@lines} lines from '#{@log_file}'"
|
||||
end
|
||||
end
|
||||
|
||||
class Scanner
|
||||
# ------------------------------------------------------------------------------------
|
||||
private
|
||||
# ------------------------------------------------------------------------------------
|
||||
|
||||
# ------------------------------------------------------------------------------------
|
||||
public
|
||||
# ------------------------------------------------------------------------------------
|
||||
def initialize(partition)
|
||||
@partition = partition
|
||||
end
|
||||
|
||||
def process(log_file)
|
||||
lfp = LogFileParser.new(log_file)
|
||||
lfp.parse_records() do | log_record |
|
||||
@partition.accumulate(log_record)
|
||||
end
|
||||
lfp.report
|
||||
end
|
||||
end
|
Loading…
Add table
Add a link
Reference in a new issue