/*************************************************************************
*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* Copyright 2008 by Sun Microsystems, Inc.
*
* OpenOffice.org - a multi-platform office productivity suite
*
* $RCSfile: ImageProducer.java,v $
* $Revision: 1.1.1.1 $
*
* This file is part of OpenOffice.org.
*
* OpenOffice.org is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License version 3
* only, as published by the Free Software Foundation.
*
* OpenOffice.org 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 version 3 for more details
* (a copy is included in the LICENSE file that accompanied this code).
*
* You should have received a copy of the GNU Lesser General Public License
* version 3 along with OpenOffice.org. If not, see
* <http://www.openoffice.org/license.html>
* for a copy of the LGPLv3 License.
*
************************************************************************/
package com.sun.star.report.pentaho.output;
import java.awt.Image;
import java.awt.Dimension;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.sql.Blob;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Arrays;
import com.sun.star.report.InputRepository;
import com.sun.star.report.OutputRepository;
import com.sun.star.report.ImageService;
import com.sun.star.report.ReportExecutionException;
import com.sun.star.report.pentaho.DefaultNameGenerator;
import com.keypoint.PngEncoder;
import java.util.Map;
import org.jfree.io.IOUtils;
import org.jfree.layouting.input.style.values.CSSNumericType;
import org.jfree.layouting.input.style.values.CSSNumericValue;
import org.jfree.util.Log;
import org.jfree.util.WaitingImageObserver;
/**
* This class manages the images embedded in a report.
*
* @author Thomas Morgner
* @since 31.03.2007
*/
public class ImageProducer
{
public static class OfficeImage
{
private final CSSNumericValue width;
private final CSSNumericValue height;
private final String embeddableLink;
public OfficeImage(final String embeddableLink, final CSSNumericValue width, final CSSNumericValue height)
{
this.embeddableLink = embeddableLink;
this.width = width;
this.height = height;
}
public CSSNumericValue getWidth()
{
return width;
}
public CSSNumericValue getHeight()
{
return height;
}
public String getEmbeddableLink()
{
return embeddableLink;
}
}
private static class ByteDataImageKey
{
private final byte[] keyData;
private Integer hashCode;
protected ByteDataImageKey(final byte[] keyData)
{
if (keyData == null)
{
throw new NullPointerException();
}
this.keyData = keyData;
}
public boolean equals(final Object o)
{
if (this != o)
{
if (o == null || getClass() != o.getClass())
{
return false;
}
final ByteDataImageKey key = (ByteDataImageKey) o;
if (!Arrays.equals(keyData, key.keyData))
{
return false;
}
}
return true;
}
public int hashCode()
{
if (hashCode != null)
{
return hashCode.intValue();
}
final int length = Math.min(keyData.length, 512);
int hashValue = 0;
for (int i = 0; i < length; i++)
{
final byte b = keyData[i];
hashValue = b + hashValue * 23;
}
this.hashCode = new Integer(hashValue);
return hashValue;
}
}
private final Map imageCache;
private final InputRepository inputRepository;
private final OutputRepository outputRepository;
private final ImageService imageService;
private final DefaultNameGenerator nameGenerator;
public ImageProducer(final InputRepository inputRepository,
final OutputRepository outputRepository,
final ImageService imageService)
{
if (inputRepository == null)
{
throw new NullPointerException();
}
if (outputRepository == null)
{
throw new NullPointerException();
}
if (imageService == null)
{
throw new NullPointerException();
}
this.inputRepository = inputRepository;
this.outputRepository = outputRepository;
this.imageService = imageService;
this.imageCache = new HashMap();
this.nameGenerator = new DefaultNameGenerator(outputRepository);
}
/**
* Image-Data can be one of the following types: String, URL, URI, byte-array, blob.
*
* @param imageData
* @param preserveIRI
* @return
*/
public OfficeImage produceImage(final Object imageData,
final boolean preserveIRI)
{
Log.debug("Want to produce image " + imageData);
if (imageData instanceof String)
{
return produceFromString((String) imageData, preserveIRI);
}
if (imageData instanceof URL)
{
return produceFromURL((URL) imageData, preserveIRI);
}
if (imageData instanceof Blob)
{
return produceFromBlob((Blob) imageData);
}
if (imageData instanceof byte[])
{
return produceFromByteArray((byte[]) imageData);
}
if (imageData instanceof Image)
{
return produceFromImage((Image) imageData);
}
// not usable ..
return null;
}
private OfficeImage produceFromImage(final Image image)
{
// quick caching ... use a weak list ...
final WaitingImageObserver obs = new WaitingImageObserver(image);
obs.waitImageLoaded();
final PngEncoder encoder = new PngEncoder(image, PngEncoder.ENCODE_ALPHA, PngEncoder.FILTER_NONE, 5);
final byte[] data = encoder.pngEncode();
return produceFromByteArray(data);
}
private OfficeImage produceFromBlob(final Blob blob)
{
try
{
final InputStream inputStream = blob.getBinaryStream();
final int length = (int) blob.length();
final ByteArrayOutputStream bout = new ByteArrayOutputStream(length);
try
{
IOUtils.getInstance().copyStreams(inputStream, bout);
} finally
{
inputStream.close();
}
return produceFromByteArray(bout.toByteArray());
}
catch (IOException e)
{
Log.warn("Failed to produce image from Blob", e);
}
catch (SQLException e)
{
Log.warn("Failed to produce image from Blob", e);
}
return null;
}
private OfficeImage produceFromByteArray(final byte[] data)
{
final ByteDataImageKey imageKey = new ByteDataImageKey(data);
final OfficeImage o = (OfficeImage) imageCache.get(imageKey);
if (o != null)
{
return o;
}
try
{
final String mimeType = imageService.getMimeType(data);
final Dimension dims = imageService.getImageSize(data);
// copy the image into the local output-storage
// todo: Implement data-fingerprinting so that we can detect the mime-type
final String name = nameGenerator.generateName("Pictures/image", mimeType);
final OutputStream outputStream = outputRepository.createOutputStream(name, mimeType);
final ByteArrayInputStream bin = new ByteArrayInputStream(data);
try
{
IOUtils.getInstance().copyStreams(bin, outputStream);
} finally
{
outputStream.close();
}
final CSSNumericValue widthVal = CSSNumericValue.createValue(CSSNumericType.MM, dims.getWidth() / 100.0);
final CSSNumericValue heightVal = CSSNumericValue.createValue(CSSNumericType.MM, dims.getHeight() / 100.0);
final OfficeImage officeImage = new OfficeImage(name, widthVal, heightVal);
imageCache.put(imageKey, officeImage);
return officeImage;
}
catch (IOException e)
{
Log.warn("Failed to load image from local input-repository", e);
}
catch (ReportExecutionException e)
{
Log.warn("Failed to create image from local input-repository", e);
}
return null;
}
private OfficeImage produceFromString(final String source,
final boolean preserveIRI)
{
try
{
final URL url = new URL(source);
return produceFromURL(url, preserveIRI);
}
catch (MalformedURLException e)
{
// ignore .. but we had to try this ..
}
final OfficeImage o = (OfficeImage) imageCache.get(source);
if (o != null)
{
return o;
}
// Next, check whether this is a local path.
if (inputRepository.isReadable(source))
{
// cool, the file exists. Let's try to read it.
try
{
final ByteArrayOutputStream bout = new ByteArrayOutputStream(8192);
final InputStream inputStream = inputRepository.createInputStream(source);
try
{
IOUtils.getInstance().copyStreams(inputStream, bout);
} finally
{
inputStream.close();
}
final byte[] data = bout.toByteArray();
final Dimension dims = imageService.getImageSize(data);
final String mimeType = imageService.getMimeType(data);
final CSSNumericValue widthVal = CSSNumericValue.createValue(CSSNumericType.MM, dims.getWidth() / 100.0);
final CSSNumericValue heightVal = CSSNumericValue.createValue(CSSNumericType.MM, dims.getHeight() / 100.0);
final String filename = copyToOutputRepository(mimeType, source, data);
final OfficeImage officeImage = new OfficeImage(filename, widthVal, heightVal);
imageCache.put(source, officeImage);
return officeImage;
}
catch (IOException e)
{
Log.warn("Failed to load image from local input-repository", e);
}
catch (ReportExecutionException e)
{
Log.warn("Failed to create image from local input-repository", e);
}
}
// Return the image as broken image instead ..
final OfficeImage officeImage = new OfficeImage(source, null, null);
imageCache.put(source, officeImage);
return officeImage;
}
private OfficeImage produceFromURL(final URL url,
final boolean preserveIRI)
{
final OfficeImage o = (OfficeImage) imageCache.get(url);
if (o != null)
{
return o;
}
try
{
final ByteArrayOutputStream bout = new ByteArrayOutputStream(8192);
final URLConnection urlConnection = url.openConnection();
final InputStream inputStream = new BufferedInputStream(urlConnection.getInputStream());
try
{
IOUtils.getInstance().copyStreams(inputStream, bout);
} finally
{
inputStream.close();
}
final byte[] data = bout.toByteArray();
final Dimension dims = imageService.getImageSize(data);
final String mimeType = imageService.getMimeType(data);
final CSSNumericValue widthVal = CSSNumericValue.createValue(CSSNumericType.MM, dims.getWidth() / 100.0);
final CSSNumericValue heightVal = CSSNumericValue.createValue(CSSNumericType.MM, dims.getHeight() / 100.0);
if (preserveIRI)
{
final OfficeImage retval = new OfficeImage(url.toString(), widthVal, heightVal);
imageCache.put(url, retval);
return retval;
}
final String file = url.getFile();
final String name = copyToOutputRepository(mimeType, file, data);
final OfficeImage officeImage = new OfficeImage(name, widthVal, heightVal);
imageCache.put(url, officeImage);
return officeImage;
}
catch (IOException e)
{
Log.warn("Failed to load image from local input-repository" + e);
}
catch (ReportExecutionException e)
{
Log.warn("Failed to create image from local input-repository" + e);
}
if (!preserveIRI)
{
final OfficeImage image = new OfficeImage(url.toString(), null, null);
imageCache.put(url, image);
return image;
}
// OK, everything failed; the image is not - repeat it - not usable.
return null;
}
private String copyToOutputRepository(final String urlMimeType, final String file, final byte[] data)
throws IOException, ReportExecutionException
{
final String mimeType;
if (urlMimeType == null)
{
mimeType = imageService.getMimeType(data);
}
else
{
mimeType = urlMimeType;
}
// copy the image into the local output-storage
final String name = nameGenerator.generateName("Pictures/image", mimeType);
final OutputStream outputStream = outputRepository.createOutputStream(name, mimeType);
final ByteArrayInputStream bin = new ByteArrayInputStream(data);
try
{
IOUtils.getInstance().copyStreams(bin, outputStream);
} finally
{
outputStream.close();
}
return name;
}
}