/* * 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.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IStatus; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.emf.ecore.util.EcoreUtil; import org.eclipse.emf.mapping.Mapping; import org.eclipse.emf.mapping.MappingRoot; import org.eclipse.xsd.XSDComponent; import org.eclipse.xsd.XSDSimpleTypeDefinition; import org.eclipse.xsd.XSDTypeDefinition; import org.eclipse.xsd.XSDVariety; import org.teiid.core.designer.ModelerCoreException; import org.teiid.core.designer.util.CoreArgCheck; import org.teiid.core.designer.util.CoreStringUtil; import org.teiid.designer.core.ModelerCore; import org.teiid.designer.core.ValidationPreferences; import org.teiid.designer.core.container.Container; import org.teiid.designer.core.index.IndexSelector; import org.teiid.designer.core.index.TargetLocationIndexSelector; import org.teiid.designer.core.metamodel.aspect.AspectManager; import org.teiid.designer.core.metamodel.aspect.sql.SqlAspect; import org.teiid.designer.core.resource.EmfResource; import org.teiid.designer.core.types.DatatypeConstants; import org.teiid.designer.core.types.DatatypeManager; import org.teiid.designer.core.util.ModelContents; import org.teiid.designer.core.util.ModelVisitor; import org.teiid.designer.core.util.ModelVisitorProcessor; import org.teiid.designer.core.validation.ObjectValidationRule; import org.teiid.designer.core.validation.ValidationContext; 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.core.validation.ValidationResultImpl; import org.teiid.designer.mapping.factory.DefaultMappableTree; import org.teiid.designer.mapping.factory.IMappableTree; import org.teiid.designer.mapping.factory.TreeMappingAdapter; import org.teiid.designer.metadata.runtime.MetadataRecord; import org.teiid.designer.metamodels.core.ModelType; import org.teiid.designer.metamodels.transformation.MappingClass; import org.teiid.designer.metamodels.transformation.MappingClassColumn; import org.teiid.designer.metamodels.transformation.TreeMappingRoot; import org.teiid.designer.metamodels.transformation.impl.MappingClassImpl; import org.teiid.designer.metamodels.xml.ChoiceOption; import org.teiid.designer.metamodels.xml.XmlAttribute; import org.teiid.designer.metamodels.xml.XmlChoice; import org.teiid.designer.metamodels.xml.XmlContainerNode; import org.teiid.designer.metamodels.xml.XmlDocument; import org.teiid.designer.metamodels.xml.XmlDocumentEntity; import org.teiid.designer.metamodels.xml.XmlDocumentNode; import org.teiid.designer.metamodels.xml.XmlDocumentPackage; import org.teiid.designer.metamodels.xml.XmlElement; import org.teiid.designer.metamodels.xml.XmlRoot; import org.teiid.designer.metamodels.xml.XmlValueHolder; import org.teiid.designer.metamodels.xml.util.XmlDocumentUtil; import org.teiid.designer.metamodels.xsd.XsdUtil; import org.teiid.designer.query.IQueryParser; import org.teiid.designer.query.IQueryService; import org.teiid.designer.query.metadata.IQueryMetadataInterface; import org.teiid.designer.query.sql.IGroupsUsedByElementsVisitor; import org.teiid.designer.query.sql.IResolverVisitor; import org.teiid.designer.query.sql.lang.ICriteria; import org.teiid.designer.query.sql.symbol.IGroupSymbol; import org.teiid.designer.transformation.TransformationPlugin; import org.teiid.designer.transformation.metadata.QueryMetadataContext; import org.teiid.designer.transformation.metadata.TransformationMetadataFactory; import org.teiid.designer.validator.IValidator; import org.teiid.designer.validator.IValidator.IValidatorFailure; import org.teiid.designer.validator.IValidator.IValidatorReport; import org.teiid.designer.xml.PluginConstants; import org.teiid.designer.xml.aspects.sql.XmlElementSqlAspect; /** * XmlDocumentValidationRule * * @since 8.0 */ public class XmlDocumentValidationRule implements ObjectValidationRule { private static String RULE_NAME = XmlDocumentValidationRule.class.getName(); // map between XmlElement and MappingClassColumn private Map elementColumnMap = null; // map between XmlElement and MappingClass private Map elementMappingClassMap = null; // validation context private ValidationContext context; /* * @See org.teiid.designer.core.validation.ObjectValidationRule#validate(org.eclipse.emf.ecore.EObject, org.teiid.designer.core.validation.ValidationContext) */ @Override public synchronized void validate( final EObject eObject, final ValidationContext context ) { CoreArgCheck.isInstanceOf(TreeMappingRoot.class, eObject); // Defect 23839 - improved XML Document model validation by moving some of this methods code further down in the method. // Basically, each tree root is validated, but we were returning if the Document for the tree root was already validated. // For large XML Documents, there are many tree roots and this method was creating a TreeMappingAdapter BEFORE it did the // hasRunRule() check. By delaying the creation of the TreeMappingAdapter, we DRASTICALLY improved validation. // A large XAL.xsd document mode (~10 MB) validates in < 1 minute, versus a few hours. TreeMappingRoot transRoot = (TreeMappingRoot)eObject; EObject target = transRoot.getTarget(); // this rule does not apply if the target is not a XMLDocument if (target == null || !(target instanceof XmlDocument)) { return; } XmlDocument document = (XmlDocument)target; // document is found once per TreeMappingRoot ...we need to validate a document just once // so tract its validation // we are overloading the use of hasRunRule(typically tracks containers of EObjects) on the validation context // to tract if a particular rule has been run on a given document String uuid = ModelerCore.getObjectIdString(document); if (context.hasRunRule(uuid, RULE_NAME)) { return; } context.recordRuleRun(uuid, RULE_NAME); Resource documentResource = document.eResource(); this.context = context; // model contents for this resource ModelContents mdlContents = new ModelContents(documentResource); Iterator contentIter = mdlContents.getTransformations(document).iterator(); if (!contentIter.hasNext()) { return; } try { // Create the initial map of outputs(XML Elements) to inputs (mapping class columns) elementColumnMap = new HashMap(); elementMappingClassMap = new HashMap(); // get the mapping root associated with the transformation while (contentIter.hasNext()) { MappingRoot mappingRoot = (MappingRoot)contentIter.next(); // if there is a mapping root if (mappingRoot != null && mappingRoot instanceof TreeMappingRoot) { List inputClasses = mappingRoot.getInputs(); List outputRootElements = mappingRoot.getOutputs(); if (!outputRootElements.isEmpty() && !inputClasses.isEmpty()) { // every mapping class could be mapped to one or more // xml elements Object input = inputClasses.iterator().next(); for (final Iterator outputIter = outputRootElements.iterator(); outputIter.hasNext();) { // fill the map with element to its mappingClass elementMappingClassMap.put(outputIter.next(), input); } } for (Iterator mappingIter = mappingRoot.getNested().iterator(); mappingIter.hasNext();) { Mapping nestedMapping = (Mapping)mappingIter.next(); // mapping Class columns List inputColumns = nestedMapping.getInputs(); // xml elements List outputElements = nestedMapping.getOutputs(); if (!outputElements.isEmpty() && !inputColumns.isEmpty()) { // every mapping class column could be mapped to one or more // xml elements/attribues Object input = inputColumns.iterator().next(); for (final Iterator outputIter = outputElements.iterator(); outputIter.hasNext();) { // fill the map with element to its mappingClass column value elementColumnMap.put(outputIter.next(), input); } } } } } // create a result for the XmlDocument ValidationResult validationResult = new ValidationResultImpl(transRoot, target); // collect all the xml Element in the document DocumentVisitor visitor = new DocumentVisitor(); ModelVisitorProcessor processor = new ModelVisitorProcessor(visitor); try { processor.walk(document, ModelVisitorProcessor.DEPTH_INFINITE); } catch (ModelerCoreException e) { ValidationProblem problem = new ValidationProblemImpl( 0, IStatus.ERROR, TransformationPlugin.Util.getString("XmlDocumentValidationRule.Error_trying_to_collect_XmlElements_in_an_XmlDocument__1") + e.getMessage()); //$NON-NLS-1$ validationResult.addProblem(problem); return; } // validate the elements and attributes in the document Collection entities = new ArrayList(); entities.addAll(visitor.getElements()); entities.addAll(visitor.getAttributes()); // validate attributes and elements in the document validateEntities(entities, validationResult, context); // validate mapping classes marked for recursion validateMappingClassRecursionAllowed(validationResult, context); // validate entities directly under a choice owner // This may take a long time, so we don't want to create a mapping adapter or a mappable tree unless we get this far. if (!visitor.getChoices().isEmpty()) { TreeMappingAdapter mappingAdapter = new TreeMappingAdapter(document); IMappableTree mappableTree = new DefaultMappableTree(document); validateChoiceEntities(visitor.getChoices(), mappingAdapter, mappableTree, validationResult, context); } // Only validate mapping column to XSD component types for virtual // XML document models used by the server if (!isLogicalModel(eObject)) { validateColumnToElementTypes(validationResult, context); } // add the result to the context context.addResult(validationResult); } finally { // clear any state on this rule, since this object may be reused elementColumnMap = null; this.context = null; elementMappingClassMap = null; } } private boolean isLogicalModel( final EObject eObject ) { CoreArgCheck.isNotNull(eObject); Resource r = eObject.eResource(); if (r instanceof EmfResource) { EmfResource eResource = (EmfResource)r; if (eResource.getModelType() == ModelType.LOGICAL_LITERAL) { return true; } } return false; } /** * checks to ensure that the column type is compatible with the element type */ private void validateColumnToElementTypes( ValidationResult validationResult, ValidationContext context ) { for (Iterator mappingIter = elementColumnMap.entrySet().iterator(); mappingIter.hasNext();) { Map.Entry entry = (Map.Entry)mappingIter.next(); if (!(entry.getKey() instanceof XmlDocumentNode)) { continue; } XmlDocumentNode element = (XmlDocumentNode)entry.getKey(); XSDTypeDefinition xsdType = XmlDocumentUtil.findXSDType(element); DatatypeManager dtm = ModelerCore.getDatatypeManager(xsdType, true); if (xsdType == null || !(xsdType instanceof XSDSimpleTypeDefinition) || isStringType(xsdType, dtm)) { continue; } MappingClassColumn column = (MappingClassColumn)entry.getValue(); EObject columnType = column.getType(); if (columnType == null || !(columnType instanceof XSDSimpleTypeDefinition) || isStringType(columnType, dtm)) { continue; } // check for exact match if (columnType == xsdType) { continue; } // check the hierarchies. this is only possible if the types are Atomic if (!isAtomicLiteral((XSDSimpleTypeDefinition)xsdType) || !isAtomicLiteral((XSDSimpleTypeDefinition)columnType)) { continue; } List xsdTypeList = createHierarcyList(xsdType, dtm); List columnTypeList = createHierarcyList(columnType, dtm); if (columnTypeList.size() > 0 && xsdTypeList.size() > 0) { boolean compatible = false; boolean nonAtomicAncestor = false; // start at the 1 index to skip the top level element type for (int i = 1; i < xsdTypeList.size(); i++) { XSDSimpleTypeDefinition std = (XSDSimpleTypeDefinition)xsdTypeList.get(i); if (!isAtomicLiteral(std)) { nonAtomicAncestor = true; break; } int index = columnTypeList.indexOf(std); if (index != -1) { // if not the first element then a common ancestor is shared if (index != 0) { final String msg = TransformationPlugin.Util.getString("XmlDocumentValidationRule.Column_and_element_types_possibly_not_compatible", new Object[] {column.getName(), dtm.getName(columnType), element.getName(), dtm.getName(xsdType)}); //$NON-NLS-1$ ValidationProblem problem = new ValidationProblemImpl(0, IStatus.WARNING, msg, getLocationPath(column), getURIString(column)); validationResult.addProblem(problem); } compatible = true; break; } } if (compatible || nonAtomicAncestor) { continue; } } // not compatible // Defect 23464 - changing this rule to us a preference with default WARNING int status = context.getPreferenceStatus(ValidationPreferences.XML_INCOMPATIBLE_ELEMENT_COLUMN_DATATYPE, IStatus.WARNING); final String msg = TransformationPlugin.Util.getString("XmlDocumentValidationRule.Column_and_element_types_not_compatible", new Object[] {column.getName(), dtm.getName(columnType), element.getName(), dtm.getName(xsdType)}); //$NON-NLS-1$ ValidationProblem problem = new ValidationProblemImpl(0, status, msg, getLocationPath(column), getURIString(column)); validationResult.addProblem(problem); } } /** * returns true if the first built in type in the heirachy is string */ private boolean isStringType( EObject xsdType, DatatypeManager dtm ) { EObject object = null; try { object = dtm.getDatatypeForXsdType(xsdType); } catch (ModelerCoreException err) { PluginConstants.Util.log(err); } if (object != null) { if (dtm.isBuiltInDatatype(object) && DatatypeConstants.BuiltInNames.STRING.equals(dtm.getName(object))) { return true; } } return false; } private boolean isAtomicLiteral( XSDSimpleTypeDefinition type ) { if (type.getVariety() == XSDVariety.ATOMIC_LITERAL) { return true; } return false; } private List createHierarcyList( EObject type, DatatypeManager dtm ) { EObject[] hierarchy = dtm.getTypeHierarchy(type); if (hierarchy.length < 2) { return Collections.EMPTY_LIST; } ArrayList result = new ArrayList(); result.addAll(Arrays.asList(hierarchy)); // remove anySimpleType result.remove(result.size() - 1); return result; } /** * Mapping classes bound to the element type of sequence, choice, or all cannot be marked for recursion */ private boolean validateMappingClassRecursionAllowed( final ValidationResult validationResult, final ValidationContext context ) { for (Iterator iter = this.elementMappingClassMap.entrySet().iterator(); iter.hasNext();) { final Map.Entry entry = (Map.Entry)iter.next(); final EObject xmlNode = (EObject)entry.getKey(); final EObject mapping = (EObject)entry.getValue(); // Error if a mapping class marked for recursion is bound to an XmlChoice, XmlSequence, or XmlAll if (mapping instanceof MappingClassImpl && xmlNode instanceof XmlContainerNode) { final MappingClassImpl mc = (MappingClassImpl)mapping; if (mc.isRecursionAllowed() && mc.isRecursive()) { final String msg = TransformationPlugin.Util.getString("XmlDocumentValidationRule.Recursion_not_allowed_on_compositor"); //$NON-NLS-1$ ValidationProblem problem = new ValidationProblemImpl(0, IStatus.ERROR, msg, getLocationPath(xmlNode), getURIString(xmlNode)); validationResult.addProblem(problem); return false; } } } return true; } private static String getURIString( EObject eoj ) { return ModelerCore.getModelEditor().getUri(eoj).toString(); } private static String getLocationPath( EObject eoj ) { return ModelerCore.getModelEditor().getModelRelativePath(eoj).toString(); } /** * A criteria statement must be defined on an element, sequence, choice or all (the "entity") only when 1) The entity exists * under a choice (the "owner") 2) The entity is not the default for the owner 3) The entity is not excluded from the document * Parse, Resolve and validate the criteria on an entity that satisfies the above conditions. */ private boolean validateChoiceEntities( final Collection choices, final TreeMappingAdapter mappingAdapter, final IMappableTree mappableTree, final ValidationResult validationResult, final ValidationContext context ) { // If this is a Logical model, just return (i.e. XML Message Structure Model) // Defect 22678 boolean isLogicalXMLModel = false; if (choices != null && choices.size() > 0) { EmfResource emfResource = (EmfResource)((EObject)choices.iterator().next()).eResource(); if (emfResource.getModelAnnotation() != null) { ModelType type = emfResource.getModelAnnotation().getModelType(); String stringURI = emfResource.getModelAnnotation().getPrimaryMetamodelUri(); if (type.equals(ModelType.LOGICAL_LITERAL) && XmlDocumentPackage.eNS_URI.equals(stringURI)) { isLogicalXMLModel = true; } } } for (Iterator choiceIter = choices.iterator(); choiceIter.hasNext();) { XmlChoice xmlChoice = (XmlChoice)choiceIter.next(); ChoiceOption defaultOption = xmlChoice.getDefaultOption(); // collect all the xml Element in the document DocumentVisitor visitor = new DocumentVisitor(); ModelVisitorProcessor processor = new ModelVisitorProcessor(visitor); try { processor.walk(xmlChoice, ModelVisitorProcessor.DEPTH_ONE); } catch (ModelerCoreException e) { ValidationProblem problem = new ValidationProblemImpl( 0, IStatus.ERROR, TransformationPlugin.Util.getString("XmlDocumentValidationRule.Error_trying_to_collect_XmlElements_in_an_XmlDocument__2") + e.getMessage(), getLocationPath(xmlChoice), getURIString(xmlChoice)); //$NON-NLS-1$ validationResult.addProblem(problem); return false; } // Defect 22678 - Don't validate the choice node if Logical XML model if (isLogicalXMLModel) { continue; } Collection choiceContents = visitor.getEntities(); for (Iterator choiceCntIter = choiceContents.iterator(); choiceCntIter.hasNext();) { // The entity exists under a choice (the "owner") Object choiceEntity = choiceCntIter.next(); // choice its self would be part of contents ignore it. if (choiceEntity.equals(xmlChoice)) { continue; } if (choiceEntity instanceof ChoiceOption) { ChoiceOption option = (ChoiceOption)choiceEntity; if (choiceEntity instanceof XmlDocumentNode) { XmlDocumentNode documentNode = (XmlDocumentNode)choiceEntity; // The entity is excluded from the document if (documentNode.isExcludeFromDocument()) { continue; } } // criteria must be defined String choiceCriteria = option.getChoiceCriteria(); if (CoreStringUtil.isEmpty(choiceCriteria)) { // The entity is the default for the owner if (ModelerCore.getModelEditor().equals(option, defaultOption)) { continue; } ValidationProblem problem = new ValidationProblemImpl( 0, IStatus.ERROR, TransformationPlugin.Util.getString("XmlDocumentValidationRule.The_option_of_a_Choice_must_either_have_the_criteria_defined,_or_be_the_default._3"), getLocationPath(option), getURIString(option)); //$NON-NLS-1$ validationResult.addProblem(problem); return false; } // If a criteria statement is defined, it must be parsable and resolvable if (!validateCriteria(option, xmlChoice, mappingAdapter, mappableTree, validationResult, context)) { return false; } } } } return true; } /** * Rules that apply to elements and attributes. An element/attribute may be mapped to a mapping class column except when any * of the following conditions are true: 1) The element/attribute is the parent of other elements or attributes 2) The * element/attribute has a value type of "DEFAULT" or "FIXED" 3) The element/attribute is excluded from the document 4) The * referenced schema component has a min occurs of 0 4) The referenced schema component has a max occurs of 1, as this may * result in a document that violates the schema. 5) The referenced schema component is nillable */ private boolean validateEntities( final Collection entities, final ValidationResult validationResult, final ValidationContext context ) { // If this is a Logical model, just return (i.e. XML Message Structure Model) // Defect 22678 boolean isLogicalXMLModel = false; if (entities != null && entities.size() > 0) { EmfResource emfResource = (EmfResource)((EObject)entities.iterator().next()).eResource(); if (emfResource.getModelAnnotation() != null) { ModelType type = emfResource.getModelAnnotation().getModelType(); String stringURI = emfResource.getModelAnnotation().getPrimaryMetamodelUri(); if (type.equals(ModelType.LOGICAL_LITERAL) && XmlDocumentPackage.eNS_URI.equals(stringURI)) { isLogicalXMLModel = true; } } } for (final Iterator entIter = entities.iterator(); entIter.hasNext();) { Object entity = entIter.next(); // ignore XmlDocument object if (entity instanceof XmlDocument || !(entity instanceof XmlDocumentNode)) { continue; } // validate the document node XmlDocumentNode node = (XmlDocumentNode)entity; // validate entities against their schema components validateSchemaComponent(node, validationResult, context); // node is an element or an attribute if (isElementOrAttribute(node)) { boolean isProcMapping = false; final SqlAspect aspect = AspectManager.getSqlAspect(node); if (aspect != null && aspect instanceof XmlElementSqlAspect) { // Ignore certain warnings for Elements mapped to Procedure Inputs isProcMapping = ((XmlElementSqlAspect)aspect).isTranformationInputParameter(node); } // get the xsdComponent for the node XSDComponent xsdComponent = node.getXsdComponent(); int minOccurs = xsdComponent != null ? XsdUtil.getMinOccurs(xsdComponent) : -1; int maxOccurs = xsdComponent != null ? XsdUtil.getMaxOccurs(xsdComponent) : -1; // if the node is mapped to a mapping class column if (hasMappedMappingClassColumn(node)) { // all elements and attributes are value holders XmlValueHolder valueHolder = (XmlValueHolder)node; // The element/attribute has a value type of "DEFAULT" or "FIXED" if (valueHolder.isValueFixed() || valueHolder.isValueDefault()) { int status = context.getPreferenceStatus(ValidationPreferences.XML_FIXED_DEFAULT_ELEMENT_MAPPED, IStatus.WARNING); if (!isProcMapping && status != IStatus.OK) { ValidationProblem problem = new ValidationProblemImpl( 0, status, TransformationPlugin.Util.getString("XmlDocumentValidationRule.This_entity_{0}_is_fixed_or_default_and_should_not_have_a_mapping_attribute_defined_in_MappingClasses_1", node.getName()), getLocationPath(node), getURIString(node)); //$NON-NLS-1$ problem.setHasPreference(this.context.hasPreferences()); validationResult.addProblem(problem); if (status == IStatus.ERROR) { return false; } } } // The element/attribute is excluded from the document if (node.isExcludeFromDocument()) { int status = context.getPreferenceStatus(ValidationPreferences.XML_EXCLUDED_ELEMENT_MAPPED, IStatus.WARNING); if (!isProcMapping && status != IStatus.OK) { ValidationProblem problem = new ValidationProblemImpl( 0, status, TransformationPlugin.Util.getString("XmlDocumentValidationRule.The_entity_{0}_has_been_selected_to_be_excluded_from_the_Document,_but_has_a_mapping_attribute_defined_in_MappingClasses_2", node.getName()), getLocationPath(node), getURIString(node)); //$NON-NLS-1$ problem.setHasPreference(this.context.hasPreferences()); validationResult.addProblem(problem); if (status == IStatus.ERROR) { return false; } } } // The referenced schema component has a min occurs = 0 // Validation may not be needed if (minOccurs == 0) { int status = context.getPreferenceStatus(ValidationPreferences.XML_ELEMENT_ZERO_MIN_MAPPED, IStatus.WARNING); if (status != IStatus.OK) { ValidationProblem problem = new ValidationProblemImpl( 0, status, TransformationPlugin.Util.getString("XmlDocumentValidationRule.The_entity_{0}_has_a_min_occurs_of_zero,_but_has_a_mapping_attribute_defined_in_MappingClasses_3", node.getName()), getLocationPath(node), getURIString(node)); //$NON-NLS-1$ validationResult.addProblem(problem); problem.setHasPreference(this.context.hasPreferences()); if (status == IStatus.ERROR) { return false; } } } // The referenced schema component is nillable if (XsdUtil.isNillable(xsdComponent)) { int status = context.getPreferenceStatus(ValidationPreferences.XML_ELEMENT_NILLABLE_MAPPED, IStatus.WARNING); if (!isProcMapping && status != IStatus.OK) { ValidationProblem problem = new ValidationProblemImpl( 0, status, TransformationPlugin.Util.getString("XmlDocumentValidationRule.The_entity_{0}__s_schema_component_reference_is_nullable,_but_has_a_mapping_attribute_defined_in_MappingClasses._5", node.getName()), getLocationPath(node), getURIString(node)); //$NON-NLS-1$ validationResult.addProblem(problem); problem.setHasPreference(this.context.hasPreferences()); if (status == IStatus.ERROR) { return false; } } } // Defect 18718 - Cannot find any reason why we check for this condition // // The element/attribute is the parent of other elements or attributes // int status = getPreferenceStatus(ValidationPreferences.XML_ELEMENT_CHILDREN_MAPPED, IStatus.WARNING); // if(status != IStatus.OK) { // for(final Iterator contentIter = node.eAllContents();contentIter.hasNext();) { // Object child = contentIter.next(); // if(isElementOrAttribute(child)) { // ValidationProblem problem = new ValidationProblemImpl(0, status, TransformationPlugin.Util.getString("XmlDocumentValidationRule.The_entity_{0}_having_element/attribute_children_may_not_have_a_mapping_attribute_defined_in_MappingClasses._6", node.getName()), getLocationPath(node), getURIString(node)); //$NON-NLS-1$ // problem.setHasPreference(this.preferences != null); // validationResult.addProblem(problem); // if(status == IStatus.ERROR) { // return false; // } // } // } // } // if the node is mapped to a mapping class } else if (hasMappedMappingClass(node)) { // Add check to ignore Logical models if (!isLogicalXMLModel) { if (node instanceof XmlRoot) { int status = context.getPreferenceStatus(ValidationPreferences.XML_ROOT_ELEMENT_MAPPING_CLASS, IStatus.WARNING); if (status != IStatus.OK) { ValidationProblem problem = new ValidationProblemImpl( 0, status, TransformationPlugin.Util.getString("XmlDocumentValidationRule.The_element_{0}_is_root_but_is_mapped_to_a_MappingClass", node.getName()), getLocationPath(node), getURIString(node)); //$NON-NLS-1$ problem.setHasPreference(this.context.hasPreferences()); validationResult.addProblem(problem); if (status == IStatus.ERROR) { return false; } } // The referenced schema component has a max occurs == 1 } else if (maxOccurs == 1 && node instanceof XmlElement) { int status = context.getPreferenceStatus(ValidationPreferences.XML_ELEMENT_ONE_MAX_MAPPED, IStatus.WARNING); if (status != IStatus.OK) { ValidationProblem problem = new ValidationProblemImpl( 0, status, TransformationPlugin.Util.getString("XmlDocumentValidationRule.The_element_{0}_has_a_max_occurs_of_one,_but_is_mapped_to_a_MappingClass", node.getName()), getLocationPath(node), getURIString(node)); //$NON-NLS-1$ problem.setHasPreference(this.context.hasPreferences()); validationResult.addProblem(problem); if (status == IStatus.ERROR) { return false; } } } } } } } return true; } /** * Rules that apply to entity's schema compatability. Potential Problems (some based on preference) 1) The entity does not * reference a schema component. 2) Number of entity's siblings referencing a schema component exceeds the maxOccurrence. 3) * Entity is excluded from the document and is not optional. 4) Entity is excluded from the document and is not under an * optional entity. */ private boolean validateSchemaComponent( final XmlDocumentNode node, final ValidationResult validationResult, final ValidationContext context ) { // get the xsdComponent for the node XSDComponent xsdComponent = node.getXsdComponent(); if (xsdComponent == null) { if (isElementOrAttribute(node)) { int status = context.getPreferenceStatus(ValidationPreferences.XML_ELEMENT_SCHEMA_REFERENCE, IStatus.WARNING); if (status != IStatus.OK) { EObject containerNode = node.eContainer(); CoreArgCheck.isInstanceOf(XmlDocumentEntity.class, containerNode); Object containerComponent = getXsdComponent((XmlDocumentEntity)containerNode); if (containerComponent != null) { ValidationProblem problem = new ValidationProblemImpl( 0, status, TransformationPlugin.Util.getString("XmlDocumentValidationRule.The_document_element/attribute_{0}_doesn__t_reference_a_schema_component._10", node.getName()), getLocationPath(node), getURIString(node)); //$NON-NLS-1$ problem.setHasPreference(this.context.hasPreferences()); validationResult.addProblem(problem); return false; } } } return true; } // resolve it if its a proxy if (xsdComponent.eIsProxy()) { // resolve the proxy try { final Container container = ModelerCore.getModelContainer(); EcoreUtil.resolve(xsdComponent, container); } catch (CoreException err) { ValidationProblem problem = new ValidationProblemImpl(0, IStatus.ERROR, err.getMessage()); validationResult.addProblem(problem); return false; } } // get the max occurs int maxOccurs = XsdUtil.getMaxOccurs(xsdComponent); // find all the siblings that reference the same schema object EObject parent = node.eContainer(); // if maxOccurs is not unlimited if (parent != null && maxOccurs > -1) { int childCount = 0; for (Iterator contentIter = parent.eContents().iterator(); contentIter.hasNext();) { Object child = contentIter.next(); if (child instanceof XmlDocumentNode) { XmlDocumentNode childNode = (XmlDocumentNode)child; if (!childNode.isExcludeFromDocument()) { // get childNodes xsd component XSDComponent childComponent = childNode.getXsdComponent(); if (childComponent != null && ModelerCore.getModelEditor().equals(xsdComponent, childComponent)) { childCount++; } } } } // check the number of children of each schema object to the max number ... if (childCount > maxOccurs) { int status = context.getPreferenceStatus(ValidationPreferences.XML_ENTITY_MAXOCCURS_VIOLATION, IStatus.WARNING); if (status != IStatus.OK) { if (maxOccurs == 0 && XsdUtil.isAttribute(xsdComponent)) { ValidationProblem problem = new ValidationProblemImpl( 0, status, TransformationPlugin.Util.getString("XmlDocumentValidationRule.The_attribute_{0}_references_a_prohibited_schema_attribute._7", node.getName()), getLocationPath(node), getURIString(node)); //$NON-NLS-1$ problem.setHasPreference(this.context.hasPreferences()); validationResult.addProblem(problem); } else { ValidationProblem problem = new ValidationProblemImpl( 0, status, TransformationPlugin.Util.getString("XmlDocumentValidationRule.The_entity_{0},_may_be_violating_maxOccurs_specified_by_the_schema._1", node.getName()), getLocationPath(node), getURIString(node)); //$NON-NLS-1$ problem.setHasPreference(this.context.hasPreferences()); validationResult.addProblem(problem); } } } } // An entity(element/attribute) may be excluded from the document when any of the following conditions are true // 1) The element/attribute is optional (i.e., has a reference to a schema component with a minOccurs of 0 or is nillable) // 2) The element/attribute exists below an element that is optional if (node.isExcludeFromDocument()) { Object container = node.eContainer(); CoreArgCheck.isInstanceOf(XmlDocumentEntity.class, container); XmlDocumentEntity containerNode = (XmlDocumentEntity)container; if (!isOptional(node) && !isOptional(containerNode)) { boolean isProcMapping = false; final SqlAspect aspect = AspectManager.getSqlAspect(node); if (aspect != null && aspect instanceof XmlElementSqlAspect) { // Ignore this warning for Elements mapping to Procedure Inputs isProcMapping = ((XmlElementSqlAspect)aspect).isTranformationInputParameter(node); } int status = context.getPreferenceStatus(ValidationPreferences.XML_REQUIRED_ELEMENT_EXCLUDE, IStatus.WARNING); if (!isProcMapping && status != IStatus.OK) { ValidationProblem problem = new ValidationProblemImpl( 0, status, TransformationPlugin.Util.getString("XmlDocumentValidationRule.The_entity_{0}_has_been_selected_to_be_excluded_from_the_Document,_but_the_neither_the_entity_nor_its_parent_are_optional._9", node.getName()), getLocationPath(node), getURIString(node)); //$NON-NLS-1$ problem.setHasPreference(this.context.hasPreferences()); validationResult.addProblem(problem); return false; } } } return true; } /** * If a criteria statement is defined, it must be parsable and resolvable. Also check if the choice is in the context of the * mapping class whose columns are used in the criteria. 1) Error -> Choice criteria is not parsable, resolvable or invalid. * 2) Error - > If the choice criteria is referencing groups other than valid mapping classes for this choice node. */ private boolean validateCriteria( final ChoiceOption option, final XmlChoice xmlChoice, final TreeMappingAdapter mappingAdapter, final IMappableTree mappableTree, final ValidationResult validationResult, final ValidationContext context ) { String choiceCriteria = option.getChoiceCriteria(); // have a criteria, validate it ICriteria criteria = null; Collection groups = null; try { // Parse IQueryService queryService = ModelerCore.getTeiidQueryService(); IQueryParser parser = queryService.getQueryParser(); criteria = parser.parseCriteria(choiceCriteria); IQueryMetadataInterface metadata = null; if (context != null && context.useServerIndexes()) { // Validating within the vdb context. The TargetLocationIndexSelector will gather // all index files under a specified directory location. IndexSelector selector = new TargetLocationIndexSelector(context.getIndexLocation()); QueryMetadataContext queryContext = new QueryMetadataContext(selector); // set the resource scope queryContext.setResources(Arrays.asList(context.getResourcesInScope())); // set the restrict search falg queryContext.setRestrictedSearch(true); metadata = TransformationMetadataFactory.getInstance().getVdbMetadata(queryContext, context.getResourceContainer()); } else { // Validating within the modeler workspace metadata = TransformationMetadataFactory.getInstance().getModelerMetadata(option, true); } // Determine groups groups = getGroups(criteria, metadata); // Resolve IResolverVisitor resolverVisitor = queryService.getResolverVisitor(); resolverVisitor.resolveLanguageObject(criteria, groups, metadata); // validate IValidator validator = queryService.getValidator(); IValidatorReport report = validator.validate(criteria, metadata); if (report.hasItems()) { Collection problems = createValidationProblems(report); for (Iterator probIter = problems.iterator(); probIter.hasNext();) { validationResult.addProblem((ValidationProblem)probIter.next()); } return false; } } catch (Throwable e) { ValidationProblem problem = new ValidationProblemImpl( 0, IStatus.ERROR, TransformationPlugin.Util.getString("XmlDocumentValidationRule.Error_trying_validate_choice_criteria__14") + choiceCriteria + TransformationPlugin.Util.getString("XmlDocumentValidationRule.__15") + e.getMessage()); //$NON-NLS-1$ //$NON-NLS-2$ validationResult.addProblem(problem); return false; } // ---------------------------------------------------------------------------------------- // Find the valid mapping classes / staging tables for this choice node // Verify that the mapping classes used in the criteria are among the valid mapping classes // ---------------------------------------------------------------------------------------- // Gather up the valid mapping classes MappingClass mc = mappingAdapter.getMappingClass(xmlChoice); if (mc == null) { EObject parent = xmlChoice.getParent(); while (parent != null && mc == null) { mc = mappingAdapter.getMappingClass(parent); parent = parent.eContainer(); } } List validMappingClasses = getParentMappingClasses(mc, mappingAdapter, mappableTree); if (mc != null) { validMappingClasses.add(mc); } // Iterate the criteria groups - ensure that they are among the valid list of mapping classes for (final Iterator grpIter = groups.iterator(); grpIter.hasNext();) { IGroupSymbol grpSymbol = (IGroupSymbol)grpIter.next(); MetadataRecord record = (MetadataRecord)grpSymbol.getMetadataID(); EObject grpObject = (EObject)context.getResourceContainer().getEObjectFinder().find(record.getUUID()); if (!validMappingClasses.contains(grpObject)) { String choicePath = ModelerCore.getModelEditor().getModelRelativePath(option).toString(); ValidationProblem problem = new ValidationProblemImpl( 0, IStatus.ERROR, TransformationPlugin.Util.getString("XmlDocumentValidationRule.0", grpSymbol, choicePath)); //$NON-NLS-1$ validationResult.addProblem(problem); return false; } } return true; } // Access the TreeMappingAdapter framework and return the collection of parent or previous // mapping classes and staging tables that can be used in the choice criteria. static List getParentMappingClasses( final MappingClass mappingClass, final TreeMappingAdapter mappingAdapter, final IMappableTree mappableTree ) { List result = new ArrayList(); if (mappingAdapter != null) { Collection parentMappingClasses = mappingAdapter.getParentMappingClasses(mappingClass, mappableTree, false); if (parentMappingClasses != null && !parentMappingClasses.isEmpty()) { result.addAll(parentMappingClasses); } } return result; } class DocumentVisitor implements ModelVisitor { Collection elements = new ArrayList(); Collection attributes = new ArrayList(); Collection choices = new ArrayList(); Collection entities = new ArrayList(); @Override public boolean visit( final EObject eObject ) { if (eObject instanceof XmlElement) { elements.add(eObject); } if (eObject instanceof XmlAttribute) { attributes.add(eObject); } if (eObject instanceof XmlChoice) { choices.add(eObject); } entities.add(eObject); return true; } public Collection getElements() { return elements; } public Collection getEntities() { return entities; } public Collection getChoices() { return choices; } public Collection getAttributes() { return attributes; } /** * @see org.teiid.designer.core.util.ModelVisitor#visit(org.eclipse.emf.ecore.resource.Resource) */ @Override public boolean visit( final Resource resource ) { return true; } } /** * Get the resolved groups for the elements on the criteria, including any groups used in subqueries. */ private Collection getGroups( final ICriteria criteria, final IQueryMetadataInterface metadata ) throws Exception { IQueryService queryService = ModelerCore.getTeiidQueryService(); IGroupsUsedByElementsVisitor groupsUsedByElementsVisitor = queryService.getGroupsUsedByElementsVisitor(); Set<IGroupSymbol> groups = groupsUsedByElementsVisitor.findGroups(criteria); for (IGroupSymbol groupSymbol : groups) { queryService.resolveGroup(groupSymbol, metadata); } return groups; } /* * Entity is an element or an attribute */ private boolean isElementOrAttribute( final Object entity ) { if (entity instanceof XmlElement || entity instanceof XmlAttribute) { return true; } return false; } /** * The element/attribute is optional (i.e., has a reference to a schema component with a minOccurs of 0 or is nillable) */ private boolean isOptional( final XmlDocumentEntity node ) { if (isElementOrAttribute(node)) { XSDComponent xsdComponent = getXsdComponent(node); CoreArgCheck.isNotNull(xsdComponent); if (XsdUtil.getMinOccurs(xsdComponent) == 0) { return true; } if (XsdUtil.isNillable(xsdComponent)) { return true; } } return false; } /** * Return the xsdComponet for the given node. */ private XSDComponent getXsdComponent( final XmlDocumentEntity node ) { XSDComponent xsdComponent = null; if (node instanceof XmlDocumentNode) { xsdComponent = ((XmlDocumentNode)node).getXsdComponent(); } else if (node instanceof XmlContainerNode) { xsdComponent = ((XmlContainerNode)node).getXsdComponent(); } return xsdComponent; } /** * Entity has a mapping to a mapping class column */ private boolean hasMappedMappingClassColumn( final Object entity ) { Object mappedObj = this.elementColumnMap.get(entity); if (mappedObj != null) { return true; } return false; } /** * Entity has a mapping to a mapping class */ private boolean hasMappedMappingClass( final Object entity ) { Object mappedObj = this.elementMappingClassMap.get(entity); if (mappedObj != null) { return true; } return false; } /** * Private method for creating a List of ValidationProblem objects from a ValidatorReport * * @param report the ValidatorReport * @return the List of ValidationProblem */ private Collection createValidationProblems( final IValidatorReport report ) { if (report != null && report.hasItems()) { Collection<? extends IValidatorFailure> items = report.getItems(); Collection problemList = new ArrayList(items.size()); for (Iterator<? extends IValidatorFailure> itemIter = items.iterator(); itemIter.hasNext();) { IValidatorFailure item = itemIter.next(); ValidationProblem problem = new ValidationProblemImpl(0, IStatus.ERROR, item.toString()); problemList.add(problem); } return problemList; } return Collections.EMPTY_LIST; } }