/* * 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.wizard; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.pentaho.reporting.engine.classic.core.ClassicEngineBoot; import org.pentaho.reporting.engine.classic.core.CompoundDataFactory; import org.pentaho.reporting.engine.classic.core.DefaultReportEnvironmentMapping; import org.pentaho.reporting.engine.classic.core.DefaultResourceBundleFactory; import org.pentaho.reporting.engine.classic.core.MetaAttributeNames; import org.pentaho.reporting.engine.classic.core.MetaTableModel; import org.pentaho.reporting.engine.classic.core.ParameterDataRow; import org.pentaho.reporting.engine.classic.core.ReportDataFactoryException; import org.pentaho.reporting.engine.classic.core.ReportEnvironment; import org.pentaho.reporting.engine.classic.core.StaticDataRow; import org.pentaho.reporting.engine.classic.core.function.Expression; import org.pentaho.reporting.engine.classic.core.metadata.ExpressionMetaData; import org.pentaho.reporting.engine.classic.core.metadata.ExpressionPropertyMetaData; import org.pentaho.reporting.engine.classic.core.metadata.ExpressionRegistry; import org.pentaho.reporting.engine.classic.core.parameters.DefaultParameterContext; import org.pentaho.reporting.engine.classic.core.parameters.ParameterContext; import org.pentaho.reporting.engine.classic.core.parameters.ParameterDefinitionEntry; import org.pentaho.reporting.engine.classic.core.parameters.PlainParameter; import org.pentaho.reporting.engine.classic.core.states.datarow.ProcessingDataSchemaCompiler; import org.pentaho.reporting.engine.classic.core.util.beans.BeanException; import org.pentaho.reporting.engine.classic.core.util.beans.BeanUtility; import org.pentaho.reporting.libraries.base.util.StringUtils; import org.pentaho.reporting.libraries.resourceloader.ResourceManager; import javax.swing.table.TableModel; import java.beans.IntrospectionException; import java.util.HashMap; import java.util.Map; public class DataSchemaCompiler { private static final Log logger = LogFactory.getLog( ProcessingDataSchemaCompiler.class ); protected static class GenericDataAttributes implements DataAttributes { private static final String[] CORE_NAMES = new String[] { MetaAttributeNames.Core.NAME, MetaAttributeNames.Core.SOURCE, MetaAttributeNames.Core.TYPE }; private static final String[] FORMATTING_NAMES = new String[] { MetaAttributeNames.Formatting.LABEL }; private static final String[] NAMESPACES = new String[] { MetaAttributeNames.Core.NAMESPACE, MetaAttributeNames.Formatting.NAMESPACE }; private String parameterName; private Class parameterType; private String source; private String label; private DataAttributes globalAttributes; protected GenericDataAttributes() { this.globalAttributes = new DefaultDataAttributes(); } public void setup( final String parameterName, final Class parameterType, final String source, final String label, final DataAttributes globalAttributes ) { if ( globalAttributes == null ) { throw new NullPointerException(); } if ( parameterName == null ) { throw new NullPointerException(); } if ( parameterType == null ) { throw new NullPointerException(); } if ( source == null ) { throw new NullPointerException(); } this.parameterName = parameterName; this.parameterType = parameterType; this.source = source; this.label = label; this.globalAttributes = globalAttributes; } public String[] getMetaAttributeDomains() { return StringUtils.merge( globalAttributes.getMetaAttributeDomains(), GenericDataAttributes.NAMESPACES ); } public String[] getMetaAttributeNames( final String domainName ) { final String[] metaNamess = globalAttributes.getMetaAttributeNames( domainName ); if ( MetaAttributeNames.Core.NAMESPACE.equals( domainName ) ) { return StringUtils.merge( GenericDataAttributes.CORE_NAMES, metaNamess ); } if ( MetaAttributeNames.Formatting.NAMESPACE.equals( domainName ) ) { return StringUtils.merge( GenericDataAttributes.FORMATTING_NAMES, metaNamess ); } return metaNamess; } public Object getMetaAttribute( final String domain, final String name, final Class type, final DataAttributeContext context ) { return getMetaAttribute( domain, name, type, context, null ); } public Object getMetaAttribute( final String domain, final String name, final Class type, final DataAttributeContext context, final Object defaultValue ) { if ( domain == null ) { throw new NullPointerException(); } if ( name == null ) { throw new NullPointerException(); } if ( context == null ) { throw new NullPointerException(); } if ( MetaAttributeNames.Core.NAMESPACE.equals( domain ) ) { if ( MetaAttributeNames.Core.NAME.equals( name ) ) { return parameterName; } if ( MetaAttributeNames.Core.SOURCE.equals( name ) ) { return source; } if ( MetaAttributeNames.Core.TYPE.equals( name ) ) { return parameterType; } } final Object result = globalAttributes.getMetaAttribute( domain, name, type, context, defaultValue ); if ( result != null ) { return result; } if ( MetaAttributeNames.Formatting.NAMESPACE.equals( domain ) ) { if ( MetaAttributeNames.Formatting.LABEL.equals( name ) ) { return label; } } return null; } public Object clone() throws CloneNotSupportedException { throw new CloneNotSupportedException( "This is an internal class and should not have been leaked to the outside world" ); } public ConceptQueryMapper getMetaAttributeMapper( final String domain, final String name ) { return DefaultConceptQueryMapper.INSTANCE; } } protected static class ParameterDataAttributes implements DataAttributes { private static final String[] NAMES = new String[] { MetaAttributeNames.Core.NAME, MetaAttributeNames.Core.SOURCE, MetaAttributeNames.Core.TYPE }; private static final String[] NAMESPACES = new String[] { MetaAttributeNames.Core.NAMESPACE }; private DataAttributes globalAttributes; private ParameterDefinitionEntry entry; private ParameterContext parameterContext; protected ParameterDataAttributes() { this.globalAttributes = new DefaultDataAttributes(); } public void setup( final ParameterDefinitionEntry parameter, final DataAttributes globalAttributes, final ReportEnvironment reportEnvironment, final ResourceManager resourceManager ) throws ReportDataFactoryException { if ( globalAttributes == null ) { throw new NullPointerException(); } if ( parameter == null ) { throw new NullPointerException(); } this.globalAttributes = globalAttributes; this.entry = parameter; this.parameterContext = new DefaultParameterContext( new CompoundDataFactory(), new StaticDataRow(), ClassicEngineBoot.getInstance() .getGlobalConfig(), new DefaultResourceBundleFactory(), resourceManager, null, reportEnvironment ); } public String[] getMetaAttributeDomains() { return StringUtils.merge( globalAttributes.getMetaAttributeDomains(), StringUtils.merge( ParameterDataAttributes.NAMESPACES, entry.getParameterAttributeNamespaces() ) ); } public String[] getMetaAttributeNames( final String domainName ) { final String[] metaNamess = globalAttributes.getMetaAttributeNames( domainName ); if ( MetaAttributeNames.Core.NAMESPACE.equals( domainName ) ) { return StringUtils.merge( ParameterDataAttributes.NAMES, metaNamess ); } return StringUtils.merge( metaNamess, entry.getParameterAttributeNames( domainName ) ); } public Object getMetaAttribute( final String domain, final String name, final Class type, final DataAttributeContext context ) { return getMetaAttribute( domain, name, type, context, null ); } public Object getMetaAttribute( final String domain, final String name, final Class type, final DataAttributeContext context, final Object defaultValue ) { if ( domain == null ) { throw new NullPointerException(); } if ( name == null ) { throw new NullPointerException(); } if ( context == null ) { throw new NullPointerException(); } if ( MetaAttributeNames.Core.NAMESPACE.equals( domain ) ) { if ( MetaAttributeNames.Core.NAME.equals( name ) ) { return entry.getName(); } if ( MetaAttributeNames.Core.SOURCE.equals( name ) ) { return MetaAttributeNames.Core.SOURCE_VALUE_PARAMETER; } if ( MetaAttributeNames.Core.TYPE.equals( name ) ) { return entry.getValueType(); } } final Object override = entry.getParameterAttribute( domain, name, parameterContext ); if ( override != null ) { return override; } return globalAttributes.getMetaAttribute( domain, name, type, context, defaultValue ); } public Object clone() throws CloneNotSupportedException { throw new CloneNotSupportedException( "This is an internal class and should not have been leaked to the outside world" ); } public ConceptQueryMapper getMetaAttributeMapper( final String domain, final String name ) { return DefaultConceptQueryMapper.INSTANCE; } } protected static class ExpressionsDataAttributes implements DataAttributes { private static final String[] CORENAMES = new String[] { MetaAttributeNames.Core.NAME, MetaAttributeNames.Core.SOURCE, MetaAttributeNames.Core.TYPE }; private static final String[] NAMESPACES = new String[] { MetaAttributeNames.Core.NAMESPACE, MetaAttributeNames.Expressions.NAMESPACE }; private static final String[] EXPRESSIONNAMES = new String[] { MetaAttributeNames.Expressions.CLASS }; private ExpressionMetaData expressionMetaData; private Class expressionType; private Class resultType; private String expressionName; private BeanUtility beanUtility; private String[] expressionProperties; private static final String[] EMPTY_STRINGS = new String[0]; public ExpressionsDataAttributes( final Expression expression ) { if ( expression == null ) { throw new NullPointerException(); } if ( expression.getName() == null ) { throw new IllegalStateException(); } this.expressionType = expression.getClass(); if ( ExpressionRegistry.getInstance().isExpressionRegistered( expressionType.getName() ) ) { this.expressionMetaData = ExpressionRegistry.getInstance().getExpressionMetaData( expressionType.getName() ); this.resultType = expressionMetaData.getResultType(); this.expressionProperties = StringUtils.merge( expressionMetaData.getPropertyNames(), ExpressionsDataAttributes.EXPRESSIONNAMES ); } else { this.expressionMetaData = null; this.resultType = Object.class; this.expressionProperties = ExpressionsDataAttributes.EXPRESSIONNAMES; } this.expressionName = expression.getName(); try { this.beanUtility = new BeanUtility( expression ); } catch ( IntrospectionException e ) { // should not happen, but if it does, we simply ignore the bean-metadata. } } public String[] getMetaAttributeDomains() { return ExpressionsDataAttributes.NAMESPACES.clone(); } public String[] getMetaAttributeNames( final String domainName ) { if ( MetaAttributeNames.Core.NAMESPACE.equals( domainName ) ) { return ExpressionsDataAttributes.CORENAMES.clone(); } if ( MetaAttributeNames.Expressions.NAMESPACE.equals( domainName ) ) { return expressionProperties.clone(); } return ExpressionsDataAttributes.EMPTY_STRINGS; } public Object getMetaAttribute( final String domain, final String name, final Class type, final DataAttributeContext context ) { return getMetaAttribute( domain, name, type, context, null ); } public Object getMetaAttribute( final String domain, final String name, final Class type, final DataAttributeContext context, final Object defaultValue ) { if ( domain == null ) { throw new NullPointerException(); } if ( name == null ) { throw new NullPointerException(); } if ( context == null ) { throw new NullPointerException(); } if ( MetaAttributeNames.Core.NAMESPACE.equals( domain ) ) { if ( MetaAttributeNames.Core.NAME.equals( name ) ) { return expressionName; } if ( MetaAttributeNames.Core.SOURCE.equals( name ) ) { return MetaAttributeNames.Core.SOURCE_VALUE_EXPRESSION; } if ( MetaAttributeNames.Core.TYPE.equals( name ) ) { return resultType; } } if ( MetaAttributeNames.Expressions.NAMESPACE.equals( domain ) ) { if ( MetaAttributeNames.Expressions.CLASS.equals( name ) ) { return expressionType; } if ( beanUtility != null && expressionMetaData != null ) { final ExpressionPropertyMetaData propertyMetaData = expressionMetaData.getPropertyDescription( name ); if ( propertyMetaData != null ) { try { return beanUtility.getProperty( name ); } catch ( BeanException e ) { // ignore once more .. fall back to the default meta-data or return null.. } } } } return defaultValue; } public ConceptQueryMapper getMetaAttributeMapper( final String domain, final String name ) { return DefaultConceptQueryMapper.INSTANCE; } public Object clone() throws CloneNotSupportedException { throw new CloneNotSupportedException( "This is an internal class and should not have been leaked to the outside world" ); } } private GenericDataAttributes environmentDataAttributes; private GenericDataAttributes tableDataAttributes; private ParameterDataAttributes parameterDataAttributes; private DefaultDataAttributes globalAttributes; private DefaultDataAttributeReferences globalReferences; private MetaSelectorRule[] indirectRules; private DirectFieldSelectorRule[] directRules; private DataAttributeContext context; private DataSchemaDefinition reportSchemaDefinition; private ResourceManager resourceManager; private boolean initialized; public DataSchemaCompiler( final DataSchemaDefinition reportSchemaDefinition, final DataAttributeContext context ) { this( reportSchemaDefinition, context, createDefaultResourceManager() ); } private static ResourceManager createDefaultResourceManager() { return new ResourceManager(); } public DataSchemaCompiler( final DataSchemaDefinition reportSchemaDefinition, final DataAttributeContext context, final ResourceManager resourceManager ) { if ( reportSchemaDefinition == null ) { throw new NullPointerException(); } if ( context == null ) { throw new NullPointerException(); } if ( resourceManager == null ) { throw new NullPointerException(); } this.context = context; this.reportSchemaDefinition = reportSchemaDefinition; this.resourceManager = resourceManager; } protected void init() { final DefaultDataSchemaDefinition schemaDefinition = new DefaultDataSchemaDefinition(); schemaDefinition.merge( parseGlobalDefaults( resourceManager ) ); schemaDefinition.merge( reportSchemaDefinition ); this.tableDataAttributes = new GenericDataAttributes(); this.environmentDataAttributes = new GenericDataAttributes(); this.parameterDataAttributes = new ParameterDataAttributes(); this.globalAttributes = new DefaultDataAttributes(); this.globalReferences = new DefaultDataAttributeReferences(); final DataSchemaRule[] globalRules = schemaDefinition.getGlobalRules(); for ( int i = 0; i < globalRules.length; i++ ) { final DataSchemaRule rule = globalRules[i]; final DataAttributes attributes = rule.getStaticAttributes(); globalAttributes.merge( attributes, context ); final DataAttributeReferences mappedAttributes = rule.getMappedAttributes(); globalReferences.merge( mappedAttributes ); } indirectRules = schemaDefinition.getIndirectRules(); directRules = schemaDefinition.getDirectRules(); initialized = true; } protected DataSchemaDefinition parseGlobalDefaults( final ResourceManager resourceManager ) { return DataSchemaUtility.parseDefaults( resourceManager ); } public DataSchema compile( final TableModel data ) throws ReportDataFactoryException { return compile( data, null, null, null, null ); } public boolean isInitialized() { return initialized; } public DataSchema compile( final TableModel data, final Expression[] expressions, final ParameterDataRow parameters, final ParameterDefinitionEntry[] parameterDefinitions, final ReportEnvironment reportEnvironment ) throws ReportDataFactoryException { if ( initialized == false ) { init(); } if ( data == null ) { throw new NullPointerException(); } final DefaultDataSchema defaultDataSchema = new DefaultDataSchema(); if ( reportEnvironment != null ) { processReportEnvironment( globalAttributes, indirectRules, directRules, defaultDataSchema ); } if ( parameters != null ) { processParameters( parameters, parameterDefinitions, reportEnvironment, globalAttributes, indirectRules, directRules, defaultDataSchema ); } // expressions if ( expressions != null ) { for ( int i = 0; i < expressions.length; i++ ) { final Expression expression = expressions[i]; final String name = expression.getName(); if ( name == null ) { continue; } final DefaultDataAttributes computedParameterDataAttributes = new DefaultDataAttributes(); computedParameterDataAttributes.merge( globalAttributes, context ); computedParameterDataAttributes.merge( new ExpressionsDataAttributes( expression ), context ); applyRules( indirectRules, directRules, computedParameterDataAttributes ); defaultDataSchema.setAttributes( name, computedParameterDataAttributes ); } } if ( data instanceof MetaTableModel == false ) { final int count = data.getColumnCount(); for ( int i = 0; i < count; i++ ) { final String colName = data.getColumnName( i ); if ( colName == null ) { continue; } tableDataAttributes.setup( colName, data.getColumnClass( i ), MetaAttributeNames.Core.SOURCE_VALUE_TABLE, colName, globalAttributes ); final DefaultDataAttributes computedParameterDataAttributes = new DefaultDataAttributes(); computedParameterDataAttributes.merge( this.tableDataAttributes, context ); applyRules( indirectRules, directRules, computedParameterDataAttributes ); defaultDataSchema.setAttributes( colName, computedParameterDataAttributes ); } } else { final MetaTableModel mt = (MetaTableModel) data; final DefaultDataAttributes tableGlobalAttributes = new DefaultDataAttributes(); tableGlobalAttributes.merge( globalAttributes, context ); tableGlobalAttributes.merge( mt.getTableAttributes(), context ); try { defaultDataSchema.setTableAttributes( tableGlobalAttributes ); } catch ( CloneNotSupportedException e ) { logger.warn( "Unable to copy global data-attributes", e ); } final int count = data.getColumnCount(); for ( int i = 0; i < count; i++ ) { final String colName = data.getColumnName( i ); if ( colName == null ) { continue; } final DefaultDataAttributes computedParameterDataAttributes = new DefaultDataAttributes(); computedParameterDataAttributes.merge( tableGlobalAttributes, context ); computedParameterDataAttributes.merge( mt.getColumnAttributes( i ), context ); tableDataAttributes.setup( colName, data.getColumnClass( i ), MetaAttributeNames.Core.SOURCE_VALUE_TABLE, null, EmptyDataAttributes.INSTANCE ); computedParameterDataAttributes.merge( tableDataAttributes, context ); applyRules( indirectRules, directRules, computedParameterDataAttributes ); defaultDataSchema.setAttributes( colName, computedParameterDataAttributes ); } } return defaultDataSchema; } protected void applyRules( final DataSchemaRule[] indirectRules, final DataSchemaRule[] directRules, final DefaultDataAttributes computedParameterDataAttributes ) { for ( int j = 0; j < indirectRules.length; j++ ) { final DataSchemaRule rule = indirectRules[j]; if ( rule.isMatch( computedParameterDataAttributes, context ) ) { computedParameterDataAttributes.merge( rule.getStaticAttributes(), context ); } } for ( int j = 0; j < directRules.length; j++ ) { final DataSchemaRule rule = directRules[j]; if ( rule.isMatch( computedParameterDataAttributes, context ) ) { computedParameterDataAttributes.merge( rule.getStaticAttributes(), context ); } } computedParameterDataAttributes.mergeReferences( globalReferences, context ); for ( int j = 0; j < indirectRules.length; j++ ) { final DataSchemaRule rule = indirectRules[j]; if ( rule.isMatch( computedParameterDataAttributes, context ) ) { final DataAttributeReferences mappedAttributes = rule.getMappedAttributes(); computedParameterDataAttributes.mergeReferences( mappedAttributes, context ); } } for ( int j = 0; j < directRules.length; j++ ) { final DataSchemaRule rule = directRules[j]; if ( rule.isMatch( computedParameterDataAttributes, context ) ) { final DataAttributeReferences mappedAttributes = rule.getMappedAttributes(); computedParameterDataAttributes.mergeReferences( mappedAttributes, context ); } } } protected void processReportEnvironment( final DefaultDataAttributes globalAttributes, final DataSchemaRule[] indirectRules, final DataSchemaRule[] directRules, final DefaultDataSchema schema ) { final Map<String, String> names = DefaultReportEnvironmentMapping.INSTANCE.createEnvironmentMapping(); final String[] parameterNames = names.keySet().toArray( new String[names.size()] ); for ( int i = 0; i < parameterNames.length; i++ ) { final String envName = parameterNames[i]; final String name = names.get( envName ); if ( envName.endsWith( "-array" ) ) { environmentDataAttributes.setup( name, String[].class, MetaAttributeNames.Core.SOURCE_VALUE_ENVIRONMENT, name, globalAttributes ); } else { environmentDataAttributes.setup( name, String.class, MetaAttributeNames.Core.SOURCE_VALUE_ENVIRONMENT, name, globalAttributes ); } final DefaultDataAttributes computedParameterDataAttributes = new DefaultDataAttributes(); computedParameterDataAttributes.merge( this.environmentDataAttributes, context ); applyRules( indirectRules, directRules, computedParameterDataAttributes ); schema.setAttributes( name, computedParameterDataAttributes ); } } protected void processParameters( final ParameterDataRow parameters, final ParameterDefinitionEntry[] parameterDefinitionEntries, final ReportEnvironment reportEnvironment, final DefaultDataAttributes globalAttributes, final DataSchemaRule[] indirectRules, final DataSchemaRule[] directRules, final DefaultDataSchema schema ) throws ReportDataFactoryException { final Map<String, ParameterDefinitionEntry> map = normalizeParameterDefinitions( parameters, parameterDefinitionEntries ); for ( final Map.Entry<String, ParameterDefinitionEntry> entry : map.entrySet() ) { final ParameterDefinitionEntry parameter = entry.getValue(); parameterDataAttributes.setup( parameter, globalAttributes, reportEnvironment, resourceManager ); final DefaultDataAttributes computedParameterDataAttributes = new DefaultDataAttributes(); computedParameterDataAttributes.merge( this.parameterDataAttributes, context ); applyRules( indirectRules, directRules, computedParameterDataAttributes ); schema.setAttributes( parameter.getName(), computedParameterDataAttributes ); } } private Map<String, ParameterDefinitionEntry> normalizeParameterDefinitions( final ParameterDataRow parameters, final ParameterDefinitionEntry[] parameterDefinitionEntries ) { final String[] parameterNames = parameters.getColumnNames(); final HashMap<String, ParameterDefinitionEntry> map = new HashMap<String, ParameterDefinitionEntry>(); if ( parameterDefinitionEntries != null ) { for ( int i = 0; i < parameterDefinitionEntries.length; i++ ) { final ParameterDefinitionEntry entry = parameterDefinitionEntries[i]; map.put( entry.getName(), entry ); } } for ( int i = 0; i < parameterNames.length; i++ ) { final String name = parameterNames[i]; if ( map.containsKey( name ) ) { continue; } final Object value = parameters.get( name ); if ( value != null ) { map.put( name, new PlainParameter( name, value.getClass() ) ); } else { map.put( name, new PlainParameter( name, Object.class ) ); } } return map; } protected GenericDataAttributes getTableDataAttributes() { return tableDataAttributes; } protected ParameterDataAttributes getParameterDataAttributes() { return parameterDataAttributes; } protected DefaultDataAttributes getGlobalAttributes() { return globalAttributes; } protected DefaultDataAttributeReferences getGlobalReferences() { return globalReferences; } protected MetaSelectorRule[] getIndirectRules() { return indirectRules; } protected DirectFieldSelectorRule[] getDirectRules() { return directRules; } protected DataAttributeContext getContext() { return context; } }