/*
* 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) 2008 - 2009 Pentaho Corporation and Contributors. All rights reserved.
*/
package org.pentaho.reporting.libraries.docbundle;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.Format;
import java.text.MessageFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.pentaho.reporting.libraries.base.util.IOUtils;
import org.pentaho.reporting.libraries.base.util.ObjectUtilities;
import org.pentaho.reporting.libraries.docbundle.metadata.writer.DocumentMetaDataWriter;
import org.pentaho.reporting.libraries.repository.ContentEntity;
import org.pentaho.reporting.libraries.repository.ContentIOException;
import org.pentaho.reporting.libraries.repository.ContentItem;
import org.pentaho.reporting.libraries.repository.LibRepositoryBoot;
import org.pentaho.reporting.libraries.repository.Repository;
import org.pentaho.reporting.libraries.repository.RepositoryUtilities;
import org.pentaho.reporting.libraries.repository.file.FileRepository;
import org.pentaho.reporting.libraries.repository.zipwriter.ZipRepository;
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 BundleUtilities
{
private static final Log logger = LogFactory.getLog(BundleUtilities.class);
public static final String STICKY_FLAG = "sticky";
public static final String HIDDEN_FLAG = "hidden";
private BundleUtilities()
{
}
public static void copyInto(final WriteableDocumentBundle bundle,
final String targetPath,
final ResourceKey dataKey,
final ResourceManager resourceManager)
throws IOException, ResourceLoadingException
{
if (bundle == null)
{
throw new NullPointerException();
}
if (targetPath == null)
{
throw new NullPointerException();
}
if (resourceManager == null)
{
throw new NullPointerException();
}
if (dataKey == null)
{
throw new NullPointerException();
}
final ResourceData resourceData = resourceManager.load(dataKey);
String contentType = (String) resourceData.getAttribute(ResourceData.CONTENT_TYPE);
if (contentType == null)
{
contentType = "application/octet-stream";
}
final InputStream stream = resourceData.getResourceAsStream(resourceManager);
try
{
final OutputStream outStream = bundle.createEntry(targetPath, contentType);
try
{
IOUtils.getInstance().copyStreams(stream, outStream);
}
finally
{
outStream.close();
}
}
finally
{
stream.close();
}
}
public static String getBundleType(final Repository repository)
{
if (repository == null)
{
throw new NullPointerException();
}
try
{
final ContentEntity mimeTypeContentEntity = repository.getRoot().getEntry("mimetype");
if (mimeTypeContentEntity instanceof ContentItem)
{
final ContentItem mimeTypeItem = (ContentItem) mimeTypeContentEntity;
final ByteArrayOutputStream bout = new ByteArrayOutputStream();
final InputStream in = mimeTypeItem.getInputStream();
try
{
IOUtils.getInstance().copyStreams(in, bout);
}
finally
{
in.close();
}
return bout.toString("ASCII");
}
return null;
}
catch (Exception e)
{
return null;
}
}
public static String getBundleMapping(final String bundleType)
{
final String defaultType = LibDocBundleBoot.getInstance().getGlobalConfig().getConfigProperty
("org.pentaho.reporting.libraries.docbundle.bundleloader.mapping");
if (bundleType == null)
{
return defaultType;
}
return LibDocBundleBoot.getInstance().getGlobalConfig().getConfigProperty
("org.pentaho.reporting.libraries.docbundle.bundleloader.mapping." + bundleType, defaultType);
}
public static void writeAsZip(final File target, final DocumentBundle bundle)
throws IOException, ContentIOException
{
if (target == null)
{
throw new NullPointerException();
}
if (bundle == null)
{
throw new NullPointerException();
}
final FileOutputStream fout = new FileOutputStream(target);
final BufferedOutputStream bout = new BufferedOutputStream(fout);
try
{
writeAsZip(bout, bundle);
}
finally
{
bout.close();
}
}
public static void writeAsZip(final OutputStream targetStream, final DocumentBundle bundle)
throws ContentIOException, IOException
{
if (targetStream == null)
{
throw new NullPointerException();
}
if (bundle == null)
{
throw new NullPointerException();
}
final ZipRepository repository = new ZipRepository(targetStream);
writeToRepository(repository, bundle);
repository.close();
}
public static void writeToDirectory(final File target, final DocumentBundle bundle)
throws ContentIOException, IOException
{
if (target == null)
{
throw new NullPointerException();
}
if (bundle == null)
{
throw new NullPointerException();
}
final FileRepository repository = new FileRepository(target);
writeToRepository(repository, bundle);
}
public static void writeToRepository(final Repository repository, final DocumentBundle bundle)
throws ContentIOException, IOException
{
if (repository == null)
{
throw new NullPointerException();
}
if (bundle == null)
{
throw new NullPointerException();
}
final String bundleType = bundle.getEntryMimeType("/");
if (bundleType == null)
{
logger.warn("Document-Bundle has no bundle-type declared.");
}
else
{
final ContentItem contentItem = RepositoryUtilities.createItem
(repository, RepositoryUtilities.splitPath("mimetype", "/"));
contentItem.setAttribute
(LibRepositoryBoot.ZIP_DOMAIN, LibRepositoryBoot.ZIP_METHOD_ATTRIBUTE, LibRepositoryBoot.ZIP_METHOD_STORED);
final byte[] rawData = bundleType.getBytes("ASCII");
final OutputStream outputStream = contentItem.getOutputStream();
try
{
outputStream.write(rawData);
}
finally
{
outputStream.close();
}
}
final DocumentMetaDataWriter metaDataWriter = new DocumentMetaDataWriter(bundle.getMetaData());
final ContentItem manifestItem = RepositoryUtilities.createItem
(repository, RepositoryUtilities.splitPath("META-INF/manifest.xml", "/"));
final OutputStream manifestStream = manifestItem.getOutputStream();
try
{
metaDataWriter.writeManifest(manifestStream);
}
finally
{
manifestStream.close();
}
final ContentItem metaDataItem = RepositoryUtilities.createItem
(repository, RepositoryUtilities.splitPath("meta.xml", "/"));
final OutputStream metaDataStream = metaDataItem.getOutputStream();
try
{
metaDataWriter.writeMetaData(metaDataStream);
}
finally
{
metaDataStream.close();
}
final DocumentMetaData bundleMetaData = bundle.getMetaData();
final String[] entryNames = bundleMetaData.getManifestEntryNames();
Arrays.sort(entryNames);
for (int i = 0; i < entryNames.length; i++)
{
final String entryName = entryNames[i];
if ("/".equals(entryName))
{
continue;
}
if ("mimetype".equals(entryName))
{
continue;
}
if ("META-DATA/manifest.xml".equals(entryName))
{
continue;
}
if ("meta.xml".equals(entryName))
{
continue;
}
logger.debug("Processing " + entryName);
final String[] entityNameArray = RepositoryUtilities.splitPath(entryName, "/");
if (entryName.length() > 0 && entryName.charAt(entryName.length() - 1) == '/')
{
if (RepositoryUtilities.isExistsEntity(repository, entityNameArray))
{
continue;
}
// Skip, it is a directory-entry.
RepositoryUtilities.createLocation(repository, entityNameArray);
continue;
}
final ContentItem dataItem = RepositoryUtilities.createItem
(repository, entityNameArray);
final OutputStream dataStream = dataItem.getOutputStream();
try
{
final InputStream inStream = bundle.getEntryAsStream(entryName);
try
{
IOUtils.getInstance().copyStreams(inStream, dataStream);
}
finally
{
inStream.close();
}
}
finally
{
dataStream.close();
}
}
}
public static void copyInto(final WriteableDocumentBundle targetBundle,
final DocumentBundle sourceBundle) throws IOException
{
if (targetBundle == null)
{
throw new NullPointerException();
}
if (sourceBundle == null)
{
throw new NullPointerException();
}
final WriteableDocumentMetaData targetBundleMetaData = targetBundle.getWriteableDocumentMetaData();
final DocumentMetaData bundleMetaData = sourceBundle.getMetaData();
targetBundleMetaData.setBundleType(bundleMetaData.getBundleType());
// copy the meta-data
final String[] namespaces = bundleMetaData.getMetaDataNamespaces();
for (int namespaceIdx = 0; namespaceIdx < namespaces.length; namespaceIdx++)
{
final String namespace = namespaces[namespaceIdx];
final String[] dataNames = bundleMetaData.getMetaDataNames(namespace);
for (int dataNameIdx = 0; dataNameIdx < dataNames.length; dataNameIdx++)
{
final String dataName = dataNames[dataNameIdx];
final Object value = bundleMetaData.getBundleAttribute(namespace, dataName);
targetBundleMetaData.setBundleAttribute(namespace, dataName, value);
}
}
// copy the entries ...
final String[] entryNames = bundleMetaData.getManifestEntryNames();
for (int i = 0; i < entryNames.length; i++)
{
final String entryName = entryNames[i];
if ("/".equals(entryName))
{
continue;
}
if ("mimetype".equals(entryName))
{
continue;
}
if ("META-DATA/manifest.xml".equals(entryName))
{
continue;
}
if ("meta.xml".equals(entryName))
{
continue;
}
logger.debug("Processing " + entryName);
final String entryMimeType = bundleMetaData.getEntryMimeType(entryName);
if (entryMimeType == null)
{
throw new IllegalStateException("Found an entry with an invalid mime-type: " + entryName);
}
if (entryName.length() > 0 && entryName.charAt(entryName.length() - 1) == '/')
{
targetBundle.createDirectoryEntry(entryName, entryMimeType);
}
else
{
final OutputStream dataStream = targetBundle.createEntry(entryName, entryMimeType);
try
{
final InputStream inStream = sourceBundle.getEntryAsStream(entryName);
try
{
IOUtils.getInstance().copyStreams(inStream, dataStream);
}
finally
{
inStream.close();
}
}
finally
{
dataStream.close();
}
}
final DocumentMetaData sourceMetaData = sourceBundle.getMetaData();
final String[] attributeNames = sourceMetaData.getEntryAttributeNames(entryName);
for (int j = 0; j < attributeNames.length; j++)
{
final String attributeName = attributeNames[j];
targetBundle.getWriteableDocumentMetaData().setEntryAttribute
(entryName, attributeName, sourceMetaData.getEntryAttribute(entryName, attributeName));
}
}
}
public static void copyStickyInto(final WriteableDocumentBundle targetBundle,
final DocumentBundle sourceBundle) throws IOException
{
if (targetBundle == null)
{
throw new NullPointerException();
}
if (sourceBundle == null)
{
throw new NullPointerException();
}
final WriteableDocumentMetaData targetBundleMetaData = targetBundle.getWriteableDocumentMetaData();
final DocumentMetaData bundleMetaData = sourceBundle.getMetaData();
targetBundleMetaData.setBundleType(bundleMetaData.getBundleType());
// copy the meta-data
final String[] namespaces = bundleMetaData.getMetaDataNamespaces();
for (int namespaceIdx = 0; namespaceIdx < namespaces.length; namespaceIdx++)
{
final String namespace = namespaces[namespaceIdx];
final String[] dataNames = bundleMetaData.getMetaDataNames(namespace);
for (int dataNameIdx = 0; dataNameIdx < dataNames.length; dataNameIdx++)
{
final String dataName = dataNames[dataNameIdx];
final Object value = bundleMetaData.getBundleAttribute(namespace, dataName);
targetBundleMetaData.setBundleAttribute(namespace, dataName, value);
}
}
// copy the entries ...
final String[] entryNames = bundleMetaData.getManifestEntryNames();
for (int i = 0; i < entryNames.length; i++)
{
final String entryName = entryNames[i];
if ("/".equals(entryName))
{
continue;
}
if ("mimetype".equals(entryName))
{
continue;
}
if ("META-DATA/manifest.xml".equals(entryName))
{
continue;
}
if ("meta.xml".equals(entryName))
{
continue;
}
if ("true".equals(bundleMetaData.getEntryAttribute(entryName, STICKY_FLAG)) == false)
{
continue;
}
logger.debug("Processing " + entryName);
final String entryMimeType = bundleMetaData.getEntryMimeType(entryName);
if (entryMimeType == null)
{
bundleMetaData.getEntryMimeType(entryName);
throw new IllegalStateException("Found an entry with an invalid mime-type: " + entryName);
}
if (entryName.length() > 0 && entryName.charAt(entryName.length() - 1) == '/')
{
targetBundle.createDirectoryEntry(entryName, entryMimeType);
continue;
}
else
{
final OutputStream dataStream = targetBundle.createEntry(entryName, entryMimeType);
try
{
final InputStream inStream = sourceBundle.getEntryAsStream(entryName);
try
{
IOUtils.getInstance().copyStreams(inStream, dataStream);
}
finally
{
inStream.close();
}
}
finally
{
dataStream.close();
}
}
final DocumentMetaData sourceMetaData = sourceBundle.getMetaData();
final String[] attributeNames = sourceMetaData.getEntryAttributeNames(entryName);
for (int j = 0; j < attributeNames.length; j++)
{
final String attributeName = attributeNames[j];
targetBundle.getWriteableDocumentMetaData().setEntryAttribute
(entryName, attributeName, sourceMetaData.getEntryAttribute(entryName, attributeName));
}
}
}
public static void copyInto(final WriteableDocumentBundle targetBundle,
final DocumentBundle sourceBundle,
final String[] files) throws IOException
{
copyInto(targetBundle, sourceBundle, files, false);
}
public static void copyInto(final WriteableDocumentBundle targetBundle,
final DocumentBundle sourceBundle,
final String[] files,
final boolean ignoreSticky) throws IOException
{
if (targetBundle == null)
{
throw new NullPointerException();
}
if (sourceBundle == null)
{
throw new NullPointerException();
}
if (files == null)
{
throw new NullPointerException();
}
final HashSet<String> fileSet = new HashSet<String>(Arrays.asList(files));
final WriteableDocumentMetaData targetBundleMetaData = targetBundle.getWriteableDocumentMetaData();
final DocumentMetaData bundleMetaData = sourceBundle.getMetaData();
targetBundleMetaData.setBundleType(bundleMetaData.getBundleType());
// copy the meta-data
final String[] namespaces = bundleMetaData.getMetaDataNamespaces();
for (int namespaceIdx = 0; namespaceIdx < namespaces.length; namespaceIdx++)
{
final String namespace = namespaces[namespaceIdx];
final String[] dataNames = bundleMetaData.getMetaDataNames(namespace);
for (int dataNameIdx = 0; dataNameIdx < dataNames.length; dataNameIdx++)
{
final String dataName = dataNames[dataNameIdx];
final Object value = bundleMetaData.getBundleAttribute(namespace, dataName);
targetBundleMetaData.setBundleAttribute(namespace, dataName, value);
}
}
// copy the entries ...
final String[] entryNames = bundleMetaData.getManifestEntryNames();
for (int i = 0; i < entryNames.length; i++)
{
final String entryName = entryNames[i];
if ("/".equals(entryName))
{
continue;
}
if ("mimetype".equals(entryName))
{
continue;
}
if ("META-DATA/manifest.xml".equals(entryName))
{
continue;
}
if ("meta.xml".equals(entryName))
{
continue;
}
if (fileSet.contains(entryName) == false)
{
continue;
}
if (ignoreSticky && "true".equals(bundleMetaData.getEntryAttribute(entryName, STICKY_FLAG)))
{
continue;
}
logger.debug("Processing " + entryName);
final String entryMimeType = bundleMetaData.getEntryMimeType(entryName);
if (entryMimeType == null)
{
bundleMetaData.getEntryMimeType(entryName);
throw new IllegalStateException("Found an entry with an invalid mime-type: " + entryName);
}
if (entryName.length() > 0 && entryName.charAt(entryName.length() - 1) == '/')
{
targetBundle.createDirectoryEntry(entryName, entryMimeType);
continue;
}
else
{
final OutputStream dataStream = targetBundle.createEntry(entryName, entryMimeType);
try
{
final InputStream inStream = sourceBundle.getEntryAsStream(entryName);
try
{
IOUtils.getInstance().copyStreams(inStream, dataStream);
}
finally
{
inStream.close();
}
}
finally
{
dataStream.close();
}
}
final DocumentMetaData sourceMetaData = sourceBundle.getMetaData();
final String[] attributeNames = sourceMetaData.getEntryAttributeNames(entryName);
for (int j = 0; j < attributeNames.length; j++)
{
final String attributeName = attributeNames[j];
targetBundle.getWriteableDocumentMetaData().setEntryAttribute
(entryName, attributeName, sourceMetaData.getEntryAttribute(entryName, attributeName));
}
}
}
/**
* Returns an unique name for the given pattern, producing a file relative to the parent file name. The
* returned path will be an <b>absolute</b> path starting from the root of the bundle. When linking to this
* path via href-references that imply relative paths, use
* {@link org.pentaho.reporting.libraries.base.util.IOUtils#createRelativePath(java.lang.String, java.lang.String)}
* to transform the absolute path returned here into a path relative to your current context.
*
* @param bundle the document bundle for which we seek a new unique file name.
* @param parent the parent path to which the pattern is relative to.
* @param pattern the file name pattern. We expect one parameter only.
* @return the unique file name, never null.
* @throws IllegalStateException if the first 2 million entries we test do not yield a unique name we can use.
*/
public static String getUniqueName(final DocumentBundle bundle, final String parent, final String pattern)
{
final String fullPattern = IOUtils.getInstance().getAbsolutePath(pattern, parent);
return getUniqueName(bundle, fullPattern);
}
public static String getUniqueName(final DocumentBundle bundle, final String pattern)
{
if (bundle == null)
{
throw new NullPointerException();
}
if (pattern == null)
{
throw new NullPointerException();
}
final MessageFormat message = new MessageFormat(pattern);
final Object[] objects = {""};
final String plain = message.format(objects);
if (bundle.isEntryExists(plain) == false)
{
return plain;
}
final Format[] formats = message.getFormats();
if (formats.length == 0)
{
// there is no variation in this name.
return null;
}
int count = 1;
while (count < 2000000)
{
objects[0] = String.valueOf(count);
final String testFile = message.format(objects);
if (bundle.isEntryExists(testFile) == false)
{
return testFile;
}
count += 1;
}
// If you have more than 2 million entries, you would hate me to test for the two billion entries, wont you?
throw new IllegalStateException();
}
public static boolean isSameBundle(final ResourceKey elementSource, final ResourceKey attributeValue)
{
if (attributeValue == null)
{
throw new NullPointerException();
}
if (elementSource == null)
{
return false;
}
if (elementSource.getParent() != null && attributeValue.getParent() != null)
{
// Check whether both keys are part of the same bundle.
return (ObjectUtilities.equal(elementSource.getParent(), attributeValue.getParent()));
}
// Not bundle keys? Check whether both keys at least refer to the same schema ..
// if (ObjectUtilities.equal(elementSource.getSchema(), attributeValue.getSchema()))
// {
// return true;
// }
return false;
}
public static DocumentBundle getBundle(final File file) throws ResourceException
{
if (file == null)
{
throw new NullPointerException();
}
final ResourceManager resManager = new ResourceManager();
final Resource directly = resManager.createDirectly(file, DocumentBundle.class);
return (DocumentBundle) directly.getResource();
}
private static final String[] DATEFORMATS = new String[]{
"yyyy-MM-dd'T'hh:mm:ss.SSS z",
"yyyy-MM-dd'T'hh:mm:ss z",
"yyyy-MM-dd'T'hh:mm:ss.SSS'Z'",
"yyyy-MM-dd'T'hh:mm:ss'Z'",
"yyyy-MM-dd'T'hh:mm:ss.SSS",
"yyyy-MM-dd'T'hh:mm:ss",
"yyyy-MM-dd zzz",
"yyyy-MM-dd'Z'",
"yyyy-MM-dd"
};
private static final long SECONDS = 1000;
private static final long MINUTES = 60 * SECONDS;
private static final long HOURS = 60 * MINUTES;
public static Date parseDate(final String date)
{
if (date.startsWith("PT"))
{
return parseDuration(date);
}
final SimpleDateFormat dateFormat = new SimpleDateFormat();
dateFormat.setLenient(false);
for (int i = 0; i < DATEFORMATS.length; i++)
{
try
{
final String dateformat = DATEFORMATS[i];
dateFormat.applyPattern(dateformat);
return dateFormat.parse(date);
}
catch (ParseException e)
{
// ignore
}
}
return null;
}
public static Date parseDuration(final String duration)
{
if (duration.startsWith("PT") == false)
{
return null;
}
double div = 1;
long date = 0;
int item = 0;
final char[] chars = duration.toCharArray();
for (int i = 1; i < chars.length; i++)
{
final char c = chars[i];
if (c == 'T')
{
item = 0;
div = 1;
continue;
}
if (Character.isDigit(c))
{
div *= 10;
item = item * 10 + ((int) c - '0');
}
else if (c == 'H')
{
date += item * HOURS;
item = 0;
div = 1;
}
else if (c == 'M')
{
date += item * MINUTES;
item = 0;
div = 1;
}
else if (c == 'S')
{
date += item * SECONDS;
item = 0;
div = 1;
}
else if (c == '.')
{
div = 1;
}
else
{
return null;
}
}
date += (item / div) * 1000;
return new Date(date);
}
public static void copyMetaData(final MemoryDocumentBundle memoryDocumentBundle, final DocumentBundle bundle)
{
final WriteableDocumentMetaData memMeta = memoryDocumentBundle.getWriteableDocumentMetaData();
final DocumentMetaData metaData = bundle.getMetaData();
memMeta.setBundleType(metaData.getBundleType());
final String[] metaNamespaces = metaData.getMetaDataNamespaces();
for (int i = 0; i < metaNamespaces.length; i++)
{
final String metaNamespace = metaNamespaces[i];
final String[] metaDataNames = metaData.getMetaDataNames(metaNamespace);
for (int j = 0; j < metaDataNames.length; j++)
{
final String metaDataName = metaDataNames[j];
final Object value = metaData.getBundleAttribute(metaNamespace, metaDataName);
memMeta.setBundleAttribute(metaNamespace, metaDataName, value);
}
}
}
}