All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.exist.xquery.modules.xslfo.ApacheFopProcessorAdapter Maven / Gradle / Ivy

There is a newer version: 6.3.0
Show newest version
/*
 * eXist-db Open Source Native XML Database
 * Copyright (C) 2001 The eXist-db Authors
 *
 * [email protected]
 * http://www.exist-db.org
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */
package org.exist.xquery.modules.xslfo;

import java.io.OutputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashSet;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Properties;
import java.util.Set;

import org.apache.avalon.framework.configuration.Configuration;
import org.apache.avalon.framework.configuration.SAXConfigurationHandler;
import org.apache.fop.apps.*;
import org.apache.fop.apps.io.ResourceResolverFactory;
import org.apache.fop.configuration.ConfigurationException;
import org.apache.fop.events.Event;
import org.apache.fop.events.EventFormatter;
import org.apache.fop.events.EventListener;
import org.apache.fop.events.model.EventSeverity;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
import org.apache.xmlgraphics.io.ResourceResolver;
import org.apache.xmlgraphics.io.URIResolverAdapter;
import org.exist.storage.DBBroker;
import org.exist.util.EXistURISchemeURIResolver;
import org.exist.xquery.value.NodeValue;
import org.exist.xslt.EXistURIResolver;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;

import javax.annotation.Nullable;


/**
 * @author Adam Retter
 */
public class ApacheFopProcessorAdapter implements ProcessorAdapter {

    private static final Logger LOG = LogManager.getLogger(ApacheFopProcessorAdapter.class);
    private static final String DEFAULT_BASE_URI = "exist://localhost/db/";

    @Override
    public ContentHandler getContentHandler(final DBBroker broker, final NodeValue processorConfig, final Properties parameters, final String mediaType, final OutputStream os) throws SAXException {

        // setup the FopFactory
        final FopFactoryBuilder builder;
        try {
            if (processorConfig != null) {
                final FopConfigurationBuilder cfgBuilder = new FopConfigurationBuilder(broker);
                final Configuration cfg = cfgBuilder.buildFromNode(processorConfig);
                final URI defaultBaseURI;
                if (processorConfig instanceof org.exist.dom.memtree.NodeImpl) {
                    //in-memory documents don't have a BaseURI
                    defaultBaseURI = new URI(DEFAULT_BASE_URI);
                } else {
                    defaultBaseURI = new URI("exist://localhost" + processorConfig.getOwnerDocument().getBaseURI());
                }
                final EnvironmentProfile environment = EnvironmentalProfileFactory.createDefault(defaultBaseURI, getResourceResolver(broker, defaultBaseURI.toString()));
                builder = new FopFactoryBuilder(environment).setConfiguration(new FopAvalonConfigurationAdapter(cfg));
            } else {
                final URI defaultBaseURI = new URI(DEFAULT_BASE_URI);
                final EnvironmentProfile environment = EnvironmentalProfileFactory.createDefault(defaultBaseURI, getResourceResolver(broker, defaultBaseURI.toString()));
                builder = new FopFactoryBuilder(environment);
            }

            final FopFactory fopFactory = builder.build();

            // setup the foUserAgent, using given parameters held in the
            // transformer handler
            final FOUserAgent foUserAgent = setupFOUserAgent(fopFactory.newFOUserAgent(), parameters);

            foUserAgent.getEventBroadcaster().addEventListener(new ExistLoggingEventListener(LOG));

            // create new instance of FOP using the mimetype, the created user
            // agent, and the output stream
            final Fop fop = fopFactory.newFop(mediaType, foUserAgent, os);
            // Obtain FOP's DefaultHandler
            return fop.getDefaultHandler();
        } catch (final URISyntaxException e) {
            throw new SAXException("Unable to parse baseURI", e);
        }
    }

    @Override
    public void cleanup() {
    }

