Merge branch 'develop' of https://github.com/KuelapInc/portfolio into develop
diff --git a/component-test/src/main/java/io/mifos/portfolio/AbstractPortfolioTest.java b/component-test/src/main/java/io/mifos/portfolio/AbstractPortfolioTest.java
index adadf68..4c7dfcd 100644
--- a/component-test/src/main/java/io/mifos/portfolio/AbstractPortfolioTest.java
+++ b/component-test/src/main/java/io/mifos/portfolio/AbstractPortfolioTest.java
@@ -21,6 +21,7 @@
 import io.mifos.core.test.fixture.TenantDataStoreContextTestRule;
 import io.mifos.core.test.listener.EnableEventRecording;
 import io.mifos.core.test.listener.EventRecorder;
+import io.mifos.customer.api.v1.client.CustomerManager;
 import io.mifos.individuallending.api.v1.client.IndividualLending;
 import io.mifos.individuallending.api.v1.domain.product.AccountDesignators;
 import io.mifos.individuallending.api.v1.domain.workflow.Action;
@@ -32,6 +33,7 @@
 import io.mifos.portfolio.service.internal.util.RhythmAdapter;
 import org.junit.*;
 import org.junit.runner.RunWith;
+import org.mockito.Mockito;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -114,6 +116,9 @@
   @MockBean
   LedgerManager ledgerManager;
 
+  @MockBean
+  CustomerManager customerManager;
+
   @SuppressWarnings("SpringAutowiredFieldsWarningInspection")
   @Autowired
   @Qualifier(LOGGER_NAME)
@@ -123,6 +128,7 @@
   public void prepTest() {
     userContext = this.tenantApplicationSecurityEnvironment.createAutoUserContext(TEST_USER);
     AccountingFixture.mockAccountingPrereqs(ledgerManager);
+    Mockito.doReturn(true).when(customerManager).isCustomerInGoodStanding(Fixture.CUSTOMER_IDENTIFIER);
   }
 
   @After
diff --git a/component-test/src/main/java/io/mifos/portfolio/Fixture.java b/component-test/src/main/java/io/mifos/portfolio/Fixture.java
index 5b3785e..0f02397 100644
--- a/component-test/src/main/java/io/mifos/portfolio/Fixture.java
+++ b/component-test/src/main/java/io/mifos/portfolio/Fixture.java
@@ -41,6 +41,7 @@
   static final int MINOR_CURRENCY_UNIT_DIGITS = 2;
   static final BigDecimal INTEREST_RATE = BigDecimal.valueOf(0.10).setScale(4, RoundingMode.HALF_EVEN);
   static final BigDecimal ACCRUAL_PERIODS = BigDecimal.valueOf(365.2425);
+  public static final String CUSTOMER_IDENTIFIER = "alice";
 
   private static int uniquenessSuffix = 0;
 
@@ -134,7 +135,7 @@
   {
     final CaseParameters ret = new CaseParameters(generateUniqueIdentifer("fred"));
 
-    ret.setCustomerIdentifier("alice");
+    ret.setCustomerIdentifier(CUSTOMER_IDENTIFIER);
     ret.setMaximumBalance(fixScale(BigDecimal.valueOf(2000L)));
     ret.setTermRange(new TermRange(ChronoUnit.MONTHS, 18));
     ret.setPaymentCycle(new PaymentCycle(ChronoUnit.MONTHS, 1, 1, null, null));
diff --git a/component-test/src/main/java/io/mifos/portfolio/TestCases.java b/component-test/src/main/java/io/mifos/portfolio/TestCases.java
index 7493e81..c7f730e 100644
--- a/component-test/src/main/java/io/mifos/portfolio/TestCases.java
+++ b/component-test/src/main/java/io/mifos/portfolio/TestCases.java
@@ -29,6 +29,7 @@
 import io.mifos.portfolio.api.v1.events.EventConstants;
 import org.junit.Assert;
 import org.junit.Test;
+import org.mockito.Mockito;
 
 import java.math.BigDecimal;
 import java.util.*;
@@ -199,6 +200,23 @@
   }
 
   @Test
