1

我们使用 Apache FOP 及其 XMLGraphics 库,根据客户设计的模板为他们生成文档。他们的模板通常包含从基于 Web 的资源中获取的图像。然而,很明显,在某些情况下,当一些较旧的模板是使用http协议使用外部图像设计的并且资源现在位于https协议后面时,我们会遇到困难。这是因为 xmlgraphicsDefaultImageSessionContext在尝试从 Web 服务预加载图像时不支持重定向。这是因为它只是执行一个URL.openStream()方法。

我想做的是创建自己的实现来替换默认的实现。这应该是可能的DefaultImageSessionContextextends a public abstract class AbstractImageSessionContext,而后者又实现了ImageSessionContext.

有谁知道我如何向 FOP 注册我的实现?

4

1 回答 1

1

这是我原来的答案:


对不起,如果我给你一些错误的信息,很长时间没有使用 FOP。

Apache XML 图形图像加载框架文档中所述,为了预加载图像,可能在您的代码的某些部分中,您有如下内容:

ImageSessionContext sessionContext = new DefaultImageSessionContext(
  getImageManager().getImageContext(), null);

ImageInfo info = getImageManager().getImageInfo(url.toString(), sessionContext);

如果您想支持重定向,正如您建议的那样,您可以提供您的自定义实现ImageSessionContext.

查看 的源代码,可能最好的方法是创建一个覆盖该方法DefaultImageSessionContext的新类。resolveURI请考虑以下基于 的代码,HttpURLConnection它应该根据需要遵循重定向:

public class HttpRedirectsAwareImageSessionContext extends DefaultImageSessionContext {

  @Override
  protected Source resolveURI(String uri) {
    HttpURLConnection urlConnection = null;

    try {
      URL url = new URL(uri);

      urlConnection = (HttpURLConnection) url.openConnection();
 
      // Just read the InputStream, to a byte[] for instance. As in the example
      // you can use the convenient methods of Apache Commons IOUtils for the task
      // as well. See: https://commons.apache.org/proper/commons-io/apidocs/org/apache/commons/io/IOUtils.html#toBufferedInputStream-java.io.InputStream-
      InputStream in = IOUtils.toBufferedInputStream(urlConnection.getInputStream());

      return new StreamSource(in, url.toExternalForm());
    } catch (MalformedURLException e) {
      // Legacy DefaultImageSessionContext code
      File f = new File(baseDir, uri);
      if (f.isFile()) {
        return new StreamSource(f);
      } else {
        return null;
      }
    } catch (IOException ioe) {
      return null;
    } finally {
      if (urlConnection != null) {
        urlConnection.disconnect();
      }
    }
  }
}

然后,改用新创建的ImageSessionContext

ImageSessionContext sessionContext = new HttpRedirectsAwareImageSessionContext(
  getImageManager().getImageContext(), null);

ImageInfo info = getImageManager().getImageInfo(url.toString(), sessionContext);

但恐怕我没有完全理解您的要求。

例如,如您所见,在FOPUserAgent源代码中,FOP 提供了自己的底层ImageSessionContext实现:

   private final ImageSessionContext imageSessionContext;

   /**
     * Main constructor. <b>This constructor should not be called directly. Please use the
     * methods from FopFactory to construct FOUserAgent instances!</b>
     * @param factory the factory that provides environment-level information
     * @param resourceResolver the resolver used to acquire resources
     * @see org.apache.fop.apps.FopFactory
     */
    FOUserAgent(final FopFactory factory, InternalResourceResolver resourceResolver) {
        this.factory = factory;
        this.resourceResolver = resourceResolver;
        setTargetResolution(factory.getTargetResolution());
        setAccessibility(factory.isAccessibilityEnabled());
        setKeepEmptyTags(factory.isKeepEmptyTags());
        imageSessionContext = new AbstractImageSessionContext(factory.getFallbackResolver()) {

            public ImageContext getParentContext() {
                return factory;
            }

            public float getTargetResolution() {
                return FOUserAgent.this.getTargetResolution();
            }

            public Source resolveURI(String uri) {
                return FOUserAgent.this.resolveURI(uri);
            }
        };
    }


    /**
     * Attempts to resolve the given URI.
     * Will use the configured resolver and if not successful fall back
     * to the default resolver.
     * @param uri URI to access
     * @return A {@link javax.xml.transform.Source} object, or null if the URI
     * cannot be resolved.
     */
    public StreamSource resolveURI(String uri) {
        // TODO: What do we want to do when resources aren't found??? We also need to remove this
        // method entirely
        try {
            // Have to do this so we can resolve data URIs
            StreamSource src = new StreamSource(resourceResolver.getResource(uri));
            src.setSystemId(getResourceResolver().getBaseURI().toASCIIString());
            return src;
        } catch (URISyntaxException use) {
            return null;
        } catch (IOException ioe) {
            return null;
        }
    }

