Java tutorial
/* * 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: ImageProviderPipeline.java 924666 2010-03-18 08:26:30Z jeremias $ */ package org.apache.xmlgraphics.image.loader.pipeline; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import org.apache.commons.io.IOUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.xmlgraphics.image.loader.Image; import org.apache.xmlgraphics.image.loader.ImageException; import org.apache.xmlgraphics.image.loader.ImageFlavor; import org.apache.xmlgraphics.image.loader.ImageInfo; import org.apache.xmlgraphics.image.loader.ImageSessionContext; import org.apache.xmlgraphics.image.loader.cache.ImageCache; import org.apache.xmlgraphics.image.loader.impl.ImageRawStream; import org.apache.xmlgraphics.image.loader.spi.ImageConverter; import org.apache.xmlgraphics.image.loader.spi.ImageImplRegistry; import org.apache.xmlgraphics.image.loader.spi.ImageLoader; import org.apache.xmlgraphics.image.loader.util.Penalty; /** * Represents a pipeline of ImageConverters with an ImageLoader at the beginning of the * pipeline. */ public class ImageProviderPipeline { /** logger */ protected static Log log = LogFactory.getLog(ImageProviderPipeline.class); private ImageCache cache; private ImageLoader loader; private List converters = new java.util.ArrayList(); /** * Main constructor. * @param cache the image cache (may be null if no caching is desired) * @param loader the image loader to drive the pipeline with */ public ImageProviderPipeline(ImageCache cache, ImageLoader loader) { this.cache = cache; setImageLoader(loader); } /** * Constructor for operation without caching. * @param loader the image loader to drive the pipeline with */ public ImageProviderPipeline(ImageLoader loader) { this(null, loader); } /** * Default constructor without caching and without an ImageLoader (or the ImageLoader may * be set later). */ public ImageProviderPipeline() { this(null, null); } /** * Executes the image converter pipeline. First, the image indicated by the ImageInfo instance * is loaded through an ImageLoader and then optionally converted by a series of * ImageConverters. At the end of the pipeline, the fully loaded and converted image is * returned. * @param info the image info object indicating the image to load * @param hints a Map of image conversion hints * @param context the session context * @return the requested image * @throws ImageException if an error occurs while loader or converting the image * @throws IOException if an I/O error occurs */ public Image execute(ImageInfo info, Map hints, ImageSessionContext context) throws ImageException, IOException { return execute(info, null, hints, context); } /** * Executes the image converter pipeline. First, the image indicated by the ImageInfo instance * is loaded through an ImageLoader and then optionally converted by a series of * ImageConverters. At the end of the pipeline, the fully loaded and converted image is * returned. * @param info the image info object indicating the image to load * @param originalImage the original image to start the pipeline off or null if an ImageLoader * is used * @param hints a Map of image conversion hints * @param context the session context * @return the requested image * @throws ImageException if an error occurs while loader or converting the image * @throws IOException if an I/O error occurs */ public Image execute(ImageInfo info, Image originalImage, Map hints, ImageSessionContext context) throws ImageException, IOException { if (hints == null) { hints = Collections.EMPTY_MAP; } long start, duration; start = System.currentTimeMillis(); Image img = null; //Remember the last image in the pipeline that is cacheable and cache that. Image lastCacheableImage = null; int converterCount = converters.size(); int startingPoint = 0; if (cache != null) { for (int i = converterCount - 1; i >= 0; i--) { ImageConverter converter = getConverter(i); ImageFlavor flavor = converter.getTargetFlavor(); img = cache.getImage(info, flavor); if (img != null) { startingPoint = i + 1; break; } } if (img == null && loader != null) { //try target flavor of loader from cache ImageFlavor flavor = loader.getTargetFlavor(); img = cache.getImage(info, flavor); } } if (img == null && originalImage != null) { img = originalImage; } boolean entirelyInCache = true; if (img == null && loader != null) { //Load image img = loader.loadImage(info, hints, context); if (log.isTraceEnabled()) { duration = System.currentTimeMillis() - start; log.trace("Image loading using " + loader + " took " + duration + " ms."); } //Caching entirelyInCache = false; if (img.isCacheable()) { lastCacheableImage = img; } } if (img == null) { throw new ImageException("Pipeline fails. No ImageLoader and no original Image available."); } if (converterCount > 0) { for (int i = startingPoint; i < converterCount; i++) { ImageConverter converter = getConverter(i); start = System.currentTimeMillis(); img = converter.convert(img, hints); if (log.isTraceEnabled()) { duration = System.currentTimeMillis() - start; log.trace("Image conversion using " + converter + " took " + duration + " ms."); } //Caching entirelyInCache = false; if (img.isCacheable()) { lastCacheableImage = img; } } } //Note: Currently we just cache the end result of the pipeline, not all intermediate //results as it is expected that the cache hit ration would be rather small. if (cache != null && !entirelyInCache) { if (lastCacheableImage == null) { //Try to make the Image cacheable lastCacheableImage = forceCaching(img); } if (lastCacheableImage != null) { if (log.isTraceEnabled()) { log.trace("Caching image: " + lastCacheableImage); } cache.putImage(lastCacheableImage); } } return img; } private ImageConverter getConverter(int index) { return (ImageConverter) converters.get(index); } /** * In some cases the provided Image is not cacheable, nor is any of the intermediate Image * instances (for example, when loading a raw JPEG file). If the image is loaded over a * potentially slow network, it is preferrable to download the whole file and cache it * in memory or in a temporary file. It's not always possible to convert an Image into a * cacheable variant. * @param img the Image to investigate * @return the converted, cacheable Image or null if the Image cannot be converted * @throws IOException if an I/O error occurs */ protected Image forceCaching(Image img) throws IOException { if (img instanceof ImageRawStream) { ImageRawStream raw = (ImageRawStream) img; if (log.isDebugEnabled()) { log.debug("Image is made cacheable: " + img.getInfo()); } //Read the whole stream and hold it in memory so the image can be cached ByteArrayOutputStream baout = new ByteArrayOutputStream(); InputStream in = raw.createInputStream(); try { IOUtils.copy(in, baout); } finally { IOUtils.closeQuietly(in); } final byte[] data = baout.toByteArray(); raw.setInputStreamFactory(new ImageRawStream.ByteArrayStreamFactory(data)); return raw; } return null; } /** * Sets the ImageLoader that is used at the beginning of the pipeline if the image is not * loaded, yet. * @param imageLoader the image loader implementation */ public void setImageLoader(ImageLoader imageLoader) { this.loader = imageLoader; } /** * Adds an additional ImageConverter to the end of the pipeline. * @param converter the ImageConverter instance */ public void addConverter(ImageConverter converter) { //TODO check for compatibility this.converters.add(converter); } /** {@inheritDoc} */ public String toString() { StringBuffer sb = new StringBuffer(); sb.append("Loader: ").append(loader); if (converters.size() > 0) { sb.append(" Converters: "); sb.append(converters); } return sb.toString(); } /** * Returns the overall conversion penalty for the pipeline. This can be used to choose among * different possible pipelines. * @return the overall penalty (a non-negative integer) */ public int getConversionPenalty() { return getConversionPenalty(null).getValue(); } /** * Returns the overall conversion penalty for the pipeline. This can be used to choose among * different possible pipelines. * @param registry the image implementation registry * @return the overall penalty (a non-negative integer) */ public Penalty getConversionPenalty(ImageImplRegistry registry) { Penalty penalty = Penalty.ZERO_PENALTY; if (loader != null) { penalty = penalty.add(loader.getUsagePenalty()); if (registry != null) { penalty = penalty.add(registry.getAdditionalPenalty(loader.getClass().getName())); } } Iterator iter = converters.iterator(); while (iter.hasNext()) { ImageConverter converter = (ImageConverter) iter.next(); penalty = penalty.add(converter.getConversionPenalty()); if (registry != null) { penalty = penalty.add(registry.getAdditionalPenalty(converter.getClass().getName())); } } return penalty; } /** * Returns the target flavor generated by this pipeline. * @return the target flavor */ public ImageFlavor getTargetFlavor() { if (converters.size() > 0) { return getConverter(converters.size() - 1).getTargetFlavor(); } else if (this.loader != null) { return this.loader.getTargetFlavor(); } else { return null; } } }