/*
* 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.resourceloader;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.pentaho.reporting.libraries.base.util.CSVQuoter;
import org.pentaho.reporting.libraries.base.util.CSVTokenizer;
import org.pentaho.reporting.libraries.base.util.IOUtils;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
/**
* Provides a setup of utility methods for operating on a ResourceKey class.
*
* @author David Kincade
*/
public class ResourceKeyUtils {
private static final String DELIMITER = ";";
public static final String SERIALIZATION_PREFIX = "resourcekey:";
private static final Log logger = LogFactory.getLog( ResourceManager.class );
/**
* Returns a string representation of the ResourceKey based on the pieces that are passed as parameters
*
* @param schema the string representation of the schema
* @param identifier the string representation of the identifier
* @param factoryParameters the set of factory parameters (<code>null</code> allowed)
* @return the string version with the pieces delimited and concatenated
*/
public static String createStringResourceKey( final String schema,
final String identifier,
final Map factoryParameters ) {
final String factoryParamString = convertFactoryParametersToString( factoryParameters );
final CSVQuoter quoter = new CSVQuoter( ';' );
return quoter.doQuoting( SERIALIZATION_PREFIX + schema ) + DELIMITER +
quoter.doQuoting( identifier ) +
( factoryParamString == null ? "" : DELIMITER + quoter.doQuoting( factoryParamString ) );
}
/**
* Parses the string version of the Resource Key into the components
*
* @return
*/
public static ResourceKeyData parse( final String resourceKeyString ) throws ResourceKeyCreationException {
if ( resourceKeyString == null ) {
throw new IllegalArgumentException( "Source string can not be null" );
}
if ( !resourceKeyString.startsWith( SERIALIZATION_PREFIX ) ) {
throw new ResourceKeyCreationException( "The source string does not start with the string ["
+ SERIALIZATION_PREFIX + "]" );
}
final CSVTokenizer tokenizer = new CSVTokenizer( resourceKeyString, DELIMITER, "\"", false );
if ( tokenizer.hasMoreElements() == false ) {
throw new ResourceKeyCreationException( "Schema is missing" );
}
final String rawSchema = tokenizer.nextToken();
if ( rawSchema.startsWith( SERIALIZATION_PREFIX ) == false ) {
throw new ResourceKeyCreationException( "Prefix is wrong" );
}
final String schema = rawSchema.substring( SERIALIZATION_PREFIX.length() );
if ( tokenizer.hasMoreElements() == false ) {
throw new ResourceKeyCreationException( "Identifier is missing" );
}
final String id = tokenizer.nextToken();
final Map parameters;
if ( tokenizer.hasMoreElements() ) {
parameters = parseFactoryParametersFromString( tokenizer.nextToken() );
} else {
parameters = null;
}
// The 1st component is the schema... the 2nd is the identifier... the 3rd is the factory parameters (optional)
return new ResourceKeyData( schema, id, parameters );
}
/**
* Returns the list of factory parameters for the specified ResourceKey as a String representation in the format:
* <pre>
* key=value:key=value:...:key=value
* </pre>
* The colon (:) is the separator between parameters and the equal sign (=) is the separator between the key and the
* value.
* <p/>
* If the factory parameters is empty, <code>null</code> will be returned
*
* @param factoryParameters the parameter map.
* @return a String representation of the factory parameters for the ResourceKey
*/
public static String convertFactoryParametersToString( final Map factoryParameters ) {
if ( factoryParameters == null || factoryParameters.size() <= 0 ) {
return null;
}
final CSVQuoter innerQuoter = new CSVQuoter( '=' );
final CSVQuoter quoter = new CSVQuoter( ':' );
final StringBuilder sb = new StringBuilder();
for ( Iterator iterator = factoryParameters.keySet().iterator(); iterator.hasNext(); ) {
if ( sb.length() > 0 ) {
sb.append( ':' );
}
final StringBuilder entrySb = new StringBuilder();
final Object key = iterator.next();
if ( key instanceof FactoryParameterKey ) {
final FactoryParameterKey fkey = (FactoryParameterKey) key;
entrySb.append( innerQuoter.doQuoting( "f:" + fkey.getName() ) );
} else if ( key instanceof LoaderParameterKey ) {
final LoaderParameterKey fkey = (LoaderParameterKey) key;
entrySb.append( innerQuoter.doQuoting( "l:" + fkey.getName() ) );
} else {
throw new IllegalArgumentException( String.valueOf( key ) );
}
final Object value = factoryParameters.get( key );
entrySb.append( '=' );
if ( value != null ) {
// todo: This String.valueOf is probably and very likely wrong
entrySb.append( innerQuoter.doQuoting( String.valueOf( value ) ) );
}
sb.append( quoter.doQuoting( entrySb.toString() ) );
}
logger.debug( "Converted ResourceKey's Factory Parameters to String: [" + sb.toString() + "]" );
return sb.toString();
}
/**
* Returns a Map of parameters based on the input string. The string will be parsed using the same format as defined
* in the <code>getFactoryParametersAsString()</code> method.
*
* @param factoryParameters the String representation of factory parameters
* @return a Map of factory parameters parsed from the string, or <code>null</code> if the source string was null or
* contained no data
*/
public static Map parseFactoryParametersFromString( final String factoryParameters ) {
if ( factoryParameters == null ) {
return null;
}
final Map<ParameterKey, Object> params = new HashMap<ParameterKey, Object>();
final CSVTokenizer tokenizer = new CSVTokenizer( factoryParameters, ":", "\"", false );
while ( tokenizer.hasMoreTokens() ) {
final String entry = tokenizer.nextToken();
final CSVTokenizer innerTokenizer = new CSVTokenizer( entry, "=", "\"", false );
final ParameterKey key;
if ( innerTokenizer.hasMoreElements() ) {
final String keyString = innerTokenizer.nextToken();
if ( keyString.startsWith( "f:" ) ) {
key = new FactoryParameterKey( keyString.substring( 2 ) );
} else if ( keyString.startsWith( "l:" ) ) {
key = new LoaderParameterKey( keyString.substring( 2 ) );
} else {
throw new IllegalStateException( "Invalid prefix: Key '" + keyString
+ "' must be either a loader-parameter-key or a factory-parameter-key" );
}
} else {
throw new IllegalStateException();
}
if ( innerTokenizer.hasMoreElements() ) {
final Object value = innerTokenizer.nextToken();
if ( "".equals( value ) ) {
params.put( key, null );
} else {
params.put( key, value );
}
}
}
if ( params.isEmpty() ) {
return null;
}
if ( logger.isDebugEnabled() ) {
logger.debug( "Converted ResourceKey's Factory Parameter String to a Map: [" + factoryParameters
+ "] -> map of size " + params.size() );
}
return params;
}
/**
* Returns the schema portion of the serialized ResourceKey string. If the string is invalid, <code>null</code> will
* be returned.
*
* @param data the String serialized version of a <code>ResourceKey</code>
* @return the schema object.
*/
public static Object readSchemaFromString( final String data ) {
if ( data == null ) {
return null;
}
final CSVTokenizer tokenizer = new CSVTokenizer( data, DELIMITER, "\"", false );
if ( tokenizer.hasMoreElements() == false ) {
return null;
}
final String tempData = tokenizer.nextToken();
if ( tempData.startsWith( SERIALIZATION_PREFIX ) ) {
return tempData.substring( SERIALIZATION_PREFIX.length() );
}
return null;
}
/**
* Performs a simple attempt at a "smart" conversion to a ResourceKey. <ol> <li>If the <code>value</code> is
* <code>null</code>, this method will return <code>null</code></li> <li>If the <code>value</code> is a
* <code>ResourceKey</code>, the <code>value</code> will be returned</li> <li>If the <code>value</code> is a
* <code>String</code> and is syntactically valid as a <code>URL</code>, it will be converted to a <code>URL</code>
* and then used to create a <code>ResourceKey</code></li> <li>If the <code>value</code> is a <code>String</code> and
* is NOT syntactically valid as a <code>URL</code>, it will be converted ot a <code>File</code> and then used to
* create a <code>ResourceKey</code></li> <li>All other types will be passed along to attempt a key creation as is
* </ol>
*
* @param value the object to convert to a <code>ResourceKey</code>
* @param resourceManager the resource manager used in key creation
* @param parameters the parameters that should be passed to generate a resource key
* @return the resource key created
* @throws ResourceKeyCreationException indicates the value can not be used to create a Resource Key
*/
public static ResourceKey toResourceKey( final Object value,
final ResourceManager resourceManager,
final ResourceKey contextKey,
final Map parameters )
throws ResourceKeyCreationException {
if ( value == null ) {
return null;
}
if ( value instanceof ResourceKey ) {
return (ResourceKey) value;
}
if ( resourceManager == null ) {
throw new NullPointerException( "ResourceManager is null" );
}
// If the value is a String, try a URL or a File
Object tempObject = value;
if ( tempObject instanceof String ) {
final String spec = (String) value;
if ( contextKey != null ) {
try {
return resourceManager.deriveKey( contextKey, spec, parameters );
} catch ( ResourceKeyCreationException e ) {
// ignored ..
}
}
try {
tempObject = new URL( spec );
} catch ( MalformedURLException e ) {
tempObject = new File( spec );
}
}
return resourceManager.createKey( tempObject, parameters );
}
/**
* Returns a new ResourceKey with the specified source resource embedded inside as a byte []
*
* @param source the ResourceKey to the source which will be embedded - NOTE: the pattern can specify an
* exact name or a pattern for creating a temporary name. If the name exists, it will be
* replaced.
* @param factoryParameters any factory parameters which should be added to the ResourceKey being created
* @return the ResourceKey for the newly created embedded entry
*/
public static ResourceKey embedResourceInKey( final ResourceManager manager,
final ResourceKey source,
final Map factoryParameters )
throws IOException, ResourceKeyCreationException, ResourceLoadingException {
if ( manager == null ) {
throw new IllegalArgumentException();
}
if ( source == null ) {
throw new IllegalArgumentException();
}
final ResourceData resourceData = manager.load( source );
// Load the resource into a byte array
final InputStream in = resourceData.getResourceAsStream( manager );
final ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
IOUtils.getInstance().copyStreams( in, out );
// Create a resource key with the byte array
return manager.createKey( out.toByteArray(), factoryParameters );
} finally {
try {
in.close();
} catch ( IOException e ) {
logger.error( "Error closing input stream", e );
}
}
}
}