package org.gbif.checklistbank.ws.resources;
import org.gbif.api.model.Constants;
import org.gbif.api.model.checklistbank.Description;
import org.gbif.api.model.checklistbank.Distribution;
import org.gbif.api.model.checklistbank.NameUsage;
import org.gbif.api.model.checklistbank.NameUsageMediaObject;
import org.gbif.api.model.checklistbank.NameUsageMetrics;
import org.gbif.api.model.checklistbank.ParsedName;
import org.gbif.api.model.checklistbank.Reference;
import org.gbif.api.model.checklistbank.SpeciesProfile;
import org.gbif.api.model.checklistbank.TableOfContents;
import org.gbif.api.model.checklistbank.TypeSpecimen;
import org.gbif.api.model.checklistbank.VerbatimNameUsage;
import org.gbif.api.model.checklistbank.VernacularName;
import org.gbif.api.model.checklistbank.search.NameUsageSearchParameter;
import org.gbif.api.model.checklistbank.search.NameUsageSearchRequest;
import org.gbif.api.model.checklistbank.search.NameUsageSearchResult;
import org.gbif.api.model.checklistbank.search.NameUsageSuggestRequest;
import org.gbif.api.model.checklistbank.search.NameUsageSuggestResult;
import org.gbif.api.model.common.Identifier;
import org.gbif.api.model.common.paging.Pageable;
import org.gbif.api.model.common.paging.PagingResponse;
import org.gbif.api.model.common.search.SearchRequest;
import org.gbif.api.model.common.search.SearchResponse;
import org.gbif.api.service.checklistbank.DescriptionService;
import org.gbif.api.service.checklistbank.DistributionService;
import org.gbif.api.service.checklistbank.IdentifierService;
import org.gbif.api.service.checklistbank.MultimediaService;
import org.gbif.api.service.checklistbank.NameUsageSearchService;
import org.gbif.api.service.checklistbank.NameUsageService;
import org.gbif.api.service.checklistbank.ReferenceService;
import org.gbif.api.service.checklistbank.SpeciesProfileService;
import org.gbif.api.service.checklistbank.TypeSpecimenService;
import org.gbif.api.service.checklistbank.VernacularNameService;
import org.gbif.api.vocabulary.Rank;
import org.gbif.checklistbank.model.TreeContainer;
import org.gbif.checklistbank.model.UsageCount;
import org.gbif.checklistbank.service.mybatis.mapper.UsageCountMapper;
import org.gbif.ws.server.interceptor.NullToNotFound;
import org.gbif.ws.util.ExtraMediaTypes;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.UUID;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableSet;
import com.google.inject.Inject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Species resource.
*/
@Path("/species")
@Produces({MediaType.APPLICATION_JSON, ExtraMediaTypes.APPLICATION_JAVASCRIPT})
public class SpeciesResource {
private static final Logger LOG = LoggerFactory.getLogger(SpeciesResource.class);
private static final String DATASET_KEY = "datasetKey";
private static final int DEEP_PAGING_OFFSET_LIMIT = 100000;
private final NameUsageService nameUsageService;
private final VernacularNameService vernacularNameService;
private final TypeSpecimenService typeSpecimenService;
private final SpeciesProfileService speciesProfileService;
private final ReferenceService referenceService;
private final MultimediaService imageService;
private final DescriptionService descriptionService;
private final DistributionService distributionService;
private final IdentifierService identifierService;
private final NameUsageSearchService searchService;
private final UsageCountMapper usageCountMapper;
@Inject
public SpeciesResource(
NameUsageService nameUsageService, VernacularNameService vernacularNameService,
TypeSpecimenService typeSpecimenService, SpeciesProfileService speciesProfileService,
ReferenceService referenceService, MultimediaService imageService, DescriptionService descriptionService,
DistributionService distributionService, IdentifierService identifierService, NameUsageSearchService searchService,
UsageCountMapper usageCountMapper) {
this.nameUsageService = nameUsageService;
this.vernacularNameService = vernacularNameService;
this.typeSpecimenService = typeSpecimenService;
this.speciesProfileService = speciesProfileService;
this.referenceService = referenceService;
this.imageService = imageService;
this.descriptionService = descriptionService;
this.distributionService = distributionService;
this.identifierService = identifierService;
this.searchService = searchService;
this.usageCountMapper = usageCountMapper;
}
/**
* This retrieves a list of all NameUsage from ChecklistBank.
*
* @param locale identifier for a region
* @param datasetKeys the optional checklist keys to limit paging to
* @param page the limit, offset paging information
*
* @return requested list of NameUsage or an empty list if none could be found
*/
@GET
public PagingResponse<NameUsage> list(@Context Locale locale, @QueryParam(DATASET_KEY) Set<UUID> datasetKeys,
@QueryParam("sourceId") String sourceId, @QueryParam("name") String canonicalName, @Context Pageable page) {
if (datasetKeys == null) {
datasetKeys = ImmutableSet.of();
}
if (Strings.isNullOrEmpty(canonicalName)) {
return nameUsageService.list(locale,
datasetKeys.isEmpty() ? null : datasetKeys.iterator().next(), sourceId, page);
} else {
return nameUsageService.listByCanonicalName(locale, canonicalName, page,
datasetKeys.isEmpty() ? null : datasetKeys.toArray(new UUID[datasetKeys.size()]));
}
}
/**
* This retrieves a NameUsage by its key from ChecklistBank.
*
* @param usageKey NameUsage key
* @param locale identifier for a region
*
* @return requested NameUsage or null if none could be found. List of NameUsage in case of a search.
*
* @see NameUsageService#get(int, Locale)
*/
@GET
@Path("{id}")
@NullToNotFound
public NameUsage get(@PathParam("id") int usageKey, @Context Locale locale) {
return nameUsageService.get(usageKey, locale);
}
@GET
@Path("{id}/metrics")
@NullToNotFound
public NameUsageMetrics getMetrics(@PathParam("id") int usageKey) {
return nameUsageService.getMetrics(usageKey);
}
@GET
@Path("{id}/name")
@NullToNotFound
public ParsedName getParsedName(@PathParam("id") int usageKey) {
return nameUsageService.getParsedName(usageKey);
}
@GET
@Path("{id}/verbatim")
@NullToNotFound
public VerbatimNameUsage getVerbatim(@PathParam("id") int usageKey) {
return nameUsageService.getVerbatim(usageKey);
}
/**
* This retrieves a list of children NameUsage for a parent NameUsage from ChecklistBank.
*
* @param parentKey parent NameUsage key
* @param locale identifier for a region
* @param page the limit, offset paging information
*
* @return requested list of NameUsage or an empty list if none could be found
*
* @see NameUsageService#listChildren(int, Locale, Pageable)
*/
@GET
@Path("{id}/children")
public PagingResponse<NameUsage> listChildren(@PathParam("id") int parentKey, @Context Locale locale, @Context Pageable page) {
return nameUsageService.listChildren(parentKey, locale, page);
}
@GET
@Path("{id}/childrenAll")
public List<UsageCount> listAllChildren(@PathParam("id") int parentKey) {
return usageCountMapper.children(parentKey);
}
/**
* This retrieves a list of synonym NameUsage for a NameUsage from ChecklistBank.
*
* @param usageKey parent NameUsage key
* @param locale identifier for a region
* @param page the limit, offset, and count paging information
*
* @return requested list of NameUsage or an empty list if none could be found
*
* @see NameUsageService#listChildren(int, Locale, Pageable)
*/
@GET
@Path("{id}/synonyms")
public PagingResponse<NameUsage> listSynonyms(@PathParam("id") int usageKey, @Context Locale locale, @Context Pageable page) {
return nameUsageService.listSynonyms(usageKey, locale, page);
}
/**
* This retrieves all VernacularNames for a NameUsage from ChecklistBank.
*
* @param usageKey NameUsage key
* @param page The page and offset and count information
*
* @return a list of all VernacularNames
*
* @see VernacularNameService#listByUsage(int, Pageable)
*/
@GET
@Path("{id}/vernacularNames")
public PagingResponse<VernacularName> listVernacularNamesByNameUsage(@PathParam("id") int usageKey, @Context Pageable page) {
return vernacularNameService.listByUsage(usageKey, page);
}
/**
* This retrieves all TypeSpecimens for a NameUsage from ChecklistBank.
*
* @param usageKey NameUsage key
* @param page The page and offset and count information
*
* @return a list of all TypeSpecimens
*
* @see TypeSpecimenService#listByUsage(int, Pageable)
*/
@GET
@Path("{id}/typeSpecimens")
public PagingResponse<TypeSpecimen> listTypeSpecimensByNameUsage(@PathParam("id") int usageKey, @Context Pageable page) {
return typeSpecimenService.listByUsage(usageKey, page);
}
/**
* This retrieves all SpeciesProfiles for a NameUsage from ChecklistBank.
*
* @param usageKey NameUsage key
* @param page The page and offset and count information
*
* @return a list of all SpeciesProfiles
*
* @see SpeciesProfileService#listByUsage(int, Pageable)
*/
@GET
@Path("{id}/speciesProfiles")
public PagingResponse<SpeciesProfile> listSpeciesProfilesByNameUsage(@PathParam("id") int usageKey, @Context Pageable page) {
return speciesProfileService.listByUsage(usageKey, page);
}
/**
* This retrieves all References for a NameUsage from ChecklistBank.
*
* @param usageKey NameUsage key
* @param page The page and offset and count information
*
* @return a list of all References
*
* @see ReferenceService#listByUsage(int, Pageable)
*/
@GET
@Path("{id}/references")
public PagingResponse<Reference> listReferencesByNameUsage(@PathParam("id") int usageKey, @Context Pageable page) {
return referenceService.listByUsage(usageKey, page);
}
/**
* This retrieves all multimedia objects for a NameUsage from ChecklistBank.
*
* @param usageKey NameUsage key
* @param page The page and offset and count information
*
* @return a list of all Media objects
*/
@GET
@Path("{id}/media")
public PagingResponse<NameUsageMediaObject> listImagesByNameUsage(@PathParam("id") int usageKey, @Context Pageable page) {
return imageService.listByUsage(usageKey, page);
}
/**
* This retrieves all Descriptions for a NameUsage from ChecklistBank.
*
* @param usageKey NameUsage key
* @param page The page and offset and count information
*
* @return a list of all Descriptions
*
* @see DescriptionService#listByUsage(int, Pageable)
*/
@GET
@Path("{id}/descriptions")
public PagingResponse<Description> listDescriptionsByNameUsage(@PathParam("id") int usageKey, @Context Pageable page) {
return descriptionService.listByUsage(usageKey, page);
}
/**
* This retrieves a table of contents for all descriptions of a name usage from ChecklistBank.
*/
@GET
@Path("{id}/toc")
@NullToNotFound
public TableOfContents get(@PathParam("id") Integer key) {
return descriptionService.getToc(key);
}
/**
* This retrieves all Distributions for a NameUsage from ChecklistBank.
*
* @param usageKey NameUsage key
* @param page The page and offset and count information
*
* @return a list of all Distributions
*
* @see DistributionService#listByUsage(int, Pageable)
*/
@GET
@Path("{id}/distributions")
public PagingResponse<Distribution> listDistributionsByNameUsage(@PathParam("id") int usageKey, @Context Pageable page) {
return distributionService.listByUsage(usageKey, page);
}
/**
* This retrieves all Identifier for a NameUsage from ChecklistBank.
*
* @param usageKey NameUsage key
* @param page The page and offset and count information
*
* @return a list of all Identifier
*/
@GET
@Path("{id}/identifier")
public PagingResponse<Identifier> listIdentifierByNameUsage(@PathParam("id") int usageKey, @Context Pageable page) {
return identifierService.listByUsage(usageKey, page);
}
/**
* This retrieves all related Usages for a NameUsage from ChecklistBank.
*
* @param usageKey NameUsage key
* @param datasetKeys The optional list of dataset keys to filter related usages
*
* @return a list of all Related usages
*
*/
@GET
@Path("{id}/related")
public PagingResponse<NameUsage> listRelatedByNameUsage(@PathParam("id") int usageKey, @Context Locale locale, @Context Pageable page,
@QueryParam(DATASET_KEY) Set<UUID> datasetKeys) {
return nameUsageService.listRelated(usageKey, locale, page, datasetKeys.toArray(new UUID[datasetKeys.size()]));
}
@GET
@Path("{id}/combinations")
public List<NameUsage> listCombinations(@PathParam("id") int basionymKey, @Context Locale locale) {
return nameUsageService.listCombinations(basionymKey, locale);
}
/**
* This retrieves all Parents for a NameUsage from ChecklistBank.
*
* @param usageKey NameUsage key
* @param page The page and offset and count information
*
* @return a list of all Parents
*
* @see NameUsageService#listParents(int, Locale)
*/
@GET
@Path("{id}/parents")
public List<NameUsage> listParentsByNameUsage(@PathParam("id") int usageKey, @Context Locale locale, @Context Pageable page) {
return nameUsageService.listParents(usageKey, locale);
}
/**
* This retrieves a list of root NameUsage for a Checklist from ChecklistBank.
*
* @param datasetKey UUID or case insensitive shortname of the Checklist to retrieve
* @param locale identifier for a region
* @param page the limit, offset, and count paging information
*
* @return requested list of NameUsage or an empty list if none could be found
*
* @see NameUsageService#listRoot(UUID, Locale, Pageable)
*/
@GET
@Path("root/{datasetKey}")
public PagingResponse<NameUsage> listRootUsages(@PathParam(DATASET_KEY) UUID datasetKey, @Context Locale locale, @Context Pageable page) {
return nameUsageService.listRoot(datasetKey, locale, page);
}
@GET
@Path("rootAll/{datasetKey}")
public List<UsageCount> root(@PathParam("datasetKey") UUID datasetKey) {
return usageCountMapper.root(datasetKey);
}
@GET
@Path("rootNub")
public TreeContainer<UsageCount, Integer> rootNub() {
TreeContainer<UsageCount, Integer> tree = new TreeContainer<>();
// kingdoms
tree.setRoot(usageCountMapper.root(Constants.NUB_DATASET_KEY));
for (UsageCount k : tree.getRoot()) {
// phyla ~140, classes ~350, orders ~1400, families are over 22.000 skip
addChildrenRecursively(tree, k.getKey(), 0, Rank.PHYLUM, Rank.CLASS, Rank.ORDER);
}
return tree;
}
private void addChildrenRecursively(TreeContainer<UsageCount, Integer> tree, int parent, int rankIdx, Rank ... ranks) {
List<UsageCount> children = usageCountMapper.childrenUntilRank(parent, ranks[rankIdx]);
if (!children.isEmpty()) {
tree.getChildren().put(parent, children);
if (++rankIdx < ranks.length) {
for (UsageCount c : children) {
addChildrenRecursively(tree, c.getKey(), rankIdx, ranks);
}
}
}
}
@GET
@Path("search")
public SearchResponse<NameUsageSearchResult, NameUsageSearchParameter> search(@Context NameUsageSearchRequest searchRequest) {
// POR-2801
// protect SOLR against deep paging requests which blow heap
checkDeepPaging(searchRequest);
return searchService.search(searchRequest);
}
@Path("suggest")
@GET
public List<NameUsageSuggestResult> suggest(@Context NameUsageSuggestRequest searchSuggestRequest) {
// POR-2801
// protect SOLR against deep paging requests which blow heap
checkDeepPaging(searchSuggestRequest);
return searchService.suggest(searchSuggestRequest);
}
/**
* POR-2801
*
* @throws java.lang.IllegalArgumentException if the offset is considered too high
*/
private static void checkDeepPaging(SearchRequest searchRequest) {
if (searchRequest.getOffset() > DEEP_PAGING_OFFSET_LIMIT) {
throw new IllegalArgumentException("Offset is limited for this operation to " + DEEP_PAGING_OFFSET_LIMIT);
}
}
}