/*
* 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 - 2009 Object Refinery Ltd, Pentaho Corporation and Contributors.. All rights reserved.
*/
package org.pentaho.reporting.engine.classic.core.wizard;
import java.beans.IntrospectionException;
import java.util.HashMap;
import java.util.Map;
import javax.swing.table.TableModel;
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.MetaAttributeNames;
import org.pentaho.reporting.engine.classic.core.MetaTableModel;
import org.pentaho.reporting.engine.classic.core.ParameterDataRow;
import org.pentaho.reporting.engine.classic.core.ReportEnvironment;
import org.pentaho.reporting.engine.classic.core.ReportEnvironmentDataRow;
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.LinkedMap;
import org.pentaho.reporting.libraries.base.util.StringUtils;
import org.pentaho.reporting.libraries.resourceloader.ResourceManager;
/**
* Todo: Document Me
*
* @author Thomas Morgner
* @noinspection PublicConstructorInNonPublicClass
*/
public class DataSchemaCompiler
{
private static final Log logger = LogFactory.getLog(ProcessingDataSchemaCompiler.class);
protected static class GenericDataAttributes 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 String parameterName;
private Class parameterType;
private String source;
private DataAttributes globalAttributes;
protected GenericDataAttributes()
{
this.globalAttributes = new DefaultDataAttributes();
}
public void setup(final String parameterName,
final Class parameterType,
final String source,
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.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.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;
}
}
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 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)
{
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(), null, null, 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()
{
final ResourceManager resourceManager = new ResourceManager();
resourceManager.registerDefaults();
return 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)
{
return compile(data, null, null, null, null);
}
public boolean isInitialized()
{
return initialized;
}
/**
* @param data
* @param expressions
* @param parameters
* @param reportEnvironment
* @return
* @deprecated pleaes pass the parameter-definition around.
*/
public DataSchema compile(final TableModel data,
final Expression[] expressions,
final ParameterDataRow parameters,
final ReportEnvironment reportEnvironment)
{
return compile(data, expressions, parameters, null, reportEnvironment);
}
public DataSchema compile(final TableModel data,
final Expression[] expressions,
final ParameterDataRow parameters,
final ParameterDefinitionEntry[] parameterDefinitions,
final ReportEnvironment reportEnvironment)
{
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, 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, 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 LinkedMap names = ReportEnvironmentDataRow.createEnvironmentMapping();
final String[] parameterNames = (String[]) names.keys(new String[names.size()]);
for (int i = 0; i < parameterNames.length; i++)
{
final String envName = parameterNames[i];
final String name = (String) names.get(envName);
if (envName.endsWith("-array"))
{
environmentDataAttributes.setup(name, String[].class, MetaAttributeNames.Core.SOURCE_VALUE_ENVIRONMENT, globalAttributes);
}
else
{
environmentDataAttributes.setup(name, String.class, MetaAttributeNames.Core.SOURCE_VALUE_ENVIRONMENT, 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)
{
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);
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;
}
}