Implemented product deletion.
diff --git a/api/src/main/java/io/mifos/portfolio/api/v1/client/PortfolioManager.java b/api/src/main/java/io/mifos/portfolio/api/v1/client/PortfolioManager.java
index b2868e5..c76e05a 100644
--- a/api/src/main/java/io/mifos/portfolio/api/v1/client/PortfolioManager.java
+++ b/api/src/main/java/io/mifos/portfolio/api/v1/client/PortfolioManager.java
@@ -93,6 +93,15 @@
                      final Product product);
 
   @RequestMapping(
+          value = "/products/{productidentifier}",
+          method = RequestMethod.DELETE,
+          produces = MediaType.ALL_VALUE,
+          consumes = MediaType.APPLICATION_JSON_VALUE
+  )
+  @ThrowsException(status = HttpStatus.CONFLICT, exception = ProductInUseException.class)
+  void deleteProduct(@PathVariable("productidentifier") final String productIdentifier);
+
+  @RequestMapping(
           value = "/products/{productidentifier}/incompleteaccountassignments",
           method = RequestMethod.GET,
           produces = MediaType.APPLICATION_JSON_VALUE,
diff --git a/api/src/main/java/io/mifos/portfolio/api/v1/events/EventConstants.java b/api/src/main/java/io/mifos/portfolio/api/v1/events/EventConstants.java
index cb687ae..87ba261 100644
--- a/api/src/main/java/io/mifos/portfolio/api/v1/events/EventConstants.java
+++ b/api/src/main/java/io/mifos/portfolio/api/v1/events/EventConstants.java
@@ -25,6 +25,7 @@
   String INITIALIZE = "initialize";
   String POST_PRODUCT = "post-product";
   String PUT_PRODUCT = "put-product";
+  String DELETE_PRODUCT = "delete-product";
   String PUT_PRODUCT_ENABLE = "put-enable";
   String POST_CASE = "post-case";
   String PUT_CASE = "put-case";
@@ -36,6 +37,7 @@
   String SELECTOR_INITIALIZE = SELECTOR_NAME + " = '" + INITIALIZE + "'";
   String SELECTOR_POST_PRODUCT = SELECTOR_NAME + " = '" + POST_PRODUCT + "'";
   String SELECTOR_PUT_PRODUCT = SELECTOR_NAME + " = '" + PUT_PRODUCT + "'";
+  String SELECTOR_DELETE_PRODUCT = SELECTOR_NAME + " = '" + DELETE_PRODUCT + "'";
   String SELECTOR_PUT_PRODUCT_ENABLE = SELECTOR_NAME + " = '" + PUT_PRODUCT_ENABLE + "'";
   String SELECTOR_POST_CASE = SELECTOR_NAME + " = '" + POST_CASE + "'";
   String SELECTOR_PUT_CASE = SELECTOR_NAME + " = '" + PUT_CASE + "'";
diff --git a/component-test/src/main/java/io/mifos/portfolio/TestProducts.java b/component-test/src/main/java/io/mifos/portfolio/TestProducts.java
index 41160a4..2028e57 100644
--- a/component-test/src/main/java/io/mifos/portfolio/TestProducts.java
+++ b/component-test/src/main/java/io/mifos/portfolio/TestProducts.java
@@ -16,6 +16,7 @@
 package io.mifos.portfolio;
 
 import com.google.gson.Gson;
+import io.mifos.core.api.util.NotFoundException;
 import io.mifos.core.test.domain.TimeStampChecker;
 import io.mifos.individuallending.api.v1.domain.product.ProductParameters;
 import io.mifos.individuallending.api.v1.domain.workflow.Action;
@@ -271,6 +272,43 @@
     Assert.assertFalse(portfolioManager.getProductEnabled(product.getIdentifier()));
   }
 
