/*
* 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) 2009 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.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 ing 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 budle 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;
}
}