/*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software
* Foundation.
*
* You should have received a copy of the GNU Lesser General Public License along with this
* program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
* or from the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* This program 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.
*
* Copyright (c) 2006 - 2013 Pentaho Corporation.. All rights reserved.
*/
package org.pentaho.reporting.engine.classic.core.modules.output.table.html.helper;
import java.awt.Image;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.util.HashMap;
import java.util.HashSet;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.pentaho.reporting.engine.classic.core.ImageContainer;
import org.pentaho.reporting.engine.classic.core.LocalImageContainer;
import org.pentaho.reporting.engine.classic.core.URLImageContainer;
import org.pentaho.reporting.engine.classic.core.layout.output.RenderUtility;
import org.pentaho.reporting.engine.classic.core.modules.output.table.html.HtmlContentGenerator;
import org.pentaho.reporting.engine.classic.core.modules.output.table.html.URLRewriteException;
import org.pentaho.reporting.libraries.base.encoder.UnsupportedEncoderException;
import org.pentaho.reporting.libraries.base.util.IOUtils;
import org.pentaho.reporting.libraries.base.util.StringUtils;
import org.pentaho.reporting.libraries.repository.ContentCreationException;
import org.pentaho.reporting.libraries.repository.ContentIOException;
import org.pentaho.reporting.libraries.repository.ContentItem;
import org.pentaho.reporting.libraries.repository.ContentLocation;
import org.pentaho.reporting.libraries.repository.LibRepositoryBoot;
import org.pentaho.reporting.libraries.repository.NameGenerator;
import org.pentaho.reporting.libraries.resourceloader.Resource;
import org.pentaho.reporting.libraries.resourceloader.ResourceData;
import org.pentaho.reporting.libraries.resourceloader.ResourceException;
import org.pentaho.reporting.libraries.resourceloader.ResourceKey;
import org.pentaho.reporting.libraries.resourceloader.ResourceLoadingException;
import org.pentaho.reporting.libraries.resourceloader.ResourceManager;
public class DefaultHtmlContentGenerator implements HtmlContentGenerator
{
private static class ImageData
{
private byte[] imageData;
private String mimeType;
private String originalFileName;
private ImageData(final byte[] imageData, final String mimeType, final String originalFileName)
{
if (imageData == null)
{
throw new NullPointerException();
}
if (mimeType == null)
{
throw new NullPointerException();
}
if (originalFileName == null)
{
throw new NullPointerException();
}
this.imageData = imageData;
this.mimeType = mimeType;
this.originalFileName = originalFileName;
}
public byte[] getImageData()
{
return imageData;
}
public String getMimeType()
{
return mimeType;
}
public String getOriginalFileName()
{
return originalFileName;
}
}
private static final Log logger = LogFactory.getLog(DefaultHtmlContentGenerator.class);
private final HashSet<String> validRawTypes;
private ResourceManager resourceManager;
private HashMap<ResourceKey, String> knownResources;
private HashMap<String, String> knownImages;
private boolean copyExternalImages;
private ContentLocation dataLocation;
private NameGenerator dataNameGenerator;
private ContentUrlReWriteService rewriterService;
public DefaultHtmlContentGenerator(final ResourceManager resourceManager)
{
this.knownImages = new HashMap<String, String>();
this.resourceManager = resourceManager;
this.knownResources = new HashMap<ResourceKey, String>();
this.validRawTypes = new HashSet<String>();
this.validRawTypes.add("image/gif");
this.validRawTypes.add("image/x-xbitmap");
this.validRawTypes.add("image/gi_");
this.validRawTypes.add("image/jpeg");
this.validRawTypes.add("image/jpg");
this.validRawTypes.add("image/jp_");
this.validRawTypes.add("application/jpg");
this.validRawTypes.add("application/x-jpg");
this.validRawTypes.add("image/pjpeg");
this.validRawTypes.add("image/pipeg");
this.validRawTypes.add("image/vnd.swiftview-jpeg");
this.validRawTypes.add("image/x-xbitmap");
this.validRawTypes.add("image/png");
this.validRawTypes.add("application/png");
this.validRawTypes.add("application/x-png");
}
public void setDataWriter(final ContentLocation dataLocation,
final NameGenerator dataNameGenerator,
final ContentUrlReWriteService rewriterService)
{
this.dataNameGenerator = dataNameGenerator;
this.dataLocation = dataLocation;
this.rewriterService = rewriterService;
}
public void setCopyExternalImages(final boolean copyExternalImages)
{
this.copyExternalImages = copyExternalImages;
}
public boolean isCopyExternalImages()
{
return copyExternalImages;
}
public ResourceManager getResourceManager()
{
return resourceManager;
}
public void registerFailure(final ResourceKey source)
{
knownResources.put(source, null);
}
public void registerContent(final ResourceKey source, final String name)
{
knownResources.put(source, name);
}
public boolean isRegistered(final ResourceKey source)
{
return knownResources.containsKey(source);
}
public String getRegisteredName(final ResourceKey source)
{
final String o = knownResources.get(source);
if (o != null)
{
return o;
}
return null;
}
public String writeRaw(final ResourceKey source) throws IOException
{
if (source == null)
{
throw new NullPointerException();
}
if (copyExternalImages == false)
{
final Object identifier = source.getIdentifier();
if (identifier instanceof URL)
{
final URL url = (URL) identifier;
final String protocol = url.getProtocol();
if ("http".equalsIgnoreCase(protocol) ||
"https".equalsIgnoreCase(protocol) ||
"ftp".equalsIgnoreCase(protocol))
{
return url.toExternalForm();
}
}
}
if (dataLocation == null)
{
return null;
}
try
{
final ResourceData resourceData = resourceManager.load(source);
final String mimeType = queryMimeType(resourceData);
if (isValidImage(mimeType))
{
// lets do some voodo ..
final ContentItem item = dataLocation.createItem
(dataNameGenerator.generateName(extractFilename(resourceData), mimeType));
if (item.isWriteable())
{
item.setAttribute(LibRepositoryBoot.REPOSITORY_DOMAIN, LibRepositoryBoot.CONTENT_TYPE, mimeType);
// write it out ..
final InputStream stream = new BufferedInputStream(resourceData.getResourceAsStream(resourceManager));
try
{
final OutputStream outputStream = new BufferedOutputStream(item.getOutputStream());
try
{
IOUtils.getInstance().copyStreams(stream, outputStream);
}
finally
{
outputStream.close();
}
}
finally
{
stream.close();
}
return rewriterService.rewriteContentDataItem(item);
}
}
}
catch (ResourceLoadingException e)
{
// Ok, loading the resource failed. Not a problem, so we will
// recode the raw-object instead ..
}
catch (ContentIOException e)
{
// ignore it ..
}
catch (URLRewriteException e)
{
logger.warn("Rewriting the URL failed.", e);
throw new RuntimeException("Failed", e);
}
return null;
}
public String writeImage(final ImageContainer image,
final String encoderType,
final float quality,
final boolean alpha)
throws ContentIOException, IOException
{
if (image == null)
{
throw new NullPointerException();
}
if (dataLocation == null)
{
return null;
}
final String cacheKey;
if (image instanceof URLImageContainer)
{
final URLImageContainer uic = (URLImageContainer) image;
cacheKey = uic.getSourceURLString();
final String retval = knownImages.get(cacheKey);
if (retval != null)
{
return retval;
}
final String sourceURLString = uic.getSourceURLString();
if (uic.isLoadable() == false && sourceURLString != null)
{
knownImages.put(cacheKey, sourceURLString);
return sourceURLString;
}
}
else
{
cacheKey = null;
}
try
{
final ImageData data = getImageData(image, encoderType, quality, alpha);
if (data == null)
{
return null;
}
// write the encoded picture ...
final String filename = IOUtils.getInstance().stripFileExtension(data.getOriginalFileName());
final ContentItem dataFile = dataLocation.createItem
(dataNameGenerator.generateName(filename, data.getMimeType()));
final String contentURL = rewriterService.rewriteContentDataItem(dataFile);
// a png encoder is included in JCommon ...
final OutputStream out = new BufferedOutputStream(dataFile.getOutputStream());
try
{
out.write(data.getImageData());
out.flush();
}
finally
{
out.close();
}
if (cacheKey != null)
{
knownImages.put(cacheKey, contentURL);
}
return contentURL;
}
catch (ContentCreationException cce)
{
// Can't create the content
logger.warn("Failed to create the content image: Reason given was: " + cce.getMessage());
return null;
}
catch (URLRewriteException re)
{
// cannot handle this ..
logger.warn("Failed to write the URL: Reason given was: " + re.getMessage());
return null;
}
catch (UnsupportedEncoderException e)
{
logger.warn("Failed to write the URL: Reason given was: " + e.getMessage());
return null;
}
}
private String queryMimeType(final ResourceData resourceData)
throws ResourceLoadingException, IOException
{
final Object contentType = resourceData.getAttribute(ResourceData.CONTENT_TYPE);
if (contentType instanceof String)
{
return (String) contentType;
}
// now we are getting very primitive .. (Kids, dont do this at home)
final byte[] data = new byte[12];
resourceData.getResource(resourceManager, data, 0, data.length);
return queryMimeType(data);
}
private String queryMimeType(final byte[] data) throws IOException
{
final ByteArrayInputStream stream = new ByteArrayInputStream(data);
if (isGIF(stream))
{
return "image/gif";
}
stream.reset();
if (isJPEG(stream))
{
return "image/jpeg";
}
stream.reset();
if (isPNG(stream))
{
return "image/png";
}
return null;
}
private boolean isPNG(final ByteArrayInputStream data)
{
final int[] PNF_FINGERPRINT = {137, 80, 78, 71, 13, 10, 26, 10};
for (int i = 0; i < PNF_FINGERPRINT.length; i++)
{
if (PNF_FINGERPRINT[i] != data.read())
{
return false;
}
}
return true;
}
private boolean isJPEG(final InputStream data) throws IOException
{
final int[] JPG_FINGERPRINT_1 = {0xFF, 0xD8, 0xFF, 0xE0};
for (int i = 0; i < JPG_FINGERPRINT_1.length; i++)
{
if (JPG_FINGERPRINT_1[i] != data.read())
{
return false;
}
}
// then skip two bytes ..
if (data.read() == -1)
{
return false;
}
if (data.read() == -1)
{
return false;
}
final int[] JPG_FINGERPRINT_2 = {0x4A, 0x46, 0x49, 0x46, 0x00};
for (int i = 0; i < JPG_FINGERPRINT_2.length; i++)
{
if (JPG_FINGERPRINT_2[i] != data.read())
{
return false;
}
}
return true;
}
private boolean isGIF(final InputStream data) throws IOException
{
final int[] GIF_FINGERPRINT = {'G', 'I', 'F', '8'};
for (int i = 0; i < GIF_FINGERPRINT.length; i++)
{
if (GIF_FINGERPRINT[i] != data.read())
{
return false;
}
}
return true;
}
private String extractFilename(final ResourceData resourceData)
{
final String filename = (String) resourceData.getAttribute(ResourceData.FILENAME);
if (filename == null)
{
return "image";
}
final String pureFileName = IOUtils.getInstance().getFileName(filename);
return IOUtils.getInstance().stripFileExtension(pureFileName);
}
private boolean isValidImage(final String mimeType)
{
return validRawTypes.contains(mimeType);
}
private ImageData getImageData(final ImageContainer image,
final String encoderType,
final float quality,
final boolean alpha) throws IOException, UnsupportedEncoderException
{
ResourceManager resourceManager = getResourceManager();
ResourceKey url = null;
// The image has an assigned URL ...
if (image instanceof URLImageContainer)
{
final URLImageContainer urlImage = (URLImageContainer) image;
url = urlImage.getResourceKey();
// if we have an source to load the image data from ..
if (url != null)
{
if (urlImage.isLoadable() && isSupportedImageFormat(url))
{
try
{
final ResourceData data = resourceManager.load(url);
final byte[] imageData = data.getResource(resourceManager);
final String mimeType = queryMimeType(imageData);
final URL maybeRealURL = resourceManager.toURL(url);
if (maybeRealURL != null)
{
final String originalFileName = IOUtils.getInstance().getFileName(maybeRealURL);
return new ImageData(imageData, mimeType, originalFileName);
}
else
{
return new ImageData(imageData, mimeType, "picture");
}
}
catch (ResourceException re)
{
// ok, try as local ...
logger.debug("Failed to process image as raw-data, trying as processed data next", re);
}
}
}
}
if (image instanceof LocalImageContainer)
{
// Check, whether the imagereference contains an AWT image.
// if so, then we can use that image instance for the recoding
final LocalImageContainer li = (LocalImageContainer) image;
Image awtImage = li.getImage();
if (awtImage == null)
{
if (url != null)
{
try
{
final Resource resource = resourceManager.createDirectly(url, Image.class);
awtImage = (Image) resource.getResource();
}
catch (ResourceException e)
{
// ignore.
}
}
}
if (awtImage != null)
{
// now encode the image. We don't need to create digest data
// for the image contents, as the image is perfectly identifyable
// by its URL
final byte[] imageData = RenderUtility.encodeImage(awtImage, encoderType, quality, alpha);
final String originalFileName;
if (url != null)
{
final URL maybeRealURL = resourceManager.toURL(url);
if (maybeRealURL != null)
{
originalFileName = IOUtils.getInstance().getFileName(maybeRealURL);
}
else
{
// we just need the picture part, the file-extension will be replaced by one that matches
// the mime-type.
originalFileName = "picture";
}
}
else
{
// we just need the picture part, the file-extension will be replaced by one that matches
// the mime-type.
originalFileName = "picture";
}
return new ImageData(imageData, encoderType, originalFileName);
}
}
return null;
}
/**
* Tests, whether the given URL points to a supported file format for common browsers. Returns true if the URL
* references a JPEG, PNG or GIF image, false otherwise.
* <p/>
* The checked filetypes are the ones recommended by the W3C.
*
* @param key the url that should be tested.
* @return true, if the content type is supported by the browsers, false otherwise.
*/
protected boolean isSupportedImageFormat(final ResourceKey key)
{
ResourceManager resourceManager = getResourceManager();
final URL url = resourceManager.toURL(key);
if (url == null)
{
return false;
}
final String file = url.getFile();
if (StringUtils.endsWithIgnoreCase(file, ".jpg"))
{
return true;
}
if (StringUtils.endsWithIgnoreCase(file, ".jpeg"))
{
return true;
}
if (StringUtils.endsWithIgnoreCase(file, ".png"))
{
return true;
}
if (StringUtils.endsWithIgnoreCase(file, ".gif"))
{
return true;
}
return false;
}
public ContentItem createItem(final String name, final String mimeType) throws ContentIOException
{
return dataLocation.createItem(dataNameGenerator.generateName(name, mimeType));
}
public boolean isExternalContentAvailable()
{
return dataLocation != null;
}
}