Merge branch 'develop' of https://github.com/vivo-project/VIVO into develop

This commit is contained in:
hudajkhan 2014-05-20 11:01:09 -04:00
commit 8a73bd5ae9
37 changed files with 2054 additions and 10 deletions

View file

@ -198,6 +198,31 @@ VitroConnection.DataSource.validationQuery = SELECT 1
# languages.selectableLocales = en_US, es_GO # 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 # OTHER OPTIONS
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------

View file

@ -40,3 +40,6 @@ productMods/js/jquery_plugins/jquery.truncator.js
# Part of the OpenSocial integration - should this really be under the Apache license? # Part of the OpenSocial integration - should this really be under the Apache license?
themes/wilma/css/openSocial/gadgets.css themes/wilma/css/openSocial/gadgets.css
# JQuery for the Selenium test helper app.
utilities/acceptance-tests/testApp/js/jquery.js

View file

@ -191,3 +191,7 @@ xsdlib
None None
---- ----
Trippi Trippi
ORCID API client
----------------
Written as part of the ORCID A&I grant. Source is available at https://github.com/j2blake/orcid-api-client

Binary file not shown.

View file

@ -85,5 +85,7 @@ org.apache.commons.fileupload.servlet.FileCleanerCleanup
# and the PermissionRegistry must already be set up. # and the PermissionRegistry must already be set up.
edu.cornell.mannlib.vitro.webapp.dao.jena.VClassGroupCache$Setup 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. # 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 edu.cornell.mannlib.vitro.webapp.servlet.setup.SolrSmokeTest

View file