+  public void shouldThrowWhenCustomerNotInGoodStanding() throws InterruptedException {
+    Mockito.doReturn(false).when(customerManager).isCustomerInGoodStanding("don");
+
+    final Product product = createProduct();
+
+    final CaseParameters newCaseParameters = Fixture.createAdjustedCaseParameters(x -> x.setCustomerIdentifier("don"));
+    final String originalParameters = new Gson().toJson(newCaseParameters);
+
+    try {
+      createAdjustedCase(product.getIdentifier(), x -> x.setParameters(originalParameters));
+      Assert.fail("This should cause an illegal argument exception because Don is not a customer in good standing.");
+    }
+    catch (final IllegalArgumentException ignored){
+    }
+  }
+
+  @Test
   public void shouldThrowWhenProductNotActivated() throws InterruptedException {
     final Product product = createProduct();
 
diff --git a/service/build.gradle b/service/build.gradle
index f4f79d6..8194a22 100644
--- a/service/build.gradle
+++ b/service/build.gradle
@@ -36,6 +36,7 @@
             [group: 'io.mifos.rhythm', name: 'api', version: versions.mifosrhythm],
             [group: 'io.mifos.accounting', name: 'api', version: versions.frameworkaccounting],
             [group: 'io.mifos.anubis', name: 'library', version: versions.frameworkanubis],
+            [group: 'io.mifos.customer', name: 'api', version: versions.mifoscustomer],
             [group: 'com.google.code.gson', name: 'gson'],
             [group: 'io.mifos.core', name: 'api', version: versions.frameworkapi],
             [group: 'io.mifos.core', name: 'lang', version: versions.frameworklang],
diff --git a/service/src/main/java/io/mifos/individuallending/IndividualLendingPatternFactory.java b/service/src/main/java/io/mifos/individuallending/IndividualLendingPatternFactory.java
index 07c8d76..9ce0d61 100644
--- a/service/src/main/java/io/mifos/individuallending/IndividualLendingPatternFactory.java
+++ b/service/src/main/java/io/mifos/individuallending/IndividualLendingPatternFactory.java
@@ -18,6 +18,7 @@
 import com.google.common.collect.Sets;
 import com.google.gson.Gson;
 import io.mifos.core.lang.ServiceException;
+import io.mifos.customer.api.v1.client.CustomerManager;
 import io.mifos.individuallending.api.v1.domain.caseinstance.CaseParameters;
 import io.mifos.individuallending.api.v1.domain.product.ChargeIdentifiers;
 import io.mifos.individuallending.api.v1.domain.product.ChargeProportionalDesignator;
@@ -58,18 +59,21 @@
   final static private String INDIVIDUAL_LENDING_PACKAGE = "io.mifos.individuallending.api.v1";
   private final CaseParametersRepository caseParametersRepository;
   private final CostComponentService costComponentService;
+  private final CustomerManager customerManager;
   private final IndividualLendingCommandDispatcher individualLendingCommandDispatcher;
   private final Gson gson;
 
   @Autowired
   IndividualLendingPatternFactory(
-          final CaseParametersRepository caseParametersRepository,
-          final CostComponentService costComponentService,
-          final IndividualLendingCommandDispatcher individualLendingCommandDispatcher,
-          @Qualifier(ServiceConstants.GSON_NAME) final Gson gson)
+      final CaseParametersRepository caseParametersRepository,
+      final CostComponentService costComponentService,
+      final CustomerManager customerManager,
+      final IndividualLendingCommandDispatcher individualLendingCommandDispatcher,
+      @Qualifier(ServiceConstants.GSON_NAME) final Gson gson)
   {
     this.caseParametersRepository = caseParametersRepository;
     this.costComponentService = costComponentService;
+    this.customerManager = customerManager;
     this.individualLendingCommandDispatcher = individualLendingCommandDispatcher;
     this.gson = gson;
   }
@@ -240,9 +244,18 @@
     return ret;
   }
 