此实现依赖于类InternalResourceResolver,而后者又依赖于ResourceResolverXML 图形图像加载框架提供的抽象。

为了解决您的问题,您可以ResourceResolver在初始化 FOP 时通过FopFactoryBuilder.

尽管已经过时,但 Apache FOP 文档在此处本篇以及主要是其他文章中提供了一些有用的介绍。例如,根据上一个链接中提供的信息:

ResourceResolver resolver = new ResourceResolver() {
  public OutputStream getOutputStream(URI uri) throws IOException {
    URL url = uri.toURL();
    return url.openConnection().getOutputStream();
  }

  public Resource getResource(URI uri) throws IOException {
    // Based on the above-mentioned code
    HttpURLConnection urlConnection = null;

    try {
      URL url = uri.toURL();

      // Please, see the companion comment and SO answer if you need
      // http to https redirect
      // https://stackoverflow.com/questions/1884230/httpurlconnection-doesnt-follow-redirect-from-http-to-https/26046079#26046079
      urlConnection = (HttpURLConnection) url.openConnection();
 
      // Just read the InputStream, to a byte[] for instance. As in the example
      // you can use the convenient methods of Apache Commons IOUtils for the task
      // as well. See: https://commons.apache.org/proper/commons-io/apidocs/org/apache/commons/io/IOUtils.html#toBufferedInputStream-java.io.InputStream-
      InputStream in = IOUtils.toBufferedInputStream(urlConnection.getInputStream());

      return new StreamSource(in, url.toExternalForm());
    } catch (MalformedURLException e) {
      // Legacy DefaultImageSessionContext code
      File f = new File(baseDir, uri);
      if (f.isFile()) {
        return new StreamSource(f);
      } else {
        return null;
      }
    } catch (IOException ioe) {
      return null;
    } finally {
      if (urlConnection != null) {
        urlConnection.disconnect();
      }
    }
};

//Setting up the FOP factory
FopFactoryBuilder builder = new FopFactoryBuilder(new File(".").toURI(), resolver);
fopFactory = builder.build();

请注意,这是一个非常简单的示例,实际ResourceResolver由 FOP 构建的ResourceResolverFactory非常复杂,但也许可以为您提供一些关于如何实现自己的想法:

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.
 */

/* $Id$ */

package org.apache.fop.apps.io;

import java.io.File;
import java.io.FileOutputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URI;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.xmlgraphics.io.Resource;
import org.apache.xmlgraphics.io.ResourceResolver;
import org.apache.xmlgraphics.io.TempResourceResolver;
import org.apache.xmlgraphics.io.TempResourceURIGenerator;

/**
 * A factory class for {@link ResourceResolver}s.
 */
public final class ResourceResolverFactory {

    private ResourceResolverFactory() {
    }

    /**
     * Returns the default resource resolver, this is most basic resolver which can be used when
     * no there are no I/O or file access restrictions.
     *
     * @return the default resource resolver
     */
    public static ResourceResolver createDefaultResourceResolver() {
        return DefaultResourceResolver.INSTANCE;
    }

    /**
     * A helper merthod that creates an internal resource resolver using the default resover:
     * {@link ResourceResolverFactory#createDefaultResourceResolver()}.
     *
     * @param baseURI the base URI from which to resolve URIs
     * @return the default internal resource resolver
     */
    public static InternalResourceResolver createDefaultInternalResourceResolver(URI baseURI) {
        return new InternalResourceResolver(baseURI, createDefaultResourceResolver());
    }

    /**
     * Creates an interal resource resolver given a base URI and a resource resolver.
     *
     * @param baseURI the base URI from which to resolve URIs
     * @param resolver the resource resolver
     * @return the internal resource resolver
     */
    public static InternalResourceResolver createInternalResourceResolver(URI baseURI,
            ResourceResolver resolver) {
        return new InternalResourceResolver(baseURI, resolver);
    }

    /**
     * Creates a temporary-resource-scheme aware resource resolver. Temporary resource URIs are
     * created by {@link TempResourceURIGenerator}.
     *
     * @param tempResourceResolver the temporary-resource-scheme resolver to use
     * @param defaultResourceResolver the default resource resolver to use
     * @return the ressource resolver
     */
    public static ResourceResolver createTempAwareResourceResolver(
            TempResourceResolver tempResourceResolver,
            ResourceResolver defaultResourceResolver) {
        return new TempAwareResourceResolver(tempResourceResolver, defaultResourceResolver);
    }

    /**
     * This creates the builder class for binding URI schemes to implementations of
     * {@link ResourceResolver}. This allows users to define their own URI schemes such that they
     * have finer control over the acquisition of resources.
     *
     * @param defaultResolver the default resource resolver that should be used in the event that
     * none of the other registered resolvers match the scheme
     * @return the scheme aware {@link ResourceResolver} builder
     */
    public static SchemeAwareResourceResolverBuilder createSchemeAwareResourceResolverBuilder(
            ResourceResolver defaultResolver) {
        return new SchemeAwareResourceResolverBuilderImpl(defaultResolver);
    }

    private static final class DefaultResourceResolver implements ResourceResolver {

        private static final ResourceResolver INSTANCE = new DefaultResourceResolver();

        private final TempAwareResourceResolver delegate;

        private DefaultResourceResolver() {
            delegate = new TempAwareResourceResolver(new DefaultTempResourceResolver(),
                    new NormalResourceResolver());
        }

        /** {@inheritDoc} */
        public Resource getResource(URI uri) throws IOException {
            return delegate.getResource(uri);
        }

        /** {@inheritDoc} */
        public OutputStream getOutputStream(URI uri) throws IOException {
            return delegate.getOutputStream(uri);
        }

    }

    private static final class TempAwareResourceResolver implements ResourceResolver {

        private final TempResourceResolver tempResourceResolver;

        private final ResourceResolver defaultResourceResolver;

        public TempAwareResourceResolver(TempResourceResolver tempResourceHandler,
                ResourceResolver defaultResourceResolver) {
            this.tempResourceResolver = tempResourceHandler;
            this.defaultResourceResolver = defaultResourceResolver;
        }

        private static boolean isTempURI(URI uri) {
            return TempResourceURIGenerator.isTempURI(uri);
        }

        /** {@inheritDoc} */
        public Resource getResource(URI uri) throws IOException {
            if (isTempURI(uri)) {
                return tempResourceResolver.getResource(uri.getPath());
            } else {
                return defaultResourceResolver.getResource(uri);
            }
        }

        /** {@inheritDoc} */
        public OutputStream getOutputStream(URI uri) throws IOException {
            if (isTempURI(uri)) {
                return tempResourceResolver.getOutputStream(uri.getPath());
            } else {
                return defaultResourceResolver.getOutputStream(uri);
            }
        }
    }

    private static class DefaultTempResourceResolver implements TempResourceResolver {

        private final ConcurrentHashMap<String, File> tempFiles = new ConcurrentHashMap<String, File>();

        private File getTempFile(String uri) throws IllegalStateException {
            File tempFile = tempFiles.remove(uri);
            if (tempFile == null) {
                throw new IllegalStateException(uri + " was never created or has been deleted");
            }
            return tempFile;
        }

        private File createTempFile(String path) throws IOException {
            File tempFile = File.createTempFile(path, ".fop.tmp");
            File oldFile = tempFiles.put(path, tempFile);
            if (oldFile != null) {
                String errorMsg = oldFile.getAbsolutePath() + " has been already created for " + path;
                boolean newTempDeleted = tempFile.delete();
                if (!newTempDeleted) {
                    errorMsg += ". " + tempFile.getAbsolutePath() + " was not deleted.";
                }
                throw new IOException(errorMsg);
            }
            return tempFile;
        }

        /** {@inheritDoc} */
        public Resource getResource(String id) throws IOException {
            return new Resource(new FileDeletingInputStream(getTempFile(id)));
        }

        /** {@inheritDoc} */
        public OutputStream getOutputStream(String id) throws IOException {
            return new FileOutputStream(createTempFile(id));
        }
    }

    private static class FileDeletingInputStream extends FilterInputStream {

        private final File file;

        protected FileDeletingInputStream(File file) throws MalformedURLException, IOException {
            super(file.toURI().toURL().openStream());
            this.file = file;
        }

        @Override
        public void close() throws IOException {
            try {
                super.close();
            } finally {
                file.delete();
            }
        }
    }

    private static class NormalResourceResolver implements ResourceResolver {
        public Resource getResource(URI uri) throws IOException {
            return new Resource(uri.toURL().openStream());
        }

        public OutputStream getOutputStream(URI uri) throws IOException {
            return new FileOutputStream(new File(uri));
        }
    }

    private static final class SchemeAwareResourceResolver implements ResourceResolver {

        private final Map<String, ResourceResolver> schemeHandlingResourceResolvers;

        private final ResourceResolver defaultResolver;

        private SchemeAwareResourceResolver(
                Map<String, ResourceResolver> schemEHandlingResourceResolvers,
                ResourceResolver defaultResolver) {
            this.schemeHandlingResourceResolvers = schemEHandlingResourceResolvers;
            this.defaultResolver = defaultResolver;
        }

        private ResourceResolver getResourceResolverForScheme(URI uri) {
            String scheme = uri.getScheme();
            if (schemeHandlingResourceResolvers.containsKey(scheme)) {
                return schemeHandlingResourceResolvers.get(scheme);
            } else {
                return defaultResolver;
            }
        }

        /** {@inheritDoc} */
        public Resource getResource(URI uri) throws IOException {
            return getResourceResolverForScheme(uri).getResource(uri);
        }

        /** {@inheritDoc} */
        public OutputStream getOutputStream(URI uri) throws IOException {
            return getResourceResolverForScheme(uri).getOutputStream(uri);
        }
    }

    /**
     * Implementations of this interface will be builders for {@link ResourceResolver}, they bind
     * URI schemes to their respective resolver. This gives users more control over the mechanisms
     * by which URIs are resolved.
     * <p>
     * Here is an example of how this could be used:
     * </p>
     * <p><code>
     * SchemeAwareResourceResolverBuilder builder
     *      = ResourceResolverFactory.createSchemeAwareResourceResolverBuilder(defaultResolver);
     * builder.registerResourceResolverForScheme("test", testResolver);
     * builder.registerResourceResolverForScheme("anotherTest", test2Resolver);
     * ResourceResolver resolver = builder.build();
     * </code></p>
     * This will result in all URIs for the form "test:///..." will be resolved using the
     * <code>testResolver</code> object; URIs of the form "anotherTest:///..." will be resolved
     * using <code>test2Resolver</code>; all other URIs will be resolved from the defaultResolver.
     */
    public interface SchemeAwareResourceResolverBuilder {

        /**
         * Register a scheme with its respective {@link ResourceResolver}. This resolver will be
         * used as the only resolver for the specified scheme.
         *
         * @param scheme the scheme to be used with the given resolver
         * @param resourceResolver the resource resolver
         */
        void registerResourceResolverForScheme(String scheme, ResourceResolver resourceResolver);

        /**
         * Builds a {@link ResourceResolver} that will delegate to the respective resource resolver
         * when a registered URI scheme is given
         *
         * @return a resolver that delegates to the appropriate scheme resolver
         */
        ResourceResolver build();
    }

    private static final class CompletedSchemeAwareResourceResolverBuilder
            implements SchemeAwareResourceResolverBuilder {

        private static final SchemeAwareResourceResolverBuilder INSTANCE
                = new CompletedSchemeAwareResourceResolverBuilder();

        /** {@inheritDoc} */
        public ResourceResolver build() {
            throw new IllegalStateException("Resource resolver already built");
        }

        /** {@inheritDoc} */
        public void registerResourceResolverForScheme(String scheme,
                ResourceResolver resourceResolver) {
            throw new IllegalStateException("Resource resolver already built");
        }
    }

    private static final class ActiveSchemeAwareResourceResolverBuilder
            implements SchemeAwareResourceResolverBuilder {

        private final Map<String, ResourceResolver> schemeHandlingResourceResolvers
                = new HashMap<String, ResourceResolver>();

        private final ResourceResolver defaultResolver;

        private ActiveSchemeAwareResourceResolverBuilder(ResourceResolver defaultResolver) {
            this.defaultResolver = defaultResolver;
        }

        /** {@inheritDoc} */
        public void registerResourceResolverForScheme(String scheme,
                ResourceResolver resourceResolver) {
            schemeHandlingResourceResolvers.put(scheme, resourceResolver);
        }

        /** {@inheritDoc} */
        public ResourceResolver build() {
            return new SchemeAwareResourceResolver(
                    Collections.unmodifiableMap(schemeHandlingResourceResolvers), defaultResolver);
        }

    }

    private static final class SchemeAwareResourceResolverBuilderImpl
            implements SchemeAwareResourceResolverBuilder {

        private SchemeAwareResourceResolverBuilder delegate;

        private SchemeAwareResourceResolverBuilderImpl(ResourceResolver defaultResolver) {
            this.delegate = new ActiveSchemeAwareResourceResolverBuilder(defaultResolver);
        }

        /** {@inheritDoc} */
        public void registerResourceResolverForScheme(String scheme,
                ResourceResolver resourceResolver) {
            delegate.registerResourceResolverForScheme(scheme, resourceResolver);
        }

        /** {@inheritDoc} */
        public ResourceResolver build() {
            ResourceResolver resourceResolver = delegate.build();
            delegate = CompletedSchemeAwareResourceResolverBuilder.INSTANCE;
            return resourceResolver;
        }
    }
}

注意NormalResourceResolvergetResource方法的实现。

提到的所有源代码都与可从此处下载的 FOP 2.6 版有关。

于 2021-02-26T10:06:31.087 回答