CropsController.java

  1. /*
  2.  * Copyright 2018 Global Crop Diversity Trust
  3.  *
  4.  * Licensed under the Apache License, Version 2.0 (the "License");
  5.  * you may not use this file except in compliance with the License.
  6.  * You may obtain a copy of the License at
  7.  *
  8.  *   http://www.apache.org/licenses/LICENSE-2.0
  9.  *
  10.  * Unless required by applicable law or agreed to in writing, software
  11.  * distributed under the License is distributed on an "AS IS" BASIS,
  12.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13.  * See the License for the specific language governing permissions and
  14.  * limitations under the License.
  15.  */

  16. package org.genesys.server.api.v1;

  17. import java.util.List;
  18. import java.util.Locale;
  19. import java.util.concurrent.ExecutionException;
  20. import java.util.concurrent.TimeUnit;

  21. import org.genesys.blocks.model.JsonViews;
  22. import org.genesys.blocks.security.serialization.Permissions;
  23. import org.genesys.filerepository.InvalidRepositoryPathException;
  24. import org.genesys.server.api.ApiBaseController;
  25. import org.genesys.server.api.ModelValidationException;
  26. import org.genesys.server.api.v1.facade.CropApiService;
  27. import org.genesys.server.api.v1.mapper.APIv1Mapper;
  28. import org.genesys.server.api.v1.model.Article;
  29. import org.genesys.server.api.v1.model.Crop;
  30. import org.genesys.server.api.v1.model.CropDetails;
  31. import org.genesys.server.exception.NotFoundElement;
  32. import org.genesys.server.service.CRMException;
  33. import org.genesys.spring.CSVMessageConverter;
  34. import org.springframework.beans.factory.annotation.Autowired;
  35. import org.springframework.context.i18n.LocaleContextHolder;
  36. import org.springframework.core.task.TaskExecutor;
  37. import org.springframework.http.CacheControl;
  38. import org.springframework.http.MediaType;
  39. import org.springframework.http.ResponseEntity;
  40. import org.springframework.security.access.prepost.PreAuthorize;
  41. import org.springframework.web.bind.annotation.GetMapping;
  42. import org.springframework.web.bind.annotation.PathVariable;
  43. import org.springframework.web.bind.annotation.PostMapping;
  44. import org.springframework.web.bind.annotation.RequestBody;
  45. import org.springframework.web.bind.annotation.RequestMapping;
  46. import org.springframework.web.bind.annotation.RequestMethod;
  47. import org.springframework.web.bind.annotation.RestController;

  48. import com.fasterxml.jackson.annotation.JsonIgnoreType;
  49. import com.fasterxml.jackson.annotation.JsonView;
  50. import com.fasterxml.jackson.core.JsonProcessingException;
  51. import com.fasterxml.jackson.databind.DeserializationFeature;
  52. import com.fasterxml.jackson.databind.MapperFeature;
  53. import com.fasterxml.jackson.databind.ObjectMapper;
  54. import com.fasterxml.jackson.databind.SerializationFeature;
  55. import com.fasterxml.jackson.databind.json.JsonMapper;
  56. import com.fasterxml.jackson.datatype.hibernate5.Hibernate5Module;
  57. import com.fasterxml.jackson.datatype.hibernate5.Hibernate5Module.Feature;
  58. import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;

  59. import io.swagger.annotations.Api;
  60. import net.sf.oval.ConstraintViolation;
  61. import net.sf.oval.Validator;

  62. @RestController("cropApi1")
  63. @RequestMapping(value = { CropsController.CONTROLLER_URL })
  64. @Api(tags = { "crop" })
  65. public class CropsController extends ApiBaseController {

  66.     public static final String CONTROLLER_URL = ApiBaseController.APIv1_BASE + "/crops";

  67.     @Autowired
  68.     private CropApiService cropApiService;

  69.     @Autowired
  70.     private TaskExecutor taskExecutor;

  71.     private static ObjectMapper noPermissionsMapper;

  72.     @JsonIgnoreType
  73.     public static final class MixinIgnoreType {}

  74.     static {
  75.         var noPermissionsBuilder = JsonMapper.builder();
  76.         Hibernate5Module hibernateModule = new Hibernate5Module();
  77.         hibernateModule.disable(Feature.FORCE_LAZY_LOADING);
  78.         hibernateModule.disable(Feature.SERIALIZE_IDENTIFIER_FOR_LAZY_NOT_LOADED_OBJECTS);
  79.         noPermissionsBuilder.addModule(hibernateModule);

  80.         // JSR310 java.time
  81.         var javaTimeModule = new JavaTimeModule();
  82.         noPermissionsBuilder.addModule(javaTimeModule);

  83.         // serialization
  84.         noPermissionsBuilder.disable(SerializationFeature.EAGER_SERIALIZER_FETCH);
  85.         // deserialization
  86.         noPermissionsBuilder.enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);
  87.         // // Never ignore stuff we don't understand
  88.         // mapper.enable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
  89.         // ignore stuff we don't understand
  90.         noPermissionsBuilder.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
  91.         // explicit JSON views: every fields needs to be annotated, therefore enabled
  92.         noPermissionsBuilder.enable(MapperFeature.DEFAULT_VIEW_INCLUSION);
  93.         // enable upgrading to arrays
  94.         noPermissionsBuilder.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY);

  95.         // Don't do permission checks
  96.         noPermissionsBuilder.addMixIn(Permissions.class, MixinIgnoreType.class);

  97.         // Build
  98.         noPermissionsMapper = noPermissionsBuilder.build();
  99.     }

  100.     /**
  101.      * List of Crop details
  102.      *
  103.      * @return list of crop details
  104.      * @throws JsonProcessingException
  105.      * @throws ExecutionException
  106.      */
  107.     @GetMapping(value = "")
  108.     public ResponseEntity<String> listCropDetails() throws JsonProcessingException, ExecutionException {
  109.         LOG.info("Listing crop details");
  110.         var crops = cropApiService.listDetails();
  111.         LOG.debug("Got crops");
  112.         String json = noPermissionsMapper.writeValueAsString(crops);
  113.         LOG.debug("Serialized to JSON");
  114.         return ResponseEntity.ok().cacheControl(CacheControl.maxAge(5, TimeUnit.MINUTES)).body(json);
  115.     }

  116.     /**
  117.      * List all crops
  118.      *
  119.      * @return list of crops
  120.      */
  121.     @GetMapping(value = "/list", produces = { MediaType.APPLICATION_JSON_VALUE, CSVMessageConverter.TEXT_CSV_VALUE })
  122.     public ResponseEntity<List<Crop>> listCrops() {
  123.         LOG.info("Listing crops");
  124.         return ResponseEntity.ok().cacheControl(CacheControl.maxAge(1, TimeUnit.MINUTES)).body(cropApiService.list(LocaleContextHolder.getLocale()));
  125.     }

  126.     /**
  127.      * Add a crop
  128.      *
  129.      * @return saved crop
  130.      */
  131.     @PreAuthorize("hasRole('ADMINISTRATOR')")
  132.     @RequestMapping(value = { "/save" }, method = { RequestMethod.PUT, RequestMethod.POST }, produces = { MediaType.APPLICATION_JSON_VALUE })
  133.     public Crop saveCrop(@RequestBody Crop cropJson) {
  134.         LOG.info("Creating crop");
  135.         final Validator validator = new Validator();
  136.         final List<ConstraintViolation> violations = validator.validate(cropJson);
  137.         if (violations.size() > 0) {
  138.             throw new ModelValidationException("Crop does not validate", violations);
  139.         }
  140.         Crop crop = cropApiService.getCrop(cropJson.getShortName());

  141.         if (crop == null) {
  142.             crop = cropApiService.create(cropJson);
  143.         } else {
  144.             crop = cropApiService.update(cropJson);
  145.         }

  146.         return crop;
  147.     }

  148.     /**
  149.      * Update crop blurb /crops/{shortName}/blurb
  150.      *
  151.      * @return updated article
  152.      * @throws CRMException
  153.      */
  154.     @PostMapping(value = "/{shortName}/blurb", produces = { MediaType.APPLICATION_JSON_VALUE })
  155.     public Article updateBlurb(@PathVariable("shortName") String shortName, @RequestBody Article article) throws CRMException {
  156.         Crop crop = cropApiService.getCrop(shortName);
  157.         Locale locale = new Locale(article.getLang());

  158.         return cropApiService.updateBlurb(crop, article.getTitle(), article.getSummary(), article.getBody(), locale);
  159.     }

  160.     /**
  161.      * Get crop /crops/{shortName}
  162.      *
  163.      * @return
  164.      */
  165.     @RequestMapping(value = "/{shortName}", method = RequestMethod.GET, produces = { MediaType.APPLICATION_JSON_VALUE })
  166.     public Crop getCrop(@PathVariable("shortName") String shortName) {
  167.         LOG.info("Getting crop {}", shortName);
  168.         return cropApiService.getCrop(shortName);
  169.     }

  170.     /**
  171.      * Get crop details /crops/{shortName}/details
  172.      *
  173.      * @throws InvalidRepositoryPathException
  174.      */
  175.     @JsonView(JsonViews.Root.class)
  176.     @RequestMapping(value = "/{shortName}/details", method = RequestMethod.GET, produces = { MediaType.APPLICATION_JSON_VALUE })
  177.     public CropDetails getCropDetails(@PathVariable("shortName") String shortName) throws InvalidRepositoryPathException {
  178.         LOG.info("Getting crop details {}", shortName);
  179.         return cropApiService.getDetails(shortName, LocaleContextHolder.getLocale());
  180.     }

  181.     /**
  182.      * Delete crop /crops/{shortName}
  183.      *
  184.      * @return
  185.      */
  186.     @PreAuthorize("hasRole('ADMINISTRATOR')")
  187.     @RequestMapping(value = "/{shortName}", method = RequestMethod.DELETE, produces = { MediaType.APPLICATION_JSON_VALUE })
  188.     public Crop deleteCrop(@PathVariable("shortName") String shortName) {
  189.         LOG.info("Getting crop {}", shortName);
  190.         return cropApiService.remove(cropApiService.getCrop(shortName));
  191.     }

  192.     /**
  193.      * Link accession#cropName with crop
  194.      *
  195.      * @return
  196.      */
  197.     @PreAuthorize("hasRole('ADMINISTRATOR')")
  198.     @RequestMapping(value = "{shortName}/relink", params= { "accessions" }, method = RequestMethod.POST, produces = { MediaType.APPLICATION_JSON_VALUE })
  199.     public void relinkAccessions(@PathVariable("shortName") String shortName) {
  200.         LOG.info("Linking accessions to crop {}", shortName);
  201.         final Crop crop = cropApiService.getCrop(shortName);
  202.         if (crop == null)
  203.             throw new NotFoundElement("No crop " + shortName);

  204.         taskExecutor.execute(() -> {
  205.             cropApiService.unlinkAccessionsForCrop(crop);
  206.             cropApiService.linkAccessionsWithCrop(crop);
  207.         });
  208.     }

  209. }