First cut at REST errors

 - Added a new client-facing class to hold a formatted error
 - Renamed the existing RestError to be RestErrorCatalogEntry
 - Modified the Intent POST operation to return an actual error if the
   POST can't parse the inbound object

Change-Id: I05d8919626f1a9350262d4aee7919c0fc8a4fa09
diff --git a/src/main/java/net/onrc/onos/api/rest/RestError.java b/src/main/java/net/onrc/onos/api/rest/RestError.java
index 8b958de..dd61d26 100644
--- a/src/main/java/net/onrc/onos/api/rest/RestError.java
+++ b/src/main/java/net/onrc/onos/api/rest/RestError.java
@@ -1,60 +1,84 @@
 package net.onrc.onos.api.rest;
 
 /**
- * Describes a REST error.
- * code is a unique identifier for the error.
- * summary is indended to be a short description of what happened.
- * description is a long description of the problem, and can be formatted using
- * variable replacement.  Variable placeholders are indicated with the string
- * "{}" in the description.
- * Objects of this class are immutable.
+ * Object to represent an instance of a particular REST error.  The error
+ * contains a formatted description which has information about the particular
+ * occurence.  Objects of this type are passed back to REST callers if an error
+ * is encountered.
  */
-
 public final class RestError {
 
     private final RestErrorCodes.RestErrorCode code;
     private final String summary;
-    private final String description;
+    private final String formattedDescription;
 
     /**
-     * Constructs a new RestError object from a code, summary and description.
-     *
-     * @param newCode code for the new error
-     * @param newSummary short summary for the new error
-     * @param newDescription formatable description for the new error
+     * Hidden default constructor to force usage of the factory method.
      */
-    public RestError(final RestErrorCodes.RestErrorCode newCode,
-                     final String newSummary,
-                     final String newDescription) {
-        code = newCode;
-        summary = newSummary;
-        description = newDescription;
+    private RestError() {
+        // This is never called, but Java requires these initializations
+        // because the members are final.
+        code = RestErrorCodes.RestErrorCode.INTENT_ALREADY_EXISTS;
+        summary = "";
+        formattedDescription = "";
     }
 
     /**
-     * Gets the summary of the error.
+     * Constructor to make a new Error.  Called by factory method.
      *
-     * @return string for the summary
+     * @param code code for the new error
+     * @param summary summary string for the new error
+     * @param formattedDescription formatted full description of the error
      */
-    public String getSummary() {
-        return summary;
+    private RestError(final RestErrorCodes.RestErrorCode code,
+                      final String summary,
+                      final String formattedDescription) {
+        this.code = code;
+        this.summary = summary;
+        this.formattedDescription = formattedDescription;
     }
 
     /**
-     * Gets the unique code for this error.
+     * Fetch the code for this error.
      *
-     * @return unique code
+     * @return error code
      */
     public RestErrorCodes.RestErrorCode getCode() {
         return code;
     }
 
     /**
-     * Gets the unformatted description string for the error.
+     * Fetch the summary for this error.
      *
-     * @return the unformatted description string.
+     * @return summary
      */
-    public String getDescription() {
-        return description;
+    public String getSummary() {
+        return summary;
+    }
+
+    /**
+     * Fetch the formatted descritpion for this error.
+     *
+     * @return formatted error
+     */
+    public String getFormattedDescription() {
+        return formattedDescription;
+    }
+
+    /**
+     * Creates an object to represent an instance of a REST error.  The
+     * descrption is formatted for this particular instance.
+     *
+     * @param code code of the error in the catalog
+     * @param parameters list of positional parameters to use in formatting
+     *                   the description
+     * @return new RestError representing this intance
+     */
+    public static RestError createRestError(final RestErrorCodes.RestErrorCode code,
+                                            final Object... parameters) {
+        final RestErrorCatalogEntry error = RestErrorCatalog.getRestError(code);
+        final String formattedDescription =
+                RestErrorFormatter.formatErrorMessage(error, parameters);
+        return new RestError(code, error.getSummary(), formattedDescription);
     }
 }
diff --git a/src/main/java/net/onrc/onos/api/rest/RestErrorCatalog.java b/src/main/java/net/onrc/onos/api/rest/RestErrorCatalog.java
index d81805f..4a8a765 100644
--- a/src/main/java/net/onrc/onos/api/rest/RestErrorCatalog.java
+++ b/src/main/java/net/onrc/onos/api/rest/RestErrorCatalog.java
@@ -1,5 +1,6 @@
 package net.onrc.onos.api.rest;
 
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -17,17 +18,20 @@
      * Static list of known errors.  Someday this will be read in from an
      * external file.
      */
-    private static final RestError[] ERROR_LIST = {
-        new RestError(RestErrorCodes.RestErrorCode.INTENT_NOT_FOUND,
+    private static final RestErrorCatalogEntry[] ERROR_LIST = {
+        new RestErrorCatalogEntry(RestErrorCodes.RestErrorCode.INTENT_NOT_FOUND,
                       "Intent not found",
                       "An intent with the identifier {} was not found."),
-        new RestError(RestErrorCodes.RestErrorCode.INTENT_ALREADY_EXISTS,
+        new RestErrorCatalogEntry(RestErrorCodes.RestErrorCode.INTENT_ALREADY_EXISTS,
                       "Intent already exists",
                       "An intent with the identifier {} could not be created " +
                       "because one already exists."),
-        new RestError(RestErrorCodes.RestErrorCode.INTENT_NO_PATH,
+        new RestErrorCatalogEntry(RestErrorCodes.RestErrorCode.INTENT_NO_PATH,
                       "No path found",
                       "No path found between {} and {}"),
+        new RestErrorCatalogEntry(RestErrorCodes.RestErrorCode.INTENT_INVALID,
+                      "Intent invalid",
+                      "The intent provided is empty or invalid"),
 
     };
 
@@ -35,17 +39,19 @@
      * Singleton implementation using the demand holder idiom.
      */
     private static class RestErrorMapHolder {
+        private static Map<Integer, RestErrorCatalogEntry> restErrorMap = initializeRestErrorMap();
+
         /**
          * Load up the error map.
          *
          * @return REST error map
          */
-        private static Map<Integer, RestError> initializeRestErrorMap() {
+        private static Map<Integer, RestErrorCatalogEntry> initializeRestErrorMap() {
             restErrorMap = new HashMap<>();
-            for (final RestError restError : ERROR_LIST) {
-                restErrorMap.put(restError.getCode().ordinal(), restError);
+            for (final RestErrorCatalogEntry restErrorCatalogEntry : ERROR_LIST) {
+                restErrorMap.put(restErrorCatalogEntry.getCode().ordinal(), restErrorCatalogEntry);
             }
-            return restErrorMap;
+            return Collections.unmodifiableMap(restErrorMap);
         }
 
         /**
@@ -54,12 +60,10 @@
          * @return map of the Rest Errors that was created from the known error
          * list.
          */
-        public static Map<Integer, RestError> getRestErrorMap() {
+        public static Map<Integer, RestErrorCatalogEntry> getRestErrorMap() {
             return restErrorMap;
         }
 
-
-        private static Map<Integer, RestError> restErrorMap = initializeRestErrorMap();
     }
 
     /**
@@ -67,18 +71,18 @@
      *
      * @return map of possible REST errors.
      */
-    public static Map<Integer, RestError> getRestErrorMap() {
+    public static Map<Integer, RestErrorCatalogEntry> getRestErrorMap() {
         return RestErrorMapHolder.getRestErrorMap();
     }
 
     /**
-     * Fetch the RestError for the given code.
+     * Fetch the RestErrorCatalogEntry for the given code.
      *
      * @param code the code for the message to look up.
      * @return the REST error for the code if one exists, null if it does not
      *         exist.
      */
-    public static RestError getRestError(final RestErrorCodes.RestErrorCode code) {
+    public static RestErrorCatalogEntry getRestError(final RestErrorCodes.RestErrorCode code) {
         return getRestErrorMap().get(code.ordinal());
     }
 }
diff --git a/src/main/java/net/onrc/onos/api/rest/RestErrorCatalogEntry.java b/src/main/java/net/onrc/onos/api/rest/RestErrorCatalogEntry.java
new file mode 100644
index 0000000..96b3eea
--- /dev/null
+++ b/src/main/java/net/onrc/onos/api/rest/RestErrorCatalogEntry.java
@@ -0,0 +1,60 @@
+package net.onrc.onos.api.rest;
+
+/**
+ * Describes a REST error.
+ * code is a unique identifier for the error.
+ * summary is indended to be a short description of what happened.
+ * descriptionFormatString is a long description of the problem, and can be formatted using
+ * variable replacement.  Variable placeholders are indicated with the string
+ * "{}" in the descriptionFormatString.
+ * Objects of this class are immutable.
+ */
+
+public final class RestErrorCatalogEntry {
+
+    private final RestErrorCodes.RestErrorCode code;
+    private final String summary;
+    private final String descriptionFormatString;
+
+    /**
+     * Constructs a new RestErrorCatalogEntry object from a code, summary and descriptionFormatString.
+     *
+     * @param newCode code for the new error
+     * @param newSummary short summary for the new error
+     * @param newDescriptionFormatString formatable description for the new error
+     */
+    public RestErrorCatalogEntry(final RestErrorCodes.RestErrorCode newCode,
+                                 final String newSummary,
+                                 final String newDescriptionFormatString) {
+        code = newCode;
+        summary = newSummary;
+        descriptionFormatString = newDescriptionFormatString;
+    }
+
+    /**
+     * Gets the summary of the error.
+     *
+     * @return string for the summary
+     */
+    public String getSummary() {
+        return summary;
+    }
+
+    /**
+     * Gets the unique code for this error.
+     *
+     * @return unique code
+     */
+    public RestErrorCodes.RestErrorCode getCode() {
+        return code;
+    }
+
+    /**
+     * Gets the unformatted description string for the error.
+     *
+     * @return the unformatted description string.
+     */
+    public String getDescriptionFormatString() {
+        return descriptionFormatString;
+    }
+}
diff --git a/src/main/java/net/onrc/onos/api/rest/RestErrorCodes.java b/src/main/java/net/onrc/onos/api/rest/RestErrorCodes.java
index 1d34615..09626d2 100644
--- a/src/main/java/net/onrc/onos/api/rest/RestErrorCodes.java
+++ b/src/main/java/net/onrc/onos/api/rest/RestErrorCodes.java
@@ -24,7 +24,10 @@
         INTENT_ALREADY_EXISTS,
 
         /** No path available for the Intent. */
-        INTENT_NO_PATH
+        INTENT_NO_PATH,
+
+        /** An object specified for an intent is invalid (parsing error). */
+        INTENT_INVALID
     }
 
 }
diff --git a/src/main/java/net/onrc/onos/api/rest/RestErrorFormatter.java b/src/main/java/net/onrc/onos/api/rest/RestErrorFormatter.java
index edc39b2..b9b2ba5 100644
--- a/src/main/java/net/onrc/onos/api/rest/RestErrorFormatter.java
+++ b/src/main/java/net/onrc/onos/api/rest/RestErrorFormatter.java
@@ -14,19 +14,19 @@
     private RestErrorFormatter() { }
 
     /**
-     * Takes a RestError template and formats the description using a supplied
+     * Takes a RestErrorCatalogEntry template and formats the description using a supplied
      * list of replacement parameters.
      *
-     * @param error the RestError to format
+     * @param error the RestErrorCatalogEntry to format
      * @param parameters parameter list to use as positional parameters in the
      *                   result string
      *
      * @return the String object for the formatted message.
      */
-    static String formatErrorMessage(final RestError error,
+    static String formatErrorMessage(final RestErrorCatalogEntry error,
                                      final Object... parameters) {
         final FormattingTuple formattingResult =
-                MessageFormatter.arrayFormat(error.getDescription(), parameters);
+                MessageFormatter.arrayFormat(error.getDescriptionFormatString(), parameters);
         return formattingResult.getMessage();
     }
 }