/* * 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.content; 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.ClassicEngineBoot; import org.pentaho.reporting.engine.classic.core.ClassicEngineInfo; import org.pentaho.reporting.engine.classic.core.DataFactory; import org.pentaho.reporting.engine.classic.core.MasterReport; import org.pentaho.reporting.engine.classic.core.ReportDataFactoryException; import org.pentaho.reporting.engine.classic.core.function.Expression; import org.pentaho.reporting.engine.classic.core.modules.parser.base.ReportParserUtil; import org.pentaho.reporting.engine.classic.core.modules.parser.bundle.BundleNamespaces; import org.pentaho.reporting.engine.classic.core.modules.parser.bundle.data.DataDefinition; import org.pentaho.reporting.engine.classic.core.modules.parser.bundle.settings.BundleSettings; import org.pentaho.reporting.engine.classic.core.parameters.ReportParameterDefinition; import org.pentaho.reporting.engine.classic.core.wizard.DataSchemaDefinition; import org.pentaho.reporting.libraries.base.config.Configuration; import org.pentaho.reporting.libraries.docbundle.DocumentBundle; import org.pentaho.reporting.libraries.docbundle.DocumentMetaData; import org.pentaho.reporting.libraries.resourceloader.FactoryParameterKey; import org.pentaho.reporting.libraries.resourceloader.Resource; 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 org.pentaho.reporting.libraries.xmlns.parser.AbstractXmlReadHandler; import org.pentaho.reporting.libraries.xmlns.parser.IgnoreAnyChildReadHandler; import org.pentaho.reporting.libraries.xmlns.parser.ParseException; import org.pentaho.reporting.libraries.xmlns.parser.RootXmlReadHandler; import org.pentaho.reporting.libraries.xmlns.parser.XmlReadHandler; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; import java.util.Enumeration; import java.util.Map; /** * The content root handler is the first handler that is parsed when dealing with bundle-reports. This file contains all * the forwards to the various report-files. * <p/> * A bundle always contains a master-report. It is not possible to parse a full bundle into a subreport. However, it is * possible to parse the subreport xml files contained in a bundle into subreports, if needed. * * @author Thomas Morgner */ public class ContentRootElementHandler extends AbstractXmlReadHandler { private static final Log logger = LogFactory.getLog( ContentRootElementHandler.class ); public static final String PRPT_SPEC_VERSION = "prpt-spec-version"; private MasterReport report; public ContentRootElementHandler() { } /** * Initialises the handler. * * @param rootHandler * the root handler. * @param tagName * the tag name. */ public void init( final RootXmlReadHandler rootHandler, final String uri, final String tagName ) throws SAXException { super.init( rootHandler, uri, tagName ); rootHandler.setHelperObject( "property-expansion", Boolean.FALSE ); } /** * Starts parsing. * * @param attrs * the attributes. * @throws SAXException * if there is a parsing error. */ protected void startParsing( final Attributes attrs ) throws SAXException { final Object maybeReport = getRootHandler().getHelperObject( ReportParserUtil.HELPER_OBJ_REPORT_NAME ); if ( maybeReport instanceof MasterReport == false ) { // replace it .. report = new MasterReport(); report.setAttribute( AttributeNames.Internal.NAMESPACE, AttributeNames.Internal.FILEFORMAT, "unified-fileformat" ); final ResourceKey key = getRootHandler().getSource(); if ( key.getParent() != null ) { final ResourceManager resourceManager = getRootHandler().getResourceManager(); try { final Resource bundleData = resourceManager.create( key.getParent(), null, DocumentBundle.class ); final DocumentBundle documentBundle = (DocumentBundle) bundleData.getResource(); report.setBundle( documentBundle ); final DocumentMetaData metaData = documentBundle.getMetaData(); final int versionMajorRaw = getBundleAttribute( metaData, "prpt-spec.version.major" ); final int versionMinorRaw = getBundleAttribute( metaData, "prpt-spec.version.minor" ); final int versionPatchRaw = getBundleAttribute( metaData, "prpt-spec.version.patch" ); if ( versionMajorRaw == -1 || versionMinorRaw == -1 || versionPatchRaw == -1 || ( versionMajorRaw == 0 && versionMinorRaw == 0 && versionPatchRaw == 0 ) ) { // any of the version attributes is missing. Assume we are running with a legacy report. getRootHandler().setHelperObject( PRPT_SPEC_VERSION, ClassicEngineBoot.computeVersionId( 3, 8, 0 ) ); } else { validateVersionNumbers( versionMajorRaw, versionMinorRaw, versionPatchRaw ); // file has been created with at least 3.9.0 or a development version. Therefore the // version number is set to either the number or zero for dev versions. getRootHandler().setHelperObject( PRPT_SPEC_VERSION, ClassicEngineBoot.computeVersionId( versionMajorRaw, versionMinorRaw, versionPatchRaw ) ); } } catch ( ResourceException e ) { getRootHandler().warning( new SAXParseException( "Unable to load the bundle. Bundle data may be unavailable.", getLocator() ) ); } } } else { report = (MasterReport) maybeReport; } } private void validateVersionNumbers( final int prptVersionMajorRaw, final int prptVersionMinorRaw, final int prptVersionPatchRaw ) throws ParseException { final ClassicEngineBoot.VersionValidity v = ClassicEngineBoot.isValidVersion( prptVersionMajorRaw, prptVersionMinorRaw, prptVersionPatchRaw ); if ( v == ClassicEngineBoot.VersionValidity.INVALID_RELEASE ) { throw new ParseException( String.format( "The report file you are trying to load was created with " + "Pentaho Reporting %d.%d but you are trying to run it with Pentaho Reporting %s. " + "Please update your reporting installation to match the report designer that was used " + "to create this file.", prptVersionMajorRaw, prptVersionMinorRaw, ClassicEngineInfo.getInstance() .getVersion() ) ); } else if ( v == ClassicEngineBoot.VersionValidity.INVALID_PATCH ) { logger.warn( String.format( "The report file you are trying to load was created with Pentaho " + "Reporting %d.%d.%d but you are trying to run it with Pentaho Reporting %s. " + "Your reporting engine version may not have all features or bug-fixes required to display " + "this report properly.", prptVersionMajorRaw, prptVersionMinorRaw, prptVersionPatchRaw, ClassicEngineInfo .getInstance().getVersion() ) ); } } private int getBundleAttribute( final DocumentMetaData metaData, final String name ) { final Object raw = metaData.getBundleAttribute( ClassicEngineBoot.METADATA_NAMESPACE, name ); if ( raw == null ) { return -1; } try { return Integer.parseInt( String.valueOf( raw ) ); } catch ( NumberFormatException nfe ) { return -1; } } /** * Returns the handler for a child element. * * @param uri * the URI of the namespace of the current element. * @param tagName * the tag name. * @param atts * the attributes. * @return the handler or null, if the tagname is invalid. * @throws SAXException * if there is a parsing error. */ protected XmlReadHandler getHandlerForChild( final String uri, final String tagName, final Attributes atts ) throws SAXException { if ( BundleNamespaces.CONTENT.equals( uri ) == false ) { return null; } if ( "settings".equals( tagName ) ) { final String primaryFile = atts.getValue( getUri(), "ref" ); if ( primaryFile == null ) { throw new ParseException( "Required attribute 'ref' is not specified", getLocator() ); } if ( parseSettings( primaryFile ) == false ) { final String fallbackFile = atts.getValue( getUri(), "local-copy" ); if ( fallbackFile != null ) { if ( parseSettings( fallbackFile ) == false ) { throw new ParseException( "Parsing the specified local-copy failed", getLocator() ); } } } return new IgnoreAnyChildReadHandler(); } if ( "data-definition".equals( tagName ) ) { final String primaryFile = atts.getValue( getUri(), "ref" ); if ( primaryFile == null ) { throw new ParseException( "Required attribute 'ref' is not specified", getLocator() ); } if ( parseDataDefinition( primaryFile ) == false ) { final String fallbackFile = atts.getValue( getUri(), "local-copy" ); if ( fallbackFile != null ) { if ( parseDataDefinition( fallbackFile ) == false ) { throw new ParseException( "Parsing the specified local-copy failed", getLocator() ); } } } return new IgnoreAnyChildReadHandler(); } if ( "styles".equals( tagName ) ) { final String primaryFile = atts.getValue( getUri(), "ref" ); if ( primaryFile == null ) { throw new ParseException( "Required attribute 'ref' is not specified", getLocator() ); } if ( parseStyles( primaryFile ) == false ) { final String fallbackFile = atts.getValue( getUri(), "local-copy" ); if ( fallbackFile != null ) { if ( parseStyles( fallbackFile ) == false ) { throw new ParseException( "Parsing the specified local-copy failed", getLocator() ); } } } return new IgnoreAnyChildReadHandler(); } if ( "layout".equals( tagName ) ) { final String primaryFile = atts.getValue( getUri(), "ref" ); if ( primaryFile == null ) { throw new ParseException( "Required attribute 'ref' is not specified", getLocator() ); } if ( parseLayout( primaryFile ) == false ) { final String fallbackFile = atts.getValue( getUri(), "local-copy" ); if ( fallbackFile != null ) { if ( parseLayout( fallbackFile ) == false ) { throw new ParseException( "Parsing the specified local-copy failed", getLocator() ); } } } return new IgnoreAnyChildReadHandler(); } return null; } /** * Done parsing. * * @throws SAXException * if there is a parsing error. */ protected void doneParsing() throws SAXException { // Now, after all the user-defined and global files have been parsed, finally override whatever had been // defined in these files with the contents from the bundle. This will merge all the settings from the bundle // with the global definitions but grants the local settings higer preference parseLocalFiles(); final Object definedCompatLevel = report.getCompatibilityLevel(); if ( definedCompatLevel instanceof Integer == false ) { final Object specRaw = getRootHandler().getHelperObject( PRPT_SPEC_VERSION ); final Integer x = ClassicEngineBoot.computeVersionId( 999, 999, 999 ); if ( specRaw instanceof Integer && x.equals( specRaw ) == false ) { report.setCompatibilityLevel( (Integer) specRaw ); } else { report.setCompatibilityLevel( x ); } } } private void parseLocalFiles() throws ParseException { parseSettings( "settings.xml" ); parseDataDefinition( "datadefinition.xml" ); parseDataSchema(); parseStyles( "styles.xml" ); parseLayout( "layout.xml" ); } private boolean parseLayout( final String layout ) throws ParseException { try { final Map parameters = deriveParseParameters(); parameters.put( new FactoryParameterKey( ReportParserUtil.HELPER_OBJ_REPORT_NAME ), report ); parameters.put( new FactoryParameterKey( ReportParserUtil.INCLUDE_PARSING_KEY ), ReportParserUtil.INCLUDE_PARSING_VALUE ); final MasterReport report = (MasterReport) performExternalParsing( layout, MasterReport.class, parameters ); return report == this.report; } catch ( ResourceLoadingException e ) { ContentRootElementHandler.logger.warn( "Unable to parse the parameter for this bundle from file: " + layout ); return false; } } private boolean parseStyles( final String stylefile ) throws ParseException { try { final Map parameters = deriveParseParameters(); parameters.put( new FactoryParameterKey( ReportParserUtil.HELPER_OBJ_REPORT_NAME ), report ); parameters.put( new FactoryParameterKey( ReportParserUtil.INCLUDE_PARSING_KEY ), ReportParserUtil.INCLUDE_PARSING_VALUE ); final MasterReport report = (MasterReport) performExternalParsing( stylefile, MasterReport.class, parameters ); return report == this.report; } catch ( ResourceLoadingException e ) { ContentRootElementHandler.logger.warn( "Unable to parse the parameter for this bundle from file: " + stylefile ); return false; } } private boolean parseDataDefinition( final String parameterFile ) throws ParseException { try { final Map parameters = deriveParseParameters(); parameters.put( new FactoryParameterKey( ReportParserUtil.HELPER_OBJ_REPORT_NAME ), null ); final DataDefinition dataDefinition = (DataDefinition) performExternalParsing( parameterFile, DataDefinition.class, parameters ); report.setQuery( dataDefinition.getQuery() ); report.setQueryLimit( dataDefinition.getQueryLimit() ); report.setQueryTimeout( dataDefinition.getQueryTimeout() ); final DataFactory dataFactory = dataDefinition.getDataFactory(); if ( dataFactory != null ) { report.setDataFactory( dataFactory ); } final ReportParameterDefinition definition = dataDefinition.getParameterDefinition(); if ( definition != null ) { report.setParameterDefinition( definition ); } final Expression[] expressions = dataDefinition.getExpressions(); if ( expressions != null ) { for ( int i = 0; i < expressions.length; i++ ) { final Expression expression = expressions[i]; report.addExpression( expression ); } } return true; } catch ( ResourceLoadingException e ) { ContentRootElementHandler.logger.warn( "Unable to parse the parameter for this bundle from file: " + parameterFile ); return false; } catch ( ReportDataFactoryException e ) { ContentRootElementHandler.logger.warn( "Unable to parse the parameter for this bundle from file: " + parameterFile ); return false; } } private boolean parseSettings( final String settingsFile ) throws ParseException { try { final Map parameters = deriveParseParameters(); parameters.put( new FactoryParameterKey( ReportParserUtil.HELPER_OBJ_REPORT_NAME ), null ); final BundleSettings settings = (BundleSettings) performExternalParsing( settingsFile, BundleSettings.class, parameters ); // todo: Apply settings final Configuration configuration = settings.getConfiguration(); final Enumeration configProperties = configuration.getConfigProperties(); while ( configProperties.hasMoreElements() ) { final String key = (String) configProperties.nextElement(); final String value = configuration.getConfigProperty( key ); if ( value != null ) { report.getReportConfiguration().setConfigProperty( key, value ); } } return true; } catch ( ResourceLoadingException e ) { ContentRootElementHandler.logger.warn( "Unable to parse the settingsFile for this bundle from file: " + settingsFile ); return false; } } /** * Parsing the meta-data is error-resistant. If parsing the meta-data fails for a "file-not-found-error", the parse * continues. Meta-data parsing is not mergeable - there is only one source for the meta-data of a report-bundle. * * @return true, if the meta-data bundle has been updated, false otherwise. * @throws ParseException * if the parsing fails. */ private boolean parseDataSchema() throws ParseException { try { final Map parameters = deriveParseParameters(); parameters.put( new FactoryParameterKey( ReportParserUtil.HELPER_OBJ_REPORT_NAME ), null ); final DataSchemaDefinition metaData = (DataSchemaDefinition) performExternalParsing( "dataschema.xml", DataSchemaDefinition.class, parameters ); report.setDataSchemaDefinition( metaData ); return true; } catch ( ResourceLoadingException e ) { ContentRootElementHandler.logger.warn( "Unable to parse the dataschema for this bundle." ); return false; } } /** * Returns the object for this element or null, if this element does not create an object. * * @return the object. * @throws SAXException * if an parser error occured. */ public Object getObject() throws SAXException { return report; } }