+  @Test(expected = ProductInUseException.class)
+  public void shouldFailToDeleteProductAfterCaseHasBeenCreated() throws InterruptedException {
+    final Product product = createAndEnableProduct();
+
+    createCase(product.getIdentifier());
+
+    portfolioManager.deleteProduct(product.getIdentifier());
+  }
+
+  @Test(expected = NotFoundException.class)
+  public void shouldFailToDeleteNonExistentProduct() throws InterruptedException {
+    portfolioManager.deleteProduct("habberdash");
+  }
+
+  @Test
+  public void shouldDeleteProduct() throws InterruptedException {
+    final Product product = createAndEnableProduct();
+
+    try {
+      portfolioManager.deleteProduct(product.getIdentifier());
+      Assert.fail("Product is enabled.  It shouldn't be possible to delete it.");
+    }
+    catch (final ProductInUseException ignored) {}
+
+    portfolioManager.enableProduct(product.getIdentifier(), false);
+    Assert.assertTrue(this.eventRecorder.wait(EventConstants.PUT_PRODUCT_ENABLE, product.getIdentifier()));
+
+    portfolioManager.deleteProduct(product.getIdentifier());
+    Assert.assertTrue(this.eventRecorder.wait(EventConstants.DELETE_PRODUCT, product.getIdentifier()));
+
+    try {
+      portfolioManager.getProduct(product.getIdentifier());
+      Assert.fail("product should not be found after delete.");
+    }
+    catch (final NotFoundException ignored) {}
+  }
+
   private Product getTestProductWithMaximumLengthEverything()
   {
     final Product product = new Product();
diff --git a/component-test/src/main/java/io/mifos/portfolio/listener/ProductEventListener.java b/component-test/src/main/java/io/mifos/portfolio/listener/ProductEventListener.java
index 1e8b18b..1bc18bb 100644
--- a/component-test/src/main/java/io/mifos/portfolio/listener/ProductEventListener.java
+++ b/component-test/src/main/java/io/mifos/portfolio/listener/ProductEventListener.java
@@ -61,6 +61,16 @@
   @JmsListener(
           subscription = EventConstants.DESTINATION,
           destination = EventConstants.DESTINATION,
+          selector = EventConstants.SELECTOR_DELETE_PRODUCT
+  )
+  public void onDeleteProduct(@Header(TenantHeaderFilter.TENANT_HEADER) final String tenant,
+                              final String payload) {
+    this.eventRecorder.event(tenant, EventConstants.DELETE_PRODUCT, payload, String.class);
+  }
+
+  @JmsListener(
+          subscription = EventConstants.DESTINATION,
+          destination = EventConstants.DESTINATION,
           selector = EventConstants.SELECTOR_PUT_PRODUCT_ENABLE
   )
   public void onEnableProduct(@Header(TenantHeaderFilter.TENANT_HEADER) final String tenant,
diff --git a/service/src/main/java/io/mifos/portfolio/service/internal/command/DeleteProductCommand.java b/service/src/main/java/io/mifos/portfolio/service/internal/command/DeleteProductCommand.java
new file mode 100644
index 0000000..a333a75
--- /dev/null
+++ b/service/src/main/java/io/mifos/portfolio/service/internal/command/DeleteProductCommand.java
@@ -0,0 +1,38 @@
+/*
+ * 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.
+ */
+package io.mifos.portfolio.service.internal.command;
+
+/**
+ * @author Myrle Krantz
+ */
+public class DeleteProductCommand {
+  private final String productIdentifier;
+
+  public DeleteProductCommand(final String productIdentifier) {
+    this.productIdentifier = productIdentifier;
+  }
+
+  public String getProductIdentifier() {
+    return productIdentifier;
+  }
+
+  @Override
+  public String toString() {
+    return "DeleteProductCommand{" +
+            "productIdentifier='" + productIdentifier + '\'' +
+            '}';
+  }
+}
diff --git a/service/src/main/java/io/mifos/portfolio/service/internal/command/handler/ProductCommandHandler.java b/service/src/main/java/io/mifos/portfolio/service/internal/command/handler/ProductCommandHandler.java
index e9de6d8..7173e76 100644
--- a/service/src/main/java/io/mifos/portfolio/service/internal/command/handler/ProductCommandHandler.java
+++ b/service/src/main/java/io/mifos/portfolio/service/internal/command/handler/ProductCommandHandler.java
@@ -27,6 +27,7 @@
 import io.mifos.portfolio.service.internal.command.ChangeEnablingOfProductCommand;
 import io.mifos.portfolio.service.internal.command.ChangeProductCommand;
 import io.mifos.portfolio.service.internal.command.CreateProductCommand;
+import io.mifos.portfolio.service.internal.command.DeleteProductCommand;
 import io.mifos.portfolio.service.internal.mapper.ChargeDefinitionMapper;
 import io.mifos.portfolio.service.internal.mapper.ProductMapper;
 import io.mifos.portfolio.service.internal.pattern.PatternFactoryRegistry;
@@ -110,6 +111,25 @@
 
   @Transactional
   @CommandHandler(logStart = CommandLogLevel.INFO, logFinish = CommandLogLevel.INFO)
+  @EventEmitter(selectorName = EventConstants.SELECTOR_NAME, selectorValue = EventConstants.DELETE_PRODUCT)
+  public String process(final DeleteProductCommand deleteProductCommand) {
+    final String productIdentifier = deleteProductCommand.getProductIdentifier();
+    final ProductEntity product = productRepository.findByIdentifier(productIdentifier)
+            .orElseThrow(() -> ServiceException.notFound("Instance with identifier ''{0}'' doesn''t exist.", productIdentifier));
+
+    if (product.getEnabled())
+      throw ServiceException.conflict("Cannot delete product with identifier ''{0}'', because it is enabled.", productIdentifier);
+
+    if (caseRepository.existsByProductIdentifier(productIdentifier))
+      throw ServiceException.conflict("Cannot delete product with identifier ''{0}'', because there are already cases defined on it.", productIdentifier);
+
+    productRepository.delete(product);
+
+    return deleteProductCommand.getProductIdentifier();
+  }
+
+  @Transactional
+  @CommandHandler(logStart = CommandLogLevel.INFO, logFinish = CommandLogLevel.INFO)
   @EventEmitter(selectorName = EventConstants.SELECTOR_NAME, selectorValue =  EventConstants.PUT_PRODUCT_ENABLE)
   public String process(final ChangeEnablingOfProductCommand changeEnablingOfProductCommand)
   {
diff --git a/service/src/main/java/io/mifos/portfolio/service/internal/mapper/ProductMapper.java b/service/src/main/java/io/mifos/portfolio/service/internal/mapper/ProductMapper.java
index b6e02cd..e571632 100644
--- a/service/src/main/java/io/mifos/portfolio/service/internal/mapper/ProductMapper.java
+++ b/service/src/main/java/io/mifos/portfolio/service/internal/mapper/ProductMapper.java
@@ -149,6 +149,8 @@
 
       return newAccountAssignmentEntity;
     }).collect(Collectors.toSet());
+    newEntity.setChargeDefinitions(oldEntity.getChargeDefinitions());
+    newEntity.setTaskDefinitions(oldEntity.getTaskDefinitions());
 
 
     newEntity.setAccountAssignments(newAccountAssignmentEntities);
diff --git a/service/src/main/java/io/mifos/portfolio/service/internal/repository/ChargeDefinitionEntity.java b/service/src/main/java/io/mifos/portfolio/service/internal/repository/ChargeDefinitionEntity.java
index a4d7c87..92c436c 100644
--- a/service/src/main/java/io/mifos/portfolio/service/internal/repository/ChargeDefinitionEntity.java
+++ b/service/src/main/java/io/mifos/portfolio/service/internal/repository/ChargeDefinitionEntity.java
@@ -37,7 +37,7 @@
   private String identifier;
 
   @ManyToOne(fetch = FetchType.LAZY)
-  @JoinColumn(name = "product_id")
+  @JoinColumn(name = "product_id", nullable = false)
   private ProductEntity product;
 
   @Column(name = "a_name")
diff --git a/service/src/main/java/io/mifos/portfolio/service/internal/repository/ProductEntity.java b/service/src/main/java/io/mifos/portfolio/service/internal/repository/ProductEntity.java
index da0196f..d13d6fc 100644
--- a/service/src/main/java/io/mifos/portfolio/service/internal/repository/ProductEntity.java
+++ b/service/src/main/java/io/mifos/portfolio/service/internal/repository/ProductEntity.java
@@ -87,6 +87,12 @@
   @OneToMany(targetEntity = ProductAccountAssignmentEntity.class, cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "product")
   private Set<ProductAccountAssignmentEntity> accountAssignments;
 
+  @OneToMany(targetEntity = ChargeDefinitionEntity.class, cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "product")
+  private Set<ChargeDefinitionEntity> chargeDefinitions;
+
+  @OneToMany(targetEntity = TaskDefinitionEntity.class, cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "product")
+  private Set<TaskDefinitionEntity> taskDefinitions;
+
   @Column(name = "parameters")
   private String parameters;
 
@@ -246,6 +252,22 @@
     this.accountAssignments = accountAssignments;
   }
 
