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;