+  @Override
+  public void checkParameters(final String parameters) {
+    final CaseParameters caseParameters = gson.fromJson(parameters, CaseParameters.class);
+    final String customerIdentifier = caseParameters.getCustomerIdentifier();
+    if (!customerManager.isCustomerInGoodStanding(customerIdentifier))
+      throw ServiceException.badRequest("Customer ''{0}'' is either not a customer or is not in good standing.");
+  }
+
   @Transactional
   @Override
   public void persistParameters(final Long caseId, final String parameters) {
+    checkParameters(parameters);
     final CaseParameters caseParameters = gson.fromJson(parameters, CaseParameters.class);
     final CaseParametersEntity caseParametersEntity = CaseParametersMapper.map(caseId, caseParameters);
     caseParametersRepository.save(caseParametersEntity);
@@ -277,7 +290,8 @@
 
   @Transactional
   @Override
-  public void changeParameters(Long caseId, String parameters) {
+  public void changeParameters(final Long caseId, final String parameters) {
+    checkParameters(parameters);
     final CaseParameters caseParameters = gson.fromJson(parameters, CaseParameters.class);
     final CaseParametersEntity oldCaseParameters = caseParametersRepository.findByCaseId(caseId)
             .orElseThrow(() -> new IllegalArgumentException("Case id does not represent an individual loan: " + caseId));
diff --git a/service/src/main/java/io/mifos/portfolio/service/config/PortfolioServiceConfiguration.java b/service/src/main/java/io/mifos/portfolio/service/config/PortfolioServiceConfiguration.java
index adcb3b3..9668332 100644
--- a/service/src/main/java/io/mifos/portfolio/service/config/PortfolioServiceConfiguration.java
+++ b/service/src/main/java/io/mifos/portfolio/service/config/PortfolioServiceConfiguration.java
@@ -25,6 +25,7 @@
 import io.mifos.core.lang.config.EnableServiceException;
 import io.mifos.core.lang.config.EnableTenantContext;
 import io.mifos.core.mariadb.config.EnableMariaDB;
+import io.mifos.customer.api.v1.client.CustomerManager;
 import io.mifos.individuallending.IndividualLendingConfiguration;
 import io.mifos.portfolio.service.ServiceConstants;
 import io.mifos.rhythm.api.v1.client.RhythmManager;
@@ -60,7 +61,7 @@
 })
 @EnableJpaRepositories(basePackages = "io.mifos.portfolio.service.internal.repository")
 @EntityScan(basePackages = "io.mifos.portfolio.service.internal.repository")
-@EnableFeignClients(clients = {LedgerManager.class, RhythmManager.class})
+@EnableFeignClients(clients = {LedgerManager.class, RhythmManager.class, CustomerManager.class})
 @RibbonClient(name = "portfolio-v1")
 @EnableApplicationName
 @Import(IndividualLendingConfiguration.class)
diff --git a/service/src/main/java/io/mifos/portfolio/service/internal/checker/CaseChecker.java b/service/src/main/java/io/mifos/portfolio/service/internal/checker/CaseChecker.java
new file mode 100644
index 0000000..ab8b20b
--- /dev/null
+++ b/service/src/main/java/io/mifos/portfolio/service/internal/checker/CaseChecker.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * 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.portfolio.service.internal.checker;
+
+import io.mifos.core.lang.ServiceException;
+import io.mifos.portfolio.api.v1.domain.Case;
+import io.mifos.portfolio.service.internal.pattern.PatternFactoryRegistry;
+import io.mifos.portfolio.service.internal.repository.ProductEntity;
+import io.mifos.portfolio.service.internal.repository.ProductRepository;
+import io.mifos.portfolio.service.internal.service.CaseService;
+import io.mifos.portfolio.service.internal.service.ProductService;
+import io.mifos.products.spi.PatternFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.Optional;
+
+/**
+ * @author Myrle Krantz
+ */
+@Component
+public class CaseChecker {
+  private final CaseService caseService;
+  private final ProductService productService;
+  private final ProductRepository productRepository;
+  private final PatternFactoryRegistry patternFactoryRegistry;
+
+  @Autowired
+  public CaseChecker(final CaseService caseService,
+                     final ProductService productService,
+                     final ProductRepository productRepository,
+                     final PatternFactoryRegistry patternFactoryRegistry) {
+    this.caseService = caseService;
+    this.productService = productService;
+    this.productRepository = productRepository;
+    this.patternFactoryRegistry = patternFactoryRegistry;
+  }
+
+  public void checkForCreate(final String productIdentifier, final Case instance) {
+    caseService.findByIdentifier(productIdentifier, instance.getIdentifier())
+        .ifPresent(x -> {throw ServiceException.conflict("Duplicate identifier: " + productIdentifier + "." + x.getIdentifier());});
+
+    final Optional<Boolean> productEnabled = productService.findEnabledByIdentifier(productIdentifier);
+    if (!productEnabled.orElseThrow(() -> ServiceException.internalError("Product should exist, but doesn't"))) {
+      throw ServiceException.badRequest("Product must be enabled before cases for it can be created: " + productIdentifier);}
+
+    getPatternFactory(productIdentifier).checkParameters(instance.getParameters());
+  }
+
+  public void checkForChange(final String productIdentifier, final Case instance) {
+    getPatternFactory(productIdentifier).checkParameters(instance.getParameters());
+  }
+
+  private PatternFactory getPatternFactory(final String productIdentifier) {
+    return productRepository.findByIdentifier(productIdentifier)
+        .map(ProductEntity::getPatternPackage)
+        .flatMap(patternFactoryRegistry::getPatternFactoryForPackage)
+        .orElseThrow(() -> new IllegalArgumentException("Case references unsupported product type."));
+  }
+}
\ No newline at end of file
diff --git a/service/src/main/java/io/mifos/portfolio/service/internal/command/handler/CaseCommandHandler.java b/service/src/main/java/io/mifos/portfolio/service/internal/command/handler/CaseCommandHandler.java
index a8700c0..aa91b80 100644
--- a/service/src/main/java/io/mifos/portfolio/service/internal/command/handler/CaseCommandHandler.java
+++ b/service/src/main/java/io/mifos/portfolio/service/internal/command/handler/CaseCommandHandler.java
@@ -80,7 +80,7 @@
     return new CaseEvent(caseInstance.getProductIdentifier(), caseInstance.getIdentifier());
   }
 
