/*
* JBoss, Home of Professional Open Source.
*
* See the LEGAL.txt file distributed with this work for information regarding copyright ownership and licensing.
*
* See the AUTHORS.txt file distributed with this work for a full listing of individual contributors.
*/
package org.teiid.designer.transformation.aspects.validation.rules;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.emf.ecore.EObject;
import org.teiid.core.designer.id.UUID;
import org.teiid.designer.core.ModelerCore;
import org.teiid.designer.core.container.Container;
import org.teiid.designer.core.metamodel.aspect.AspectManager;
import org.teiid.designer.core.metamodel.aspect.sql.SqlColumnAspect;
import org.teiid.designer.core.metamodel.aspect.sql.SqlDatatypeAspect;
import org.teiid.designer.core.validation.ValidationProblem;
import org.teiid.designer.core.validation.ValidationProblemImpl;
import org.teiid.designer.core.validation.ValidationResult;
import org.teiid.designer.metadata.runtime.ColumnRecord;
import org.teiid.designer.metamodels.transformation.MappingClass;
import org.teiid.designer.metamodels.transformation.SqlTransformationMappingRoot;
import org.teiid.designer.query.sql.lang.ICommand;
import org.teiid.designer.query.sql.lang.IExpression;
import org.teiid.designer.query.sql.lang.util.CommandHelper;
import org.teiid.designer.query.sql.symbol.IElementSymbol;
import org.teiid.designer.transformation.TransformationPlugin;
import org.teiid.designer.transformation.util.AttributeMappingHelper;
import org.teiid.designer.transformation.util.TransformationHelper;
import org.teiid.designer.transformation.validation.TransformationValidator;
import org.teiid.designer.type.IDataTypeManagerService;
import org.teiid.designer.type.IDataTypeManagerService.DataTypeName;
/**
* This rule compares the number of projected symbols of the sql transformation with the number of mappings. Also compare the
* datatypes of the projected symbols with the datatypes of the outputs of the mappings.
*
* @since 8.0
*/
public class ProjectSymbolsValidationHelper {
/**
* Compare the project symbols on the command with the mappings and target attributes.
*
* @param command The command for project symbols
* @param transRoot The mapping root for the transformation
* @param validationResult The validation result that gets updated with problems.
* @since 4.3
*/
public void validateProjectedSymbols( final ICommand command,
final SqlTransformationMappingRoot transRoot,
final ValidationResult validationResult ) {
List projSymbols = CommandHelper.getProjectedSymbols(command);
applyMappingValidationRules(projSymbols, transRoot, validationResult);
}
/**
* Apply validation rules specific to Procedure mappings when input parameters are present. In this case exra columns are
* added to the target group to provide mappings for the input parameter to allow for the paremeter values to be passed to the
* paremeter execution at runtime. Source of a transformation is a procedure and the target is a table, mapping class or a
* procedure.
*
* @param projSymbols The list or projected symbols from the command.
* @param transRoot The mappingroot whose source is a procedure
* @param validationResult The validation result that gets updated with problems.
* @since 4.3
*/
public void applyMappingValidationRules( final List projSymbols,
final SqlTransformationMappingRoot transRoot,
final ValidationResult validationResult ) {
// list of target attributes
// create new collection to prevent modification of EList
List attributes = new ArrayList(TransformationValidator.getOutputColumns(transRoot));
// list of all nexted mappings
// create new collection to prevent modification of EList
Collection mappings = new ArrayList(transRoot.getNested());
// compare project symbols with nested mappings
checkMappingsForProjectSymbols(transRoot, mappings, projSymbols, validationResult);
// compare project symbols with target attributes
checkTargetAttributesForProjectSymbols(transRoot, attributes, projSymbols, validationResult);
// compare project symbols and target attribues
compareProjectSymbolsAndTargetColumns(transRoot, projSymbols, attributes, validationResult);
}
/**
* There should be one nested mapping per projected symbol these are column level mappings, each mapping may/not have a input
* but it should have exactly one output(virtual group attribute) this is verified in TransformationMappingValidationRule
*
* @param mappings
* @param projSymbols
* @since 4.3
*/
public void checkMappingsForProjectSymbols( final SqlTransformationMappingRoot transRoot,
Collection mappings,
Collection projSymbols,
final ValidationResult validationResult ) {
EObject target = transRoot.getTarget();
String problemMsg = null;
if (projSymbols.size() > mappings.size()) {
problemMsg = TransformationPlugin.Util.getString("SqlTransformationMappingRootValidationRule.The_number_of_columns/elements_in_{0}_are_less_than_the_number_defined_in_the_sql_transformation._1", TransformationHelper.getSqlEObjectName(target)); //$NON-NLS-1$
} else if (projSymbols.size() < mappings.size()) {
problemMsg = TransformationPlugin.Util.getString("SqlTransformationMappingRootValidationRule.The_number_of_columns/elements_in_{0}_are_greater_than_the_number_defined_in_the_sql_transformation._2", TransformationHelper.getSqlEObjectName(target)); //$NON-NLS-1$
}
// create validation problem and additional to the results
if (problemMsg != null) {
ValidationProblem typeProblem = new ValidationProblemImpl(0, IStatus.ERROR, problemMsg);
validationResult.addProblem(typeProblem);
}
}
/**
* There should be one target attribute per projected symbol.
*
* @param mappings
* @param projSymbols
* @since 4.3
*/
public void checkTargetAttributesForProjectSymbols( final SqlTransformationMappingRoot transRoot,
Collection attributes,
Collection projSymbols,
final ValidationResult validationResult ) {
EObject target = transRoot.getTarget();
String problemMsg = null;
if (projSymbols.size() > attributes.size()) {
problemMsg = TransformationPlugin.Util.getString("SqlTransformationMappingRootValidationRule.The_number_of_columns/elements_in_{0}_are_less_than_the_number_defined_in_the_sql_transformation._1", TransformationHelper.getSqlEObjectName(target)); //$NON-NLS-1$
} else if (projSymbols.size() < attributes.size()) {
problemMsg = TransformationPlugin.Util.getString("SqlTransformationMappingRootValidationRule.The_number_of_columns/elements_in_{0}_are_greater_than_the_number_defined_in_the_sql_transformation._2", TransformationHelper.getSqlEObjectName(target)); //$NON-NLS-1$
}
// create validation problem and additional to the results
if (problemMsg != null) {
ValidationProblem typeProblem = new ValidationProblemImpl(0, IStatus.ERROR, problemMsg);
validationResult.addProblem(typeProblem);
}
}
/**
* Compare the project symbols from a SqlTransformation query to the columns on the target table or procedure resultset. The
* names and datatypes of columns in the target should match those in the sql transformation query. The order of columns in
* the target should be same as the order of project symbols from the sql.
*
* @param transRoot Mapping root whose nested mappings are validated.
* @param projSymbols The list of project symbols from the sql transformation
* @param validationResult The validation result that gets updated with problems.
* @since 4.3
*/
public void compareProjectSymbolsAndTargetColumns( final SqlTransformationMappingRoot transRoot,
final List projSymbols,
final List targetColumns,
final ValidationResult validationResult ) {
// get the target to the transformation and list of
// output columns on it
EObject target = transRoot.getTarget();
final Iterator colIter = targetColumns.iterator();
final Iterator projIter = projSymbols.iterator();
while (projIter.hasNext() && colIter.hasNext()) {
boolean foundMatch = false;
EObject outputColumn = (EObject)colIter.next();
String outputColumnName = TransformationHelper.getSqlColumnName(outputColumn);
IExpression singleElementSymbol = (IExpression)projIter.next();
String symbolName = AttributeMappingHelper.getSymbolShortName(singleElementSymbol);
if (outputColumnName.equalsIgnoreCase(symbolName)) {
foundMatch = true;
} else if (symbolName != null && symbolName.toLowerCase().startsWith(UUID.PROTOCOL)) {
// If the metadataID for the symbol is a TempMetadataID and
// not a MetadataRecord instance then the symbol name may be
// in the form of a UUID. Try to resolve this UUID to an EObject
// so that we can ultimately get the user name of this symbol.
// Fix for defect 17764
String uuid = symbolName.toLowerCase();
Container cntr = ModelerCore.getContainer(outputColumn);
if (cntr != null) {
Object obj = cntr.getEObjectFinder().find(uuid);
if (obj instanceof EObject) {
symbolName = TransformationHelper.getSqlColumnName((EObject)obj);
if (outputColumnName.equalsIgnoreCase(symbolName)) {
foundMatch = true;
}
}
}
}
// -----------------------------------------------------------------
// Check #1 - column name in virtual table does not match any symbol
// -----------------------------------------------------------------
if (!foundMatch) {
// create validation problem and additional to the results
ValidationProblem typeProblem = new ValidationProblemImpl(
0,
IStatus.ERROR,
TransformationPlugin.Util.getString("SqlTransformationMappingRootValidationRule.The_target_attribute_matches_no_symbol_in_the_query_{0}._1", outputColumnName)); //$NON-NLS-1$
validationResult.addProblem(typeProblem);
return;
}
// ------------------------------------------------------------------------------
// Check #2 - column datatype does not match the symbol datatype
// ------------------------------------------------------------------------------
IDataTypeManagerService service = ModelerCore.getTeiidDataTypeManagerService();
Class sourceType = singleElementSymbol.getType();
String problemMsg = null;
// check only if the source is a valid type
SqlColumnAspect columnAspect = (SqlColumnAspect)AspectManager.getSqlAspect(outputColumn);
if (sourceType != null && sourceType != service.getDefaultDataClass(DataTypeName.NULL)) {
EObject datatype = columnAspect.getDatatype(outputColumn);
SqlDatatypeAspect typeAspect = datatype != null ? (SqlDatatypeAspect)AspectManager.getSqlAspect(datatype) : null;
if (typeAspect != null) {
Class targetType = service.getDataTypeClass(typeAspect.getRuntimeTypeName(datatype));
if (!sourceType.equals(targetType)) {
problemMsg = TransformationPlugin.Util.getString("SqlTransformationMappingRootValidationRule.The_datatype_type_of_the_column_{0}_does_not_match_the_source_column_type._1", //$NON-NLS-1$
new Object[] {outputColumnName,
service.getDataTypeName(targetType),
service.getDataTypeName(sourceType)});
// create validation problem and additional to the results
ValidationProblem typeProblem = new ValidationProblemImpl(0, IStatus.ERROR, problemMsg);
validationResult.addProblem(typeProblem);
continue;
}
} else {
problemMsg = TransformationPlugin.Util.getString("SqlTransformationMappingRootValidationRule.The_datatype_type_of_the_column_{0}_is_not_set_or_cannot_be_resolved_in_the_workspace._1", outputColumnName); //$NON-NLS-1$
// create validation problem and additional to the results
ValidationProblem typeProblem = new ValidationProblemImpl(0, IStatus.ERROR, problemMsg);
validationResult.addProblem(typeProblem);
continue;
}
}
// -------------------------------------------------------------------------
// Check #3 - A "nullable" field in a physical group should also be nullable
// in its corresponding virtual group
// -------------------------------------------------------------------------
// The target attribute may not be a MappingClass column (fix for defect 10917)
if (!(target instanceof MappingClass)) {
if ((singleElementSymbol instanceof IElementSymbol) && (outputColumn != null)) {
final IElementSymbol eSymbol = (IElementSymbol)singleElementSymbol;
final Object metadataID = eSymbol.getMetadataID();
if ((metadataID != null) && (metadataID instanceof ColumnRecord)) {
ColumnRecord colRecord = (ColumnRecord)metadataID;
int symbolNullType = colRecord.getNullType();
columnAspect = (SqlColumnAspect)AspectManager.getSqlAspect(outputColumn);
int columnNullType = columnAspect.getNullType(outputColumn);
if (symbolNullType != columnNullType) {
problemMsg = TransformationPlugin.Util.getString("SqlTransformationMappingRootValidationRule.The_Nullable_value_of_virtual_group_attribute_{0}_doesn____t_match_that_of_the_attribute_it_mapps_to_in_query_transform._1", outputColumnName); //$NON-NLS-1$
// create validation problem and additional to the results
ValidationProblem typeProblem = new ValidationProblemImpl(0, IStatus.WARNING, problemMsg);
validationResult.addProblem(typeProblem);
continue;
}
}
}
}
}
}
}