    /**
     * Setup the UserAgent for FOP, from given parameters *
     *
     * @param foUserAgent The user agent to set parameters for
     * @param parameters  any user defined parameters to the XSL-FO process
     * @return FOUserAgent The generated FOUserAgent to include any parameters
     * passed in
     */
    private FOUserAgent setupFOUserAgent(final FOUserAgent foUserAgent, final Properties parameters) {

        // setup the foUserAgent as per the parameters given
        foUserAgent.setProducer("eXist-db with Apache FOP");

        if (parameters != null) {
            for (final Entry paramEntry : parameters.entrySet()) {
                final String key = (String) paramEntry.getKey();
                final String value = (String) paramEntry.getValue();

                switch (key) {
                    case "FOPauthor":
                        foUserAgent.setAuthor(value);
                        break;
                    case "FOPtitle":
                        foUserAgent.setTitle(value);
                        break;
                    case "FOPkeywords":
                        foUserAgent.setTitle(value);
                        break;
                    case "FOPdpi":
                        try {
                            foUserAgent.setTargetResolution(Integer.parseInt(value));
                        } catch (final NumberFormatException nfe) {
                            LOG.warn("Unable to set DPI to: {}", value);
                        }
                        break;
                }
            }
        }

        return foUserAgent;
    }

    /**
     * Returns a scheme aware ResourceResolver which supports:
     * file://
     * exist:// (which will be translated to xmldb:exist://)
     * http://
     * https://
     *
     * @return The resource resolver
     */
    private ResourceResolver getResourceResolver(final DBBroker broker, final String baseUri) {
        final ResourceResolverFactory.SchemeAwareResourceResolverBuilder builder = ResourceResolverFactory.createSchemeAwareResourceResolverBuilder(ResourceResolverFactory.createDefaultResourceResolver());
        final URIResolverAdapter uriResolver = new URIResolverAdapter(
                new EXistURISchemeURIResolver(new EXistURIResolver(broker.getBrokerPool(), baseUri))
        );
        builder.registerResourceResolverForScheme("exist", uriResolver);
        builder.registerResourceResolverForScheme("http", uriResolver);
        builder.registerResourceResolverForScheme("https", uriResolver);
        return builder.build();
    }

    /**
     * Extension of the Apache Avalon DefaultConfigurationBuilder Allows better
     * integration with Nodes passed in from eXist as Configuration files
     */
    private static class FopConfigurationBuilder
            extends org.apache.avalon.framework.configuration.DefaultConfigurationBuilder {

        private final DBBroker broker;

        public FopConfigurationBuilder(final DBBroker broker) {
            super();
            this.broker = broker;
        }

        @SuppressWarnings("unused")
        public FopConfigurationBuilder(final DBBroker broker, final boolean enableNamespaces) {
            super(enableNamespaces);
            this.broker = broker;
        }

        public Configuration buildFromNode(final NodeValue configFile) throws SAXException {
            final SAXConfigurationHandler handler = getHandler();
            handler.clear();
            configFile.toSAX(broker, handler, new Properties());
            return handler.getConfiguration();
        }
    }

