Commit 3bb78f5b authored by Viacheslav Pavlov's avatar Viacheslav Pavlov Committed by Matija Obreza

Crops API v1

- annex1 to crop
- crop service to create/update by crop
- overview data
- simple tests
- added cachable to accessionCount
parent 6eeff38d
/**
* Copyright 2014 Global Crop Diversity Trust
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
package org.genesys2.server.api.v1;
import com.fasterxml.jackson.annotation.JsonView;
import io.swagger.annotations.Api;
import net.sf.oval.ConstraintViolation;
import net.sf.oval.Validator;
import org.genesys.blocks.model.JsonViews;
import org.genesys.filerepository.InvalidRepositoryPathException;
import org.genesys2.server.api.ApiBaseController;
import org.genesys2.server.api.ModelValidationException;
import org.genesys2.server.api.OAuth2Cleanup;
import org.genesys2.server.exception.AuthorizationException;
import org.genesys2.server.model.genesys.Parameter;
import org.genesys2.server.model.impl.Crop;
import org.genesys2.server.model.impl.CropRule;
import org.genesys2.server.model.impl.CropTaxonomy;
import org.genesys2.server.service.CropService;
import org.genesys2.server.service.GenesysService;
import org.genesys2.server.service.TraitService;
import org.genesys2.spring.ResourceNotFoundException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.http.MediaType;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
import javax.xml.bind.ValidationException;
import java.util.List;
@RestController("cropApi1")
@RequestMapping(value = { CropsController.CONTROLLER_URL, "/json/v1/crops" })
@Api(tags = { "crop" })
public class CropsController extends ApiBaseController {
public static final String CONTROLLER_URL = ApiBaseController.APIv1_BASE + "/crops";
@Autowired
GenesysService genesysService;
@Autowired
TraitService traitService;
@Autowired
CropService cropService;
/**
* List all crops
* @return
*
* @return
* @throws AuthorizationException
*/
@RequestMapping(value = "", method = RequestMethod.GET, produces = { MediaType.APPLICATION_JSON_VALUE })
public List<Crop> listCrops() {
LOG.info("Listing crops");
final List<Crop> crops = cropService.list(LocaleContextHolder.getLocale());
return crops;
}
/**
* Add a crop
* @return
*
* @return
* @throws ValidationException
*/
@PreAuthorize("hasRole('ADMINISTRATOR')")
@RequestMapping(value = { "/save" }, method = { RequestMethod.PUT, RequestMethod.POST }, produces = { MediaType.APPLICATION_JSON_VALUE })
public Crop saveCrop(@RequestBody Crop cropJson) throws ValidationException {
LOG.info("Creating crop");
final Validator validator = new Validator();
final List<ConstraintViolation> violations = validator.validate(cropJson);
if (violations.size() > 0) {
throw new ModelValidationException("Crop does not validate", violations);
}
Crop crop = cropService.getCrop(cropJson.getShortName());
if (crop == null) {
crop = cropService.addCrop(cropJson);
} else {
crop = cropService.updateCrop(cropJson);
}
return crop;
}
/**
* Get crop /crops/{shortName}
* @return
*
* @return
* @throws AuthorizationException
*/
@RequestMapping(value = "/{shortName}", method = RequestMethod.GET, produces = { MediaType.APPLICATION_JSON_VALUE })
public Crop getCrop(@PathVariable("shortName") String shortName) throws AuthorizationException {
LOG.info("Getting crop {}", shortName);
return cropService.getCrop(shortName);
}
/**
* Get crop details /crops/{shortName}/details
* @return
*
* @return
* @throws AuthorizationException
*/
@JsonView(JsonViews.Public.class)
@RequestMapping(value = "/{shortName}/details/{lang}", method = RequestMethod.GET, produces = { MediaType.APPLICATION_JSON_VALUE })
public CropService.CropDetails getCropDetails(@PathVariable("shortName") String shortName, @PathVariable("lang") String lang) throws InvalidRepositoryPathException {
LOG.info("Getting crop details {}", shortName);
return cropService.getDetails(shortName, lang);
}
/**
* Delete crop /crops/{shortName}
*
* @return
* @throws AuthorizationException
*/
@PreAuthorize("hasRole('ADMINISTRATOR')")
@RequestMapping(value = "/{shortName}", method = RequestMethod.DELETE, produces = { MediaType.APPLICATION_JSON_VALUE })
public @ResponseBody Crop deleteCrop(@PathVariable("shortName") String shortName) throws AuthorizationException {
LOG.info("Getting crop {}", shortName);
return cropService.delete(cropService.getCrop(shortName));
}
/**
* Get crop descriptors /crops/{shortName}/descriptors
* @return
*
* @return
* @throws AuthorizationException
*/
@RequestMapping(value = "/{shortName}/descriptors", method = RequestMethod.GET, produces = { MediaType.APPLICATION_JSON_VALUE })
public Page<Parameter> getCropDescriptors(@PathVariable("shortName") String shortName) throws AuthorizationException {
LOG.info("Getting crop descriptors {}", shortName);
final Crop crop = cropService.getCrop(shortName);
final Page<Parameter> descriptors = traitService.listTraits(crop, new PageRequest(0, 50));
return OAuth2Cleanup.clean(descriptors);
}
/**
* Get crop taxonomy rules /crops/{shortName}/rules
* @return
*
* @return
* @throws AuthorizationException
*/
@RequestMapping(value = "/{shortName}/rules", method = RequestMethod.GET, produces = { MediaType.APPLICATION_JSON_VALUE })
public List<CropRule> getCropRules(@PathVariable("shortName") String shortName) throws AuthorizationException {
LOG.info("Getting crop rules {}", shortName);
final Crop crop = cropService.getCrop(shortName);
final List<CropRule> cropRules = cropService.getCropRules(crop);
return cropRules;
}
/**
* Get crop taxonomy rules /crops/{shortName}/rules
*
* @return
*
* @return
* @throws AuthorizationException
*/
@PreAuthorize("hasRole('ADMINISTRATOR')")
@RequestMapping(value = "/{shortName}/rules", method = { RequestMethod.PUT, RequestMethod.POST }, produces = { MediaType.APPLICATION_JSON_VALUE })
public @ResponseBody List<CropRule> updateCropRules(@PathVariable("shortName") String shortName, @RequestBody List<CropRule> rules) throws AuthorizationException {
LOG.info("Updating crop rules for {}", shortName);
final Crop crop = cropService.getCrop(shortName);
if (crop == null)
throw new ResourceNotFoundException("No crop " + shortName);
cropService.setCropRules(crop, rules);
return cropService.getCropRules(crop);
}
/**
* Get crop taxonomies /crops/{shortName}/taxa
* @return
*
* @return
* @throws AuthorizationException
*/
@RequestMapping(value = "/{shortName}/taxa", method = RequestMethod.GET, produces = { MediaType.APPLICATION_JSON_VALUE })
public Page<CropTaxonomy> getCropTaxa(@PathVariable("shortName") String shortName) throws AuthorizationException {
LOG.info("Getting crop taxa {}", shortName);
final Crop crop = cropService.getCrop(shortName);
final Page<CropTaxonomy> cropTaxa = cropService.getCropTaxonomies(crop, new PageRequest(0, 50));
return OAuth2Cleanup.clean(cropTaxa);
}
/**
* Rebuild taxonomy-crop lists
*
* @return
* @throws AuthorizationException
*/
@PreAuthorize("hasRole('ADMINISTRATOR')")
@RequestMapping(value = "/rebuild", method = RequestMethod.POST, produces = { MediaType.APPLICATION_JSON_VALUE })
public void rebuild() {
cropService.rebuildTaxonomies();
}
/**
* Rebuild taxonomy-crop lists
*
* @return
* @throws AuthorizationException
*/
@PreAuthorize("hasRole('ADMINISTRATOR')")
@RequestMapping(value = "{shortName}/rebuild", method = RequestMethod.POST, produces = { MediaType.APPLICATION_JSON_VALUE })
public void rebuildCrop(@PathVariable("shortName") String shortName) {
LOG.info("Updating crop rules for {}", shortName);
final Crop crop = cropService.getCrop(shortName);
if (crop == null)
throw new ResourceNotFoundException("No crop " + shortName);
cropService.rebuildTaxonomies(crop);
}
}
......@@ -80,6 +80,9 @@ public class Crop extends GlobalVersionedAuditedModel implements AclAwareModel {
@Column(nullable = false, length = 200)
private String name;
@Column(name = "annex1")
private boolean annex1 = false;
@Lob
@Type(type = "org.hibernate.type.TextType")
private String description;
......@@ -255,4 +258,12 @@ public class Crop extends GlobalVersionedAuditedModel implements AclAwareModel {
public void setOtherNames(List<String> otherNames) {
this.otherNames = otherNames;
}
public boolean isAnnex1() {
return annex1;
}
public void setAnnex1(boolean annex1) {
this.annex1 = annex1;
}
}
......@@ -18,8 +18,12 @@ package org.genesys2.server.service;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import org.genesys.filerepository.InvalidRepositoryPathException;
import org.genesys.filerepository.model.RepositoryImage;
import org.genesys2.server.model.genesys.Taxonomy2;
import org.genesys2.server.model.impl.Article;
import org.genesys2.server.model.impl.Crop;
import org.genesys2.server.model.impl.CropRule;
import org.genesys2.server.model.impl.CropTaxonomy;
......@@ -30,6 +34,8 @@ public interface CropService {
Crop getCrop(String shortName);
CropDetails getDetails(String shortName, String lang) throws InvalidRepositoryPathException;
List<Crop> listCrops();
Page<Crop> listCrops(Pageable pageable);
......@@ -57,6 +63,7 @@ public interface CropService {
*/
Crop addCrop(String shortName, String name, String description, String i18n);
Crop addCrop(Crop crop);
/**
* Updates a crop record
*
......@@ -68,6 +75,8 @@ public interface CropService {
*/
Crop updateCrop(Crop crop, String name, String description, String i18n);
Crop updateCrop(Crop crop);
CropRule addCropRule(Crop crop, String genus, String species, boolean included);
List<CropRule> getCropRules(Crop crop);
......@@ -110,4 +119,23 @@ public interface CropService {
Crop updateAliases(Crop crop, List<String> otherNames);
class CropDetails {
public Crop crop;
public Article blurb;
public long accessionCount;
public List<RepositoryImage> covers;
public Map<String, ElasticsearchService.TermResult> overview;
public static CropDetails from(Crop crop, Article blurb, long accessionCount, List<RepositoryImage> covers, Map<String, ElasticsearchService.TermResult> overview){
CropDetails cropDetails = new CropDetails();
cropDetails.crop = crop;
cropDetails.blurb = blurb;
cropDetails.accessionCount = accessionCount;
cropDetails.covers = covers;
cropDetails.overview = overview;
return cropDetails;
}
}
}
......@@ -15,6 +15,7 @@
*/
package org.genesys2.server.service.filter;
import java.io.Serializable;
import java.util.Set;
import java.util.UUID;
......@@ -32,7 +33,7 @@ import com.querydsl.core.types.Predicate;
/**
* Filters for {@link Accession}.
*/
public class AccessionFilter extends UuidModelFilter<AccessionFilter, Accession> {
public class AccessionFilter extends UuidModelFilter<AccessionFilter, Accession> implements Serializable {
/** The historic. */
public Boolean historic;
......
......@@ -150,6 +150,7 @@ public class AccessionServiceImpl implements AccessionService {
}
@Override
@Cacheable(value = "accessionCount")
public long countAccessions(AccessionFilter filter) {
long total = elasticsearchService.count(Accession.class, filter);
......
......@@ -16,19 +16,20 @@
package org.genesys2.server.service.impl;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.nio.file.Paths;
import java.util.*;
import java.util.stream.Collectors;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.Predicate;
import org.apache.commons.lang3.StringUtils;
import org.genesys.filerepository.InvalidRepositoryPathException;
import org.genesys.filerepository.model.ImageGallery;
import org.genesys.filerepository.model.RepositoryImage;
import org.genesys.filerepository.service.ImageGalleryService;
import org.genesys2.server.model.genesys.Accession;
import org.genesys2.server.model.genesys.Taxonomy2;
import org.genesys2.server.model.impl.Article;
import org.genesys2.server.model.impl.Crop;
import org.genesys2.server.model.impl.CropRule;
import org.genesys2.server.model.impl.CropTaxonomy;
......@@ -36,10 +37,8 @@ import org.genesys2.server.persistence.CropRepository;
import org.genesys2.server.persistence.CropRuleRepository;
import org.genesys2.server.persistence.CropTaxonomyRepository;
import org.genesys2.server.persistence.Taxonomy2Repository;
import org.genesys2.server.service.CRMException;
import org.genesys2.server.service.ContentService;
import org.genesys2.server.service.CropService;
import org.genesys2.server.service.HtmlSanitizer;
import org.genesys2.server.service.*;
import org.genesys2.server.service.filter.AccessionFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
......@@ -47,7 +46,9 @@ import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.oauth2.common.exceptions.UnauthorizedUserException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
......@@ -76,6 +77,15 @@ public class CropServiceImpl implements CropService {
@Autowired
private ContentService contentService;
@Autowired
private AccessionService accessionService;
@Autowired
private ElasticsearchService elasticsearchService;
@Autowired
private ImageGalleryService imageGalleryService;
@Autowired
private Taxonomy2Repository taxonomy2Repository;
......@@ -100,6 +110,27 @@ public class CropServiceImpl implements CropService {
return crop;
}
@Override
@Transactional(readOnly = true, noRollbackFor = AccessDeniedException.class)
public CropDetails getDetails(String shortName, String lang) throws AccessDeniedException, InvalidRepositoryPathException {
Locale locale = lang == null ? Locale.getDefault() : Locale.forLanguageTag(lang);
Crop crop = getCrop(shortName);
Article article = contentService.getArticle(crop, "blurp", locale);
AccessionFilter byCrop = new AccessionFilter();
byCrop.cropName = crop.getName();
long accessionCount = accessionService.countAccessions(byCrop);
ImageGallery imageGallery = imageGalleryService.loadImageGallery(Paths.get("/crop/"+ crop.getShortName() +"/cover"));
List<RepositoryImage> covers = imageGallery == null ? Collections.emptyList() : imageGallery.getImages();
Map<String, ElasticsearchService.TermResult> overview = getOverviewData(byCrop);
return CropDetails.from(crop, article, accessionCount, covers, overview);
}
@PreAuthorize("hasRole('ADMINISTRATOR')")
@Override
@Transactional
......@@ -346,6 +377,24 @@ public class CropServiceImpl implements CropService {
return crop;
}
@Override
@PreAuthorize("hasRole('ADMINISTRATOR') or hasPermission(#crop, 'ADMINISTRATION')")
@Transactional(readOnly = false)
@CacheEvict(allEntries = true, value = CACHE_CROPS)
public Crop updateCrop(Crop crop) {
final Crop toUpdate = getCrop(crop.getShortName());
copyValues(toUpdate, crop);
return cropRepository.save(toUpdate);
}
@Override
@PreAuthorize("hasRole('ADMINISTRATOR')")
@Transactional(readOnly = false)
@CacheEvict(allEntries = true, value = CACHE_CROPS)
public Crop addCrop(Crop crop) {
return cropRepository.save(crop);
}
@Override
@PreAuthorize("hasRole('ADMINISTRATOR') or hasPermission(#crop, 'ADMINISTRATION')")
@Transactional(readOnly = false)
......@@ -445,4 +494,30 @@ public class CropServiceImpl implements CropService {
cropTaxonomyRepository.save(toAdd);
}
}
private Map<String, ElasticsearchService.TermResult> getOverviewData(AccessionFilter byCropFilter) {
String[] terms = new String[] {"taxonomy.genus", "taxonomy.genusSpecies", "institute.code",
"institute.country.code3", "mlsStatus", "available"};
try {
return elasticsearchService.termStatisticsAuto(Accession.class, byCropFilter, 10, terms);
} catch (SearchException e) {
LOG.error("Error occurred during search", e);
return null;
}
}
private void copyValues(Crop target, Crop source) {
target.setDescription(source.getDescription());
target.setI18n(source.getI18n());
target.setName(source.getName());
if (source.getOtherNames() != null) {
target.setOtherNames(source.getOtherNames().stream().distinct().map(otherName -> StringUtils.trim(otherName)).filter(otherName -> StringUtils.isNotBlank(otherName)).sorted().collect(Collectors.toList()));
}
if (source.getCropRules() != null)
target.getCropRules().addAll(source.getCropRules());
}
}
......@@ -4373,6 +4373,19 @@ databaseChangeLog:
indexName: IX_id3
tableName: accession
- changeSet:
id: 1539794655476-1
author: vpavlov
comment: added annex1 to crop
changes:
- addColumn:
columns:
- column:
name: annex1
type: boolean
defaultValue: false
tableName: crop
# ENABLE AFTER SOME TIME
# - changeSet:
......
/*
* Copyright 2018 Global Crop Diversity Trust
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.genesys.test.server.api.v0;
import static org.hamcrest.Matchers.*;
......@@ -154,6 +169,7 @@ public class CropsControllerTest extends AbstractApiTest {
fieldWithPath("active").ignored(),
fieldWithPath("_class").ignored(),
fieldWithPath("_permissions.*").ignored(),
fieldWithPath("annex1").ignored(),
fieldWithPath("createdBy").ignored(),
fieldWithPath("createdDate").ignored(),
fieldWithPath("lastModifiedBy").ignored(),
......@@ -193,6 +209,7 @@ public class CropsControllerTest extends AbstractApiTest {
fieldWithPath("active").ignored(),
fieldWithPath("_class").ignored(),
fieldWithPath("_permissions.*").ignored(),
fieldWithPath("annex1").ignored(),
fieldWithPath("createdBy").ignored(),
fieldWithPath("createdDate").ignored(),
fieldWithPath("lastModifiedBy").ignored(),
......
......@@ -142,6 +142,7 @@ public class ApiCropsTest extends AbstractApiTest {
fieldWithPath("active").type(JsonFieldType.BOOLEAN).description("Record is active"),
fieldWithPath("_class").ignored(),
fieldWithPath("_permissions.*").ignored(),
fieldWithPath("annex1").ignored(),
fieldWithPath("createdDate").ignored(),
fieldWithPath("lastModifiedBy").ignored(),
fieldWithPath("lastModifiedDate").ignored())));
......@@ -167,6 +168,7 @@ public class ApiCropsTest extends AbstractApiTest {
fieldWithPath("i18n").type(JsonFieldType.STRING).optional().description("i18n map"), fieldWithPath("createdBy").ignored(),
fieldWithPath("_class").ignored(),
fieldWithPath("_permissions.*").ignored(),
fieldWithPath("annex1").ignored(),
fieldWithPath("createdDate").ignored(),
fieldWithPath("lastModifiedBy").ignored(),
fieldWithPath("lastModifiedDate").ignored())));
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment