/*
* 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 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;
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;
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 );
}
}
}
}