Commit 87b61dac authored by Maxym Borodenko's avatar Maxym Borodenko

Crop API

parent 43ddc48d
/*
* 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.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 org.genesys.filerepository.InvalidRepositoryFileDataException;
import org.genesys.filerepository.InvalidRepositoryPathException;
import org.gringlobal.api.v1.ApiBaseController;
import org.gringlobal.api.v1.FilteredCRUDController;
import org.gringlobal.api.v1.FilteredPage;
import org.gringlobal.api.v1.Pagination;
import org.gringlobal.custom.elasticsearch.SearchException;
import org.gringlobal.model.Crop;
import org.gringlobal.model.CropAttach;
import org.gringlobal.model.QCrop;
import org.gringlobal.service.CropAttachmentService;
import org.gringlobal.service.CropService;
import org.gringlobal.service.filter.CropFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
@RestController("cropApi1")
@RequestMapping(CropController.API_URL)
@PreAuthorize("isAuthenticated()")
@Tag(name = "Crop")
public class CropController extends FilteredCRUDController<Crop, CropService, CropFilter> {
/** The Constant API_URL. */
public static final String API_URL = ApiBaseController.APIv1_BASE + "/crop";
@Autowired
private CropAttachmentService cropAttachmentService;
@Override
protected Class<CropFilter> filterType() {
return CropFilter.class;
}
@Override
protected OrderSpecifier<?>[] defaultSort() {
return new OrderSpecifier[] { QCrop.crop.id.asc() };
}
@Override
@PreAuthorize("hasAuthority('GROUP_ADMINS')")
@Operation(operationId = "createCrop", description = "Create a Crop", summary = "Add")
public Crop create(@RequestBody final Crop entity) {
return super.create(entity);
}
@Override
@PreAuthorize("hasAuthority('GROUP_ADMINS')")
@Operation(operationId = "updateCrop", description = "Update a Crop", summary = "Update")
public Crop update(@RequestBody final Crop entity) {
return super.update(entity);
}
@Override
@PreAuthorize("hasAuthority('GROUP_ADMINS')")
@Operation(operationId = "deleteCrop", description = "Delete a Crop by ID", summary = "Delete")
public Crop remove(@PathVariable("id") final long id) {
return super.remove(id);
}
@PreAuthorize("hasAuthority('GROUP_ADMINS')")
@PostMapping(value = "/attach/{cropId}", produces = { MediaType.APPLICATION_JSON_VALUE })
@Operation(operationId = "uploadFile", description = "Attach crop file", summary = "Attach file")
public CropAttach uploadFile(@PathVariable(name = "cropId") final Long cropId, @RequestPart(name = "file") final MultipartFile file,
@RequestPart(name = "metadata") final CropAttachmentService.CropAttachmentRequest metadata) throws InvalidRepositoryPathException, InvalidRepositoryFileDataException, IOException {
return cropAttachmentService.uploadFile(crudService.get(cropId), file, metadata);
}
@PreAuthorize("hasAuthority('GROUP_ADMINS')")
@DeleteMapping(value = "/attach/{cropId}/{attachmentId}", produces = { MediaType.APPLICATION_JSON_VALUE })
@Operation(operationId = "removeFile", description = "Remove attached file", summary = "Remove file")
public CropAttach removeFile(@PathVariable(name = "cropId") final Long cropId, @PathVariable(name = "attachmentId") final Long attachmentId) {
return cropAttachmentService.removeFile(crudService.get(cropId), attachmentId);
}
@Override
@Operation(operationId = "listCrops", description = "List Crops", summary = "List")
public FilteredPage<Crop, CropFilter> list(@Parameter(hidden = true) final Pagination page, @RequestBody(required = false) final CropFilter filter) throws SearchException, IOException {
return super.list(page, filter);
}
@Override
@Operation(operationId = "filterCrops", description = "Filter Crops", summary = "Filter")
public FilteredPage<Crop, CropFilter> filter(@RequestParam(name = "f", required = false) final String filterCode, @Parameter(hidden = true) final Pagination page,
@RequestBody(required = false) final CropFilter filter) throws IOException, SearchException {
return super.filter(filterCode, page, filter);
}
}
......@@ -24,6 +24,8 @@ import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Lob;
import javax.persistence.Table;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import org.genesys.blocks.model.Copyable;
......@@ -48,6 +50,8 @@ public class Crop extends CooperatorOwnedModel implements Copyable<Crop> {
@Column(name = "crop_id")
private Long id;
@NotNull
@Size(min = 1, max = 20)
@Basic
@Column(nullable = false, length = 20)
private String name;
......@@ -64,6 +68,15 @@ public class Crop extends CooperatorOwnedModel implements Copyable<Crop> {
this.id = id;
}
public Crop(final String name) {
this.name = name;
}
public Crop(final String name, final String note) {
this.name = name;
this.note = note;
}
public Long getId() {
return id;
}
......
......@@ -22,6 +22,7 @@ import javax.persistence.*;
import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.ObjectIdGenerators;
import org.genesys.blocks.model.Copyable;
import org.gringlobal.custom.validation.javax.CodeValueField;
import org.gringlobal.model.community.CommunityCodeValues;
......@@ -32,7 +33,7 @@ import org.gringlobal.model.community.CommunityCodeValues;
@Entity
@Table(name = "crop_attach")
@JsonIdentityInfo(scope = CropAttach.class, generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class CropAttach extends CooperatorAttachment {
public class CropAttach extends CooperatorAttachment implements Copyable<CropAttach> {
private static final long serialVersionUID = 252653365102135759L;
@Id
......
/*
* 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;
import org.gringlobal.model.Crop;
import org.gringlobal.model.CropAttach;
/**
* @author Maxym Borodenko
*/
public interface CropAttachmentService extends AttachmentService<Crop, CropAttach, CropAttachmentService.CropAttachmentRequest> {
class CropAttachmentRequest extends AttachmentService.AttachmentRequest<CropAttach> {
}
}
......@@ -18,11 +18,12 @@ package org.gringlobal.service;
import org.gringlobal.model.Crop;
import org.gringlobal.model.TaxonomyCropMap;
import org.gringlobal.model.TaxonomySpecies;
import org.gringlobal.service.filter.CropFilter;
/**
* The Interface CropService.
*/
public interface CropService extends CRUDService<Crop> {
public interface CropService extends FilteredCRUDService<Crop, CropFilter> {
/**
* Gets the crop.
......
/*
* 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.io.Serializable;
import java.util.List;
import com.querydsl.core.types.Predicate;
import org.genesys.blocks.model.filters.StringFilter;
import org.gringlobal.model.Crop;
import org.gringlobal.model.QCrop;
/**
* Filters for {@link Crop}
*/
public class CropFilter extends CooperatorOwnedModelFilter<CropFilter, Crop> implements Serializable {
private static final long serialVersionUID = -4989267636323460260L;
/** The crop name. */
public StringFilter name;
/**
* Builds the query.
*
* @return the predicate
*/
@Override
public List<Predicate> collectPredicates() {
return collectPredicates(QCrop.crop);
}
/**
* Builds the query.
*
* @param crop the crop
* @return the predicate
*/
public List<Predicate> collectPredicates(QCrop crop) {
final List<Predicate> predicates = super.collectPredicates(crop, crop._super);
if (name != null) {
predicates.add(name.buildQuery(crop.name));
}
return predicates;
}
public synchronized StringFilter name() {
return this.name == null ? this.name = new StringFilter() : this.name;
}
}
......@@ -15,44 +15,122 @@
*/
package org.gringlobal.service.impl;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import org.genesys.filerepository.InvalidRepositoryFileDataException;
import org.genesys.filerepository.InvalidRepositoryPathException;
import org.gringlobal.api.InvalidApiUsageException;
import org.gringlobal.custom.elasticsearch.SearchException;
import org.gringlobal.model.Crop;
import org.gringlobal.model.CropAttach;
import org.gringlobal.model.QCropAttach;
import org.gringlobal.model.QTaxonomyCropMap;
import org.gringlobal.model.TaxonomyCropMap;
import org.gringlobal.model.TaxonomySpecies;
import org.gringlobal.persistence.CropRepository;
import org.gringlobal.persistence.TaxonomyCropMapRepository;
import org.gringlobal.service.CropAttachmentService;
import org.gringlobal.service.CropAttachmentService.CropAttachmentRequest;
import org.gringlobal.service.CropService;
import org.gringlobal.service.filter.CropFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.multipart.MultipartFile;
/**
* The Class CropServiceImpl.
*/
@Service
@Transactional(readOnly = true)
public class CropServiceImpl extends CRUDServiceImpl<Crop, CropRepository> implements CropService {
@Validated
public class CropServiceImpl extends FilteredCRUDServiceImpl<Crop, CropFilter, CropRepository> implements CropService {
@Autowired
private TaxonomyCropMapRepository taxonomyCropMapRepository;
@Component
protected static class AttachmentSupport extends BaseAttachmentSupport<Crop, CropAttach, CropAttachmentRequest> implements CropAttachmentService {
public AttachmentSupport() {
super(QCropAttach.cropAttach.crop.id, QCropAttach.cropAttach.id);
}
@Override
@PreAuthorize("hasAuthority('GROUP_ADMINS')")
public CropAttach uploadFile(Crop entity, MultipartFile file, CropAttachmentRequest metadata) throws IOException, InvalidRepositoryPathException, InvalidRepositoryFileDataException {
return super.uploadFile(entity, file, metadata);
}
@Override
@PreAuthorize("hasAuthority('GROUP_ADMINS')")
public CropAttach removeFile(Crop entity, Long attachmentId) {
return super.removeFile(entity, attachmentId);
}
@Override
protected Path createRepositoryPath(Crop crop) {
crop = owningEntityRepository.getOne(crop.getId());
return Paths.get("/crop/" + crop.getName());
}
@Override
protected CropAttach createAttach(Crop entity, CropAttach source) {
CropAttach attach = new CropAttach();
attach.apply(source);
attach.setCrop(entity);
return attach;
}
}
@Override
public Crop getCrop(String cropName) {
return repository.getByName(cropName);
}
@Override
@Transactional
public Crop create(Crop source) {
@PreAuthorize("hasAuthority('GROUP_ADMINS')")
public Crop create(final Crop source) {
LOG.debug("Create Crop. Input data {}", source);
Crop crop = new Crop();
crop.apply(source);
return repository.save(crop);
Crop saved = repository.save(crop);
saved.lazyLoad();
return saved;
}
@Override
public Crop update(Crop entity) {
// TODO Auto-generated method stub
return null;
@Transactional
@PreAuthorize("hasAuthority('GROUP_ADMINS')")
public Crop update(final Crop input) {
if (input == null) {
throw new InvalidApiUsageException("Crop must be provided.");
}
final Crop loaded = get(input.getId());
LOG.debug("Update Crop. Input data {}", input);
loaded.apply(input);
Crop saved = repository.save(loaded);
saved.lazyLoad();
return saved;
}
@Override
@PreAuthorize("hasAuthority('GROUP_ADMINS')")
// This method is overridden because we need permission check
public Crop remove(Crop entity) {
return super.remove(entity);
}
@Override
......@@ -69,4 +147,8 @@ public class CropServiceImpl extends CRUDServiceImpl<Crop, CropRepository> imple
return taxonomyCropMap;
}
@Override
public Page<Crop> list(CropFilter filter, Pageable page) throws SearchException {
return super.list(Crop.class, filter, page);
}
}
/*
* 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.test.service;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;
import org.genesys.filerepository.model.RepositoryImage;
import org.gringlobal.model.Crop;
import org.gringlobal.model.CropAttach;
import org.gringlobal.model.community.CommunityCodeValues;
import org.gringlobal.persistence.CropAttachRepository;
import org.gringlobal.persistence.CropRepository;
import org.gringlobal.service.CropAttachmentService;
import org.gringlobal.service.CropService;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.security.test.context.support.WithUserDetails;
import javax.transaction.Transactional;
import javax.validation.ConstraintViolationException;
/**
* @author Maxym Borodenko
*/
@WithUserDetails(value = AbstractServicesTest.USER_ADMINISTRATOR)
public class CropAttachServiceTest extends AbstractServicesTest {
private Crop crop;
@Autowired
private CropAttachmentService cropAttachmentService;
@Autowired
private CropService cropService;
@Autowired
private CropAttachRepository attachRepository;
@Autowired
private CropRepository cropRepository;
@Before
@Transactional
public void beforeTest() throws Exception {
super.beforeTest();
crop = cropService.create(new Crop("banana"));
assertThat(crop, notNullValue());
assertThat(cropRepository.count(), is(1L));
}
@After
@Transactional
public void cleanup() throws Exception {
attachRepository.deleteAll();
repositoryFileRepository.deleteAll();
deleteFolders(repositoryFolderRepository.findAll());
cropRepository.deleteAll();
super.cleanup();
}
@Test
public void uploadFileTest() throws Exception {
var imageFile = new MockMultipartFile("image", "image.png", "image/png", new byte[1]);
// build file metadata
var imageMetadata = new RepositoryImage();
imageMetadata.setDescription("Test description of image.");
// build attach metadata
CropAttach attach = new CropAttach();
attach.setCategoryCode(CommunityCodeValues.ATTACH_CATEGORY_IMAGE.value);
attach.setIsWebVisible(TRUE);
// build a request
var request = new CropAttachmentService.CropAttachmentRequest();
request.fileMetadata = imageMetadata;
request.attachMetadata = attach;
assertThat(attachRepository.count(), is(0L));
assertThat(repositoryFileRepository.count(), is(0L));
// upload
CropAttach saved = cropAttachmentService.uploadFile(crop, imageFile, request);
assertThat(saved, is(notNullValue()));
assertThat(attachRepository.count(), is(1L));
assertThat(repositoryFileRepository.count(), is(1L));
assertThat(saved.getRepositoryFile(), is(notNullValue()));
assertThat(saved.getRepositoryFile().getId(), is(greaterThan(0L)));
assertThat(saved.getCategoryCode(), is(CommunityCodeValues.ATTACH_CATEGORY_IMAGE.value));
assertThat(saved.getIsWebVisible(), is(TRUE));
assertThat(saved.getCrop(), is(notNullValue()));
assertThat(saved.getCrop().getId(), is(crop.getId()));
}
@Test(expected = ConstraintViolationException.class)
public void attachMetadataMustToBeProvidedTest() throws Exception {
var imageFile = new MockMultipartFile("image", "image.png", "image/png", new byte[1]);
// build file metadata
var imageMetadata = new RepositoryImage();
imageMetadata.setDescription("Test description of image.");
// build a request
var request = new CropAttachmentService.CropAttachmentRequest();
request.fileMetadata = imageMetadata;
request.attachMetadata = null;
assertThat(attachRepository.count(), is(0L));
// should fail
cropAttachmentService.uploadFile(crop, imageFile, request);
}
@Test
public void removeFileTest() throws Exception {
var imageFile = new MockMultipartFile("image", "image.png", "image/png", new byte[1]);
// build file metadata
RepositoryImage imageMetadata = new RepositoryImage();
imageMetadata.setDescription("Test description of image.");
// build attach metadata
CropAttach attach = new CropAttach();
attach.setCategoryCode(CommunityCodeValues.ATTACH_CATEGORY_IMAGE.value);
attach.setIsWebVisible(TRUE);
// build a request
var request = new CropAttachmentService.CropAttachmentRequest();
request.fileMetadata = imageMetadata;
request.attachMetadata = attach;
assertThat(attachRepository.count(), is(0L));
assertThat(repositoryFileRepository.count(), is(0L));
// upload
CropAttach saved = cropAttachmentService.uploadFile(crop, imageFile, request);
assertThat(saved, is(notNullValue()));
assertThat(attachRepository.count(), is(1L));
assertThat(repositoryFileRepository.count(), is(1L));
cropAttachmentService.removeFile(crop, saved.getId());
assertThat(attachRepository.count(), is(0L));
}
}
/*
* 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.test.service;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;
import org.gringlobal.custom.elasticsearch.SearchException;
import org.gringlobal.model.Crop;
import org.gringlobal.persistence.CropRepository;
import org.gringlobal.service.CropService;
import org.gringlobal.service.filter.CropFilter;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.security.access.AccessDeniedException;