-  private PatternFactory getPatternFactory(String productIdentifier) {
+  private PatternFactory getPatternFactory(final String productIdentifier) {
     return productRepository.findByIdentifier(productIdentifier)
               .map(ProductEntity::getPatternPackage)
               .flatMap(patternFactoryRegistry::getPatternFactoryForPackage)
diff --git a/service/src/main/java/io/mifos/portfolio/service/rest/CaseRestController.java b/service/src/main/java/io/mifos/portfolio/service/rest/CaseRestController.java
index f2a4ed5..3117f67 100644
--- a/service/src/main/java/io/mifos/portfolio/service/rest/CaseRestController.java
+++ b/service/src/main/java/io/mifos/portfolio/service/rest/CaseRestController.java
@@ -25,6 +25,7 @@
 import io.mifos.portfolio.api.v1.domain.CasePage;
 import io.mifos.portfolio.api.v1.domain.Command;
 import io.mifos.portfolio.api.v1.domain.CostComponent;
+import io.mifos.portfolio.service.internal.checker.CaseChecker;
 import io.mifos.portfolio.service.internal.command.ChangeCaseCommand;
 import io.mifos.portfolio.service.internal.command.CreateCaseCommand;
 import io.mifos.portfolio.service.internal.service.CaseService;
@@ -40,7 +41,6 @@
 import javax.validation.Valid;
 import java.math.BigDecimal;
 import java.util.List;
-import java.util.Optional;
 import java.util.Set;
 
 /**
@@ -53,17 +53,21 @@
 
   private final CommandGateway commandGateway;
   private final CaseService caseService;
+  private final CaseChecker caseChecker;
   private final ProductService productService;
   private final TaskInstanceService taskInstanceService;
 
-  @Autowired public CaseRestController(
+  @Autowired
+  public CaseRestController(
       final CommandGateway commandGateway,
       final CaseService caseService,
+      final CaseChecker caseChecker,
       final ProductService productService,
       final TaskInstanceService taskInstanceService) {
     super();
     this.commandGateway = commandGateway;
     this.caseService = caseService;
+    this.caseChecker = caseChecker;
     this.productService = productService;
     this.taskInstanceService = taskInstanceService;
   }
@@ -94,13 +98,6 @@
   {
     checkThatProductExists(productIdentifier);
 
-    caseService.findByIdentifier(productIdentifier, instance.getIdentifier())
-            .ifPresent(x -> {throw ServiceException.conflict("Duplicate identifier: " + productIdentifier + "." + x.getIdentifier());});
-
-    final Optional<Boolean> productEnabled = productService.findEnabledByIdentifier(productIdentifier);
-    if (!productEnabled.orElseThrow(() -> ServiceException.internalError("Product should exist, but doesn't"))) {
-      throw ServiceException.badRequest("Product must be enabled before cases for it can be created: " + productIdentifier);}
-
     if (!instance.getProductIdentifier().equals(productIdentifier))
       throw ServiceException.badRequest("Product identifier in request body must match product identifier in request path.");
 
@@ -120,6 +117,8 @@
     if (instance.getLastModifiedOn() != null)
       throw ServiceException.badRequest("LastModifiedOn must 'null' be upon initial creation.");
 
+    caseChecker.checkForCreate(productIdentifier, instance);
+
     this.commandGateway.process(new CreateCaseCommand(instance));
     return new ResponseEntity<>(HttpStatus.ACCEPTED);
   }
@@ -159,6 +158,8 @@
     if (!caseIdentifier.equals(instance.getIdentifier()))
       throw ServiceException.badRequest("Instance identifier may not be changed.");
 
+    caseChecker.checkForChange(productIdentifier, instance);
+
     this.commandGateway.process(new ChangeCaseCommand(instance));
     return new ResponseEntity<>(HttpStatus.ACCEPTED);
     //TODO: Make sure case can't be changed from certain states.
@@ -245,5 +246,5 @@
       throw ServiceException.notFound("Product with identifier ''{0}'' doesn''t exist.", productIdentifier);
   }
 
-  //TODO: check that case parameters are within product parameters in put and post.
+  //TODO: createCheck that case parameters are within product parameters in put and post.
 }
\ No newline at end of file
diff --git a/service/src/main/java/io/mifos/products/spi/PatternFactory.java b/service/src/main/java/io/mifos/products/spi/PatternFactory.java
index 5357e39..4b9f3c2 100644
--- a/service/src/main/java/io/mifos/products/spi/PatternFactory.java
+++ b/service/src/main/java/io/mifos/products/spi/PatternFactory.java
@@ -32,6 +32,7 @@
 public interface PatternFactory {
   Pattern pattern();
   List<ChargeDefinition> charges();
+  void checkParameters(String parameters);
   void persistParameters(Long caseId, String parameters);
   void changeParameters(Long caseId, String parameters);
   Optional<String> getParameters(Long caseId, int minorCurrencyUnitDigits);
diff --git a/shared.gradle b/shared.gradle
index 88fd123..410d57d 100644
--- a/shared.gradle
+++ b/shared.gradle
@@ -12,6 +12,7 @@
         frameworkasync      : '0.1.0-BUILD-SNAPSHOT',
         frameworkaccounting : '0.1.0-BUILD-SNAPSHOT',
         mifosrhythm         : '0.1.0-BUILD-SNAPSHOT',
+        mifoscustomer       : '0.1.0-BUILD-SNAPSHOT',
         validator    : '5.3.0.Final',
         javamoneylib : '0.9-SNAPSHOT'
 ]
@@ -70,5 +71,6 @@
         uxf = 'XML_STYLE'
     }
     ext.year = Calendar.getInstance().get(Calendar.YEAR)
-    ext.name = 'The Mifos Initiative'
+    ext.name = 'Kuelap, Inc'
+    skipExistingHeaders true
 }