blob: 45fe2666b57edf445729611a33fdf4fd114f33fc [file] [log] [blame]
/*
* 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.xmlgraphics.image.loader.impl;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import javax.imageio.ImageIO;
import javax.imageio.stream.ImageInputStream;
import javax.xml.transform.Source;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamSource;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.xmlgraphics.image.loader.ImageSessionContext;
import org.apache.xmlgraphics.image.loader.ImageSource;
import org.apache.xmlgraphics.image.loader.util.ImageUtil;
import org.apache.xmlgraphics.image.loader.util.SoftMapCache;
/**
* Abstract base class for classes implementing ImageSessionContext. This class provides all the
* special treatment for Source creation, i.e. it provides optimized Source objects where possible.
*/
public abstract class AbstractImageSessionContext implements ImageSessionContext {
/** logger */
private static Log log = LogFactory.getLog(AbstractImageSessionContext.class);
/**
* Attempts to resolve the given URI.
* @param uri URI to access
* @return A {@link javax.xml.transform.Source} object, or null if the URI
* cannot be resolved.
*/
protected abstract Source resolveURI(String uri);
/** {@inheritDoc} */
public Source newSource(String uri) {
Source source = resolveURI(uri);
if (source == null) {
if (log.isDebugEnabled()) {
log.debug("URI could not be resolved: " + uri);
}
return null;
}
if (!(source instanceof StreamSource)) {
//Return any non-stream Sources and let the ImageLoaders deal with them
return source;
}
ImageSource imageSource = null;
String resolvedURI = source.getSystemId();
URL url;
try {
url = new URL(resolvedURI);
} catch (MalformedURLException e) {
url = null;
}
File f = FileUtils.toFile(url);
if (f != null) {
boolean directFileAccess = true;
InputStream in = null;
if (source instanceof StreamSource) {
StreamSource streamSource = (StreamSource)source;
in = streamSource.getInputStream();
if (in == null) {
try {
in = new java.io.FileInputStream(f);
} catch (FileNotFoundException fnfe) {
log.error("Error while opening file."
+ " Could not load image from system identifier '"
+ source.getSystemId() + "' (" + fnfe.getMessage() + ")");
return null;
}
}
in = ImageUtil.decorateMarkSupported(in);
try {
if (ImageUtil.isGZIPCompressed(in)) {
//GZIPped stream are not seekable, so buffer/cache like other URLs
directFileAccess = false;
}
} catch (IOException ioe) {
log.error("Error while checking the InputStream for GZIP compression."
+ " Could not load image from system identifier '"
+ source.getSystemId() + "' (" + ioe.getMessage() + ")");
return null;
}
}
if (directFileAccess) {
//Close as the file is reopened in a more optimal way
IOUtils.closeQuietly(in);
try {
//We let the OS' file system cache do the caching for us
//--> lower Java memory consumption, probably no speed loss
imageSource = new ImageSource(ImageIO.createImageInputStream(f),
resolvedURI, true);
} catch (IOException ioe) {
log.error("Unable to create ImageInputStream for local file"
+ " from system identifier '"
+ source.getSystemId() + "' (" + ioe.getMessage() + ")");
}
}
}
if (imageSource == null) {
// Got a valid source, obtain an InputStream from it
InputStream in = null;
if (source instanceof StreamSource) {
StreamSource ssrc = (StreamSource)source;
if (ssrc.getReader() != null && ssrc.getInputStream() == null) {
//We don't handle Reader instances here so return the Source unchanged
return ssrc;
}
in = ssrc.getInputStream();
}
if (in == null && url != null) {
try {
in = url.openStream();
} catch (Exception ex) {
log.error("Unable to obtain stream from system identifier '"
+ source.getSystemId() + "'");
}
}
if (in == null) {
log.error("The Source that was returned from URI resolution didn't contain"
+ " an InputStream for URI: " + uri);
return null;
}
try {
//Buffer and uncompress if necessary
in = ImageUtil.autoDecorateInputStream(in);
imageSource = new ImageSource(
ImageIO.createImageInputStream(in), source.getSystemId(), false);
} catch (IOException ioe) {
log.error("Unable to create ImageInputStream for InputStream"
+ " from system identifier '"
+ source.getSystemId() + "' (" + ioe.getMessage() + ")");
}
}
return imageSource;
}
private SoftMapCache sessionSources = new SoftMapCache(false); //no need for synchronization
/** {@inheritDoc} */
public Source getSource(String uri) {
return (Source)sessionSources.remove(uri);
}
/** {@inheritDoc} */
public Source needSource(String uri) throws FileNotFoundException {
Source src = getSource(uri);
if (src == null) {
if (log.isDebugEnabled()) {
log.debug("Creating new Source for " + uri);
}
src = newSource(uri);
if (src == null) {
throw new FileNotFoundException("Image not found: " + uri);
}
} else {
if (log.isDebugEnabled()) {
log.debug("Reusing Source for " + uri);
}
}
return src;
}
/** {@inheritDoc} */
public void returnSource(String uri, Source src) {
//Safety check to make sure the Preloaders behave
ImageInputStream in = ImageUtil.getImageInputStream(src);
try {
if (in != null && in.getStreamPosition() != 0) {
throw new IllegalStateException("ImageInputStream is not reset for: " + uri);
}
} catch (IOException ioe) {
//Ignore exception
ImageUtil.closeQuietly(src);
}
if (isReusable(src)) {
//Only return the Source if it's reusable
log.debug("Returning Source for " + uri);
sessionSources.put(uri, src);
} else {
//Otherwise, try to close if possible and forget about it
ImageUtil.closeQuietly(src);
}
}
/**
* Indicates whether a Source is reusable. A Source object is reusable if it's an
* {@link ImageSource} (containing an {@link ImageInputStream}) or a {@link DOMSource}.
* @param src the Source object
* @return true if the Source is reusable
*/
protected boolean isReusable(Source src) {
if (src instanceof ImageSource) {
ImageSource is = (ImageSource)src;
if (is.getImageInputStream() != null) {
return true;
}
}
if (src instanceof DOMSource) {
return true;
}
return false;
}
}