+  public Set<ChargeDefinitionEntity> getChargeDefinitions() {
+    return chargeDefinitions;
+  }
+
+  public void setChargeDefinitions(Set<ChargeDefinitionEntity> chargeDefinitions) {
+    this.chargeDefinitions = chargeDefinitions;
+  }
+
+  public Set<TaskDefinitionEntity> getTaskDefinitions() {
+    return taskDefinitions;
+  }
+
+  public void setTaskDefinitions(Set<TaskDefinitionEntity> taskDefinitions) {
+    this.taskDefinitions = taskDefinitions;
+  }
+
   public String getParameters() {
     return parameters;
   }
diff --git a/service/src/main/java/io/mifos/portfolio/service/internal/repository/TaskDefinitionEntity.java b/service/src/main/java/io/mifos/portfolio/service/internal/repository/TaskDefinitionEntity.java
index 1db14aa..ca8e4bc 100644
--- a/service/src/main/java/io/mifos/portfolio/service/internal/repository/TaskDefinitionEntity.java
+++ b/service/src/main/java/io/mifos/portfolio/service/internal/repository/TaskDefinitionEntity.java
@@ -34,7 +34,7 @@
   private String identifier;
 
   @ManyToOne(fetch = FetchType.LAZY)
-  @JoinColumn(name = "product_id")
+  @JoinColumn(name = "product_id", nullable = false)
   private ProductEntity product;
 
   @Column(name = "a_name")
