This is an automated email from the ASF dual-hosted git repository. myrle pushed a commit to branch develop in repository https://gitbox.apache.org/repos/asf/fineract-cn-customer.git
commit 498c48ec0336bfe0ba0c17b26d042876cb31bf34 Author: Mark <mark.vanv...@gmail.com> AuthorDate: Wed Jul 19 18:24:20 2017 +0200 Add identification card upload --- .../customer/api/v1/CustomerEventConstants.java | 6 + .../customer/api/v1/client/CustomerManager.java | 83 +++++-- .../api/v1/client/ScanAlreadyExistsException.java | 19 +- .../api/v1/client/ScanNotFoundException.java | 19 +- .../api/v1/client/ScanValidationException.java | 17 +- .../api/v1/domain/IdentificationCardScan.java | 38 ++-- .../io/mifos/customer/api/v1/events/ScanEvent.java | 52 +++++ .../main/java/io/mifos/customer/TestCustomer.java | 105 +-------- .../io/mifos/customer/TestIdentificationCards.java | 252 +++++++++++++++++++++ .../src/main/java/io/mifos/customer/TestSuite.java | 1 + .../customer/listener/CustomerEventListener.java | 19 ++ .../java/io/mifos/customer/util/ScanGenerator.java | 39 ++++ .../CreateIdentificationCardScanCommand.java | 52 +++++ .../DeleteIdentificationCardScanCommand.java | 32 +-- .../command/handler/CustomerAggregate.java | 64 ++++++ .../mapper/IdentificationCardScanMapper.java | 44 ++++ .../repository/IdentificationCardScanEntity.java | 128 +++++++++++ .../IdentificationCardScanRepository.java | 35 +++ .../service/internal/service/CustomerService.java | 45 +++- .../rest/controller/CustomerRestController.java | 151 +++++++++++- .../mariadb/V4__identification_card_scans.sql | 30 +++ 21 files changed, 1016 insertions(+), 215 deletions(-) diff --git a/api/src/main/java/io/mifos/customer/api/v1/CustomerEventConstants.java b/api/src/main/java/io/mifos/customer/api/v1/CustomerEventConstants.java index 97a15c8..ab5a9a3 100644 --- a/api/src/main/java/io/mifos/customer/api/v1/CustomerEventConstants.java +++ b/api/src/main/java/io/mifos/customer/api/v1/CustomerEventConstants.java @@ -32,6 +32,9 @@ public interface CustomerEventConstants { String PUT_IDENTIFICATION_CARD = "put-identification-card"; String DELETE_IDENTIFICATION_CARD = "delete-identification-card"; + String POST_IDENTIFICATION_CARD_SCAN = "post-identification-card-scan"; + String DELETE_IDENTIFICATION_CARD_SCAN = "delete-identification-card-scan"; + String ACTIVATE_CUSTOMER = "activate-customer"; String LOCK_CUSTOMER = "lock-customer"; String UNLOCK_CUSTOMER = "unlock-customer"; @@ -55,6 +58,9 @@ public interface CustomerEventConstants { String SELECTOR_PUT_IDENTIFICATION_CARD = SELECTOR_NAME + " = '" + PUT_IDENTIFICATION_CARD + "'"; String SELECTOR_DELETE_IDENTIFICATION_CARD = SELECTOR_NAME + " = '" + DELETE_IDENTIFICATION_CARD + "'"; + String SELECTOR_POST_IDENTIFICATION_CARD_SCAN = SELECTOR_NAME + " = '" + POST_IDENTIFICATION_CARD_SCAN + "'"; + String SELECTOR_DELETE_IDENTIFICATION_CARD_SCAN = SELECTOR_NAME + " = '" + DELETE_IDENTIFICATION_CARD_SCAN + "'"; + String SELECTOR_ACTIVATE_CUSTOMER = SELECTOR_NAME + " = '" + ACTIVATE_CUSTOMER + "'"; String SELECTOR_LOCK_CUSTOMER = SELECTOR_NAME + " = '" + LOCK_CUSTOMER + "'"; String SELECTOR_UNLOCK_CUSTOMER = SELECTOR_NAME + " = '" + UNLOCK_CUSTOMER + "'"; diff --git a/api/src/main/java/io/mifos/customer/api/v1/client/CustomerManager.java b/api/src/main/java/io/mifos/customer/api/v1/client/CustomerManager.java index bac6bcb..597a3aa 100644 --- a/api/src/main/java/io/mifos/customer/api/v1/client/CustomerManager.java +++ b/api/src/main/java/io/mifos/customer/api/v1/client/CustomerManager.java @@ -17,26 +17,16 @@ package io.mifos.customer.api.v1.client; import io.mifos.core.api.annotation.ThrowsException; import io.mifos.core.api.annotation.ThrowsExceptions; -import io.mifos.core.api.util.CustomFeignClientsConfiguration; +import io.mifos.core.lang.validation.constraints.ValidIdentifier; import io.mifos.customer.api.v1.config.CustomerFeignClientConfig; -import io.mifos.customer.api.v1.domain.Address; -import io.mifos.customer.api.v1.domain.Command; -import io.mifos.customer.api.v1.domain.ContactDetail; -import io.mifos.customer.api.v1.domain.IdentificationCard; -import io.mifos.customer.api.v1.domain.Customer; -import io.mifos.customer.api.v1.domain.CustomerPage; -import io.mifos.customer.api.v1.domain.ProcessStep; -import io.mifos.customer.api.v1.domain.TaskDefinition; +import io.mifos.customer.api.v1.domain.*; import org.springframework.cloud.netflix.feign.FeignClient; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; +import javax.validation.constraints.Size; import java.util.List; @SuppressWarnings("unused") @@ -233,6 +223,71 @@ public interface CustomerManager { @PathVariable("number") final String number); @RequestMapping( + value = "/customers/{identifier}/identifications/{number}/scans", + method = RequestMethod.GET, + produces = MediaType.ALL_VALUE, + consumes = MediaType.APPLICATION_JSON_VALUE + ) + @ThrowsExceptions({ + @ThrowsException(status = HttpStatus.NOT_FOUND, exception = IdentificationCardNotFoundException.class) + }) + List<IdentificationCardScan> fetchIdentificationCardScans(@PathVariable("identifier") final String identifier, + @PathVariable("number") final String number); + + @RequestMapping( + value = "/customers/{identifier}/identifications/{number}/scans/{scanIdentifier}", + method = RequestMethod.GET, + produces = MediaType.ALL_VALUE, + consumes = MediaType.APPLICATION_JSON_VALUE + ) + @ThrowsExceptions({ + @ThrowsException(status = HttpStatus.NOT_FOUND, exception = ScanNotFoundException.class) + }) + IdentificationCardScan findIdentificationCardScan(@PathVariable("identifier") final String identifier, + @PathVariable("number") final String number, + @PathVariable("scanIdentifier") final String scanIdentifier); + + @RequestMapping( + value = "/customers/{identifier}/identifications/{number}/scans/{scanIdentifier}/image", + method = RequestMethod.GET, + produces = MediaType.ALL_VALUE, + consumes = MediaType.APPLICATION_JSON_VALUE + ) + @ThrowsExceptions({ + @ThrowsException(status = HttpStatus.NOT_FOUND, exception = ScanNotFoundException.class) + }) + byte[] fetchIdentificationCardScanImage(@PathVariable("identifier") final String identifier, + @PathVariable("number") final String number, + @PathVariable("scanIdentifier") final String scanIdentifier); + + @RequestMapping( + value = "/customers/{identifier}/identifications/{number}/scans", + method = RequestMethod.POST, + produces = MediaType.ALL_VALUE, + consumes = MediaType.MULTIPART_FORM_DATA_VALUE + ) + @ThrowsExceptions({ + @ThrowsException(status = HttpStatus.NOT_FOUND, exception = IdentificationCardNotFoundException.class), + @ThrowsException(status = HttpStatus.BAD_REQUEST, exception = ScanValidationException.class), + @ThrowsException(status = HttpStatus.CONFLICT, exception = ScanAlreadyExistsException.class) + }) + void postIdentificationCardScan(@PathVariable("identifier") final String identifier, + @PathVariable("number") final String number, + @RequestParam("scanIdentifier") @ValidIdentifier final String scanIdentifier, + @RequestParam("description") @Size(max = 4096) final String description, + @RequestBody final MultipartFile image); + + @RequestMapping( + value = "/customers/{identifier}/identifications/{number}/scans/{scanIdentifier}", + method = RequestMethod.DELETE, + produces = MediaType.ALL_VALUE, + consumes = MediaType.APPLICATION_JSON_VALUE + ) + void deleteScan(@PathVariable("identifier") final String identifier, + @PathVariable("number") final String number, + @PathVariable("scanIdentifier") final String scanIdentifier); + + @RequestMapping( value = "/customers/{identifier}/portrait", method = RequestMethod.GET, produces = MediaType.ALL_VALUE diff --git a/component-test/src/main/java/io/mifos/customer/TestSuite.java b/api/src/main/java/io/mifos/customer/api/v1/client/ScanAlreadyExistsException.java similarity index 60% copy from component-test/src/main/java/io/mifos/customer/TestSuite.java copy to api/src/main/java/io/mifos/customer/api/v1/client/ScanAlreadyExistsException.java index 2e12b10..45ef5a1 100644 --- a/component-test/src/main/java/io/mifos/customer/TestSuite.java +++ b/api/src/main/java/io/mifos/customer/api/v1/client/ScanAlreadyExistsException.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 The Mifos Initiative. + * Copyright 2016 The Mifos Initiative. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,20 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.mifos.customer; +package io.mifos.customer.api.v1.client; -import org.junit.runner.RunWith; -import org.junit.runners.Suite; - -/** - * @author Myrle Krantz - */ -@RunWith(Suite.class) -@Suite.SuiteClasses({ - TestCustomer.class, - TestInfrastructure.class, - TestTaskDefinition.class, - TestTaskInstance.class, -}) -public class TestSuite extends SuiteTestEnvironment { +public final class ScanAlreadyExistsException extends RuntimeException { } diff --git a/component-test/src/main/java/io/mifos/customer/TestSuite.java b/api/src/main/java/io/mifos/customer/api/v1/client/ScanNotFoundException.java similarity index 60% copy from component-test/src/main/java/io/mifos/customer/TestSuite.java copy to api/src/main/java/io/mifos/customer/api/v1/client/ScanNotFoundException.java index 2e12b10..3f2ada1 100644 --- a/component-test/src/main/java/io/mifos/customer/TestSuite.java +++ b/api/src/main/java/io/mifos/customer/api/v1/client/ScanNotFoundException.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 The Mifos Initiative. + * Copyright 2016 The Mifos Initiative. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,20 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.mifos.customer; +package io.mifos.customer.api.v1.client; -import org.junit.runner.RunWith; -import org.junit.runners.Suite; - -/** - * @author Myrle Krantz - */ -@RunWith(Suite.class) -@Suite.SuiteClasses({ - TestCustomer.class, - TestInfrastructure.class, - TestTaskDefinition.class, - TestTaskInstance.class, -}) -public class TestSuite extends SuiteTestEnvironment { +public final class ScanNotFoundException extends RuntimeException { } diff --git a/component-test/src/main/java/io/mifos/customer/TestSuite.java b/api/src/main/java/io/mifos/customer/api/v1/client/ScanValidationException.java similarity index 64% copy from component-test/src/main/java/io/mifos/customer/TestSuite.java copy to api/src/main/java/io/mifos/customer/api/v1/client/ScanValidationException.java index 2e12b10..cef64f9 100644 --- a/component-test/src/main/java/io/mifos/customer/TestSuite.java +++ b/api/src/main/java/io/mifos/customer/api/v1/client/ScanValidationException.java @@ -13,20 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.mifos.customer; +package io.mifos.customer.api.v1.client; -import org.junit.runner.RunWith; -import org.junit.runners.Suite; - -/** - * @author Myrle Krantz - */ -@RunWith(Suite.class) -@Suite.SuiteClasses({ - TestCustomer.class, - TestInfrastructure.class, - TestTaskDefinition.class, - TestTaskInstance.class, -}) -public class TestSuite extends SuiteTestEnvironment { +public final class ScanValidationException extends RuntimeException { } diff --git a/component-test/src/main/java/io/mifos/customer/TestSuite.java b/api/src/main/java/io/mifos/customer/api/v1/domain/IdentificationCardScan.java similarity index 53% copy from component-test/src/main/java/io/mifos/customer/TestSuite.java copy to api/src/main/java/io/mifos/customer/api/v1/domain/IdentificationCardScan.java index 2e12b10..462e4fe 100644 --- a/component-test/src/main/java/io/mifos/customer/TestSuite.java +++ b/api/src/main/java/io/mifos/customer/api/v1/domain/IdentificationCardScan.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 The Mifos Initiative. + * Copyright 2016 The Mifos Initiative. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,20 +13,28 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.mifos.customer; +package io.mifos.customer.api.v1.domain; -import org.junit.runner.RunWith; -import org.junit.runners.Suite; +public class IdentificationCardScan { + + private String identifier; + + private String description; + + public String getIdentifier() { + return identifier; + } + + public void setIdentifier(String identifier) { + this.identifier = identifier; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } -/** - * @author Myrle Krantz - */ -@RunWith(Suite.class) -@Suite.SuiteClasses({ - TestCustomer.class, - TestInfrastructure.class, - TestTaskDefinition.class, - TestTaskInstance.class, -}) -public class TestSuite extends SuiteTestEnvironment { } diff --git a/api/src/main/java/io/mifos/customer/api/v1/events/ScanEvent.java b/api/src/main/java/io/mifos/customer/api/v1/events/ScanEvent.java new file mode 100644 index 0000000..f4f8057 --- /dev/null +++ b/api/src/main/java/io/mifos/customer/api/v1/events/ScanEvent.java @@ -0,0 +1,52 @@ +/* + * Copyright 2016 The Mifos Initiative. + * + * 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 io.mifos.customer.api.v1.events; + +import java.util.Objects; + +public class ScanEvent { + + private final String number; + + private final String scanIdentifier; + + public ScanEvent(final String number, final String scanIdentifier) { + this.number = number; + this.scanIdentifier = scanIdentifier; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ScanEvent scanEvent = (ScanEvent) o; + return Objects.equals(number, scanEvent.number) && + Objects.equals(scanIdentifier, scanEvent.scanIdentifier); + } + + @Override + public int hashCode() { + return Objects.hash(number, scanIdentifier); + } + + @Override + public String toString() { + return "ScanEvent{" + + "number='" + number + '\'' + + ", scanIdentifier='" + scanIdentifier + '\'' + + '}'; + } +} diff --git a/component-test/src/main/java/io/mifos/customer/TestCustomer.java b/component-test/src/main/java/io/mifos/customer/TestCustomer.java index 7ddf59e..228bc53 100644 --- a/component-test/src/main/java/io/mifos/customer/TestCustomer.java +++ b/component-test/src/main/java/io/mifos/customer/TestCustomer.java @@ -18,7 +18,10 @@ package io.mifos.customer; import io.mifos.customer.api.v1.CustomerEventConstants; import io.mifos.customer.api.v1.client.*; import io.mifos.customer.api.v1.domain.*; -import io.mifos.customer.util.*; +import io.mifos.customer.util.AddressGenerator; +import io.mifos.customer.util.CommandGenerator; +import io.mifos.customer.util.ContactDetailGenerator; +import io.mifos.customer.util.CustomerGenerator; import org.apache.commons.lang3.RandomStringUtils; import org.junit.Assert; import org.junit.Test; @@ -303,106 +306,6 @@ public class TestCustomer extends AbstractCustomerTest { } @Test - public void shouldFetchIdentificationCards() throws Exception { - final Customer customer = CustomerGenerator.createRandomCustomer(); - - this.customerManager.createCustomer(customer); - - this.eventRecorder.wait(CustomerEventConstants.POST_CUSTOMER, customer.getIdentifier()); - - Stream.of( - IdentificationCardGenerator.createRandomIdentificationCard(), - IdentificationCardGenerator.createRandomIdentificationCard(), - IdentificationCardGenerator.createRandomIdentificationCard() - ).forEach(identificationCard -> { - this.customerManager.createIdentificationCard(customer.getIdentifier(), identificationCard); - try { - this.eventRecorder.wait(CustomerEventConstants.POST_IDENTIFICATION_CARD, identificationCard.getNumber()); - } catch (final InterruptedException ex) { - Assert.fail(ex.getMessage()); - } - }); - - final List<IdentificationCard> result = this.customerManager.fetchIdentificationCards(customer.getIdentifier()); - - Assert.assertTrue(result.size() == 3); - } - - @Test - public void shouldCreateIdentificationCard() throws Exception { - final Customer customer = CustomerGenerator.createRandomCustomer(); - - this.customerManager.createCustomer(customer); - - this.eventRecorder.wait(CustomerEventConstants.POST_CUSTOMER, customer.getIdentifier()); - - final IdentificationCard newIdentificationCard = IdentificationCardGenerator.createRandomIdentificationCard(); - - this.customerManager.createIdentificationCard(customer.getIdentifier(), newIdentificationCard); - - this.eventRecorder.wait(CustomerEventConstants.POST_IDENTIFICATION_CARD, newIdentificationCard.getNumber()); - - final IdentificationCard identificationCard = this.customerManager.findIdentificationCard(customer.getIdentifier(), newIdentificationCard.getNumber()); - - Assert.assertNotNull(identificationCard); - - Assert.assertEquals(identificationCard.getCreatedBy(), TEST_USER); - } - - @Test - public void shouldUpdateIdentificationCard() throws Exception { - final Customer customer = CustomerGenerator.createRandomCustomer(); - - this.customerManager.createCustomer(customer); - - this.eventRecorder.wait(CustomerEventConstants.POST_CUSTOMER, customer.getIdentifier()); - - final IdentificationCard newIdentificationCard = IdentificationCardGenerator.createRandomIdentificationCard(); - - this.customerManager.createIdentificationCard(customer.getIdentifier(), newIdentificationCard); - - this.eventRecorder.wait(CustomerEventConstants.POST_IDENTIFICATION_CARD, newIdentificationCard.getNumber()); - - final IdentificationCard identificationCard = this.customerManager.findIdentificationCard(customer.getIdentifier(), newIdentificationCard.getNumber()); - - final IdentificationCard updatedIdentificationCard = IdentificationCardGenerator.createRandomIdentificationCard(); - - updatedIdentificationCard.setNumber(newIdentificationCard.getNumber()); - - this.customerManager.updateIdentificationCard(customer.getIdentifier(), updatedIdentificationCard.getNumber(), updatedIdentificationCard); - - this.eventRecorder.wait(CustomerEventConstants.PUT_IDENTIFICATION_CARD, updatedIdentificationCard.getNumber()); - - final IdentificationCard changedIdentificationCard = this.customerManager.findIdentificationCard(customer.getIdentifier(), identificationCard.getNumber()); - - Assert.assertEquals(updatedIdentificationCard.getType(), changedIdentificationCard.getType()); - Assert.assertEquals(updatedIdentificationCard.getIssuer(), changedIdentificationCard.getIssuer()); - Assert.assertEquals(updatedIdentificationCard.getNumber(), changedIdentificationCard.getNumber()); - Assert.assertEquals(TEST_USER, changedIdentificationCard.getLastModifiedBy()); - } - - @Test(expected = IdentificationCardNotFoundException.class) - public void shouldDeleteIdentificationCard() throws Exception { - final Customer customer = CustomerGenerator.createRandomCustomer(); - - this.customerManager.createCustomer(customer); - - this.eventRecorder.wait(CustomerEventConstants.POST_CUSTOMER, customer.getIdentifier()); - - final IdentificationCard identificationCard = IdentificationCardGenerator.createRandomIdentificationCard(); - - this.customerManager.createIdentificationCard(customer.getIdentifier(), identificationCard); - - this.eventRecorder.wait(CustomerEventConstants.POST_IDENTIFICATION_CARD, identificationCard.getNumber()); - - this.customerManager.deleteIdentificationCard(customer.getIdentifier(), identificationCard.getNumber()); - - this.eventRecorder.wait(CustomerEventConstants.DELETE_IDENTIFICATION_CARD, identificationCard.getNumber()); - - this.customerManager.findIdentificationCard(customer.getIdentifier(), identificationCard.getNumber()); - } - - @Test public void shouldUploadPortrait() throws Exception { final Customer customer = CustomerGenerator.createRandomCustomer(); this.customerManager.createCustomer(customer); diff --git a/component-test/src/main/java/io/mifos/customer/TestIdentificationCards.java b/component-test/src/main/java/io/mifos/customer/TestIdentificationCards.java new file mode 100644 index 0000000..0740bba --- /dev/null +++ b/component-test/src/main/java/io/mifos/customer/TestIdentificationCards.java @@ -0,0 +1,252 @@ +/* + * Copyright 2016 The Mifos Initiative. + * + * 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 io.mifos.customer; + +import io.mifos.customer.api.v1.CustomerEventConstants; +import io.mifos.customer.api.v1.client.IdentificationCardNotFoundException; +import io.mifos.customer.api.v1.client.ScanAlreadyExistsException; +import io.mifos.customer.api.v1.client.ScanNotFoundException; +import io.mifos.customer.api.v1.domain.Customer; +import io.mifos.customer.api.v1.domain.IdentificationCard; +import io.mifos.customer.api.v1.domain.IdentificationCardScan; +import io.mifos.customer.api.v1.events.ScanEvent; +import io.mifos.customer.util.CustomerGenerator; +import io.mifos.customer.util.IdentificationCardGenerator; +import io.mifos.customer.util.ScanGenerator; +import org.apache.commons.lang3.RandomStringUtils; +import org.junit.Assert; +import org.junit.Test; +import org.springframework.http.MediaType; +import org.springframework.mock.web.MockMultipartFile; + +import java.util.List; +import java.util.stream.Stream; + +public class TestIdentificationCards extends AbstractCustomerTest { + + @Test + public void shouldFetchIdentificationCards() throws Exception { + final String customerIdentifier = this.createCustomer(); + + Stream.of( + IdentificationCardGenerator.createRandomIdentificationCard(), + IdentificationCardGenerator.createRandomIdentificationCard(), + IdentificationCardGenerator.createRandomIdentificationCard() + ).forEach(identificationCard -> { + this.customerManager.createIdentificationCard(customerIdentifier, identificationCard); + try { + this.eventRecorder.wait(CustomerEventConstants.POST_IDENTIFICATION_CARD, identificationCard.getNumber()); + } catch (final InterruptedException ex) { + Assert.fail(ex.getMessage()); + } + }); + + final List<IdentificationCard> result = this.customerManager.fetchIdentificationCards(customerIdentifier); + + Assert.assertTrue(result.size() == 3); + } + + @Test + public void shouldCreateIdentificationCard() throws Exception { + final String customerIdentifier = this.createCustomer(); + + final String identificationCardNumber = this.createIdentificationCard(customerIdentifier); + + final IdentificationCard identificationCard = this.customerManager.findIdentificationCard(customerIdentifier, identificationCardNumber); + + Assert.assertNotNull(identificationCard); + + Assert.assertEquals(identificationCard.getCreatedBy(), TEST_USER); + } + + @Test + public void shouldUpdateIdentificationCard() throws Exception { + final String customerIdentifier = this.createCustomer(); + + final String identificationCardNumber = this.createIdentificationCard(customerIdentifier); + + final IdentificationCard identificationCard = this.customerManager.findIdentificationCard(customerIdentifier, identificationCardNumber); + + final IdentificationCard updatedIdentificationCard = IdentificationCardGenerator.createRandomIdentificationCard(); + + updatedIdentificationCard.setNumber(identificationCardNumber); + + this.customerManager.updateIdentificationCard(customerIdentifier, updatedIdentificationCard.getNumber(), updatedIdentificationCard); + + this.eventRecorder.wait(CustomerEventConstants.PUT_IDENTIFICATION_CARD, updatedIdentificationCard.getNumber()); + + final IdentificationCard changedIdentificationCard = this.customerManager.findIdentificationCard(customerIdentifier, identificationCard.getNumber()); + + Assert.assertEquals(updatedIdentificationCard.getType(), changedIdentificationCard.getType()); + Assert.assertEquals(updatedIdentificationCard.getIssuer(), changedIdentificationCard.getIssuer()); + Assert.assertEquals(updatedIdentificationCard.getNumber(), changedIdentificationCard.getNumber()); + Assert.assertEquals(TEST_USER, changedIdentificationCard.getLastModifiedBy()); + } + + @Test(expected = IdentificationCardNotFoundException.class) + public void shouldDeleteIdentificationCard() throws Exception { + final String customerIdentifier = this.createCustomer(); + + final String identificationCardNumber = this.createIdentificationCard(customerIdentifier); + + this.customerManager.deleteIdentificationCard(customerIdentifier, identificationCardNumber); + + this.eventRecorder.wait(CustomerEventConstants.DELETE_IDENTIFICATION_CARD, identificationCardNumber); + + this.customerManager.findIdentificationCard(customerIdentifier, identificationCardNumber); + } + + @Test(expected = IdentificationCardNotFoundException.class) + public void shouldDeleteIdentificationCardWithScan() throws Exception { + final String customerIdentifier = this.createCustomer(); + + final String identificationCardNumber = this.createIdentificationCard(customerIdentifier); + + this.createScan(customerIdentifier, identificationCardNumber); + + this.customerManager.deleteIdentificationCard(customerIdentifier, identificationCardNumber); + + this.eventRecorder.wait(CustomerEventConstants.DELETE_IDENTIFICATION_CARD, identificationCardNumber); + + this.customerManager.findIdentificationCard(customerIdentifier, identificationCardNumber); + } + + @Test + public void shouldFetchScans() throws Exception { + final String customerIdentifier = this.createCustomer(); + + final String identificationCardNumber = this.createIdentificationCard(customerIdentifier); + + this.createScan(customerIdentifier, identificationCardNumber); + this.createScan(customerIdentifier, identificationCardNumber); + this.createScan(customerIdentifier, identificationCardNumber); + + final List<IdentificationCardScan> result = this.customerManager.fetchIdentificationCardScans(customerIdentifier, identificationCardNumber); + + Assert.assertTrue(result.size() == 3); + } + + @Test + public void shouldFindScan() throws Exception { + final String customerIdentifier = this.createCustomer(); + + final String identificationCardNumberOne = this.createIdentificationCard(customerIdentifier); + final String identificationCardNumberTwo = this.createIdentificationCard(customerIdentifier); + this.createScan(customerIdentifier, identificationCardNumberOne, "sameIdentifier"); + final IdentificationCardScan createdScan = this.createScan(customerIdentifier, identificationCardNumberTwo, "sameIdentifier"); + + final IdentificationCardScan scan = this.customerManager.findIdentificationCardScan(customerIdentifier, identificationCardNumberTwo, createdScan.getIdentifier()); + + Assert.assertNotNull(scan); + + Assert.assertEquals(scan.getIdentifier(), createdScan.getIdentifier()); + Assert.assertEquals(scan.getDescription(), createdScan.getDescription()); + } + + @Test + public void shouldFindScanWithImage() throws Exception { + final String customerIdentifier = this.createCustomer(); + + final String identificationCardNumber = this.createIdentificationCard(customerIdentifier); + + final IdentificationCardScan scan = ScanGenerator.createRandomScan(null); + + final byte[] imageInBytes = RandomStringUtils.randomAlphanumeric(20).getBytes(); + + final MockMultipartFile image = new MockMultipartFile("image", "test.png", MediaType.IMAGE_PNG_VALUE, imageInBytes); + + this.customerManager.postIdentificationCardScan(customerIdentifier, identificationCardNumber, scan.getIdentifier(), scan.getDescription(), image); + + this.eventRecorder.wait(CustomerEventConstants.POST_IDENTIFICATION_CARD_SCAN, new ScanEvent(identificationCardNumber, scan.getIdentifier())); + + final byte[] persistedImageInBytes = this.customerManager.fetchIdentificationCardScanImage(customerIdentifier, identificationCardNumber, scan.getIdentifier()); + + Assert.assertArrayEquals(imageInBytes, persistedImageInBytes); + } + + @Test(expected = ScanAlreadyExistsException.class) + public void shouldThrowIfScanAlreadyExists() throws Exception { + final String customerIdentifier = this.createCustomer(); + + final String identificationCardNumber = this.createIdentificationCard(customerIdentifier); + + final IdentificationCardScan scan = this.createScan(customerIdentifier, identificationCardNumber); + + final MockMultipartFile image = new MockMultipartFile("image", "test.png", MediaType.IMAGE_PNG_VALUE, RandomStringUtils.randomAlphanumeric(20).getBytes()); + + this.customerManager.postIdentificationCardScan(customerIdentifier, identificationCardNumber, scan.getIdentifier(), scan.getDescription(), image); + } + + @Test(expected = ScanNotFoundException.class) + public void shouldDeleteScan() throws Exception { + final String customerIdentifier = this.createCustomer(); + + final String identificationCardNumber = this.createIdentificationCard(customerIdentifier); + + final IdentificationCardScan createdScan = this.createScan(customerIdentifier, identificationCardNumber); + + this.customerManager.deleteScan(customerIdentifier, identificationCardNumber, createdScan.getIdentifier()); + + this.eventRecorder.wait(CustomerEventConstants.DELETE_IDENTIFICATION_CARD_SCAN, new ScanEvent(identificationCardNumber, createdScan.getIdentifier())); + + this.customerManager.findIdentificationCardScan(customerIdentifier, identificationCardNumber, createdScan.getIdentifier()); + } + + private String createCustomer() throws Exception { + final Customer customer = CustomerGenerator.createRandomCustomer(); + + this.customerManager.createCustomer(customer); + + this.eventRecorder.wait(CustomerEventConstants.POST_CUSTOMER, customer.getIdentifier()); + + return customer.getIdentifier(); + } + + private String createIdentificationCard(final String customerIdentifier) throws Exception { + final IdentificationCard identificationCard = IdentificationCardGenerator.createRandomIdentificationCard(); + + this.customerManager.createIdentificationCard(customerIdentifier, identificationCard); + + this.eventRecorder.wait(CustomerEventConstants.POST_IDENTIFICATION_CARD, identificationCard.getNumber()); + + return identificationCard.getNumber(); + } + + private IdentificationCardScan createScan(final String customerIdentifier, final String cardNumber) throws Exception { + final IdentificationCardScan scan = ScanGenerator.createRandomScan(null); + + this.postIdentificationCardScan(customerIdentifier, cardNumber, scan); + + return scan; + } + + private IdentificationCardScan createScan(final String customerIdentifier, final String cardNumber, final String identifier) throws Exception { + final IdentificationCardScan scan = ScanGenerator.createRandomScan(identifier); + + this.postIdentificationCardScan(customerIdentifier, cardNumber, scan); + + return scan; + } + + private void postIdentificationCardScan(final String customerIdentifier, final String cardNumber, final IdentificationCardScan scan) throws InterruptedException { + final MockMultipartFile image = new MockMultipartFile("image", "test.png", MediaType.IMAGE_PNG_VALUE, RandomStringUtils.randomAlphanumeric(20).getBytes()); + + this.customerManager.postIdentificationCardScan(customerIdentifier, cardNumber, scan.getIdentifier(), scan.getDescription(), image); + + this.eventRecorder.wait(CustomerEventConstants.POST_IDENTIFICATION_CARD_SCAN, new ScanEvent(cardNumber, scan.getIdentifier())); + } + +} diff --git a/component-test/src/main/java/io/mifos/customer/TestSuite.java b/component-test/src/main/java/io/mifos/customer/TestSuite.java index 2e12b10..4ed78bb 100644 --- a/component-test/src/main/java/io/mifos/customer/TestSuite.java +++ b/component-test/src/main/java/io/mifos/customer/TestSuite.java @@ -24,6 +24,7 @@ import org.junit.runners.Suite; @RunWith(Suite.class) @Suite.SuiteClasses({ TestCustomer.class, + TestIdentificationCards.class, TestInfrastructure.class, TestTaskDefinition.class, TestTaskInstance.class, diff --git a/component-test/src/main/java/io/mifos/customer/listener/CustomerEventListener.java b/component-test/src/main/java/io/mifos/customer/listener/CustomerEventListener.java index f066746..5c1d2e1 100644 --- a/component-test/src/main/java/io/mifos/customer/listener/CustomerEventListener.java +++ b/component-test/src/main/java/io/mifos/customer/listener/CustomerEventListener.java @@ -18,6 +18,7 @@ package io.mifos.customer.listener; import io.mifos.core.lang.config.TenantHeaderFilter; import io.mifos.core.test.listener.EventRecorder; import io.mifos.customer.api.v1.CustomerEventConstants; +import io.mifos.customer.api.v1.events.ScanEvent; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jms.annotation.JmsListener; import org.springframework.messaging.handler.annotation.Header; @@ -144,6 +145,24 @@ public class CustomerEventListener { @JmsListener( destination = CustomerEventConstants.DESTINATION, + selector = CustomerEventConstants.SELECTOR_POST_IDENTIFICATION_CARD_SCAN + ) + public void identificationCardScanCreateEvent(@Header(TenantHeaderFilter.TENANT_HEADER) final String tenant, + final String payload) { + this.eventRecorder.event(tenant, CustomerEventConstants.POST_IDENTIFICATION_CARD_SCAN, payload, ScanEvent.class); + } + + @JmsListener( + destination = CustomerEventConstants.DESTINATION, + selector = CustomerEventConstants.SELECTOR_DELETE_IDENTIFICATION_CARD_SCAN + ) + public void identificationCardScanDeleteEvent(@Header(TenantHeaderFilter.TENANT_HEADER) final String tenant, + final String payload) { + this.eventRecorder.event(tenant, CustomerEventConstants.DELETE_IDENTIFICATION_CARD_SCAN, payload, ScanEvent.class); + } + + @JmsListener( + destination = CustomerEventConstants.DESTINATION, selector = CustomerEventConstants.SELECTOR_PUT_PORTRAIT ) public void portraitPutEvent(@Header(TenantHeaderFilter.TENANT_HEADER) final String tenant, diff --git a/component-test/src/main/java/io/mifos/customer/util/ScanGenerator.java b/component-test/src/main/java/io/mifos/customer/util/ScanGenerator.java new file mode 100644 index 0000000..b139623 --- /dev/null +++ b/component-test/src/main/java/io/mifos/customer/util/ScanGenerator.java @@ -0,0 +1,39 @@ +/* + * Copyright 2016 The Mifos Initiative. + * + * 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 io.mifos.customer.util; + +import io.mifos.customer.api.v1.domain.IdentificationCardScan; +import org.apache.commons.lang3.RandomStringUtils; + +import javax.annotation.Nullable; + +public class ScanGenerator { + + private ScanGenerator() {} + + public static IdentificationCardScan createRandomScan(@Nullable final String identifier) { + final IdentificationCardScan scan = new IdentificationCardScan(); + + if(identifier != null) { + scan.setIdentifier(identifier); + } else { + scan.setIdentifier(RandomStringUtils.randomAlphanumeric(32)); + } + + scan.setDescription(RandomStringUtils.randomAlphanumeric(128)); + return scan; + } +} diff --git a/service/src/main/java/io/mifos/customer/service/internal/command/CreateIdentificationCardScanCommand.java b/service/src/main/java/io/mifos/customer/service/internal/command/CreateIdentificationCardScanCommand.java new file mode 100644 index 0000000..166ba69 --- /dev/null +++ b/service/src/main/java/io/mifos/customer/service/internal/command/CreateIdentificationCardScanCommand.java @@ -0,0 +1,52 @@ +/* + * Copyright 2016 The Mifos Initiative. + * + * 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 io.mifos.customer.service.internal.command; + +import io.mifos.customer.api.v1.domain.IdentificationCardScan; +import org.springframework.web.multipart.MultipartFile; + +public class CreateIdentificationCardScanCommand { + + private final String number; + + private final IdentificationCardScan scan; + + private final MultipartFile image; + + public CreateIdentificationCardScanCommand(final String number, final IdentificationCardScan scan, final MultipartFile image) { + this.number = number; + this.scan = scan; + this.image = image; + } + + public String number () { + return number; + } + + public IdentificationCardScan scan() { + return scan; + } + + public MultipartFile image() { return image; } + + @Override + public String toString() { + return "CreateIdentificationCardScanCommand{" + + "number='" + number + '\'' + + ", scan=" + scan + + '}'; + } +} diff --git a/component-test/src/main/java/io/mifos/customer/TestSuite.java b/service/src/main/java/io/mifos/customer/service/internal/command/DeleteIdentificationCardScanCommand.java similarity index 53% copy from component-test/src/main/java/io/mifos/customer/TestSuite.java copy to service/src/main/java/io/mifos/customer/service/internal/command/DeleteIdentificationCardScanCommand.java index 2e12b10..2240c0a 100644 --- a/component-test/src/main/java/io/mifos/customer/TestSuite.java +++ b/service/src/main/java/io/mifos/customer/service/internal/command/DeleteIdentificationCardScanCommand.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 The Mifos Initiative. + * Copyright 2016 The Mifos Initiative. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,20 +13,22 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.mifos.customer; +package io.mifos.customer.service.internal.command; -import org.junit.runner.RunWith; -import org.junit.runners.Suite; +public class DeleteIdentificationCardScanCommand { -/** - * @author Myrle Krantz - */ -@RunWith(Suite.class) -@Suite.SuiteClasses({ - TestCustomer.class, - TestInfrastructure.class, - TestTaskDefinition.class, - TestTaskInstance.class, -}) -public class TestSuite extends SuiteTestEnvironment { + private final String number; + + private final String scanIdentifier; + + public DeleteIdentificationCardScanCommand(final String number, final String scanIdentifier) { + this.number = number; + this.scanIdentifier = scanIdentifier; + } + + public String number() { return number; } + + public String scanIdentifier() { + return scanIdentifier; + } } diff --git a/service/src/main/java/io/mifos/customer/service/internal/command/handler/CustomerAggregate.java b/service/src/main/java/io/mifos/customer/service/internal/command/handler/CustomerAggregate.java index 953f8fd..7ec7770 100644 --- a/service/src/main/java/io/mifos/customer/service/internal/command/handler/CustomerAggregate.java +++ b/service/src/main/java/io/mifos/customer/service/internal/command/handler/CustomerAggregate.java @@ -18,11 +18,13 @@ package io.mifos.customer.service.internal.command.handler; import io.mifos.core.api.util.UserContextHolder; import io.mifos.core.command.annotation.Aggregate; import io.mifos.core.command.annotation.CommandHandler; +import io.mifos.core.command.annotation.CommandLogLevel; import io.mifos.core.command.annotation.EventEmitter; import io.mifos.core.lang.ServiceException; import io.mifos.customer.api.v1.CustomerEventConstants; import io.mifos.customer.api.v1.domain.Command; import io.mifos.customer.api.v1.domain.Customer; +import io.mifos.customer.api.v1.events.ScanEvent; import io.mifos.customer.catalog.service.internal.repository.CatalogEntity; import io.mifos.customer.catalog.service.internal.repository.CatalogRepository; import io.mifos.customer.catalog.service.internal.repository.FieldEntity; @@ -34,6 +36,7 @@ import io.mifos.customer.service.internal.mapper.*; import io.mifos.customer.service.internal.repository.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; import java.io.IOException; import java.sql.Date; @@ -51,6 +54,7 @@ public class CustomerAggregate { private final AddressRepository addressRepository; private final CustomerRepository customerRepository; private final IdentificationCardRepository identificationCardRepository; + private final IdentificationCardScanRepository identificationCardScanRepository; private final PortraitRepository portraitRepository; private final ContactDetailRepository contactDetailRepository; private final FieldValueRepository fieldValueRepository; @@ -63,6 +67,7 @@ public class CustomerAggregate { public CustomerAggregate(final AddressRepository addressRepository, final CustomerRepository customerRepository, final IdentificationCardRepository identificationCardRepository, + final IdentificationCardScanRepository identificationCardScanRepository, final PortraitRepository portraitRepository, final ContactDetailRepository contactDetailRepository, final FieldValueRepository fieldValueRepository, @@ -74,6 +79,7 @@ public class CustomerAggregate { this.addressRepository = addressRepository; this.customerRepository = customerRepository; this.identificationCardRepository = identificationCardRepository; + this.identificationCardScanRepository = identificationCardScanRepository; this.portraitRepository = portraitRepository; this.contactDetailRepository = contactDetailRepository; this.fieldValueRepository = fieldValueRepository; @@ -381,6 +387,10 @@ public class CustomerAggregate { optionalIdentificationCardEntity.ifPresent(identificationCardEntity -> { + final List<IdentificationCardScanEntity> cardScanEntities = this.identificationCardScanRepository.findByIdentificationCard(identificationCardEntity); + + this.identificationCardScanRepository.delete(cardScanEntities); + this.identificationCardRepository.delete(identificationCardEntity); final CustomerEntity customerEntity = identificationCardEntity.getCustomer(); @@ -395,6 +405,60 @@ public class CustomerAggregate { } @Transactional + @CommandHandler(logStart = CommandLogLevel.INFO, logFinish = CommandLogLevel.INFO) + @EventEmitter(selectorName = CustomerEventConstants.SELECTOR_NAME, selectorValue = CustomerEventConstants.POST_IDENTIFICATION_CARD_SCAN) + public ScanEvent createIdentificationCardScan(final CreateIdentificationCardScanCommand command) throws Exception { + final Optional<IdentificationCardEntity> identificationCardEntity = this.identificationCardRepository.findByNumber(command.number()); + + final IdentificationCardEntity cardEntity = identificationCardEntity.orElseThrow(() -> ServiceException.notFound("Identification card {0} not found.", command.number())); + + final IdentificationCardScanEntity identificationCardScanEntity = IdentificationCardScanMapper.map(command.scan()); + + final MultipartFile image = command.image(); + + final LocalDateTime now = LocalDateTime.now(Clock.systemUTC()); + + identificationCardScanEntity.setImage(image.getBytes()); + identificationCardScanEntity.setContentType(image.getContentType()); + identificationCardScanEntity.setSize(image.getSize()); + identificationCardScanEntity.setIdentificationCard(cardEntity); + identificationCardScanEntity.setCreatedBy(UserContextHolder.checkedGetUser()); + identificationCardScanEntity.setCreatedOn(now); + + identificationCardScanRepository.save(identificationCardScanEntity); + + cardEntity.setLastModifiedBy(UserContextHolder.checkedGetUser()); + cardEntity.setLastModifiedOn(now); + + identificationCardRepository.save(cardEntity); + + return new ScanEvent(command.number(), command.scan().getIdentifier()); + } + + @Transactional + @CommandHandler(logStart = CommandLogLevel.INFO, logFinish = CommandLogLevel.INFO) + @EventEmitter(selectorName = CustomerEventConstants.SELECTOR_NAME, selectorValue = CustomerEventConstants.DELETE_IDENTIFICATION_CARD_SCAN) + public ScanEvent deleteIdentificationCardScan(final DeleteIdentificationCardScanCommand command) { + final Optional<IdentificationCardEntity> cardEntity = this.identificationCardRepository.findByNumber(command.number()); + final Optional<IdentificationCardScanEntity> scanEntity = cardEntity + .flatMap(entity -> this.identificationCardScanRepository.findByIdentifierAndIdentificationCard(command.scanIdentifier(), entity)); + + scanEntity.ifPresent(identificationCardScanEntity -> { + + this.identificationCardScanRepository.delete(identificationCardScanEntity); + + final IdentificationCardEntity identificationCard = identificationCardScanEntity.getIdentificationCard(); + + identificationCard.setLastModifiedBy(UserContextHolder.checkedGetUser()); + identificationCard.setLastModifiedOn(LocalDateTime.now(Clock.systemUTC())); + + this.identificationCardRepository.save(identificationCard); + }); + + return new ScanEvent(command.number(), command.scanIdentifier()); + } + + @Transactional @CommandHandler @EventEmitter(selectorName = CustomerEventConstants.SELECTOR_NAME, selectorValue = CustomerEventConstants.POST_PORTRAIT) public String createPortrait(final CreatePortraitCommand createPortraitCommand) throws IOException { diff --git a/service/src/main/java/io/mifos/customer/service/internal/mapper/IdentificationCardScanMapper.java b/service/src/main/java/io/mifos/customer/service/internal/mapper/IdentificationCardScanMapper.java new file mode 100644 index 0000000..346adfb --- /dev/null +++ b/service/src/main/java/io/mifos/customer/service/internal/mapper/IdentificationCardScanMapper.java @@ -0,0 +1,44 @@ +/* + * Copyright 2016 The Mifos Initiative. + * + * 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 io.mifos.customer.service.internal.mapper; + +import io.mifos.customer.api.v1.domain.IdentificationCardScan; +import io.mifos.customer.service.internal.repository.IdentificationCardScanEntity; + +public class IdentificationCardScanMapper { + + private IdentificationCardScanMapper() { + super(); + } + + public static IdentificationCardScanEntity map(final IdentificationCardScan scan) { + final IdentificationCardScanEntity entity = new IdentificationCardScanEntity(); + + entity.setIdentifier(scan.getIdentifier()); + entity.setDescription(scan.getDescription()); + + return entity; + } + + public static IdentificationCardScan map(final IdentificationCardScanEntity entity) { + final IdentificationCardScan scan = new IdentificationCardScan(); + + scan.setIdentifier(entity.getIdentifier()); + scan.setDescription(entity.getDescription()); + + return scan; + } +} diff --git a/service/src/main/java/io/mifos/customer/service/internal/repository/IdentificationCardScanEntity.java b/service/src/main/java/io/mifos/customer/service/internal/repository/IdentificationCardScanEntity.java new file mode 100644 index 0000000..acd9808 --- /dev/null +++ b/service/src/main/java/io/mifos/customer/service/internal/repository/IdentificationCardScanEntity.java @@ -0,0 +1,128 @@ +/* + * Copyright 2016 The Mifos Initiative. + * + * 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 io.mifos.customer.service.internal.repository; + +import io.mifos.core.mariadb.util.LocalDateTimeConverter; +import io.mifos.customer.api.v1.domain.IdentificationCard; + +import javax.persistence.*; +import java.time.LocalDateTime; + +@Entity +@Table(name = "maat_identification_card_scans") +public class IdentificationCardScanEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private Long id; + @Column(name = "identifier") + private String identifier; + @Column(name = "description") + private String description; + @Lob + @Column(name = "image") + private byte[] image; + @Column(name = "size") + private Long size; + @Column(name = "content_type") + private String contentType; + @ManyToOne(fetch = FetchType.LAZY, optional = true) + @JoinColumn(name = "identification_card_id") + private IdentificationCardEntity identificationCard; + @Column(name = "created_by") + private String createdBy; + @Column(name = "created_on") + @Convert(converter = LocalDateTimeConverter.class) + private LocalDateTime createdOn; + + public IdentificationCardScanEntity() { + super(); + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getIdentifier() { + return identifier; + } + + public void setIdentifier(String identifier) { + this.identifier = identifier; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public byte[] getImage() { + return image; + } + + public void setImage(byte[] image) { + this.image = image; + } + + public Long getSize() { + return size; + } + + public void setSize(Long size) { + this.size = size; + } + + public String getContentType() { + return contentType; + } + + public void setContentType(String contentType) { + this.contentType = contentType; + } + + public IdentificationCardEntity getIdentificationCard() { + return identificationCard; + } + + public void setIdentificationCard(IdentificationCardEntity identificationCard) { + this.identificationCard = identificationCard; + } + + public String getCreatedBy() { + return createdBy; + } + + public void setCreatedBy(String createdBy) { + this.createdBy = createdBy; + } + + public LocalDateTime getCreatedOn() { + return createdOn; + } + + public void setCreatedOn(LocalDateTime createdOn) { + this.createdOn = createdOn; + } + +} diff --git a/service/src/main/java/io/mifos/customer/service/internal/repository/IdentificationCardScanRepository.java b/service/src/main/java/io/mifos/customer/service/internal/repository/IdentificationCardScanRepository.java new file mode 100644 index 0000000..dded654 --- /dev/null +++ b/service/src/main/java/io/mifos/customer/service/internal/repository/IdentificationCardScanRepository.java @@ -0,0 +1,35 @@ +/* + * Copyright 2016 The Mifos Initiative. + * + * 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 io.mifos.customer.service.internal.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.Optional; + +@Repository +public interface IdentificationCardScanRepository extends JpaRepository<IdentificationCardScanEntity, Long> { + + @Query("SELECT CASE WHEN COUNT(i) > 0 THEN 'true' ELSE 'false' END FROM IdentificationCardScanEntity i WHERE i.identifier = :identifier AND i.identificationCard = :identificationCard") + Boolean existsByIdentifierAndIdentificationCard(@Param("identifier") final String identifier, @Param("identificationCard") final IdentificationCardEntity identificationCardEntity); + + Optional<IdentificationCardScanEntity> findByIdentifierAndIdentificationCard(final String identifier, final IdentificationCardEntity identificationCardEntity); + + List<IdentificationCardScanEntity> findByIdentificationCard(final IdentificationCardEntity identificationCardEntity); +} diff --git a/service/src/main/java/io/mifos/customer/service/internal/service/CustomerService.java b/service/src/main/java/io/mifos/customer/service/internal/service/CustomerService.java index 5048a4c..f7fd56f 100644 --- a/service/src/main/java/io/mifos/customer/service/internal/service/CustomerService.java +++ b/service/src/main/java/io/mifos/customer/service/internal/service/CustomerService.java @@ -15,24 +15,14 @@ */ package io.mifos.customer.service.internal.service; -import io.mifos.customer.api.v1.domain.Command; -import io.mifos.customer.api.v1.domain.Customer; -import io.mifos.customer.api.v1.domain.CustomerPage; -import io.mifos.customer.api.v1.domain.IdentificationCard; -import io.mifos.customer.api.v1.domain.ProcessStep; -import io.mifos.customer.api.v1.domain.TaskDefinition; +import io.mifos.customer.api.v1.domain.*; import io.mifos.customer.catalog.api.v1.domain.Value; import io.mifos.customer.catalog.service.internal.repository.FieldEntity; import io.mifos.customer.catalog.service.internal.repository.FieldValueEntity; import io.mifos.customer.catalog.service.internal.repository.FieldValueRepository; import io.mifos.customer.service.ServiceConstants; -import io.mifos.customer.service.internal.mapper.CommandMapper; -import io.mifos.customer.service.internal.mapper.ContactDetailMapper; -import io.mifos.customer.service.internal.mapper.CustomerMapper; -import io.mifos.customer.service.internal.mapper.IdentificationCardMapper; -import io.mifos.customer.service.internal.mapper.TaskDefinitionMapper; +import io.mifos.customer.service.internal.mapper.*; import io.mifos.customer.service.internal.repository.*; -import io.mifos.customer.service.internal.mapper.AddressMapper; import org.slf4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; @@ -52,6 +42,7 @@ public class CustomerService { private final Logger logger; private final CustomerRepository customerRepository; private final IdentificationCardRepository identificationCardRepository; + private final IdentificationCardScanRepository identificationCardScanRepository; private final PortraitRepository portraitRepository; private final ContactDetailRepository contactDetailRepository; private final FieldValueRepository fieldValueRepository; @@ -63,6 +54,7 @@ public class CustomerService { public CustomerService(@Qualifier(ServiceConstants.LOGGER_NAME) final Logger logger, final CustomerRepository customerRepository, final IdentificationCardRepository identificationCardRepository, + final IdentificationCardScanRepository identificationCardScanRepository, final PortraitRepository portraitRepository, final ContactDetailRepository contactDetailRepository, final FieldValueRepository fieldValueRepository, @@ -73,6 +65,7 @@ public class CustomerService { this.logger = logger; this.customerRepository = customerRepository; this.identificationCardRepository = identificationCardRepository; + this.identificationCardScanRepository = identificationCardScanRepository; this.portraitRepository = portraitRepository; this.contactDetailRepository = contactDetailRepository; this.fieldValueRepository = fieldValueRepository; @@ -93,6 +86,12 @@ public class CustomerService { return this.identificationCardRepository.existsByNumber(number); } + public Boolean identificationCardScanExists(final String number, final String identifier) { + return this.identificationCardRepository.findByNumber(number) + .map(cardEntity -> this.identificationCardScanRepository.existsByIdentifierAndIdentificationCard(identifier, cardEntity)) + .orElse(false); + } + public Optional<Customer> findCustomer(final String identifier) { final CustomerEntity customerEntity = this.customerRepository.findByIdentifier(identifier); if (customerEntity != null) { @@ -195,6 +194,28 @@ public class CustomerService { return identificationCardEntity.map(IdentificationCardMapper::map); } + public final List<IdentificationCardScan> fetchScansByIdentificationCard(final String number) { + final Optional<IdentificationCardEntity> identificationCard = this.identificationCardRepository.findByNumber(number); + + return identificationCard.map(this.identificationCardScanRepository::findByIdentificationCard) + .map(x -> x.stream().map(IdentificationCardScanMapper::map).collect(Collectors.toList())) + .orElseGet(Collections::emptyList); + } + + private Optional<IdentificationCardScanEntity> findIdentificationCardEntity(final String number, final String identifier) { + final Optional<IdentificationCardEntity> cardEntity = this.identificationCardRepository.findByNumber(number); + final Optional<IdentificationCardScanEntity> cardScanEntity = cardEntity.flatMap(card -> this.identificationCardScanRepository.findByIdentifierAndIdentificationCard(identifier, card)); + return cardScanEntity; + } + + public Optional<IdentificationCardScan> findIdentificationCardScan(final String number, final String identifier) { + return this.findIdentificationCardEntity(number, identifier).map(IdentificationCardScanMapper::map); + } + + public Optional<byte[]> findIdentificationCardScanImage(final String number, final String identifier) { + return this.findIdentificationCardEntity(number, identifier).map(IdentificationCardScanEntity::getImage); + } + public List<ProcessStep> getProcessSteps(final String customerIdentifier) { final ArrayList<ProcessStep> processSteps = new ArrayList<>(); final CustomerEntity customerEntity = this.customerRepository.findByIdentifier(customerIdentifier); diff --git a/service/src/main/java/io/mifos/customer/service/rest/controller/CustomerRestController.java b/service/src/main/java/io/mifos/customer/service/rest/controller/CustomerRestController.java index 41546b9..f68549e 100644 --- a/service/src/main/java/io/mifos/customer/service/rest/controller/CustomerRestController.java +++ b/service/src/main/java/io/mifos/customer/service/rest/controller/CustomerRestController.java @@ -20,6 +20,7 @@ import io.mifos.anubis.annotation.Permittable; import io.mifos.core.api.util.UserContextHolder; import io.mifos.core.command.gateway.CommandGateway; import io.mifos.core.lang.ServiceException; +import io.mifos.core.lang.validation.constraints.ValidIdentifier; import io.mifos.customer.PermittableGroupIds; import io.mifos.customer.api.v1.domain.*; import io.mifos.customer.catalog.service.internal.service.FieldValueValidator; @@ -28,6 +29,7 @@ import io.mifos.customer.service.internal.command.*; import io.mifos.customer.service.internal.repository.PortraitEntity; import io.mifos.customer.service.internal.service.CustomerService; import io.mifos.customer.service.internal.service.TaskService; +import org.apache.commons.lang.RandomStringUtils; import org.slf4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; @@ -41,6 +43,7 @@ import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import javax.validation.Valid; +import javax.validation.constraints.Size; import java.util.List; import java.util.Optional; @@ -454,6 +457,120 @@ public class CustomerRestController { return ResponseEntity.accepted().build(); } + @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.IDENTIFICATIONS) + @RequestMapping( + value = "/customers/{identifier}/identifications/{number}/scans", + method = RequestMethod.GET, + produces = MediaType.APPLICATION_JSON_VALUE, + consumes = MediaType.ALL_VALUE + ) + public + @ResponseBody + ResponseEntity<List<IdentificationCardScan>> fetchIdentificationCardScans(@PathVariable("identifier") final String identifier, + @PathVariable("number") final String number) { + this.throwIfCustomerNotExists(identifier); + this.throwIfIdentificationCardNotExists(number); + + final List<IdentificationCardScan> identificationCardScans = this.customerService.fetchScansByIdentificationCard(number); + + return ResponseEntity.ok(identificationCardScans); + } + + @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.IDENTIFICATIONS) + @RequestMapping( + value = "/customers/{identifier}/identifications/{number}/scans/{scanIdentifier}", + method = RequestMethod.GET, + produces = MediaType.APPLICATION_JSON_VALUE, + consumes = MediaType.ALL_VALUE + ) + public + @ResponseBody + ResponseEntity<IdentificationCardScan> findIdentificationCardScan(@PathVariable("identifier") final String identifier, + @PathVariable("number") final String number, + @PathVariable("scanIdentifier") final String scanIdentifier) { + this.throwIfCustomerNotExists(identifier); + this.throwIfIdentificationCardNotExists(number); + + final Optional<IdentificationCardScan> identificationCardScan = this.customerService.findIdentificationCardScan(number, scanIdentifier); + + return identificationCardScan + .map(ResponseEntity::ok) + .orElseThrow(() -> ServiceException.notFound("Identification card scan {0} not found.", number)); + } + + @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.IDENTIFICATIONS) + @RequestMapping( + value = "/customers/{identifier}/identifications/{number}/scans/{scanIdentifier}/image", + method = RequestMethod.GET, + produces = MediaType.APPLICATION_JSON_VALUE, + consumes = MediaType.ALL_VALUE + ) + public + @ResponseBody + ResponseEntity<byte[]> fetchIdentificationCardScanImage(@PathVariable("identifier") final String identifier, + @PathVariable("number") final String number, + @PathVariable("scanIdentifier") final String scanIdentifier) { + this.throwIfCustomerNotExists(identifier); + this.throwIfIdentificationCardNotExists(number); + this.throwIfIdentificationCardScanNotExists(number, scanIdentifier); + + final Optional<byte[]> image = this.customerService.findIdentificationCardScanImage(number, scanIdentifier); + + return image.map(ResponseEntity::ok) + .orElseThrow(() -> ServiceException.notFound("Identification card scan {0} not found.", number)); + } + + @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.IDENTIFICATIONS) + @RequestMapping( + value = "/customers/{identifier}/identifications/{number}/scans", + produces = MediaType.APPLICATION_JSON_VALUE, + consumes = MediaType.MULTIPART_FORM_DATA_VALUE + ) + public + @ResponseBody + ResponseEntity<Void> postIdentificationCardScan(@PathVariable("identifier") final String identifier, + @PathVariable("number") final String number, + @RequestParam("scanIdentifier") @ValidIdentifier final String scanIdentifier, + @RequestParam("description") @Size(max = 4096) final String description, + @RequestBody final MultipartFile image) throws Exception { + this.throwIfCustomerNotExists(identifier); + this.throwIfIdentificationCardNotExists(number); + this.throwIfInvalidSize(image.getSize()); + this.throwIfInvalidContentType(image.getContentType()); + + if (this.customerService.identificationCardScanExists(number, scanIdentifier)) { + throw ServiceException.conflict("Scan {0} already exists.", scanIdentifier); + } + + final IdentificationCardScan scan = new IdentificationCardScan(); + scan.setIdentifier(scanIdentifier); + scan.setDescription(description); + + this.commandGateway.process(new CreateIdentificationCardScanCommand(number, scan, image)); + + return ResponseEntity.accepted().build(); + } + + @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.IDENTIFICATIONS) + @RequestMapping( + value = "/customers/{identifier}/identifications/{number}/scans/{scanIdentifier}", + method = RequestMethod.DELETE, + produces = MediaType.APPLICATION_JSON_VALUE, + consumes = MediaType.ALL_VALUE + ) + public + @ResponseBody + ResponseEntity<Void> deleteScan(@PathVariable("identifier") final String identifier, + @PathVariable("number") final String number, + @PathVariable("scanIdentifier") final String scanIdentifier) { + throwIfCustomerNotExists(identifier); + throwIfIdentificationCardNotExists(number); + + this.commandGateway.process(new DeleteIdentificationCardScanCommand(number, scanIdentifier)); + + return ResponseEntity.accepted().build(); + } + @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.PORTRAIT) @RequestMapping( value = "/customers/{identifier}/portrait", @@ -486,17 +603,8 @@ public class CustomerRestController { } this.throwIfCustomerNotExists(identifier); - - final Long maxSize = this.environment.getProperty("upload.image.max-size", Long.class); - - if(portrait.getSize() > maxSize) { - throw ServiceException.badRequest("Portrait can't exceed size of {0}", maxSize); - } - - if(!portrait.getContentType().contains(MediaType.IMAGE_JPEG_VALUE) - && !portrait.getContentType().contains(MediaType.IMAGE_PNG_VALUE)) { - throw ServiceException.badRequest("Only content type {0} and {1} allowed", MediaType.IMAGE_JPEG_VALUE, MediaType.IMAGE_PNG_VALUE); - } + this.throwIfInvalidSize(portrait.getSize()); + this.throwIfInvalidContentType(portrait.getContentType()); try { this.commandGateway.process(new DeletePortraitCommand(identifier), String.class).get(); @@ -628,4 +736,25 @@ public class CustomerRestController { throw ServiceException.notFound("Identification card {0} not found.", number); } } + + private void throwIfIdentificationCardScanNotExists(final String number, final String identifier) { + if (!this.customerService.identificationCardScanExists(number, identifier)) { + throw ServiceException.notFound("Identification card scan {0} not found.", identifier); + } + } + + private void throwIfInvalidSize(final Long size) { + final Long maxSize = this.environment.getProperty("upload.image.max-size", Long.class); + + if(size > maxSize) { + throw ServiceException.badRequest("Image can''t exceed size of {0}", maxSize); + } + } + + private void throwIfInvalidContentType(final String contentType) { + if(!contentType.contains(MediaType.IMAGE_JPEG_VALUE) + && !contentType.contains(MediaType.IMAGE_PNG_VALUE)) { + throw ServiceException.badRequest("Only content type {0} and {1} allowed", MediaType.IMAGE_JPEG_VALUE, MediaType.IMAGE_PNG_VALUE); + } + } } diff --git a/service/src/main/resources/db/migrations/mariadb/V4__identification_card_scans.sql b/service/src/main/resources/db/migrations/mariadb/V4__identification_card_scans.sql new file mode 100644 index 0000000..1a1b5e3 --- /dev/null +++ b/service/src/main/resources/db/migrations/mariadb/V4__identification_card_scans.sql @@ -0,0 +1,30 @@ +-- +-- Copyright 2017 The Mifos Initiative. +-- +-- 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. +-- + +CREATE TABLE maat_identification_card_scans ( + id BIGINT NOT NULL AUTO_INCREMENT, + identifier VARCHAR(32) NOT NULL, + description VARCHAR(4096) NOT NULL, + identification_card_id BIGINT NOT NULL, + content_type VARCHAR(256) NOT NULL, + size BIGINT NOT NULL, + image MEDIUMBLOB NOT NULL, + created_on TIMESTAMP(3) NOT NULL, + created_by VARCHAR(32) NOT NULL, + CONSTRAINT maat_ident_card_scans_pk PRIMARY KEY (id), + CONSTRAINT maat_ident_card_scans_ident_uq UNIQUE (identifier, identification_card_id), + CONSTRAINT maat_ident_card_scans_fk FOREIGN KEY (identification_card_id) REFERENCES maat_identification_cards (id) +); \ No newline at end of file -- To stop receiving notification emails like this one, please contact my...@apache.org.