Juan Hernandez has uploaded a new change for review. Change subject: restapi: IO exception mapper ......................................................................
restapi: IO exception mapper This patch introduces a new message body reader that generates always an IO exception, containing the cause of the error, and a generic exception mapper that takes that IO exception, extracts the cause and generates the corresponding message. Change-Id: Id63e6eee673723ac32e3c2740f338f7ca465c6ac Bug-Url: https://bugzilla.redhat.com/1110295 Signed-off-by: Juan Hernandez <juan.hernan...@redhat.com> (cherry picked from commit a47e95e9d5eed10a57ef38963450421ccd32a07f) --- M backend/manager/modules/restapi/interface/definition/pom.xml A backend/manager/modules/restapi/interface/definition/src/main/java/org/ovirt/engine/api/xml/JAXBMessageBodyReader.java A backend/manager/modules/restapi/interface/definition/src/main/java/org/ovirt/engine/api/xml/JAXBValidationEventHandler.java M backend/manager/modules/restapi/interface/definition/src/main/resources/META-INF/services/javax.ws.rs.ext.Providers M backend/manager/modules/restapi/jaxrs/src/main/java/org/ovirt/engine/api/restapi/BackendApplication.java A backend/manager/modules/restapi/jaxrs/src/main/java/org/ovirt/engine/api/restapi/resource/validation/IOExceptionMapper.java D backend/manager/modules/restapi/jaxrs/src/main/java/org/ovirt/engine/api/restapi/resource/validation/InvalidValueExceptionMapper.java D backend/manager/modules/restapi/jaxrs/src/main/java/org/ovirt/engine/api/restapi/resource/validation/XmlMessageBodyReader.java M backend/manager/modules/restapi/webapp/src/main/webapp/WEB-INF/web.xml 9 files changed, 264 insertions(+), 99 deletions(-) git pull ssh://gerrit.ovirt.org:29418/ovirt-engine refs/changes/47/28847/1 diff --git a/backend/manager/modules/restapi/interface/definition/pom.xml b/backend/manager/modules/restapi/interface/definition/pom.xml index 578d8bd..a03802e 100644 --- a/backend/manager/modules/restapi/interface/definition/pom.xml +++ b/backend/manager/modules/restapi/interface/definition/pom.xml @@ -52,6 +52,12 @@ <groupId>commons-lang</groupId> <artifactId>commons-lang</artifactId> </dependency> + + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-api</artifactId> + </dependency> + </dependencies> <build> diff --git a/backend/manager/modules/restapi/interface/definition/src/main/java/org/ovirt/engine/api/xml/JAXBMessageBodyReader.java b/backend/manager/modules/restapi/interface/definition/src/main/java/org/ovirt/engine/api/xml/JAXBMessageBodyReader.java new file mode 100644 index 0000000..922f401 --- /dev/null +++ b/backend/manager/modules/restapi/interface/definition/src/main/java/org/ovirt/engine/api/xml/JAXBMessageBodyReader.java @@ -0,0 +1,147 @@ +/* +* Copyright (c) 2014 Red Hat, 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 org.ovirt.engine.api.xml; + +import java.io.IOException; +import java.io.InputStream; +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; +import javax.ws.rs.Consumes; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.ext.MessageBodyReader; +import javax.ws.rs.ext.Provider; +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBElement; +import javax.xml.bind.JAXBException; +import javax.xml.bind.Unmarshaller; +import javax.xml.bind.ValidationEventHandler; +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; + +import org.ovirt.engine.api.model.API; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This class is responsible for converting XML documents into model objects. Note that it can't be a generic class + * because if it is then the JAX-RS framework will select other builtin classes that are more specific. + */ +@Provider +@Consumes(MediaType.APPLICATION_XML) +public class JAXBMessageBodyReader implements MessageBodyReader<Object> { + /** + * The logger used by this class. + */ + private Logger log = LoggerFactory.getLogger(JAXBMessageBodyReader.class); + + /** + * The factory used to create XML document readers. + */ + private XMLInputFactory parserFactory; + + /** + * The JAXB jaxbContext used to convert XML documents into the corresponding model objects. + */ + private JAXBContext jaxbContext; + + /** + * Default event handler recognizes XML parsing as error and not as warning. + */ + private ValidationEventHandler jaxbHandler = new JAXBValidationEventHandler(); + + public JAXBMessageBodyReader() { + // Create a factory that will produce XML parsers that ignore entity references and DTDs: + parserFactory = XMLInputFactory.newFactory(); + parserFactory.setProperty(XMLInputFactory.IS_REPLACING_ENTITY_REFERENCES, false); + parserFactory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false); + parserFactory.setProperty(XMLInputFactory.SUPPORT_DTD, false); + + // Create a JAXB context for the model package: + String modelPackage = API.class.getPackage().getName(); + try { + jaxbContext = JAXBContext.newInstance(modelPackage); + } + catch (JAXBException exception) { + log.error("Can't create JAXB context for package \"{}\".", modelPackage, exception); + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isReadable(Class<?> type, Type genericType, Annotation annotations[], MediaType mediaType) { + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public Object readFrom(Class<Object> type, Type genericType, Annotation annotations[], MediaType mediaType, + MultivaluedMap<String, String> httpHeaders, InputStream entityStream) + throws IOException, WebApplicationException { + XMLStreamReader reader = null; + try { + reader = parserFactory.createXMLStreamReader(entityStream, "UTF-8"); + return readFrom(reader); + } + catch (XMLStreamException exception) { + throw new IOException(exception); + } + finally { + if (reader != null) { + try { + reader.close(); + } + catch (XMLStreamException exception) { + log.warn("Can't close XML stream reader.", exception); + } + } + } + } + + /** + * Read the XML document using the given reader and convert it to an object. The given reader will be closed by the + * caller. + */ + private Object readFrom(XMLStreamReader reader) throws IOException { + try { + Unmarshaller unmarshaller = jaxbContext.createUnmarshaller(); + unmarshaller.setEventHandler(jaxbHandler); + Object result = unmarshaller.unmarshal(reader); + if (result instanceof JAXBElement) { + result = ((JAXBElement) result).getValue(); + } + return result; + } + catch (JAXBException exception) { + Throwable linked = exception.getLinkedException(); + if (linked != null) { + Throwable cause = linked; + while (cause.getCause() != null) { + cause = cause.getCause(); + } + throw new IOException(cause); + } + throw new IOException(exception); + } + } +} diff --git a/backend/manager/modules/restapi/interface/definition/src/main/java/org/ovirt/engine/api/xml/JAXBValidationEventHandler.java b/backend/manager/modules/restapi/interface/definition/src/main/java/org/ovirt/engine/api/xml/JAXBValidationEventHandler.java new file mode 100644 index 0000000..8c55f5e --- /dev/null +++ b/backend/manager/modules/restapi/interface/definition/src/main/java/org/ovirt/engine/api/xml/JAXBValidationEventHandler.java @@ -0,0 +1,38 @@ +/* +* Copyright (c) 2014 Red Hat, 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 org.ovirt.engine.api.xml; + +import javax.xml.bind.ValidationEvent; +import javax.xml.bind.ValidationEventHandler; + +/** + * This validation event handler considers all errors as fatal, and reports them so that the JAXB context will abort the + * operation and throw an exception. If this isn't used, type errors (integer overflows, for example) are silently + * ignored. + */ +public class JAXBValidationEventHandler implements ValidationEventHandler { + @Override + public boolean handleEvent(ValidationEvent event) { + switch (event.getSeverity()) { + case ValidationEvent.ERROR: + case ValidationEvent.FATAL_ERROR: + return false; + default: + return true; + } + } +} diff --git a/backend/manager/modules/restapi/interface/definition/src/main/resources/META-INF/services/javax.ws.rs.ext.Providers b/backend/manager/modules/restapi/interface/definition/src/main/resources/META-INF/services/javax.ws.rs.ext.Providers index 3d2b90c..377bfa1 100644 --- a/backend/manager/modules/restapi/interface/definition/src/main/resources/META-INF/services/javax.ws.rs.ext.Providers +++ b/backend/manager/modules/restapi/interface/definition/src/main/resources/META-INF/services/javax.ws.rs.ext.Providers @@ -1 +1,2 @@ org.ovirt.engine.api.resteasy.json.JsonProvider +org.ovirt.engine.api.xml.JAXBMessageBodyReader \ No newline at end of file diff --git a/backend/manager/modules/restapi/jaxrs/src/main/java/org/ovirt/engine/api/restapi/BackendApplication.java b/backend/manager/modules/restapi/jaxrs/src/main/java/org/ovirt/engine/api/restapi/BackendApplication.java index 5b7d9e1..d556c9b 100644 --- a/backend/manager/modules/restapi/jaxrs/src/main/java/org/ovirt/engine/api/restapi/BackendApplication.java +++ b/backend/manager/modules/restapi/jaxrs/src/main/java/org/ovirt/engine/api/restapi/BackendApplication.java @@ -58,13 +58,12 @@ import org.ovirt.engine.api.restapi.resource.BackendVmPoolsResource; import org.ovirt.engine.api.restapi.resource.BackendVmsResource; import org.ovirt.engine.api.restapi.resource.BackendVnicProfilesResource; -import org.ovirt.engine.api.restapi.resource.validation.InvalidValueExceptionMapper; +import org.ovirt.engine.api.restapi.resource.validation.IOExceptionMapper; import org.ovirt.engine.api.restapi.resource.validation.JaxbExceptionMapper; import org.ovirt.engine.api.restapi.resource.validation.JaxbMarshallExceptionMapper; import org.ovirt.engine.api.restapi.resource.validation.JsonExceptionMapper; import org.ovirt.engine.api.restapi.resource.validation.MalformedIdExceptionMapper; import org.ovirt.engine.api.restapi.resource.validation.ValidatorLocator; -import org.ovirt.engine.api.restapi.resource.validation.XmlMessageBodyReader; import org.ovirt.engine.api.restapi.security.auth.LoginValidator; import org.ovirt.engine.api.restapi.types.MappingLocator; import org.ovirt.engine.api.restapi.util.SessionHelper; @@ -165,15 +164,12 @@ singletons.add(new ResponseStatusLogger()); singletons.add(new ResponsePayloadLogger()); - // JAXB message body reader that sets default validation event handler - singletons.add(new XmlMessageBodyReader()); - // Intercepter that maps exceptions cause by illegal guid string to 400 status (BAD_REQUEST). singletons.add(new MalformedIdExceptionMapper()); singletons.add(new JaxbExceptionMapper()); singletons.add(new JaxbMarshallExceptionMapper()); singletons.add(new JsonExceptionMapper()); - singletons.add(new InvalidValueExceptionMapper()); + singletons.add(new IOExceptionMapper()); } private void addResource(final BackendResource resource) { diff --git a/backend/manager/modules/restapi/jaxrs/src/main/java/org/ovirt/engine/api/restapi/resource/validation/IOExceptionMapper.java b/backend/manager/modules/restapi/jaxrs/src/main/java/org/ovirt/engine/api/restapi/resource/validation/IOExceptionMapper.java new file mode 100644 index 0000000..d282864 --- /dev/null +++ b/backend/manager/modules/restapi/jaxrs/src/main/java/org/ovirt/engine/api/restapi/resource/validation/IOExceptionMapper.java @@ -0,0 +1,70 @@ +/* +* Copyright (c) 2014 Red Hat, 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 org.ovirt.engine.api.restapi.resource.validation; + +import java.io.IOException; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.Application; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.Request; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.Status; +import javax.ws.rs.core.UriInfo; +import javax.ws.rs.ext.ExceptionMapper; +import javax.ws.rs.ext.Provider; + +import org.ovirt.engine.api.model.Fault; +import org.ovirt.engine.api.model.UsageMessage; +import org.ovirt.engine.api.utils.InvalidValueException; + +/** + * This mapper is intended to handle the exceptions thrown by the JAXB message body reader. + */ +@Provider +public class IOExceptionMapper implements ExceptionMapper<IOException> { + + @Context + protected UriInfo uriInfo; + @Context + protected Request request; + @Context + protected Application application; + + @Override + public Response toResponse(IOException exception) { + // Check if the cause of the exception is an invalid value, and generate a specific error message: + Throwable cause = exception.getCause(); + if (cause instanceof InvalidValueException) { + Fault fault = new Fault(); + fault.setReason("Invalid value"); + fault.setDetail(cause.getMessage()); + return Response.status(Response.Status.BAD_REQUEST).entity(fault).build(); + } + + // For any other kind of exception generate an error response with information describing the correct syntax for + // the request: + try { + UsageFinder finder = new UsageFinder(); + UsageMessage usage = finder.getUsageMessage(application, uriInfo, request); + return Response.status(Status.BAD_REQUEST).entity(usage).build(); + } + catch (Exception error) { + throw new WebApplicationException(error, Response.status(Status.INTERNAL_SERVER_ERROR).build()); + } + } + +} diff --git a/backend/manager/modules/restapi/jaxrs/src/main/java/org/ovirt/engine/api/restapi/resource/validation/InvalidValueExceptionMapper.java b/backend/manager/modules/restapi/jaxrs/src/main/java/org/ovirt/engine/api/restapi/resource/validation/InvalidValueExceptionMapper.java deleted file mode 100644 index 7b328ed..0000000 --- a/backend/manager/modules/restapi/jaxrs/src/main/java/org/ovirt/engine/api/restapi/resource/validation/InvalidValueExceptionMapper.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.ovirt.engine.api.restapi.resource.validation; - -import org.ovirt.engine.api.model.Fault; -import org.ovirt.engine.api.utils.InvalidValueException; - -import javax.ws.rs.core.Response; -import javax.ws.rs.ext.ExceptionMapper; -import javax.ws.rs.ext.Provider; - -@Provider -public class InvalidValueExceptionMapper implements ExceptionMapper<InvalidValueException> { - - @Override - public Response toResponse(InvalidValueException e) { - Fault fault = new Fault(); - fault.setReason("Invalid Value"); - fault.setDetail(e.getMessage()); - return Response.status(Response.Status.BAD_REQUEST).entity(fault).build(); - } -} diff --git a/backend/manager/modules/restapi/jaxrs/src/main/java/org/ovirt/engine/api/restapi/resource/validation/XmlMessageBodyReader.java b/backend/manager/modules/restapi/jaxrs/src/main/java/org/ovirt/engine/api/restapi/resource/validation/XmlMessageBodyReader.java deleted file mode 100644 index 9d05f98..0000000 --- a/backend/manager/modules/restapi/jaxrs/src/main/java/org/ovirt/engine/api/restapi/resource/validation/XmlMessageBodyReader.java +++ /dev/null @@ -1,67 +0,0 @@ -package org.ovirt.engine.api.restapi.resource.validation; - -import org.jboss.resteasy.plugins.providers.jaxb.AbstractJAXBProvider; -import org.jboss.resteasy.plugins.providers.jaxb.JAXBMarshalException; -import org.ovirt.engine.api.utils.InvalidValueException; - -import javax.ws.rs.Consumes; -import javax.ws.rs.WebApplicationException; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.MultivaluedMap; -import javax.ws.rs.ext.Provider; -import javax.xml.bind.JAXBContext; -import javax.xml.bind.JAXBElement; -import javax.xml.bind.JAXBException; -import javax.xml.bind.Unmarshaller; -import javax.xml.bind.ValidationEventHandler; -import javax.xml.bind.helpers.DefaultValidationEventHandler; -import java.io.IOException; -import java.io.InputStream; -import java.lang.annotation.Annotation; -import java.lang.reflect.Type; - -@Provider -@Consumes({ MediaType.APPLICATION_XML }) -public class XmlMessageBodyReader extends AbstractJAXBProvider<Object> { - - /** Default event handler recognizes XML parsing as error and not as warning */ - private ValidationEventHandler errorhandler = new DefaultValidationEventHandler(); - - @Override - protected boolean isReadWritable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) { - return true; - } - - /** - * {@inheritDoc} - * <p> - * Standard JAXB unmarshaller with custom error handler- {@link #errorhandler} - */ - @Override - public Object readFrom(Class<Object> type, Type genericType, Annotation[] annotations, MediaType mediaType, - MultivaluedMap<String, String> httpHeaders, InputStream entityStream) throws IOException, WebApplicationException { - - if (entityStream == null) { - return null; - } - try { - JAXBContext ctx = findJAXBContext(type, annotations, mediaType, true); - Unmarshaller unmarshaller = ctx.createUnmarshaller(); - - AbstractJAXBProvider.decorateUnmarshaller(type, annotations, mediaType, unmarshaller); - - unmarshaller.setEventHandler(errorhandler); - Object parsedObj = unmarshaller.unmarshal(entityStream); - if (parsedObj instanceof JAXBElement) { - return ((JAXBElement) parsedObj).getValue(); - } - return parsedObj; - } catch (JAXBException e) { - if (e.getLinkedException().getCause() instanceof InvalidValueException) { - throw (InvalidValueException) e.getLinkedException().getCause(); - } - throw new JAXBMarshalException(e); - } - } - -} diff --git a/backend/manager/modules/restapi/webapp/src/main/webapp/WEB-INF/web.xml b/backend/manager/modules/restapi/webapp/src/main/webapp/WEB-INF/web.xml index b1f1606..44927e0 100644 --- a/backend/manager/modules/restapi/webapp/src/main/webapp/WEB-INF/web.xml +++ b/backend/manager/modules/restapi/webapp/src/main/webapp/WEB-INF/web.xml @@ -6,12 +6,6 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0"> - <!-- Disable entity expansion in RESTEasy: --> - <context-param> - <param-name>resteasy.document.expand.entity.references</param-name> - <param-value>false</param-value> - </context-param> - <!-- Perform authentication: --> <filter> <filter-name>AuthenticationFilter</filter-name> -- To view, visit http://gerrit.ovirt.org/28847 To unsubscribe, visit http://gerrit.ovirt.org/settings Gerrit-MessageType: newchange Gerrit-Change-Id: Id63e6eee673723ac32e3c2740f338f7ca465c6ac Gerrit-PatchSet: 1 Gerrit-Project: ovirt-engine Gerrit-Branch: ovirt-engine-3.4 Gerrit-Owner: Juan Hernandez <juan.hernan...@redhat.com> _______________________________________________ Engine-patches mailing list Engine-patches@ovirt.org http://lists.ovirt.org/mailman/listinfo/engine-patches