/*! * 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) 2002-2013 Pentaho Corporation.. All rights reserved. */ package org.pentaho.reporting.engine.classic.core.modules.parser.bundle.writer; import java.io.IOException; import java.util.HashMap; import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.pentaho.reporting.engine.classic.core.MasterReport; import org.pentaho.reporting.engine.classic.core.ReportElement; import org.pentaho.reporting.engine.classic.core.RootLevelBand; import org.pentaho.reporting.engine.classic.core.Section; import org.pentaho.reporting.engine.classic.core.SubReport; import org.pentaho.reporting.engine.classic.core.metadata.AttributeMetaData; import org.pentaho.reporting.engine.classic.core.metadata.ElementMetaData; import org.pentaho.reporting.engine.classic.core.modules.parser.base.ClassicEngineFactoryParameters; import org.pentaho.reporting.libraries.base.util.IOUtils; import org.pentaho.reporting.libraries.base.util.StringUtils; import org.pentaho.reporting.libraries.docbundle.BundleUtilities; import org.pentaho.reporting.libraries.docbundle.WriteableDocumentBundle; import org.pentaho.reporting.libraries.docbundle.WriteableDocumentBundleUtils; import org.pentaho.reporting.libraries.repository.DefaultMimeRegistry; import org.pentaho.reporting.libraries.repository.MimeRegistry; import org.pentaho.reporting.libraries.resourceloader.ResourceData; import org.pentaho.reporting.libraries.resourceloader.ResourceKey; import org.pentaho.reporting.libraries.resourceloader.loader.raw.RawResourceLoader; /** * A handler that writes resources into the document bundle based on the resource keys found inside the report * definition. * * @author David Kincade */ public class ResourceWriter implements BundleWriterHandler { // The logger used in this class private static final Log log = LogFactory.getLog( ResourceWriter.class ); public ResourceWriter() { } /** * Returns a relatively low processing order indicating this BundleWriterHandler should be one of the first processed. * This is due to the fact that any Resource which is writen into the DocumentBundle will cause the report's * definition to be modified with a new Resource Key. * * @return the relative processing order for this BundleWriterHandler */ public int getProcessingOrder() { return 0; } /** * Writes a certain aspect into a own file. The name of file inside the bundle is returned as string. The file name * returned is always absolute and can be made relative by using the IOUtils of LibBase. If the writer-handler did not * generate a file on its own, it should return null. * * @param bundle * the bundle where to write to. * @param state * the writer state to hold the current processing information. * @return the name of the newly generated file or null if no file was created. * @throws IOException * if any error occured * @throws BundleWriterException * if a bundle-management error occured. */ public String writeReport( final WriteableDocumentBundle bundle, final BundleWriterState state ) throws IOException, BundleWriterException { BundleUtilities.copyStickyInto( bundle, state.getGlobalBundle() ); // Process all nodes starting at the top processSection( bundle, state.getMasterReport(), state.getMasterReport() ); // Don't return anything ... we may have created none-or-more files return null; } private void processSection( final WriteableDocumentBundle documentBundle, final MasterReport report, final Section section ) throws BundleWriterException { final int count = section.getElementCount(); for ( int i = 0; i < count; i++ ) { final ReportElement element = section.getElement( i ); if ( element instanceof Section ) { processSection( documentBundle, report, (Section) element ); } if ( element instanceof RootLevelBand ) { final RootLevelBand rl = (RootLevelBand) element; final SubReport[] reports = rl.getSubReports(); for ( int j = 0; j < reports.length; j++ ) { final SubReport subReport = reports[j]; processSection( documentBundle, report, subReport ); } } // Process the attributes if they are a resource key final ElementMetaData metaData = element.getMetaData(); final AttributeMetaData[] attributeDescriptions = metaData.getAttributeDescriptions(); for ( int j = 0; j < attributeDescriptions.length; j++ ) { final AttributeMetaData attributeDescription = attributeDescriptions[j]; if ( "Resource".equals( attributeDescription.getValueRole() ) == false ) { continue; } final Object attribute = element.getAttribute( attributeDescription.getNameSpace(), attributeDescription.getName() ); if ( attribute instanceof ResourceKey == false ) { continue; } final ResourceKey resourceKey = (ResourceKey) attribute; final ResourceKey replacementKey = processResourceKeyAttribute( documentBundle, report, resourceKey ); if ( replacementKey != null ) { element.setAttribute( attributeDescription.getNameSpace(), attributeDescription.getName(), replacementKey ); } } } } /** * Processes the resource key to see if it refers to a resource which should be embedded. If it should be embedded, * the data will be embedded and a replacement resource key will be generated. * * @param documentBundle * the document bundle in which the resources will be embedded * @param resourceKey * the resource key which may refer to a resource which should be embedded * @return a resource key which should replace the key passed in * @throws BundleWriterException * indicates an error trying to embed the resource into the document bundle */ private static ResourceKey processResourceKeyAttribute( final WriteableDocumentBundle documentBundle, final MasterReport report, final ResourceKey resourceKey ) throws BundleWriterException { // See if this key is already embedded if ( documentBundle.isEmbeddedKey( resourceKey ) ) { return null; } final boolean embedded = isEmbeddedKey( report, resourceKey ); final DefaultMimeRegistry mimeRegistry = new DefaultMimeRegistry(); // Determine if this key should be embedded final Map factoryParameters = resourceKey.getFactoryParameters(); if ( embedded == false && "true".equals( factoryParameters.get( ClassicEngineFactoryParameters.EMBED ) ) == false && RawResourceLoader.SCHEMA_NAME.equals( resourceKey.getSchema() ) == false ) { return null; } try { // Embed the key into the document bundle String mimeType = (String) factoryParameters.get( ClassicEngineFactoryParameters.MIME_TYPE ); final String originalValue = (String) factoryParameters.get( ClassicEngineFactoryParameters.ORIGINAL_VALUE ); if ( mimeType == null ) { final ResourceData resourceData = report.getResourceManager().load( resourceKey ); final Object originalMimeType = resourceData.getAttribute( ResourceData.CONTENT_TYPE ); if ( originalMimeType instanceof String ) { mimeType = (String) originalMimeType; } else { mimeType = mimeRegistry.getMimeType( originalValue ); } } String pattern = (String) factoryParameters.get( ClassicEngineFactoryParameters.PATTERN ); if ( pattern == null ) { pattern = derivePatternFromPath( mimeRegistry, mimeType, resourceKey.getIdentifierAsString() ); } log.debug( "Embedding resource : originalValue=[" + originalValue + "] pattern=[" + pattern + "] mimeType=[" + mimeType + "]" ); // Clean up the factory parameters - we are only keeping the Original Value parameter Map newFactoryParameters = null; if ( originalValue != null ) { newFactoryParameters = new HashMap(); newFactoryParameters.put( ClassicEngineFactoryParameters.ORIGINAL_VALUE, originalValue ); } // Embed the resource final ResourceKey newResourceKey = WriteableDocumentBundleUtils.embedResource( documentBundle, report.getResourceManager(), resourceKey, pattern, mimeType, newFactoryParameters ); if ( log.isDebugEnabled() ) { log.debug( "Resouce Embedded: [" + newResourceKey.getIdentifierAsString() + "]" ); } return newResourceKey; } catch ( Exception e ) { throw new BundleWriterException( "Error embedding the resource into the document bundle: " + e.getMessage(), e ); } } public static boolean isEmbeddedKey( final MasterReport report, final ResourceKey resourceKey ) { final ResourceKey contentBase = report.getContentBase(); if ( contentBase == null ) { return false; } ResourceKey bundleKey = contentBase.getParent(); while ( bundleKey != null ) { if ( bundleKey.equals( resourceKey.getParent() ) ) { return true; } bundleKey = bundleKey.getParent(); } return false; } public static String derivePatternFromPath( final MimeRegistry mimeRegistry, final String mimeType, final String path ) { if ( mimeType == null ) { throw new NullPointerException(); } if ( path == null ) { if ( mimeType.startsWith( "image/" ) ) { return "resources/image{0}." + mimeRegistry.getSuffix( mimeType ); } else { return "resources/data{0}." + mimeRegistry.getSuffix( mimeType ); } } final String directory = IOUtils.getInstance().getPath( path ); final String fileNameWExt = IOUtils.getInstance().getFileName( path ); String fileExtension = IOUtils.getInstance().getFileExtension( fileNameWExt ); if ( StringUtils.isEmpty( fileExtension ) ) { fileExtension = "." + mimeRegistry.getSuffix( mimeType ); } final String fileName = IOUtils.getInstance().stripFileExtension( fileNameWExt ); String pattern = stripTrailingNumbers( fileName ); if ( pattern == null ) { if ( mimeType.startsWith( "image/" ) ) { pattern = "image{0}"; } else { pattern = "data{0}"; } } else { pattern = pattern + "{0}"; } return directory + "/" + pattern + fileExtension; } private static String stripTrailingNumbers( final String path ) { for ( int i = path.length() - 1; i >= 0; i-- ) { if ( Character.isDigit( path.charAt( i ) ) == false ) { return path.substring( 0, i + 1 ); } } // if its empty or all numbers, return null. return null; } }