/**
* =============================================================================
*
* ORCID (R) Open Source
* http://orcid.org
*
* Copyright (c) 2012-2014 ORCID, Inc.
* Licensed under an MIT-Style License (MIT)
* http://orcid.org/open-source-license
*
* This copyright and license information (including a link to the full license)
* shall be included in its entirety in all copies or substantial portion of
* the software.
*
* =============================================================================
*/
package org.orcid.api.common.delegator.impl;
import static org.orcid.core.api.OrcidApiConstants.STATUS_OK_MESSAGE;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.Resource;
import javax.persistence.NoResultException;
import javax.ws.rs.core.Response;
import javax.xml.datatype.XMLGregorianCalendar;
import org.orcid.api.common.delegator.OrcidApiServiceDelegator;
import org.orcid.core.adapter.Jpa2JaxbAdapter;
import org.orcid.core.exception.OrcidBadRequestException;
import org.orcid.core.exception.OrcidDeprecatedException;
import org.orcid.core.exception.OrcidNotFoundException;
import org.orcid.core.exception.OrcidSearchException;
import org.orcid.core.locale.LocaleManager;
import org.orcid.core.manager.ClientDetailsManager;
import org.orcid.core.manager.OrcidProfileManagerReadOnly;
import org.orcid.core.manager.OrcidSearchManager;
import org.orcid.core.manager.OrcidSecurityManager;
import org.orcid.core.security.aop.NonLocked;
import org.orcid.core.security.visibility.aop.AccessControl;
import org.orcid.core.security.visibility.aop.VisibilityControl;
import org.orcid.core.utils.OrcidMessageUtil;
import org.orcid.jaxb.model.message.OrcidMessage;
import org.orcid.jaxb.model.message.OrcidProfile;
import org.orcid.jaxb.model.message.OrcidSearchResult;
import org.orcid.jaxb.model.message.OrcidSearchResults;
import org.orcid.jaxb.model.message.ScopePathType;
import org.orcid.persistence.jpa.entities.ClientDetailsEntity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Required;
import com.sun.jersey.api.client.ClientResponse.Status;
/**
* This class will retrieve {@link OrcidProfile}s and return them for use in the
* Tier 1 API. Its is worth noting that this will not return
* {@link OrcidProfile}s that have not been confirmed, but it does this by
* checking the status on the object rather than at database level.
* <p/>
*
* @author Declan Newman (declan) Date: 02/03/2012
*/
public class OrcidApiServiceDelegatorImpl implements OrcidApiServiceDelegator {
@Resource(name = "orcidProfileManagerReadOnly")
private OrcidProfileManagerReadOnly orcidProfileManager;
private OrcidSearchManager orcidSearchManager;
@Resource
private ClientDetailsManager clientDetailsManager;
@Resource
private Jpa2JaxbAdapter jpa2JaxbAdapter;
@Resource
LocaleManager localeManager;
@Resource
private OrcidMessageUtil orcidMessageUtil;
@Resource
private OrcidSecurityManager orcidSecurityManager;
private static final Logger LOGGER = LoggerFactory.getLogger(OrcidApiServiceDelegatorImpl.class);
private static final int MAX_SEARCH_ROWS = 100;
@Required
public void setOrcidSearchManager(OrcidSearchManager orcidSearchManager) {
this.orcidSearchManager = orcidSearchManager;
}
/**
* @return Plain text message indicating health of service
*/
@Override
public Response viewStatusText() {
return Response.ok(STATUS_OK_MESSAGE).build();
}
/**
* finds and returns the {@link org.orcid.jaxb.model.message.OrcidMessage}
* wrapped in a {@link javax.xml.ws.Response} with only the profile's bio
* details
*
* @param orcid
* the ORCID to be used to identify the record
* @return the {@link javax.xml.ws.Response} with the
* {@link org.orcid.jaxb.model.message.OrcidMessage} within it
*/
@Override
@VisibilityControl
@NonLocked
public Response findBioDetails(String orcid) {
OrcidProfile profile = orcidProfileManager.retrieveClaimedOrcidBio(orcid);
return getOrcidMessageResponse(profile, orcid);
}
@Override
@VisibilityControl()
@AccessControl(requiredScope = ScopePathType.READ_PUBLIC, enableAnonymousAccess = true)
@NonLocked
public Response findBioDetailsFromPublicCache(String orcid) {
try {
OrcidMessage orcidMessage = orcidSearchManager.findPublicProfileById(orcid);
if (orcidMessage != null) {
OrcidProfile orcidProfile = orcidMessage.getOrcidProfile();
if (orcidProfile != null) {
orcidProfile.downgradeToBioOnly();
}
}
return getOrcidMessageResponse(orcidMessage, orcid);
} catch (OrcidSearchException e) {
LOGGER.warn("Error searching, so falling back to DB", e);
return findBioDetails(orcid);
}
}
/**
* finds and returns the {@link org.orcid.jaxb.model.message.OrcidMessage}
* wrapped in a {@link javax.xml.ws.Response} with only the profile's
* external identifier details
*
* @param orcid
* the ORCID to be used to identify the record
* @return the {@link javax.xml.ws.Response} with the
* {@link org.orcid.jaxb.model.message.OrcidMessage} within it
*/
@Override
@VisibilityControl
@NonLocked
public Response findExternalIdentifiers(String orcid) {
OrcidProfile profile = orcidProfileManager.retrieveClaimedExternalIdentifiers(orcid);
return getOrcidMessageResponse(profile, orcid);
}
@Override
@VisibilityControl()
@AccessControl(requiredScope = ScopePathType.READ_PUBLIC, enableAnonymousAccess = true)
@NonLocked
public Response findExternalIdentifiersFromPublicCache(String orcid) {
try {
OrcidMessage orcidMessage = orcidSearchManager.findPublicProfileById(orcid);
if (orcidMessage != null) {
OrcidProfile orcidProfile = orcidMessage.getOrcidProfile();
if (orcidProfile != null) {
orcidProfile.downgradeToExternalIdentifiersOnly();
}
}
return getOrcidMessageResponse(orcidMessage, orcid);
} catch (OrcidSearchException e) {
LOGGER.warn("Error searching, so falling back to DB", e);
return findExternalIdentifiers(orcid);
}
}
/**
* finds and returns the {@link org.orcid.jaxb.model.message.OrcidMessage}
* wrapped in a {@link javax.xml.ws.Response} with all of the profile's
* details
*
* @param orcid
* the ORCID to be used to identify the record
* @return the {@link javax.xml.ws.Response} with the
* {@link org.orcid.jaxb.model.message.OrcidMessage} within it
*/
@Override
@VisibilityControl
@NonLocked
public Response findFullDetails(String orcid) {
OrcidProfile profile = orcidProfileManager.retrieveClaimedOrcidProfile(orcid);
return getOrcidMessageResponse(profile, orcid);
}
@Override
@VisibilityControl()
@AccessControl(requiredScope = ScopePathType.READ_PUBLIC, enableAnonymousAccess = true)
@NonLocked
public Response findFullDetailsFromPublicCache(String orcid) {
try {
OrcidMessage orcidMessage = orcidSearchManager.findPublicProfileById(orcid);
return getOrcidMessageResponse(orcidMessage, orcid);
} catch (OrcidSearchException e) {
LOGGER.warn("Error searching, so falling back to DB", e);
return findFullDetails(orcid);
}
}
/**
* finds and returns the {@link org.orcid.jaxb.model.message.OrcidMessage}
* wrapped in a {@link javax.xml.ws.Response} with only the affiiation
* details
*
* @param orcid
* the ORCID to be used to identify the record
* @return the {@link javax.xml.ws.Response} with the
* {@link org.orcid.jaxb.model.message.OrcidMessage} within it
*/
@Override
@VisibilityControl
@NonLocked
public Response findAffiliationsDetails(String orcid) {
OrcidProfile profile = orcidProfileManager.retrieveClaimedAffiliations(orcid);
return getOrcidMessageResponse(profile, orcid);
}
@Override
@VisibilityControl()
@AccessControl(requiredScope = ScopePathType.READ_PUBLIC, enableAnonymousAccess = true)
@NonLocked
public Response findAffiliationsDetailsFromPublicCache(String orcid) {
try {
OrcidMessage orcidMessage = orcidSearchManager.findPublicProfileById(orcid);
if (orcidMessage != null) {
OrcidProfile orcidProfile = orcidMessage.getOrcidProfile();
if (orcidProfile != null) {
orcidProfile.downgradeToAffiliationsOnly();
}
}
return getOrcidMessageResponse(orcidMessage, orcid);
} catch (OrcidSearchException e) {
LOGGER.warn("Error searching, so falling back to DB", e);
return findAffiliationsDetails(orcid);
}
}
/**
* finds and returns the {@link org.orcid.jaxb.model.message.OrcidMessage}
* wrapped in a {@link javax.xml.ws.Response} with only the grants details
*
* @param orcid
* the ORCID to be used to identify the record
* @return the {@link javax.xml.ws.Response} with the
* {@link org.orcid.jaxb.model.message.OrcidMessage} within it
*/
@Override
@VisibilityControl
@NonLocked
public Response findFundingDetails(String orcid) {
OrcidProfile profile = orcidProfileManager.retrieveClaimedFundings(orcid);
return getOrcidMessageResponse(profile, orcid);
}
@Override
@VisibilityControl()
@AccessControl(requiredScope = ScopePathType.READ_PUBLIC, enableAnonymousAccess = true)
@NonLocked
public Response findFundingDetailsFromPublicCache(String orcid) {
try {
OrcidMessage orcidMessage = orcidSearchManager.findPublicProfileById(orcid);
if (orcidMessage != null) {
OrcidProfile orcidProfile = orcidMessage.getOrcidProfile();
if (orcidProfile != null) {
orcidProfile.downgradeToFundingsOnly();
}
}
return getOrcidMessageResponse(orcidMessage, orcid);
} catch (OrcidSearchException e) {
LOGGER.warn("Error searching, so falling back to DB", e);
return findAffiliationsDetails(orcid);
}
}
/**
* finds and returns the {@link org.orcid.jaxb.model.message.OrcidMessage}
* wrapped in a {@link javax.xml.ws.Response} with only the work details
*
* @param orcid
* the ORCID to be used to identify the record
* @return the {@link javax.xml.ws.Response} with the
* {@link org.orcid.jaxb.model.message.OrcidMessage} within it
*/
@Override
@VisibilityControl
@NonLocked
public Response findWorksDetails(String orcid) {
OrcidProfile profile = orcidProfileManager.retrieveClaimedOrcidWorks(orcid);
return getOrcidMessageResponse(profile, orcid);
}
@Override
@VisibilityControl()
@AccessControl(requiredScope = ScopePathType.READ_PUBLIC, enableAnonymousAccess = true)
@NonLocked
public Response findWorksDetailsFromPublicCache(String orcid) {
try {
OrcidMessage orcidMessage = orcidSearchManager.findPublicProfileById(orcid);
if (orcidMessage != null) {
OrcidProfile orcidProfile = orcidMessage.getOrcidProfile();
if (orcidProfile != null) {
orcidProfile.downgradeToWorksOnly();
}
}
return getOrcidMessageResponse(orcidMessage, orcid);
} catch (OrcidSearchException e) {
LOGGER.warn("Error searching, so falling back to DB", e);
return findWorksDetails(orcid);
}
}
@Override
public Response redirectClientToGroup(String clientId) {
ClientDetailsEntity clientDetails = clientDetailsManager.findByClientId(clientId);
if (clientDetails == null) {
return Response.status(Status.NOT_FOUND).build();
}
String groupOrcid = clientDetails.getGroupProfileId();
URI groupUri;
try {
groupUri = new URI(jpa2JaxbAdapter.getOrcidIdBase(groupOrcid).getUri());
return Response.seeOther(groupUri).build();
} catch (URISyntaxException e) {
LOGGER.error("Problem redirecting to group: {}", groupOrcid, e);
return Response.serverError().build();
}
}
/**
* See {@link OrcidApiServiceDelegator}{@link #publicSearchByQuery(Map)}
*/
@Override
@VisibilityControl
@AccessControl(requiredScope = ScopePathType.READ_PUBLIC, enableAnonymousAccess = true)
public Response publicSearchByQuery(Map<String, List<String>> queryMap) {
return searchByQuery(queryMap);
}
/**
* See {@link OrcidApiServiceDelegator}{@link #searchByQuery(Map)}
*/
@Override
@VisibilityControl
public Response searchByQuery(Map<String, List<String>> queryMap) {
validateSearchParams(queryMap);
OrcidMessage orcidMessage = orcidSearchManager.findOrcidsByQuery(queryMap);
List<OrcidSearchResult> searchResults = orcidMessage.getOrcidSearchResults() != null ? orcidMessage.getOrcidSearchResults().getOrcidSearchResult() : null;
List<OrcidSearchResult> filteredResults = new ArrayList<OrcidSearchResult>();
OrcidSearchResults orcidSearchResults = new OrcidSearchResults();
if (searchResults != null) {
orcidSearchResults.setNumFound(orcidMessage.getOrcidSearchResults().getNumFound());
if (searchResults.size() > 0) {
for (OrcidSearchResult searchResult : searchResults) {
OrcidSearchResult filteredSearchResult = new OrcidSearchResult();
OrcidProfile filteredProfile = new OrcidProfile();
filteredSearchResult.setRelevancyScore(searchResult.getRelevancyScore());
filteredProfile.setOrcid(searchResult.getOrcidProfile().getOrcid());
filteredProfile.setOrcidId(searchResult.getOrcidProfile().getOrcidId());
filteredProfile.setOrcidIdentifier(searchResult.getOrcidProfile().getOrcidIdentifier());
filteredProfile.setOrcidBio(searchResult.getOrcidProfile().getOrcidBio());
filteredSearchResult.setOrcidProfile(filteredProfile);
filteredResults.add(filteredSearchResult);
}
}
}
orcidSearchResults.getOrcidSearchResult().addAll(filteredResults);
return getOrcidSearchResultsResponse(orcidSearchResults, queryMap.toString());
}
private void validateSearchParams(Map<String, List<String>> queryMap) {
validateRows(queryMap);
validateStart(queryMap);
}
private void validateStart(Map<String, List<String>> queryMap) {
String clientId = orcidSecurityManager.getClientIdFromAPIRequest();
if (clientId == null) {
// only validate start param where no client credentials
List<String> startList = queryMap.get("start");
if (startList != null && !startList.isEmpty()) {
try {
String startString = startList.get(0);
int start = Integer.valueOf(startString);
if (start < 0 || start > OrcidSearchManager.MAX_SEARCH_START) {
throw new OrcidBadRequestException(
localeManager.resolveMessage("apiError.badrequest_invalid_search_start.exception", OrcidSearchManager.MAX_SEARCH_START));
}
} catch (NumberFormatException e) {
throw new OrcidBadRequestException(
localeManager.resolveMessage("apiError.badrequest_invalid_search_start.exception", OrcidSearchManager.MAX_SEARCH_START));
}
}
}
}
private void validateRows(Map<String, List<String>> queryMap) {
List<String> rowsList = queryMap.get("rows");
if (rowsList != null && !rowsList.isEmpty()) {
try {
String rowsString = rowsList.get(0);
int rows = Integer.valueOf(rowsString);
if (rows < 0 || rows > MAX_SEARCH_ROWS) {
throw new OrcidBadRequestException(localeManager.resolveMessage("apiError.badrequest_invalid_search_rows.exception"));
}
} catch (NumberFormatException e) {
throw new OrcidBadRequestException(localeManager.resolveMessage("apiError.badrequest_invalid_search_rows.exception"));
}
}
}
/**
* Method to perform the mundane task of checking for null and returning the
* response with an OrcidMessage entity
*
* @param profile
* @param requestedOrcid
* @return
*/
private Response getOrcidMessageResponse(OrcidProfile profile, String requestedOrcid) {
if (profile == null) {
Map<String, String> params = new HashMap<String, String>();
params.put("orcid", requestedOrcid);
throw new OrcidNotFoundException(params);
}
profile.setOrcidInternal(null);
OrcidMessage orcidMessage = new OrcidMessage(profile);
orcidMessageUtil.setSourceName(orcidMessage);
return Response.ok(orcidMessage).build();
}
private Response getOrcidMessageResponse(OrcidMessage orcidMessage, String requestedOrcid) {
boolean isProfileDeprecated = false;
if (orcidMessage == null) {
Map<String, String> params = new HashMap<String, String>();
params.put("orcid", requestedOrcid);
throw new OrcidNotFoundException(params);
}
OrcidProfile orcidProfile = orcidMessage.getOrcidProfile();
if (orcidProfile != null) {
orcidProfile.setOrcidInternal(null);
// If profile is deprecated
if (orcidMessage.getOrcidProfile().getOrcidDeprecated() != null) {
isProfileDeprecated = true;
}
}
Response response = null;
if (isProfileDeprecated) {
Map<String, String> params = new HashMap<String, String>();
params.put(OrcidDeprecatedException.ORCID, orcidProfile.getOrcidDeprecated().getPrimaryRecord().getOrcidIdentifier().getUri());
if (orcidProfile.getOrcidDeprecated().getDate() != null) {
XMLGregorianCalendar deprecatedDate = orcidProfile.getOrcidDeprecated().getDate().getValue();
params.put(OrcidDeprecatedException.DEPRECATED_DATE, deprecatedDate.toString());
}
throw new OrcidDeprecatedException(params);
} else {
orcidMessageUtil.setSourceName(orcidMessage);
response = Response.ok(orcidMessage).build();
}
return response;
}
private Response getOrcidSearchResultsResponse(OrcidSearchResults orcidSearchResults, String query) {
if (orcidSearchResults != null) {
OrcidMessage orcidMessage = new OrcidMessage();
orcidMessage.setMessageVersion("1.2");
orcidMessage.setOrcidSearchResults(orcidSearchResults);
orcidMessageUtil.setSourceName(orcidMessage); //TODO: THIS FAILS if there is no profile in SOLR (org.orcid.core.indexPublicProfile=false or via message-listener)
return Response.ok(orcidMessage).build();
} else {
Object params[] = { query };
throw new NoResultException(localeManager.resolveMessage("apiError.no_search_result.exception", params));
}
}
}