/*
* 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) 2001 - 2013 Object Refinery Ltd, Pentaho Corporation and Contributors.. All rights reserved.
*/
package org.pentaho.reporting.engine.classic.core.modules.parser.bundle.writer.elements;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.pentaho.reporting.engine.classic.core.AttributeNames;
import org.pentaho.reporting.engine.classic.core.Band;
import org.pentaho.reporting.engine.classic.core.Element;
import org.pentaho.reporting.engine.classic.core.ParameterMapping;
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.function.Expression;
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.metadata.ElementType;
import org.pentaho.reporting.engine.classic.core.metadata.ElementTypeRegistry;
import org.pentaho.reporting.engine.classic.core.metadata.ResourceReference;
import org.pentaho.reporting.engine.classic.core.modules.parser.bundle.BundleElementRegistry;
import org.pentaho.reporting.engine.classic.core.modules.parser.bundle.BundleNamespaces;
import org.pentaho.reporting.engine.classic.core.modules.parser.bundle.writer.BundleElementWriteHandler;
import org.pentaho.reporting.engine.classic.core.modules.parser.bundle.writer.BundleWriterException;
import org.pentaho.reporting.engine.classic.core.modules.parser.bundle.writer.BundleWriterState;
import org.pentaho.reporting.engine.classic.core.modules.parser.bundle.writer.ExpressionWriterUtility;
import org.pentaho.reporting.engine.classic.core.modules.parser.bundle.writer.StyleWriterUtility;
import org.pentaho.reporting.engine.classic.core.style.StyleKey;
import org.pentaho.reporting.engine.classic.core.util.beans.BeanException;
import org.pentaho.reporting.engine.classic.core.util.beans.ConverterRegistry;
import org.pentaho.reporting.libraries.base.util.IOUtils;
import org.pentaho.reporting.libraries.base.util.ObjectUtilities;
import org.pentaho.reporting.libraries.docbundle.BundleUtilities;
import org.pentaho.reporting.libraries.docbundle.WriteableDocumentBundle;
import org.pentaho.reporting.libraries.resourceloader.ResourceException;
import org.pentaho.reporting.libraries.resourceloader.ResourceKey;
import org.pentaho.reporting.libraries.resourceloader.ResourceManager;
import org.pentaho.reporting.libraries.xmlns.common.AttributeList;
import org.pentaho.reporting.libraries.xmlns.writer.XmlWriter;
import org.pentaho.reporting.libraries.xmlns.writer.XmlWriterSupport;
import java.beans.PropertyEditor;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.Map;
/**
* Provides a base implementation for element write handlers.
*
* @author Thomas Morgner
*/
public abstract class AbstractElementWriteHandler implements BundleElementWriteHandler {
private static final Log logger = LogFactory.getLog( AbstractElementWriteHandler.class );
protected AbstractElementWriteHandler() {
}
protected void copyStaticResources( final WriteableDocumentBundle bundle, final BundleWriterState state,
final Element element ) throws BundleWriterException {
if ( bundle == null ) {
throw new NullPointerException();
}
if ( state == null ) {
throw new NullPointerException();
}
if ( element == null ) {
throw new NullPointerException();
}
final ResourceKey contentBase = element.getContentBase();
if ( contentBase == null ) {
// treat all resources as linked resources ..
AbstractElementWriteHandler.logger.debug( "No content base, treating all content as linked." );
return;
}
final ResourceKey defSource = element.getDefinitionSource();
if ( defSource == null ) {
// treat all resources as linked resources ..
AbstractElementWriteHandler.logger.debug( "No report definition source, treating all content as linked." );
return;
}
if ( ObjectUtilities.equal( contentBase.getParent(), defSource.getParent() ) == false ) {
// treat all resources as linked resources ..
AbstractElementWriteHandler.logger
.debug( "Content base points to non-bundle location, treating all content as linked." );
return;
}
final Object contentBasePathRaw = contentBase.getIdentifier();
if ( contentBasePathRaw instanceof String == false ) {
return;
}
final String contentBasePath = String.valueOf( contentBasePathRaw );
final ResourceManager resourceManager = state.getMasterReport().getResourceManager();
final ElementType type = element.getElementType();
final ElementMetaData data = type.getMetaData();
final AttributeMetaData[] datas = data.getAttributeDescriptions();
for ( int i = 0; i < datas.length; i++ ) {
final AttributeMetaData attributeMetaData = datas[i];
if ( attributeMetaData.isTransient() ) {
continue;
}
if ( isFiltered( attributeMetaData ) ) {
continue;
}
final Object attValue = element.getAttribute( attributeMetaData.getNameSpace(), attributeMetaData.getName() );
if ( attValue == null ) {
continue;
}
final ResourceReference[] referencedResources =
attributeMetaData.getReferencedResources( element, state.getMasterReport().getResourceManager(), attValue );
for ( int j = 0; j < referencedResources.length; j++ ) {
final ResourceReference reference = referencedResources[j];
if ( reference.isLinked() ) {
AbstractElementWriteHandler.logger.debug( "Linked Resource will not be copied into bundle: " + reference );
continue;
}
final ResourceKey path = reference.getPath();
final Object identifier = path.getIdentifier();
if ( identifier instanceof String == false ) {
AbstractElementWriteHandler.logger.warn( "Resource-Bundle-Key has no parseable path: " + path );
continue;
}
final String identifierString = String.valueOf( identifier );
final String relativePath = IOUtils.getInstance().createRelativePath( identifierString, contentBasePath );
try {
BundleUtilities.copyInto( bundle, relativePath, path, resourceManager );
} catch ( Exception e ) {
throw new BundleWriterException( "Failed to copy content from key " + path, e );
}
AbstractElementWriteHandler.logger.debug( "Copied " + path + " as " + relativePath );
}
}
}
protected boolean isFiltered( final AttributeMetaData attributeMetaData ) {
if ( AttributeNames.Core.NAMESPACE.equals( attributeMetaData.getNameSpace() ) ) {
if ( AttributeNames.Core.ELEMENT_TYPE.equals( attributeMetaData.getName() ) ) {
return true;
}
}
return false;
}
protected AttributeList createMainAttributes( final Element element, final XmlWriter writer ) {
return createMainAttributes( element, writer, new AttributeList() );
}
protected AttributeList createMainAttributes( final Element element, final XmlWriter writer,
final AttributeList attList ) {
if ( element == null ) {
throw new NullPointerException();
}
if ( writer == null ) {
throw new NullPointerException();
}
if ( attList == null ) {
throw new NullPointerException();
}
final ElementMetaData metaData = element.getElementType().getMetaData();
final String[] attributeNamespaces = element.getAttributeNamespaces();
for ( int i = 0; i < attributeNamespaces.length; i++ ) {
final String namespace = attributeNamespaces[i];
final String[] attributeNames = element.getAttributeNames( namespace );
for ( int j = 0; j < attributeNames.length; j++ ) {
final String name = attributeNames[j];
final Object value = element.getAttribute( namespace, name );
if ( value == null ) {
continue;
}
final AttributeMetaData attrMeta = metaData.getAttributeDescription( namespace, name );
if ( attrMeta == null ) {
if ( value instanceof String ) {
ensureNamespaceDefined( writer, attList, namespace );
// preserve strings, but discard anything else. Until a attribute has a definition, we cannot
// hope to understand the attribute's value. String-attributes can be expressed in XML easily,
// and string is also how all unknown attributes are stored by the parser.
attList.setAttribute( namespace, name, String.valueOf( value ) );
}
continue;
}
if ( attrMeta.isTransient() ) {
continue;
}
if ( isFiltered( attrMeta ) ) {
continue;
}
if ( attrMeta.isBulk() ) {
continue;
}
ensureNamespaceDefined( writer, attList, namespace );
try {
final PropertyEditor propertyEditor = attrMeta.getEditor();
if ( propertyEditor != null ) {
propertyEditor.setValue( value );
attList.setAttribute( namespace, name, propertyEditor.getAsText() );
} else {
final String attrValue = ConverterRegistry.toAttributeValue( value );
attList.setAttribute( namespace, name, attrValue );
}
} catch ( BeanException e ) {
AbstractElementWriteHandler.logger.warn( "Attribute '" + namespace + '|' + name
+ "' is not convertible with the bean-methods" );
}
}
}
return attList;
}
protected void ensureNamespaceDefined( final XmlWriter writer, final AttributeList attList, final String namespace ) {
if ( writer.isNamespaceDefined( namespace ) == false && attList.isNamespaceUriDefined( namespace ) == false ) {
final String prefix = ElementTypeRegistry.getInstance().getNamespacePrefix( namespace );
if ( prefix != null ) {
if ( writer.isNamespacePrefixDefined( prefix ) == false ) {
attList.addNamespaceDeclaration( prefix, namespace );
return;
}
}
attList.addNamespaceDeclaration( "autoGenNs", namespace );
}
}
protected void writeElementBody( final WriteableDocumentBundle bundle, final BundleWriterState state,
final Element element, final XmlWriter writer ) throws IOException, BundleWriterException {
if ( bundle == null ) {
throw new NullPointerException();
}
if ( state == null ) {
throw new NullPointerException();
}
if ( element == null ) {
throw new NullPointerException();
}
if ( writer == null ) {
throw new NullPointerException();
}
StyleWriterUtility.writeStyleRule( BundleNamespaces.STYLE, "element-style", writer, element.getStyle() );
writeStyleExpressions( bundle, state, element, writer );
writeBulkAttributes( bundle, state, element, writer );
writeAttributeExpressions( bundle, state, element, writer );
}
private void writeBulkAttributes( final WriteableDocumentBundle bundle, final BundleWriterState state,
final Element element, final XmlWriter writer ) throws IOException, BundleWriterException {
final ElementMetaData metaData = element.getElementType().getMetaData();
final String[] attributeNamespaces = element.getAttributeNamespaces();
for ( int i = 0; i < attributeNamespaces.length; i++ ) {
final String namespace = attributeNamespaces[i];
final String[] attributeNames = element.getAttributeNames( namespace );
for ( int j = 0; j < attributeNames.length; j++ ) {
final String name = attributeNames[j];
final Object value = element.getAttribute( namespace, name );
if ( value == null ) {
continue;
}
final AttributeMetaData attrMeta = metaData.getAttributeDescription( namespace, name );
if ( attrMeta == null ) {
continue;
}
if ( attrMeta.isTransient() ) {
continue;
}
if ( isFiltered( attrMeta ) ) {
continue;
}
if ( attrMeta.isBulk() == false ) {
continue;
}
if ( "Resource".equals( attrMeta.getValueRole() ) ) {
final AttributeList attList = new AttributeList();
if ( attList.isNamespaceUriDefined( namespace ) == false && writer.isNamespaceDefined( namespace ) == false ) {
attList.addNamespaceDeclaration( "autoGenNS", namespace );
}
if ( value instanceof URL ) {
attList.setAttribute( AttributeNames.Core.NAMESPACE, "resource-type", "url" );
writer.writeTag( namespace, attrMeta.getName(), attList, XmlWriter.OPEN );
writer.writeTextNormalized( String.valueOf( value ), true );
writer.writeCloseTag();
} else if ( value instanceof ResourceKey ) {
try {
final ResourceKey key = (ResourceKey) value;
final ResourceManager resourceManager = bundle.getResourceManager();
final ResourceKey bundleKey = bundle.getBundleKey().getParent();
final String serializedKey = resourceManager.serialize( bundleKey, key );
attList.setAttribute( AttributeNames.Core.NAMESPACE, "resource-type", "resource-key" );
writer.writeTag( namespace, attrMeta.getName(), attList, XmlWriter.OPEN );
writer.writeTextNormalized( serializedKey, true );
writer.writeCloseTag();
} catch ( ResourceException re ) {
logger.error( "Could not serialize the ResourceKey: " + re.getMessage(), re );
throw new IOException( "Could not serialize the ResourceKey: ", re );
}
} else if ( value instanceof File ) {
attList.setAttribute( AttributeNames.Core.NAMESPACE, "resource-type", "file" );
writer.writeTag( namespace, attrMeta.getName(), attList, XmlWriter.OPEN );
writer.writeTextNormalized( String.valueOf( value ), true );
writer.writeCloseTag();
} else if ( value instanceof String ) {
attList.setAttribute( AttributeNames.Core.NAMESPACE, "resource-type", "local-ref" );
writer.writeTag( namespace, attrMeta.getName(), attList, XmlWriter.OPEN );
writer.writeTextNormalized( String.valueOf( value ), true );
writer.writeCloseTag();
} else {
logger.warn( "Unknown value-type in resource-attribute " + namespace + ":" + name );
}
continue;
}
if ( "Expression".equals( attrMeta.getValueRole() ) && value instanceof Expression ) {
// write attribute-expressions.
final AttributeList attList = new AttributeList();
attList.setAttribute( BundleNamespaces.LAYOUT, "attribute-namespace", namespace );
attList.setAttribute( BundleNamespaces.LAYOUT, "attribute-name", name );
ExpressionWriterUtility.writeExpressionCore( bundle, state, (Expression) value, writer,
BundleNamespaces.LAYOUT, "expression", attList );
continue;
}
try {
final PropertyEditor propertyEditor = attrMeta.getEditor();
if ( propertyEditor != null ) {
propertyEditor.setValue( value );
final String text = propertyEditor.getAsText();
final AttributeList attList = new AttributeList();
if ( attList.isNamespaceUriDefined( namespace ) == false && writer.isNamespaceDefined( namespace ) == false ) {
attList.addNamespaceDeclaration( "autoGenNS", namespace );
}
writer.writeTag( namespace, attrMeta.getName(), attList, XmlWriter.OPEN );
writer.writeTextNormalized( text, true );
writer.writeCloseTag();
} else {
final String attrValue = ConverterRegistry.toAttributeValue( value );
final AttributeList attList = new AttributeList();
if ( attList.isNamespaceUriDefined( namespace ) == false && writer.isNamespaceDefined( namespace ) == false ) {
attList.addNamespaceDeclaration( "autoGenNS", namespace );
}
writer.writeTag( namespace, attrMeta.getName(), attList, XmlWriter.OPEN );
writer.writeTextNormalized( attrValue, true );
writer.writeCloseTag();
}
} catch ( BeanException e ) {
AbstractElementWriteHandler.logger.warn( "Attribute '" + namespace + '|' + name
+ "' is not convertible with the bean-methods" );
}
}
}
}
protected void writeAttributeExpressions( final WriteableDocumentBundle bundle, final BundleWriterState state,
final Element element, final XmlWriter writer ) throws IOException, BundleWriterException {
if ( bundle == null ) {
throw new NullPointerException();
}
if ( state == null ) {
throw new NullPointerException();
}
if ( element == null ) {
throw new NullPointerException();
}
if ( writer == null ) {
throw new NullPointerException();
}
// write attribute-expressions.
final String[] attributeNamespaces = element.getAttributeExpressionNamespaces();
for ( int i = 0; i < attributeNamespaces.length; i++ ) {
final String namespace = attributeNamespaces[i];
final String[] attributeNames = element.getAttributeExpressionNames( namespace );
for ( int j = 0; j < attributeNames.length; j++ ) {
final String name = attributeNames[j];
final AttributeList attList = new AttributeList();
attList.setAttribute( BundleNamespaces.LAYOUT, "namespace", namespace );
attList.setAttribute( BundleNamespaces.LAYOUT, "name", name );
final Expression ex = element.getAttributeExpression( namespace, name );
ExpressionWriterUtility.writeExpressionCore( bundle, state, ex, writer, BundleNamespaces.LAYOUT,
"attribute-expression", attList );
}
}
}
protected void writeStyleExpressions( final WriteableDocumentBundle bundle, final BundleWriterState state,
final Element element, final XmlWriter writer ) throws IOException, BundleWriterException {
if ( bundle == null ) {
throw new NullPointerException();
}
if ( state == null ) {
throw new NullPointerException();
}
if ( element == null ) {
throw new NullPointerException();
}
if ( writer == null ) {
throw new NullPointerException();
}
// write style expressions.
final Map<StyleKey, Expression> styleExpressions = element.getStyleExpressions();
for ( final Map.Entry<StyleKey, Expression> entry : styleExpressions.entrySet() ) {
final StyleKey key = entry.getKey();
final Expression ex = entry.getValue();
ExpressionWriterUtility.writeStyleExpression( bundle, state, ex, writer, key, BundleNamespaces.LAYOUT,
"style-expression" );
}
}
protected void writeChildElements( final WriteableDocumentBundle bundle, final BundleWriterState state,
final XmlWriter xmlWriter, final Section section ) throws IOException, BundleWriterException {
if ( bundle == null ) {
throw new NullPointerException();
}
if ( state == null ) {
throw new NullPointerException();
}
if ( xmlWriter == null ) {
throw new NullPointerException();
}
if ( section == null ) {
throw new NullPointerException();
}
if ( section instanceof Band ) {
// A band can only contain other, non-root level bands and subreports. Any root-level band encountered
// should have been converted into a normal band by the author. Instead of silently failing or trying
// to fix the bad code, let's fail with a clean exception and let the developer fix their code.
for ( final Element e : section ) {
if ( e instanceof RootLevelBand ) {
throw new BundleWriterException( "This report cannot be saved. A normal band cannot contain other "
+ "root-level bands as children unless they are contained in a subreport." );
}
if ( e instanceof Section && e instanceof Band == false && e instanceof SubReport == false ) {
// must be a group, or a group-body or a master-report. This is Invalid!
throw new BundleWriterException( String.format( "This report cannot be saved. A normal band can only "
+ "contain other data elements, bands or subreports as children. You cannot add "
+ "structural elements such as '%s' here.", e.getElementTypeName() ) );
}
}
}
for ( final Element e : section ) {
final BundleElementWriteHandler writeHandler = BundleElementRegistry.getInstance().getWriteHandler( e );
writeHandler.writeElement( bundle, state, xmlWriter, e );
}
}
protected void writeRootSubReports( final WriteableDocumentBundle bundle, final BundleWriterState state,
final XmlWriter xmlWriter, final RootLevelBand rootLevelBand ) throws IOException, BundleWriterException {
if ( bundle == null ) {
throw new NullPointerException();
}
if ( state == null ) {
throw new NullPointerException();
}
if ( xmlWriter == null ) {
throw new NullPointerException();
}
if ( rootLevelBand == null ) {
throw new NullPointerException();
}
final SubReport[] reports = rootLevelBand.getSubReports();
for ( int i = 0; i < reports.length; i++ ) {
writeSubReport( bundle, state, xmlWriter, reports[i] );
}
}
protected void writeSubReport( final WriteableDocumentBundle bundle, final BundleWriterState state,
final XmlWriter xmlWriter, final SubReport subReport ) throws IOException, BundleWriterException {
if ( bundle == null ) {
throw new NullPointerException();
}
if ( state == null ) {
throw new NullPointerException();
}
if ( xmlWriter == null ) {
throw new NullPointerException();
}
if ( subReport == null ) {
throw new NullPointerException();
}
final String absolutePath = computePath( state );
final String directory = BundleUtilities.getUniqueName( bundle, absolutePath + "/subreport{0}" );
bundle.createDirectoryEntry( directory, "application/vnd.pentaho.reporting.classic.subreport" );
final String dirRelative = IOUtils.getInstance().createRelativePath( directory, absolutePath + "/." );
final BundleWriterState subReportState = new BundleWriterState( state, subReport, dirRelative + '/' );
state.getBundleWriter().writeSubReport( bundle, subReportState );
final ParameterMapping[] inputMappings = subReport.getInputMappings();
final ParameterMapping[] outputMappings = subReport.getExportMappings();
ElementMetaData metaData = subReport.getMetaData();
final String tagName = metaData.getName();
final String namespace = metaData.getNamespace();
if ( inputMappings.length == 0 && outputMappings.length == 0 ) {
xmlWriter.writeTag( namespace, tagName, "href", '/' + subReportState.getFileName() + "content.xml",
XmlWriterSupport.CLOSE );
} else {
xmlWriter.writeTag( namespace, tagName, "href", '/' + subReportState.getFileName() + "content.xml",
XmlWriterSupport.OPEN );
for ( int i = 0; i < inputMappings.length; i++ ) {
final ParameterMapping mapping = inputMappings[i];
final AttributeList attrs = new AttributeList();
attrs.setAttribute( BundleNamespaces.LAYOUT, "master-fieldname", mapping.getName() );
attrs.setAttribute( BundleNamespaces.LAYOUT, "detail-fieldname", mapping.getAlias() );
xmlWriter.writeTag( BundleNamespaces.LAYOUT, "input-parameter", attrs, XmlWriterSupport.CLOSE );
}
for ( int i = 0; i < outputMappings.length; i++ ) {
final ParameterMapping mapping = outputMappings[i];
final AttributeList attrs = new AttributeList();
attrs.setAttribute( BundleNamespaces.LAYOUT, "master-fieldname", mapping.getName() );
attrs.setAttribute( BundleNamespaces.LAYOUT, "detail-fieldname", mapping.getAlias() );
xmlWriter.writeTag( BundleNamespaces.LAYOUT, "output-parameter", attrs, XmlWriterSupport.CLOSE );
}
xmlWriter.writeCloseTag();
}
}
private String computePath( final BundleWriterState state ) {
final String absolutePathWithDummy = IOUtils.getInstance().getAbsolutePath( "dummy", state.getFileName() );
final String absolutePath = absolutePathWithDummy.substring( 0, absolutePathWithDummy.length() - "dummy".length() );
if ( absolutePath.endsWith( "/" ) ) {
return absolutePath.substring( 0, absolutePath.length() - 1 );
}
return absolutePath;
}
}