@ -1400,6 +1400,14 @@
<url-pattern>/searchService/*</url-pattern> <url-pattern>/searchService/*</url-pattern>
</servlet-mapping> </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 ============================== --> <!-- ==================== tag libraries ============================== -->
<jsp-config> <jsp-config>

View file

@ -10,6 +10,7 @@
PREFIX afn: &lt;http://jena.hpl.hp.com/ARQ/function#&gt; PREFIX afn: &lt;http://jena.hpl.hp.com/ARQ/function#&gt;
PREFIX foaf: &lt;http://xmlns.com/foaf/0.1/&gt; PREFIX foaf: &lt;http://xmlns.com/foaf/0.1/&gt;
PREFIX vitro: &lt;http://vitro.mannlib.cornell.edu/ns/vitro/0.7#&gt; PREFIX vitro: &lt;http://vitro.mannlib.cornell.edu/ns/vitro/0.7#&gt;
PREFIX vcard: &lt;http://www.w3.org/2006/vcard/ns#&gt;
SELECT DISTINCT <collated> ?subclass </collated> SELECT DISTINCT <collated> ?subclass </collated>
?authorship ?authorship
@ -27,6 +28,17 @@
?subclass rdfs:subClassOf foaf:Agent ?subclass rdfs:subClassOf foaf:Agent
} }
</collated> </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> <critical-data-required>
FILTER ( bound(?author) ) FILTER ( bound(?author) )
@ -74,5 +86,45 @@
} }
</query-construct> </query-construct>
<query-construct>
PREFIX core: &lt;http://vivoweb.org/ontology/core#&gt;
PREFIX rdfs: &lt;http://www.w3.org/2000/01/rdf-schema#&gt;
PREFIX vitro: &lt;http://vitro.mannlib.cornell.edu/ns/vitro/0.7#&gt;
PREFIX vcard: &lt;http://www.w3.org/2006/vcard/ns#&gt;
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> <template>propStatement-informationResourceInAuthorship.ftl</template>
</list-view-config> </list-view-config>

View file

@ -87,7 +87,7 @@ $(document).ready(function(){
}); });
var viewMore = "<ul id='viewMoreFac'><li><a href='" var viewMore = "<ul id='viewMoreFac'><li><a href='"
+ urlsBase + urlsBase
+ "/people/%23http://vivoweb.org/ontology/core%23FacultyMember' alt='" + "/people#http://vivoweb.org/ontology/core#FacultyMember' alt='"
+ i18nStrings.viewAllFaculty + "'>" + i18nStrings.viewAllFaculty + "'>"
+ i18nStrings.viewAllString + "</a></li?</ul>"; + i18nStrings.viewAllString + "</a></li?</ul>";
$('div#research-faculty-mbrs').append(viewMore); $('div#research-faculty-mbrs').append(viewMore);
@ -155,7 +155,7 @@ $(document).ready(function(){
html += "</ul><ul style='list-style:none'>" html += "</ul><ul style='list-style:none'>"
+ "<li style='font-size:0.9em;text-align:right;padding: 6px 16px 0 0'><a href='" + "<li style='font-size:0.9em;text-align:right;padding: 6px 16px 0 0'><a href='"
+ urlsBase + urlsBase
+ "/organizations/%23http://vivoweb.org/ontology/core%23AcademicDepartment' alt='" + "/organizations#http://vivoweb.org/ontology/core#AcademicDepartment' alt='"
+ i18nStrings.viewAllDepartments + "'>" + i18nStrings.viewAllDepartments + "'>"
+ i18nStrings.viewAllString + "</a></li></ul>"; + i18nStrings.viewAllString + "</a></li></ul>";
} }

View 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>

View file

@ -11,15 +11,27 @@
<h2 id="facultyResearchAreas" class="mainPropGroup"> <h2 id="facultyResearchAreas" class="mainPropGroup">
${headingText} ${headingText}
</h2> </h2>
<#assign numberRows = researchAreaResults?size/>
<ul id="individual-hasResearchArea" role="list"> <ul id="individual-hasResearchArea" role="list">
<#assign totalLength = 0 >
<#assign moreDisplayed = false> <#assign moreDisplayed = false>
<#list researchAreaResults as resultRow> <#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"> <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"]} ${resultRow["raLabel"]}
</a> </a>
</li> </li>
<#assign totalLength = totalLength + resultRow["raLabel"]?length >
</#list> </#list>
<#if ( totalLength > 380 ) ><li id="raLessContainer" style="display:none">(<a id="raLess" href="javascript:">less</a>)</li></#if>
</ul> </ul>
</#if> </#if>
<script> <script>

View file

@ -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>

View file

@ -9,6 +9,13 @@
<#macro showStatement statement> <#macro showStatement statement>
<a href="${statement.value!}" title="ORCID iD" target="_blank">${statement.value!"ORCID iD not found"}</a> <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> </#macro>

View file

@ -0,0 +1,2 @@
<http://orcid.org/1234-1231-1231-3333>
<http://vivoweb.org/ontology/core#validatedOrcidId> <http://vivo.mydomain.edu/individual/n4571> .

View 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> .

View 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 .

View file

@ -17,9 +17,9 @@ public class SiteAdminController extends BaseSiteAdminController {
private static final Log log = LogFactory.getLog(SiteAdminController.class); private static final Log log = LogFactory.getLog(SiteAdminController.class);
@Override @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)) { if (PolicyHelper.isAuthorizedForActions(vreq, ToolsRequestHandler.REQUIRED_ACTIONS)) {
urls.put("rebuildVisCache", UrlBuilder.getUrl("/vis/tools")); urls.put("rebuildVisCache", UrlBuilder.getUrl("/vis/tools"));

View 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.
}
}

View 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
+ "]";
}
}
}

View file

@ -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);
}
}

View file

@ -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());
}
}

View file

@ -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));
}
}

View file

@ -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));
}
}

View file

@ -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);
}
}
}

View file

@ -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;
}
}

View file

@ -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
+ "'");
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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");
}
}

View file

@ -21,6 +21,10 @@
<#assign languageCount = 1> <#assign languageCount = 1>
</#if> </#if>
<#assign visRequestingTemplate = "foaf-person-wilma"> <#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="individual-intro" class="vcard person" role="region">
<section id="share-contact" role="region"> <section id="share-contact" role="region">

View 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>&nbsp;</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>

View file

@ -3,7 +3,7 @@
<script src="js/jquery.js"></script> <script src="js/jquery.js"></script>
<script> <script>
function TestLOD() { function TestSparql() {
self = this; self = this;
this.setup = setupButtons; this.setup = setupButtons;
@ -38,11 +38,11 @@ function TestLOD() {
} }
$(document).ready(function() { $(document).ready(function() {
new TestLOD().setup(); new TestSparql().setup();
}); });
</script> </script>
<h1>Test the Linked Open Data requests</h1> <h1>Test the SPARQL Query API</h1>
<h3>Request data</h3> <h3>Request data</h3>
<table> <table>

Binary file not shown.

View 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.

View 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

View 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

View 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

View 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