Commit 72602764 authored by Maxym Borodenko's avatar Maxym Borodenko
Browse files

Merge branch 'crud-controller-service-update' into 'main'

CRUD controller/service update

See merge request grin-global/grin-global-server!255
parents 7ce332a0 51d709c6
......@@ -141,7 +141,7 @@ public abstract class ActionController<T extends AbstractAction<T>, F extends Ac
@PutMapping(value = "/action", produces = { MediaType.APPLICATION_JSON_VALUE })
@Operation(operationId = "update", description = "Update an existing record", summary = "Update")
public T update(@RequestBody @Valid @NotNull final T entity) {
return actionService.update(entity, actionService.reload(entity));
return actionService.reload(actionService.update(entity));
}
/**
......
......@@ -72,7 +72,7 @@ public class CodeValuesController extends ApiBaseController {
@PutMapping(value = "", produces = { MediaType.APPLICATION_JSON_VALUE })
@Operation(operationId = "update", description = "Update an existing CodeValue record", summary = "Update")
public CodeValue update(@RequestBody final CodeValue input) {
return cvService.update(input, cvService.reload(input));
return cvService.update(input);
}
/**
......
......@@ -232,7 +232,7 @@ public class CropTraitController extends ApiBaseController {
@PutMapping(value = "", produces = { MediaType.APPLICATION_JSON_VALUE })
@Operation(operationId = "update", description = "Update an existing record", summary = "Update")
public CropTrait update(@RequestBody final CropTrait entity) {
return cropTraitService.update(entity, cropTraitService.reload(entity));
return cropTraitService.update(entity);
}
@PostMapping(value = "/attach/{cropTraitId}", produces = { MediaType.APPLICATION_JSON_VALUE })
......
......@@ -15,10 +15,8 @@
*/
package org.gringlobal.api.v1.impl;
import com.querydsl.core.types.OrderSpecifier;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import java.io.IOException;
import org.apache.commons.lang3.ArrayUtils;
import org.gringlobal.api.InvalidApiUsageException;
import org.gringlobal.api.PageableAsQueryParam;
......@@ -30,8 +28,8 @@ import org.gringlobal.model.QWebUser;
import org.gringlobal.model.WebCooperator;
import org.gringlobal.model.WebUser;
import org.gringlobal.service.LanguageService;
import org.gringlobal.service.WebCooperatorService;
import org.gringlobal.service.ShortFilterService;
import org.gringlobal.service.WebCooperatorService;
import org.gringlobal.service.WebUserService;
import org.gringlobal.service.filter.WebUserFilter;
import org.springframework.beans.factory.annotation.Autowired;
......@@ -42,8 +40,8 @@ import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
......@@ -52,7 +50,11 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
import com.querydsl.core.types.OrderSpecifier;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
@RestController("webUserApi1")
@RequestMapping(WebUserController.API_URL)
......@@ -132,7 +134,7 @@ public class WebUserController extends ApiBaseController {
@PutMapping(value = "", produces = { MediaType.APPLICATION_JSON_VALUE })
@Operation(operationId = "update", description = "Update an existing record", summary = "Update")
public WebUser update(@RequestBody final WebUser entity) {
return crudService.update(entity, crudService.reload(entity));
return crudService.update(entity);
}
@PreAuthorize("hasAuthority('GROUP_ADMINS')")
......
......@@ -366,9 +366,7 @@ public class InventoryViability extends CooperatorOwnedModel implements Copyable
public void lazyLoad() {
super.lazyLoad();
if (inventory != null) {
inventory.getId();
inventory.getSite().getId(); // site isn't nullable
inventory.getAccession().getId(); // accession isn't nullable
inventory.lazyLoad();
}
if (inventoryViabilityRule != null) {
inventoryViabilityRule.getId();
......
......@@ -25,25 +25,21 @@ import javax.persistence.ManyToOne;
import javax.persistence.MappedSuperclass;
import javax.persistence.PrePersist;
import org.genesys.blocks.security.SecurityContextUtil;
import org.genesys.blocks.security.model.AclSid;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
import org.genesys.blocks.util.JsonSidConverter;
import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.fasterxml.jackson.annotation.JsonIdentityReference;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.ObjectIdGenerators;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
@MappedSuperclass
public abstract class WebOwnedModel extends WebAuditedModel {
public abstract class WebOwnedModel extends AuditedModel implements LazyLoading<WebOwnedModel> {
private static final long serialVersionUID = -2708780479620558305L;
@ManyToOne(fetch = FetchType.EAGER, cascade = {})
@ManyToOne(fetch = FetchType.LAZY, cascade = {})
@JoinColumn(name = "owned_by", nullable = false)
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
@JsonIdentityInfo(scope = AclSid.class, generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
@JsonIdentityReference(alwaysAsId = false)
@Field(type = FieldType.Object)
@JsonSerialize(converter = JsonSidConverter.class)
private AclSid ownedBy;
@Basic
......@@ -56,7 +52,7 @@ public abstract class WebOwnedModel extends WebAuditedModel {
@PrePersist
private void prePersist() {
if (ownedBy == null) {
ownedBy = new WebUser(this.getCreatedBy());
ownedBy = SecurityContextUtil.getCurrentUser();
}
if (ownedDate == null) {
ownedDate = this.getCreatedDate();
......@@ -81,7 +77,6 @@ public abstract class WebOwnedModel extends WebAuditedModel {
@Override
public void lazyLoad() {
super.lazyLoad();
if (this.ownedBy != null) {
this.ownedBy.getId();
}
......
/*
* Copyright 2020 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.gringlobal.service.filter;
import java.util.List;
import java.util.Set;
import org.apache.commons.collections4.CollectionUtils;
import org.genesys.blocks.model.filters.DateFilter;
import org.genesys.blocks.model.filters.EmptyModelFilter;
import org.gringlobal.model.QWebAuditedModel;
import org.gringlobal.model.WebAuditedModel;
import com.querydsl.core.types.Predicate;
import com.querydsl.core.types.dsl.EntityPathBase;
/**
* {@link WebAuditedModel} match by sample filters.
*
* @param <T> the generic type
* @param <R> the generic type
*/
public abstract class WebAuditedModelFilter<T extends WebAuditedModelFilter<T, R>, R extends WebAuditedModel> extends EmptyModelFilter<T, R> {
/** The created by. */
public Set<Long> createdBy;
/** The created date. */
public DateFilter createdDate;
/** The last modified by. */
public Set<Long> modifiedBy;
/** The last modified date. */
public DateFilter modifiedDate;
protected List<Predicate> collectPredicates(final EntityPathBase<R> instance, final QWebAuditedModel webAuditedModel) {
List<Predicate> predicates = super.collectPredicates(instance);
if (CollectionUtils.isNotEmpty(createdBy)) {
predicates.add(webAuditedModel.createdBy.in(createdBy));
}
if (CollectionUtils.isNotEmpty(modifiedBy)) {
predicates.add(webAuditedModel.modifiedBy.in(modifiedBy));
}
if (createdDate != null) {
predicates.add(createdDate.buildQuery(webAuditedModel.createdDate));
}
if (modifiedDate != null) {
predicates.add(modifiedDate.buildQuery(webAuditedModel.modifiedDate));
}
return predicates;
}
/**
* Created date.
*
* @return the date filter
*/
public synchronized DateFilter createdDate() {
if (createdDate == null) {
createdDate = new DateFilter();
}
return createdDate;
}
/**
* Last modified date.
*
* @return the date filter
*/
public synchronized DateFilter modifiedDate() {
if (modifiedDate == null) {
modifiedDate = new DateFilter();
}
return modifiedDate;
}
}
......@@ -20,6 +20,7 @@ import java.util.Set;
import org.apache.commons.collections4.CollectionUtils;
import org.genesys.blocks.model.filters.DateFilter;
import org.genesys.blocks.model.filters.EmptyModelFilter;
import org.gringlobal.model.QWebOwnedModel;
import org.gringlobal.model.WebOwnedModel;
......@@ -32,7 +33,7 @@ import com.querydsl.core.types.dsl.EntityPathBase;
* @param <T> the generic type
* @param <R> the generic type
*/
public abstract class WebOwnedModelFilter<T extends WebOwnedModelFilter<T, R>, R extends WebOwnedModel> extends WebAuditedModelFilter<T, R> {
public abstract class WebOwnedModelFilter<T extends WebOwnedModelFilter<T, R>, R extends WebOwnedModel> extends EmptyModelFilter<T, R> {
/** The owned by. */
public Set<Long> ownedBy;
......@@ -40,6 +41,15 @@ public abstract class WebOwnedModelFilter<T extends WebOwnedModelFilter<T, R>, R
/** The owned date. */
public DateFilter ownedDate;
/** The created by. */
public Set<Long> createdBy;
/** The created date. */
public DateFilter createdDate;
/** The last modified by. */
public Set<Long> modifiedBy;
/** The last modified date. */
public DateFilter modifiedDate;
/**
* Collects list of filter predicates
*
......@@ -86,4 +96,28 @@ public abstract class WebOwnedModelFilter<T extends WebOwnedModelFilter<T, R>, R
return value ? "Y" : "N";
}
/**
* Created date.
*
* @return the date filter
*/
public synchronized DateFilter createdDate() {
if (createdDate == null) {
createdDate = new DateFilter();
}
return createdDate;
}
/**
* Last modified date.
*
* @return the date filter
*/
public synchronized DateFilter modifiedDate() {
if (modifiedDate == null) {
modifiedDate = new DateFilter();
}
return modifiedDate;
}
}
......@@ -19,6 +19,7 @@ import org.gringlobal.model.AccessionInvAnnotation;
import org.gringlobal.persistence.AccessionInvAnnotationRepository;
import org.gringlobal.service.AccessionInvAnnotationService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
/**
......@@ -30,25 +31,22 @@ public class AccessionInvAnnotationServiceImpl extends CRUDServiceImpl<Accession
AccessionInvAnnotationService {
@Override
@Transactional
public AccessionInvAnnotation create(AccessionInvAnnotation source) {
assert(source.getId() == null);
LOG.debug("Create AccessionInvAnnotation. Input data {}", source);
AccessionInvAnnotation toSave = new AccessionInvAnnotation();
toSave.apply(source);
AccessionInvAnnotation saved = repository.save(toSave);
saved.lazyLoad();
return saved;
AccessionInvAnnotation saved = repository.save(source);
return _lazyLoad(saved);
}
@Override
@Transactional
public AccessionInvAnnotation update(AccessionInvAnnotation updated, AccessionInvAnnotation target) {
final AccessionInvAnnotation loaded = get(updated.getId());
LOG.debug("Update AccessionInvAnnotation. Input data {}", updated);
loaded.apply(updated);
target.apply(updated);
AccessionInvAnnotation saved = repository.save(loaded);
saved.lazyLoad();
return saved;
AccessionInvAnnotation saved = repository.save(target);
return _lazyLoad(saved);
}
}
......@@ -66,6 +66,7 @@ import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.security.access.prepost.PostAuthorize;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
......@@ -189,9 +190,9 @@ public class AccessionServiceImpl extends FilteredCRUDServiceImpl<Accession, Acc
@Override
@Transactional
@PreAuthorize("@ggceSec.actionAllowed('PassportData', 'CREATE', #source.site)")
@PostAuthorize("@ggceSec.actionAllowed('PassportData', 'CREATE', returnObject.site)")
public Accession create(Accession source) {
source.setId(null);
assert(source.getId() == null);
Accession accession = repository.save(source);
if (CollectionUtils.isNotEmpty(source.getAccessionSources())) {
......@@ -221,6 +222,14 @@ public class AccessionServiceImpl extends FilteredCRUDServiceImpl<Accession, Acc
return accession;
}
@Override
@Transactional
@PreAuthorize("@ggceSec.actionAllowed('PassportData', 'WRITE', #updated.site)")
@PostAuthorize("@ggceSec.actionAllowed('PassportData', 'WRITE', returnObject.site)")
public Accession update(Accession updated) {
return super.update(updated);
}
@Override
@Transactional
@PreAuthorize("@ggceSec.actionAllowed('PassportData', 'WRITE', #target.site)")
......@@ -248,7 +257,7 @@ public class AccessionServiceImpl extends FilteredCRUDServiceImpl<Accession, Acc
@Transactional
@PreAuthorize("@ggceSec.actionAllowed('PassportData', 'DELETE', #entity.site)")
public Accession remove(Accession entity) {
return super.remove(get(entity));
return super.remove(entity);
}
@Override
......
......@@ -125,7 +125,7 @@ public abstract class CRUDServiceImpl<T extends EmptyModel, R extends JpaReposit
* @param entity the source entity
* @return the reloaded entity, lazy-loading included
*/
public T reload(T source) {
public final T reload(T source) {
return load(source.getId());
}
......
......@@ -78,9 +78,7 @@ public class CooperatorServiceImpl extends FilteredCRUDServiceImpl<Cooperator, C
target.apply(updated);
Cooperator saved = repository.save(target);
saved.lazyLoad();
return saved;
return _lazyLoad(saved);
}
@Override
......
......@@ -63,7 +63,7 @@ public class InventoryViabilityRuleServiceImpl extends FilteredCRUDServiceImpl<I
public InventoryViabilityRuleMap addInventoryViabilityRuleMap(InventoryViabilityRuleMap source) {
LOG.debug("Create InventoryViabilityRuleMap. Input data {}", source);
source.setInventoryViabilityRule(reload(source.getInventoryViabilityRule()));
source.setTaxonomySpecies(taxonomySpeciesService.reload(source.getTaxonomySpecies()));
source.setTaxonomySpecies(taxonomySpeciesService.get(source.getTaxonomySpecies().getId()));
var saved = viabilityRuleMapRepository.save(source);
saved.lazyLoad();
......
......@@ -244,7 +244,7 @@ public class InventoryViabilityServiceImpl extends FilteredCRUDServiceImpl<Inven
var saved = repository.save(source);
saved.lazyLoad();
return saved;
return _lazyLoad(saved);
}
@Override
......@@ -255,7 +255,7 @@ public class InventoryViabilityServiceImpl extends FilteredCRUDServiceImpl<Inven
@Override
@Transactional
@PostAuthorize("@ggceSec.actionAllowed('ViabilityTest', 'WRITE', returnObject.inventory.site)")
@PreAuthorize("@ggceSec.actionAllowed('ViabilityTest', 'WRITE', #target.inventory.site)")
public InventoryViability update(InventoryViability input, InventoryViability target) {
if (input == null) {
throw new InvalidApiUsageException("Source object must be provided.");
......
......@@ -166,7 +166,7 @@ public class AccessionTriggers implements InitializingBean {
LOG.info("Assure system inventory for {}", createdAccession);
inventoryService.assureSystemInventory(createdAccession);
recordAccessionNumber(createdAccession.getId(), createdAccession.getAccessionNumber(), createdAccession.getAccessionNumberPart1());
recordReceivedAccessionSpecies(createdAccession);
return createdAccession;
......
......@@ -29,6 +29,7 @@ import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.context.WebApplicationContext;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.hibernate5.Hibernate5Module;
......@@ -55,16 +56,17 @@ public abstract class AbstractApiV1Test extends AbstractApiTest {
static {
verboseMapper = new ObjectMapper();
Hibernate5Module hibernateModule = new Hibernate5Module();
hibernateModule.disable(Feature.FORCE_LAZY_LOADING);
hibernateModule.disable(Feature.SERIALIZE_IDENTIFIER_FOR_LAZY_NOT_LOADED_OBJECTS);
hibernateModule.enable(Feature.WRITE_MISSING_ENTITIES_AS_NULL);
verboseMapper.registerModule(hibernateModule);
// Serializer for request objects
clientMapper = new ObjectMapper();
clientMapper.enable(MapperFeature.DEFAULT_VIEW_INCLUSION);
clientMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
clientMapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
}
......
......@@ -169,7 +169,7 @@ public class CropAttachControllerTest extends
// Get
/*@formatter:off*/
mockMvc
var responseJson = mockMvc
.perform(get(CropController.CropAttachController.API_URL.concat("/{id}"), savedAttach.getId())
// .contentType(MediaType.APPLICATION_JSON)
)
......@@ -197,19 +197,21 @@ public class CropAttachControllerTest extends
.andExpect(jsonPath("$.repositoryFile.identifier", startsWith("urn:uuid:")))
.andExpect(jsonPath("$.repositoryFile.identifier", is(savedAttach.getRepositoryFile().getIdentifier())))
.andExpect(jsonPath("$.crop.id", is(crop.getId().intValue())))
.andReturn().getResponse().getContentAsString()
;
var updated = clientMapper.readValue(responseJson, CropAttach.class);
/*@formatter:on*/
// Update
savedAttach.setTitle("The Title");
savedAttach.setNote("This is a note");
updated.setTitle("The Title");
updated.setNote("This is a note");
/*@formatter:off*/
mockMvc
.perform(put(CropController.CropAttachController.API_URL)
responseJson = mockMvc
.perform(put(CropController.CropAttachController.API_URL)
.contentType(MediaType.APPLICATION_JSON)
.characterEncoding("UTF8")
.content(verboseMapper.writeValueAsString(savedAttach))
.content(verboseMapper.writeValueAsString(updated))
)
// .andDo(org.springframework.test.web.servlet.result.MockMvcResultHandlers.print())
.andExpect(status().isOk())
......@@ -236,21 +238,23 @@ public class CropAttachControllerTest extends
.andExpect(jsonPath("$.repositoryFile.identifier", startsWith("urn:uuid:")))
.andExpect(jsonPath("$.repositoryFile.identifier", is(savedAttach.getRepositoryFile().getIdentifier())))
.andExpect(jsonPath("$.crop.id", is(crop.getId().intValue())))
.andReturn().getResponse().getContentAsString()
;
updated = clientMapper.readValue(responseJson, CropAttach.class);
/*@formatter:on*/
// Test that we can't update virtual paths
var virtualPath = savedAttach.getVirtualPath();
var thumbnailVirtualPath = savedAttach.getThumbnailVirtualPath();
savedAttach.setVirtualPath("AH-A");
savedAttach.setThumbnailVirtualPath("OH-OH");
var virtualPath = updated.getVirtualPath();
var thumbnailVirtualPath = updated.getThumbnailVirtualPath();
updated.setVirtualPath("AH-A");
updated.setThumbnailVirtualPath("OH-OH");
/*@formatter:off*/
mockMvc
.perform(put(CropController.CropAttachController.API_URL)
.perform(put(CropController.CropAttachController.API_URL)
.contentType(MediaType.APPLICATION_JSON)
.characterEncoding("UTF8")
.content(verboseMapper.writeValueAsString(savedAttach))
.content(verboseMapper.writeValueAsString(updated))
)
// .andDo(org.springframework.test.web.servlet.result.MockMvcResultHandlers.print())
.andExpect(status().isOk())
......
......@@ -178,7 +178,7 @@ public class CropTraitAttachControllerTest extends
// Get
/*@formatter:off*/
mockMvc
var responseJson = mockMvc
.perform(get(CropTraitController.CropTraitAttachController.API_URL.concat("/{id}"), savedAttach.getId())
// .contentType(MediaType.APPLICATION_JSON)
)
......@@ -206,19 +206,21 @@ public class CropTraitAttachControllerTest extends
.andExpect(jsonPath("$.repositoryFile.identifier", startsWith("urn:uuid:")))
.andExpect(jsonPath("$.repositoryFile.identifier", is(savedAttach.getRepositoryFile().getIdentifier())))
.andExpect(jsonPath("$.cropTrait.id", is(cropTrait.getId().intValue())))
.andReturn().getResponse().getContentAsString()
;
var updated = clientMapper.readValue(responseJson, CropTraitAttach.class);
/*@formatter:on*/
// Update
savedAttach.setTitle("The Title");
savedAttach.setNote("This is a note");
updated.setTitle("The Title");
updated.setNote("This is a note");
/*@formatter:off*/
mockMvc
.perform(put(CropTraitController.CropTraitAttachController.API_URL)
responseJson = mockMvc
.perform(put(CropTraitController.CropTraitAttachController.API_URL)
.contentType(MediaType.APPLICATION_JSON)
.characterEncoding("UTF8")
.content(verboseMapper.writeValueAsString(savedAttach))
.content(verboseMapper.writeValueAsString(updated))
)
// .andDo(org.springframework.test.web.servlet.result.MockMvcResultHandlers.print())
.andExpect(status().isOk())
......@@ -245,21 +247,23 @@ public class CropTraitAttachControllerTest extends
.andExpect(jsonPath("$.repositoryFile.identifier", startsWith("urn:uuid:")))
.andExpect(jsonPath("$.repositoryFile.identifier", is(savedAttach.getRepositoryFile().getIdentifier())))
.andExpect(jsonPath("$.cropTrait.id", is(cropTrait.getId().intValue())))
.andReturn().getResponse().getContentAsString()
;
updated = clientMapper.readValue(responseJson, CropTraitAttach.class);