diff --git a/service/src/main/java/io/mifos/portfolio/service/rest/ProductRestController.java b/service/src/main/java/io/mifos/portfolio/service/rest/ProductRestController.java
index fe117f7..569161b 100644
--- a/service/src/main/java/io/mifos/portfolio/service/rest/ProductRestController.java
+++ b/service/src/main/java/io/mifos/portfolio/service/rest/ProductRestController.java
@@ -31,6 +31,7 @@
 import io.mifos.portfolio.service.internal.command.ChangeEnablingOfProductCommand;
 import io.mifos.portfolio.service.internal.command.ChangeProductCommand;
 import io.mifos.portfolio.service.internal.command.CreateProductCommand;
+import io.mifos.portfolio.service.internal.command.DeleteProductCommand;
 import io.mifos.portfolio.service.internal.service.CaseService;
 import io.mifos.portfolio.service.internal.service.PatternService;
 import io.mifos.portfolio.service.internal.service.ProductService;
@@ -150,6 +151,28 @@
     return ResponseEntity.accepted().build();
   }
 
+  @RequestMapping(
+          value = "/{productidentifier}",
+          method = RequestMethod.DELETE,
+          consumes = MediaType.ALL_VALUE,
+          produces = MediaType.APPLICATION_JSON_VALUE
+  )
+  public @ResponseBody ResponseEntity<Void> deleteProduct(@PathVariable("productidentifier") final String productIdentifier)
+  {
+    final boolean enabled = productService.findEnabledByIdentifier(productIdentifier)
+            .orElseThrow(() -> ServiceException.notFound("Instance with identifier ''{0}'' doesn''t exist.", productIdentifier));
+
+    if (enabled)
+      throw ServiceException.conflict("Cannot delete product with identifier ''{0}'', because it is enabled.", productIdentifier);
+
+    if (caseService.existsByProductIdentifier(productIdentifier))
+      throw ServiceException.conflict("Cannot delete product with identifier ''{0}'', because there are already cases defined on it.", productIdentifier);
+
+    commandGateway.process(new DeleteProductCommand(productIdentifier));
+
+    return ResponseEntity.accepted().build();
+  }
+
   @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.PRODUCT_OPERATIONS_MANAGEMENT)
   @RequestMapping(
           value = "/{productidentifier}/incompleteaccountassignments",
@@ -159,7 +182,7 @@
   public @ResponseBody ResponseEntity<Set<AccountAssignment>> getIncompleteAccountAssignments(@PathVariable("productidentifier") final String productIdentifier)
   {
     productService.findByIdentifier(productIdentifier)
-            .orElseThrow(() -> ServiceException.notFound("Instance with identifier " + productIdentifier + " doesn't exist."));
+            .orElseThrow(() -> ServiceException.notFound("Instance with identifier ''{0}'' doesn''t exist.", productIdentifier));
 
     return ResponseEntity.ok(productService.getIncompleteAccountAssignments(productIdentifier));
   }
@@ -175,7 +198,7 @@
           @RequestBody final Boolean enabled)
   {
     productService.findByIdentifier(productIdentifier)
-            .orElseThrow(() -> ServiceException.notFound("Instance with identifier " + productIdentifier + " doesn't exist."));
+            .orElseThrow(() -> ServiceException.notFound("Instance with identifier ''{0}'' doesn''t exist.", productIdentifier));
 
     if (enabled) {
       if (!productService.areChargeDefinitionsCoveredByAccountAssignments(productIdentifier))
@@ -197,7 +220,7 @@
   public  @ResponseBody ResponseEntity<Boolean> getProductEnabled(@PathVariable("productidentifier") final String productIdentifier)
   {
     return ResponseEntity.ok(productService.findEnabledByIdentifier(productIdentifier)
-            .orElseThrow(() -> ServiceException.notFound("Instance with identifier " + productIdentifier + " doesn't exist.")));
+            .orElseThrow(() -> ServiceException.notFound("Instance with identifier ''{0}'' doesn''t exist.", productIdentifier)));
 
   }
 }