Juan Hernandez has uploaded a new change for review. Change subject: restapi: Improve exception handling in FOP writer ......................................................................
restapi: Improve exception handling in FOP writer This is basically to make Coverity happy. Change-Id: I6d3202ba59ebe111f0180956a839e46b57149506 Signed-off-by: Juan Hernandez <[email protected]> --- M backend/manager/modules/restapi/jaxrs/src/main/java/org/ovirt/engine/api/restapi/pdf/FOPMessageBodyWriter.java A backend/manager/modules/restapi/jaxrs/src/main/resources/pdf/Cluster.xsl R backend/manager/modules/restapi/jaxrs/src/main/resources/pdf/GlusterVolumeProfileDetails.xsl 3 files changed, 163 insertions(+), 44 deletions(-) git pull ssh://gerrit.ovirt.org:29418/ovirt-engine refs/changes/44/40344/1 diff --git a/backend/manager/modules/restapi/jaxrs/src/main/java/org/ovirt/engine/api/restapi/pdf/FOPMessageBodyWriter.java b/backend/manager/modules/restapi/jaxrs/src/main/java/org/ovirt/engine/api/restapi/pdf/FOPMessageBodyWriter.java index 1749393..60bca35 100644 --- a/backend/manager/modules/restapi/jaxrs/src/main/java/org/ovirt/engine/api/restapi/pdf/FOPMessageBodyWriter.java +++ b/backend/manager/modules/restapi/jaxrs/src/main/java/org/ovirt/engine/api/restapi/pdf/FOPMessageBodyWriter.java @@ -1,11 +1,30 @@ +/* +* 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.pdf; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.lang.annotation.Annotation; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Type; +import java.util.HashMap; +import java.util.Map; import javax.ws.rs.Produces; import javax.ws.rs.WebApplicationException; @@ -15,45 +34,94 @@ 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.util.JAXBSource; import javax.xml.transform.Result; import javax.xml.transform.Source; import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerConfigurationException; +import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.sax.SAXResult; import javax.xml.transform.stream.StreamSource; +import org.apache.fop.apps.FOPException; import org.ovirt.engine.api.model.ObjectFactory; import org.ovirt.engine.api.model.API; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.apache.fop.apps.FOUserAgent; import org.apache.fop.apps.Fop; import org.apache.fop.apps.FopFactory; import org.apache.fop.apps.MimeConstants; +import org.ovirt.engine.api.resource.ApiMediaType; +/** + * This writer generates PDF documents from model objects transforming them first into XML, then into FO (Formatting + * Objects) using an XSLT transformation, and then into actual PDF using FOP. + */ @Provider -@Produces("application/pdf") +@Produces(ApiMediaType.APPLICATION_PDF) public class FOPMessageBodyWriter implements MessageBodyWriter<Object> { + /** + * The factory used to create JAXB elements. + */ + private ObjectFactory objectFactory = new ObjectFactory(); - private static final Logger log = LoggerFactory.getLogger(FOPMessageBodyWriter.class); + /** + * A index used to speed up finding the factory method used to create JAXB elements. + */ + private Map<Class<?>, Method> factoryMethods = new HashMap<>(); + + /** + * The JAXB context used to convert XML documents into the corresponding model objects. + */ private JAXBContext jaxbContext; - private TransformerFactory transfact; + + /** + * The factory used to create XSLT transformers. + */ + private TransformerFactory transformerFactory = TransformerFactory.newInstance(); + + /** + * The factory used to create FOP objects. + */ private FopFactory fopFactory; + + /** + * The FO user agent. + */ private FOUserAgent foUserAgent; - private ObjectFactory objectFactory; public FOPMessageBodyWriter() { - try { - String modelPackage = API.class.getPackage().getName(); - jaxbContext = JAXBContext.newInstance(modelPackage); - transfact = TransformerFactory.newInstance(); - fopFactory = FopFactory.newInstance(); - foUserAgent = fopFactory.newFOUserAgent(); - objectFactory = new ObjectFactory(); - } catch (Exception error) { - log.error("Error while creating FOP message body writer.", error); + // In order to create the JAXB element that wraps the object we need to call the method of the object factory + // that uses the correct element name, and in order to avoid doing this with every request we populate this + // map in advance: + for (Method factoryMethod : ObjectFactory.class.getDeclaredMethods()) { + Class<?>[] parameterTypes = factoryMethod.getParameterTypes(); + if (parameterTypes.length == 1) { + factoryMethods.put(parameterTypes[0], factoryMethod); + } } + + // Create a JAXB context for the model package: + String modelPackage = API.class.getPackage().getName(); + try { + jaxbContext = JAXBContext.newInstance(modelPackage); + } + catch (JAXBException exception) { + throw new IllegalStateException( + "Can't create JAXB context for package \"" + modelPackage + "\".", + exception + ); + } + + // Create the XSLT transformer factory: + transformerFactory = TransformerFactory.newInstance(); + + // Create the FOP factory: + fopFactory = FopFactory.newInstance(); + + // Create the FO user agent: + foUserAgent = fopFactory.newFOUserAgent(); } @Override @@ -62,41 +130,65 @@ } @Override - public boolean isWriteable(Class<?> dataClass, Type arg1, Annotation[] annotations, MediaType mediaType) { + public boolean isWriteable(Class<?> dataClass, Type genericType, Annotation[] annotations, MediaType mediaType) { return true; } @Override - public void writeTo(final Object data, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream) throws IOException, WebApplicationException { - String xslName = "/" + type.getSimpleName() + "AsPdf.xsl"; - try (InputStream templateStream = type.getResourceAsStream(xslName)) { + public void writeTo(Object object, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType, + MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream) + throws IOException, WebApplicationException { + // Locate and load the XSLT template: + String templateName = "/pdf/" + type.getSimpleName() + ".xsl"; + Transformer template; + try (InputStream templateStream = FOPMessageBodyWriter.class.getResourceAsStream(templateName)) { if (templateStream != null) { - StreamSource transformSource = new StreamSource(templateStream); - - Method factoryMethod = null; - for (Method currentMethod : objectFactory.getClass().getDeclaredMethods()) { - Class<?>[] parameterTypes = currentMethod.getParameterTypes(); - if (parameterTypes.length == 1 && parameterTypes[0] == type) { - factoryMethod = currentMethod; - break; - } - } - if (data != null && factoryMethod != null) { - JAXBElement<?> element = (JAXBElement<?>) factoryMethod.invoke(objectFactory, data); - Source source = new JAXBSource(jaxbContext, element); - - Transformer xslfoTransformer = transfact.newTransformer(transformSource); - Fop fop = fopFactory.newFop(MimeConstants.MIME_PDF, foUserAgent, entityStream); - Result res = new SAXResult(fop.getDefaultHandler()); - xslfoTransformer.transform(source, res); - } else { - log.error("Data not available"); - } - } else { - log.error("Error while generating PDF. Null InputStream"); + StreamSource templateSource = new StreamSource(templateStream); + template = transformerFactory.newTransformer(templateSource); } - } catch (Exception e) { - log.error("Error while generating PDF. ", e); + else { + throw new IOException("Can't find resource for XSLT template \"" + templateName + "\"."); + } + } + catch (TransformerConfigurationException exception) { + throw new IOException("Can't load XSLT template \"" + templateName + "\".", exception); + } + + // Find the factory method used to create the JAXB element with the right tag: + Method factoryMethod = factoryMethods.get(type); + if (factoryMethod == null) { + throw new IOException("Can't find factory method for type \"" + type.getName() + "\"."); + } + + // Invoke the method to create the JAXB element: + JAXBElement<Object> element; + try { + element = (JAXBElement<Object>) factoryMethod.invoke(objectFactory, object); + } + catch (IllegalAccessException | InvocationTargetException exception) { + throw new IOException("Error invoking factory method for type \"" + type.getName() + "\".", exception); + } + + // Wrap the created JAXB element with an object that can be used as the source of the XSLT transformation: + Source source; + try { + source = new JAXBSource(jaxbContext, element); + } + catch (JAXBException exception) { + throw new IOException("Can't create transformation source from JAXB element.", exception); + } + + // Run the XSLT transformation using the JAXB element as source and the entity stream as result: + try { + Fop fop = fopFactory.newFop(MimeConstants.MIME_PDF, foUserAgent, entityStream); + Result result = new SAXResult(fop.getDefaultHandler()); + template.transform(source, result); + } + catch (TransformerException | FOPException exception) { + throw new IOException( + "Error while generating PDF document using XSLT template \"" + templateName + "\".", + exception + ); } } } diff --git a/backend/manager/modules/restapi/jaxrs/src/main/resources/pdf/Cluster.xsl b/backend/manager/modules/restapi/jaxrs/src/main/resources/pdf/Cluster.xsl new file mode 100644 index 0000000..7a7cd8d --- /dev/null +++ b/backend/manager/modules/restapi/jaxrs/src/main/resources/pdf/Cluster.xsl @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> + +<xsl:stylesheet + version="1.0" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:fo="http://www.w3.org/1999/XSL/Format"> + + <xsl:output method="xml" indent="yes"/> + + <xsl:template match="/"> + <fo:root> + <fo:layout-master-set> + <fo:simple-page-master master-name="A4-portrait" page-height="29.7cm" page-width="21.0cm" margin="2cm"> + <fo:region-body/> + </fo:simple-page-master> + </fo:layout-master-set> + <fo:page-sequence master-reference="A4-portrait"> + <fo:flow flow-name="xsl-region-body"> + <fo:block> + Hello! + </fo:block> + </fo:flow> + </fo:page-sequence> + </fo:root> + </xsl:template> + +</xsl:stylesheet> \ No newline at end of file diff --git a/backend/manager/modules/restapi/jaxrs/src/main/resources/GlusterVolumeProfileDetailsAsPdf.xsl b/backend/manager/modules/restapi/jaxrs/src/main/resources/pdf/GlusterVolumeProfileDetails.xsl similarity index 100% rename from backend/manager/modules/restapi/jaxrs/src/main/resources/GlusterVolumeProfileDetailsAsPdf.xsl rename to backend/manager/modules/restapi/jaxrs/src/main/resources/pdf/GlusterVolumeProfileDetails.xsl -- To view, visit https://gerrit.ovirt.org/40344 To unsubscribe, visit https://gerrit.ovirt.org/settings Gerrit-MessageType: newchange Gerrit-Change-Id: I6d3202ba59ebe111f0180956a839e46b57149506 Gerrit-PatchSet: 1 Gerrit-Project: ovirt-engine Gerrit-Branch: master Gerrit-Owner: Juan Hernandez <[email protected]> _______________________________________________ Engine-patches mailing list [email protected] http://lists.ovirt.org/mailman/listinfo/engine-patches
