Restructuring the form of REST API deployment to provide
for security of app's REST APIs and for consistency of
exception mappers and JSON writer.

Change-Id: Id318372bf62f82ed974355c05e7fe64e0fbfc0c5
diff --git a/utils/rest/pom.xml b/utils/rest/pom.xml
index c9a7df7..990391a 100644
--- a/utils/rest/pom.xml
+++ b/utils/rest/pom.xml
@@ -33,6 +33,25 @@
 
     <dependencies>
         <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onlab-misc</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onlab-osgi</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-annotations</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
+        </dependency>
+
+        <dependency>
             <groupId>com.sun.jersey.jersey-test-framework</groupId>
             <artifactId>jersey-test-framework-core</artifactId>
             <scope>test</scope>
@@ -42,12 +61,6 @@
             <artifactId>jersey-test-framework-grizzly2</artifactId>
             <scope>test</scope>
         </dependency>
-
-        <dependency>
-            <groupId>org.onosproject</groupId>
-            <artifactId>onlab-osgi</artifactId>
-            <version>${project.version}</version>
-        </dependency>
     </dependencies>
 
 </project>
diff --git a/utils/rest/src/main/java/org/onlab/rest/AbstractWebApplication.java b/utils/rest/src/main/java/org/onlab/rest/AbstractWebApplication.java
new file mode 100644
index 0000000..5d6b06d
--- /dev/null
+++ b/utils/rest/src/main/java/org/onlab/rest/AbstractWebApplication.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onlab.rest;
+
+import com.google.common.collect.ImmutableSet;
+import org.onlab.rest.exceptions.BadRequestMapper;
+import org.onlab.rest.exceptions.EntityNotFoundMapper;
+import org.onlab.rest.exceptions.IllegalArgumentExceptionMapper;
+import org.onlab.rest.exceptions.IllegalStateExceptionMapper;
+import org.onlab.rest.exceptions.NotFoundMapper;
+import org.onlab.rest.exceptions.ServerErrorMapper;
+import org.onlab.rest.exceptions.ServiceNotFoundMapper;
+import org.onlab.rest.exceptions.WebApplicationExceptionMapper;
+
+import javax.ws.rs.core.Application;
+import java.util.Set;
+
+/**
+ * Base web application.
+ */
+public abstract class AbstractWebApplication extends Application {
+
+    /**
+     * Returns the aggregate set of resources, writers and mappers combined
+     * with a default set of such web entities.
+     *
+     * @param classes set of resources, writers and mappers
+     * @return combined set of web entities
+     */
+    protected Set<Class<?>> getClasses(Class<?>... classes) {
+        ImmutableSet.Builder<Class<?>> builder = ImmutableSet.builder();
+        builder.add(ServiceNotFoundMapper.class,
+                    EntityNotFoundMapper.class,
+                    NotFoundMapper.class,
+                    ServerErrorMapper.class,
+                    BadRequestMapper.class,
+                    WebApplicationExceptionMapper.class,
+                    IllegalArgumentExceptionMapper.class,
+                    IllegalStateExceptionMapper.class,
+                    JsonBodyWriter.class);
+        builder.add(classes);
+        return builder.build();
+    }
+
+    @Override
+    public abstract Set<Class<?>> getClasses();
+
+}
diff --git a/utils/rest/src/main/java/org/onlab/rest/JsonBodyWriter.java b/utils/rest/src/main/java/org/onlab/rest/JsonBodyWriter.java
new file mode 100644
index 0000000..8e14167
--- /dev/null
+++ b/utils/rest/src/main/java/org/onlab/rest/JsonBodyWriter.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onlab.rest;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.ext.MessageBodyWriter;
+import javax.ws.rs.ext.Provider;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+
+/**
+ * JAX-RS Response message body writer.
+ */
+@Provider
+@Produces("application/json")
+public class JsonBodyWriter implements MessageBodyWriter<ObjectNode> {
+
+    private ObjectMapper mapper = new ObjectMapper();
+
+    @Override
+    public boolean isWriteable(Class<?> type, Type genericType,
+                               Annotation[] annotations, MediaType mediaType) {
+        return type == ObjectNode.class;
+    }
+
+    @Override
+    public long getSize(ObjectNode node, Class<?> type, Type genericType,
+                        Annotation[] annotations, MediaType mediaType) {
+        return -1;
+    }
+
+    @Override
+    public void writeTo(ObjectNode node, Class<?> type, Type genericType,
+                        Annotation[] annotations, MediaType mediaType,
+                        MultivaluedMap<String, Object> httpHeaders,
+                        OutputStream entityStream) throws IOException {
+        mapper.writer().writeValue(entityStream, node);
+        entityStream.flush();
+    }
+}
diff --git a/utils/rest/src/main/java/org/onlab/rest/exceptions/AbstractMapper.java b/utils/rest/src/main/java/org/onlab/rest/exceptions/AbstractMapper.java
new file mode 100644
index 0000000..fa94c2d
--- /dev/null
+++ b/utils/rest/src/main/java/org/onlab/rest/exceptions/AbstractMapper.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onlab.rest.exceptions;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.ExceptionMapper;
+
+import static com.google.common.base.Strings.isNullOrEmpty;
+
+/**
+ * Base exception mapper implementation.
+ */
+public abstract class AbstractMapper<E extends Throwable> implements ExceptionMapper<E> {
+
+    /**
+     * Holds the current exception for use in subclasses.
+     */
+    protected Throwable error;
+
+    /**
+     * Returns the response status to be given when the exception occurs.
+     *
+     * @return response status
+     */
+    protected abstract Response.Status responseStatus();
+
+    @Override
+    public Response toResponse(E exception) {
+        error = exception;
+        return response(responseStatus(), exception).build();
+    }
+
+    /**
+     * Produces a response builder primed with the supplied status code
+     * and JSON entity with the status code and exception message.
+     *
+     * @param status    response status
+     * @param exception exception to encode
+     * @return response builder
+     */
+    protected Response.ResponseBuilder response(Response.Status status,
+                                                Throwable exception) {
+        error = exception;
+        ObjectMapper mapper = new ObjectMapper();
+        String message = messageFrom(exception);
+        ObjectNode result = mapper.createObjectNode()
+                .put("code", status.getStatusCode())
+                .put("message", message);
+        return Response.status(status).entity(result.toString());
+    }
+
+    /**
+     * Produces a response message from the supplied exception. Either it will
+     * use the exception message, if there is one, or it will use the top
+     * stack-frame message.
+     *
+     * @param exception exception from which to produce a message
+     * @return response message
+     */
+    protected String messageFrom(Throwable exception) {
+        if (isNullOrEmpty(exception.getMessage())) {
+            StackTraceElement[] trace = exception.getStackTrace();
+            return trace.length == 0 ? "Unknown error" : trace[0].toString();
+        }
+        return exception.getMessage();
+    }
+
+}
diff --git a/utils/rest/src/main/java/org/onlab/rest/exceptions/BadRequestMapper.java b/utils/rest/src/main/java/org/onlab/rest/exceptions/BadRequestMapper.java
new file mode 100644
index 0000000..10cbbea
--- /dev/null
+++ b/utils/rest/src/main/java/org/onlab/rest/exceptions/BadRequestMapper.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onlab.rest.exceptions;
+
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.Provider;
+import java.io.IOException;
+
+/**
+ * Mapper for IO exceptions to the BAD_REQUEST response code.
+ */
+@Provider
+public class BadRequestMapper extends AbstractMapper<IOException> {
+    @Override
+    protected Response.Status responseStatus() {
+        return Response.Status.BAD_REQUEST;
+    }
+}
diff --git a/utils/rest/src/main/java/org/onlab/rest/exceptions/EntityNotFoundMapper.java b/utils/rest/src/main/java/org/onlab/rest/exceptions/EntityNotFoundMapper.java
new file mode 100644
index 0000000..4a2a9ae
--- /dev/null
+++ b/utils/rest/src/main/java/org/onlab/rest/exceptions/EntityNotFoundMapper.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onlab.rest.exceptions;
+
+import org.onlab.util.ItemNotFoundException;
+
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.Provider;
+
+/**
+ * Mapper for service not found exceptions to the NOT_FOUND response code.
+ */
+@Provider
+public class EntityNotFoundMapper extends AbstractMapper<ItemNotFoundException> {
+    @Override
+    protected Response.Status responseStatus() {
+        return Response.Status.NOT_FOUND;
+    }
+}
diff --git a/utils/rest/src/main/java/org/onlab/rest/exceptions/IllegalArgumentExceptionMapper.java b/utils/rest/src/main/java/org/onlab/rest/exceptions/IllegalArgumentExceptionMapper.java
new file mode 100644
index 0000000..5d607a8
--- /dev/null
+++ b/utils/rest/src/main/java/org/onlab/rest/exceptions/IllegalArgumentExceptionMapper.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onlab.rest.exceptions;
+
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.Provider;
+
+/**
+ * Mapper for illegal argument exceptions to the BAD_REQUEST response code.
+ */
+@Provider
+public class IllegalArgumentExceptionMapper extends AbstractMapper<IllegalArgumentException> {
+    @Override
+    protected Response.Status responseStatus() {
+        return Response.Status.BAD_REQUEST;
+    }
+}
+
diff --git a/utils/rest/src/main/java/org/onlab/rest/exceptions/IllegalStateExceptionMapper.java b/utils/rest/src/main/java/org/onlab/rest/exceptions/IllegalStateExceptionMapper.java
new file mode 100644
index 0000000..3f50f00
--- /dev/null
+++ b/utils/rest/src/main/java/org/onlab/rest/exceptions/IllegalStateExceptionMapper.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onlab.rest.exceptions;
+
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.Provider;
+
+/**
+ * Mapper for illegal state exceptions to the BAD_REQUEST response code.
+ */
+@Provider
+public class IllegalStateExceptionMapper extends AbstractMapper<IllegalStateException> {
+    @Override
+    protected Response.Status responseStatus() {
+        return Response.Status.CONFLICT;
+    }
+}
+
diff --git a/utils/rest/src/main/java/org/onlab/rest/exceptions/NotFoundMapper.java b/utils/rest/src/main/java/org/onlab/rest/exceptions/NotFoundMapper.java
new file mode 100644
index 0000000..9d833a6
--- /dev/null
+++ b/utils/rest/src/main/java/org/onlab/rest/exceptions/NotFoundMapper.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onlab.rest.exceptions;
+
+import com.sun.jersey.api.NotFoundException;
+
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.Provider;
+
+/**
+ * Mapper for api not found exceptions to the NOT_FOUND response code.
+ */
+@Provider
+public class NotFoundMapper extends AbstractMapper<NotFoundException> {
+
+    @Override
+    protected Response.Status responseStatus() {
+        return Response.Status.NOT_FOUND;
+    }
+
+}
diff --git a/utils/rest/src/main/java/org/onlab/rest/exceptions/ServerErrorMapper.java b/utils/rest/src/main/java/org/onlab/rest/exceptions/ServerErrorMapper.java
new file mode 100644
index 0000000..0d87fb8
--- /dev/null
+++ b/utils/rest/src/main/java/org/onlab/rest/exceptions/ServerErrorMapper.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onlab.rest.exceptions;
+
+import org.slf4j.Logger;
+
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.Provider;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Mapper for service not found exceptions to the INTERNAL_SERVER_ERROR response code.
+ */
+@Provider
+public class ServerErrorMapper extends AbstractMapper<RuntimeException> {
+    private static final Logger log = getLogger(ServerErrorMapper.class);
+    @Override
+    protected Response.Status responseStatus() {
+        log.warn("Unhandled REST exception", error);
+        return Response.Status.INTERNAL_SERVER_ERROR;
+    }
+}
diff --git a/utils/rest/src/main/java/org/onlab/rest/exceptions/ServiceNotFoundMapper.java b/utils/rest/src/main/java/org/onlab/rest/exceptions/ServiceNotFoundMapper.java
new file mode 100644
index 0000000..6dc4f2e
--- /dev/null
+++ b/utils/rest/src/main/java/org/onlab/rest/exceptions/ServiceNotFoundMapper.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onlab.rest.exceptions;
+
+import org.onlab.osgi.ServiceNotFoundException;
+
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.Provider;
+
+/**
+ * Mapper for service not found exceptions to the SERVICE_UNAVAILABLE response code.
+ */
+@Provider
+public class ServiceNotFoundMapper extends AbstractMapper<ServiceNotFoundException> {
+    @Override
+    protected Response.Status responseStatus() {
+        return Response.Status.SERVICE_UNAVAILABLE;
+    }
+}
diff --git a/utils/rest/src/main/java/org/onlab/rest/exceptions/WebApplicationExceptionMapper.java b/utils/rest/src/main/java/org/onlab/rest/exceptions/WebApplicationExceptionMapper.java
new file mode 100644
index 0000000..8ad6ee9
--- /dev/null
+++ b/utils/rest/src/main/java/org/onlab/rest/exceptions/WebApplicationExceptionMapper.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onlab.rest.exceptions;
+
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.Provider;
+
+/**
+ * Exception mapper for WebApplicationExceptions.
+ */
+@Provider
+public class WebApplicationExceptionMapper extends AbstractMapper<WebApplicationException> {
+
+    /**
+     * Extracts and returns the response from a WebApplicationException.
+     *
+     * @param e WebApplicationException that was thrown
+     * @return precomputed Response from the exception
+     */
+    @Override
+    public Response toResponse(WebApplicationException e) {
+        return e.getResponse();
+    }
+
+    @Override
+    public Response.Status responseStatus() {
+        // This should never be called because this class overrides toResponse()
+        throw new UnsupportedOperationException(
+                "responseStatus() for a WebApplicationException should never be called");
+    }
+}
diff --git a/utils/rest/src/main/java/org/onlab/rest/exceptions/package-info.java b/utils/rest/src/main/java/org/onlab/rest/exceptions/package-info.java
new file mode 100644
index 0000000..24357f0
--- /dev/null
+++ b/utils/rest/src/main/java/org/onlab/rest/exceptions/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * 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.
+ */
+
+/**
+ * Various exception mappers to map errors to proper response status codes.
+ */
+package org.onlab.rest.exceptions;