    /**
     * Adapter between Avalon config which lets us easily use a Node as the configuration
     * source, and Apache Fop configuration which does not.
     * 

* This was needed after the Fop API changed from version 2.3 to 2.4. */ private static class FopAvalonConfigurationAdapter implements org.apache.fop.configuration.Configuration { private final Configuration avalonConfiguration; public FopAvalonConfigurationAdapter(final Configuration avalonConfiguration) { this.avalonConfiguration = Objects.requireNonNull(avalonConfiguration, "Avalon Configuration must not be null"); } @Override public org.apache.fop.configuration.Configuration getChild(final String child) { return new FopAvalonConfigurationAdapter(avalonConfiguration.getChild(child)); } @Override public @Nullable org.apache.fop.configuration.Configuration getChild(final String child, boolean createNew) { final Configuration childConfig = avalonConfiguration.getChild(child, createNew); if (childConfig == null) { return null; } return new FopAvalonConfigurationAdapter(childConfig); } @Override public @Nullable org.apache.fop.configuration.Configuration[] getChildren(final String name) { final Configuration[] children = avalonConfiguration.getChildren(name); if (children == null) { return null; } final org.apache.fop.configuration.Configuration[] newChildren = new org.apache.fop.configuration.Configuration[children.length]; for (int i = 0; i < children.length; i++) { newChildren[i] = new FopAvalonConfigurationAdapter(children[i]); } return newChildren; } @Override public String[] getAttributeNames() { return avalonConfiguration.getAttributeNames(); } @Override public String getAttribute(final String paramName) throws ConfigurationException { try { return avalonConfiguration.getAttribute(paramName); } catch (final org.apache.avalon.framework.configuration.ConfigurationException e) { throw new ConfigurationException(e.getMessage(), e); } } @Override public String getAttribute(final String name, final String defaultValue) { return avalonConfiguration.getAttribute(name, defaultValue); } @Override public boolean getAttributeAsBoolean(final String name, final boolean defaultValue) { return avalonConfiguration.getAttributeAsBoolean(name, defaultValue); } @Override public float getAttributeAsFloat(final String paramName) throws ConfigurationException { try { return avalonConfiguration.getAttributeAsFloat(paramName); } catch (final org.apache.avalon.framework.configuration.ConfigurationException e) { throw new ConfigurationException(e.getMessage(), e); } } @Override public float getAttributeAsFloat(final String name, final float defaultValue) { return avalonConfiguration.getAttributeAsFloat(name, defaultValue); } @Override public int getAttributeAsInteger(final String name, final int defaultValue) { return avalonConfiguration.getAttributeAsInteger(name, defaultValue); } @Override public String getValue() throws ConfigurationException { try { return avalonConfiguration.getValue(); } catch (final org.apache.avalon.framework.configuration.ConfigurationException e) { throw new ConfigurationException(e.getMessage(), e); } } @Override public String getValue(final String defaultValue) { return avalonConfiguration.getValue(defaultValue); } @Override public boolean getValueAsBoolean() throws ConfigurationException { try { return avalonConfiguration.getValueAsBoolean(); } catch (final org.apache.avalon.framework.configuration.ConfigurationException e) { throw new ConfigurationException(e.getMessage(), e); } } @Override public boolean getValueAsBoolean(final boolean defaultValue) { return avalonConfiguration.getValueAsBoolean(defaultValue); } @Override public int getValueAsInteger() throws ConfigurationException { try { return avalonConfiguration.getValueAsInteger(); } catch (final org.apache.avalon.framework.configuration.ConfigurationException e) { throw new ConfigurationException(e.getMessage(), e); } } @Override public int getValueAsInteger(final int defaultValue) { return avalonConfiguration.getValueAsInteger(defaultValue); } @Override public float getValueAsFloat() throws ConfigurationException { try { return avalonConfiguration.getValueAsFloat(); } catch (final org.apache.avalon.framework.configuration.ConfigurationException e) { throw new ConfigurationException(e.getMessage(), e); } } @Override public float getValueAsFloat(final float defaultValue) { return avalonConfiguration.getValueAsFloat(defaultValue); } @Override public String getLocation() { return avalonConfiguration.getLocation(); } } private static class ExistLoggingEventListener implements EventListener { private final Logger log; private final Set loggedMessages = new HashSet<>(); public ExistLoggingEventListener(final Logger log) { this.log = log; } @Override public void processEvent(final Event event) { final String msg = EventFormatter.format(event); final EventSeverity severity = event.getSeverity(); if (severity == EventSeverity.INFO) { log.info(msg); } else if (severity == EventSeverity.WARN) { // we want to prevent logging of duplicate messages in situations where they are likely // to occur; for instance, warning related to layout do not repeat (since line number // will be different) and as such we do not try to filter them here; on the other hand, // font related warnings are very likely to repeat and we try to filter them out here; // the same may happen with missing images (but not implemented yet). final String eventGroupID = event.getEventGroupID(); if (eventGroupID.equals("org.apache.fop.fonts.FontEventProducer")) { if (!loggedMessages.contains(msg)) { loggedMessages.add(msg); log.warn(msg); } } else { log.warn(msg); } } else if (severity == EventSeverity.ERROR) { if (event.getParam("e") != null) { log.error(msg, (Throwable) event.getParam("e")); } else { log.error(msg); } } else if (severity == EventSeverity.FATAL) { if (event.getParam("e") != null) { log.fatal(msg, (Throwable) event.getParam("e")); } else { log.fatal(msg); } } else { assert false; } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy