/******************************************************************************* * Copyright (c) 2010 SAP AG. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Emil Simeonov - initial API and implementation. * Dimitar Donchev - initial API and implementation. * Dimitar Tenev - initial API and implementation. * Nevena Manova - initial API and implementation. * Georgi Konstantinov - initial API and implementation. * Stanislav Nichev - initial API and implementation. *******************************************************************************/ package org.eclipse.wst.sse.sieditor.model.reconcile.adapters; import org.eclipse.emf.common.notify.Notification; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.InternalEObject; import org.eclipse.emf.ecore.impl.ENotificationImpl; import org.eclipse.wst.sse.core.internal.provisional.INodeNotifier; import org.eclipse.wst.wsdl.Operation; import org.eclipse.wst.wsdl.Part; import org.eclipse.wst.wsdl.WSDLPackage; import org.eclipse.wst.wsdl.XSDSchemaExtensibilityElement; import org.eclipse.wst.wsdl.util.WSDLConstants; import org.eclipse.xsd.XSDAttributeDeclaration; import org.eclipse.xsd.XSDConcreteComponent; import org.eclipse.xsd.XSDElementDeclaration; import org.eclipse.xsd.XSDFeature; import org.eclipse.xsd.XSDImport; import org.eclipse.xsd.XSDPackage; import org.eclipse.xsd.XSDSchema; import org.eclipse.xsd.impl.XSDImportImpl; import org.eclipse.xsd.util.XSDConstants; import org.w3c.dom.Attr; import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.eclipse.wst.sse.sieditor.model.reconcile.adapters.componentsource.IConcreteComponentSource; import org.eclipse.wst.sse.sieditor.model.utils.EmfXsdUtils; /** * Subclass of the {@link AbstractModelReconcileAdapter}. This adapter is * responsible for the fixing of problems caused by DOM element attributes * changes. */ public class AttributesReconcileAdapter extends AbstractModelReconcileAdapter { public AttributesReconcileAdapter(final IConcreteComponentSource concreteComponentSource) { super(concreteComponentSource); } @Override protected void processNotifyChanged(final INodeNotifier notifier, final int eventType, final Object changedFeature, final Object oldValue, final Object newValue, final int pos) { if (changedFeature instanceof Attr) { processAttributeChangeFeature(notifier, (Attr) changedFeature, oldValue, newValue); } } private void processAttributeChangeFeature(final INodeNotifier notifier, final Attr attr, final Object oldValue, final Object newValue) { processNameAttribute(notifier, attr.getLocalName()); processRefAttribute(notifier, attr.getLocalName(), newValue); processSchemaLocationAttribute(notifier, attr.getLocalName(), newValue); processAttributeStartingWithXMLNS(notifier, oldValue, newValue, attr); processParameterOrderAttribute(notifier, attr.getLocalName()); processTargetNamespaceAttribute(notifier, attr.getName()); processNamespaceAttribute(notifier, attr.getName()); processElementAttribute(notifier, attr.getLocalName(), newValue); processTypeAttribute(notifier, attr.getLocalName(), newValue); processDefaultAttribute(notifier, attr.getLocalName(), newValue); } private void processNameAttribute(final INodeNotifier notifier, final String attributeName) { if (XSDConstants.NAME_ATTRIBUTE.equals(attributeName)) { final EObject eObject = concreteComponentSource.getConcreteComponentFor((Element) notifier); if (eObject instanceof XSDElementDeclaration) { this.getModelReconcileRegistry().setNeedsReconciling(true); } } } private void processRefAttribute(final INodeNotifier notifier, final String attributeName, final Object newValue) { if (XSDConstants.REF_ATTRIBUTE.equals(attributeName) && newValue != null) { /* * we are in the following scenario: we are setting "ref" to element * attribute. if the element we are updating has "type" attribute, * the EMF model does not set it to null. that way, the updated * element has both "type" and "ref" attributes, which is invalid */ final EObject eObject = concreteComponentSource.getConcreteComponentFor((Element) notifier); if (eObject instanceof XSDConcreteComponent) { final XSDConcreteComponent element = (XSDConcreteComponent) eObject; if (element instanceof XSDElementDeclaration) { ((XSDElementDeclaration) element).setTypeDefinition(null); } else if (element instanceof XSDAttributeDeclaration) { ((XSDAttributeDeclaration) element).setTypeDefinition(null); } } } } private void processSchemaLocationAttribute(final INodeNotifier notifier, final String attributeName, final Object newValue) { // ************************************************** // This method is mostly duplicated functionality from the // com.sap.tc.esmp.tools.sieditor.model.reconcile.utils.XsdReconcileUtils.reconcileSchemaContentsInternal(XSDSchema, // Map<String, String>, ObjectsForResolveContainer, Set<XSDSchema>, int, // NamespaceResolverType) // line : 116 // The main difference is that the reconciler recursively reaches a // certain level in the tree of schema imports. // ****************************** // if (XSDConstants.SCHEMALOCATION_ATTRIBUTE.equals(attributeName)) { // final EObject eObject = // concreteComponentSource.getConcreteComponentFor((Element) notifier); // // if (eObject instanceof XSDImportImpl) { // final XSDSchema resolvedSchema = resolveImportedSchema(newValue, // eObject); // getModelReconcileRegistry().addChangedSchema(resolvedSchema); // getModelReconcileRegistry().setNeedsReconciling(true); // } // } } // ****************************** // Obsolete code, as in the method before // ****************************** // private XSDSchema resolveImportedSchema(final Object newValue, final // EObject eObject) { // XSDSchema schema = null; // // final XSDImportImpl xsdImport = (XSDImportImpl) eObject; // if (xsdImport.getResolvedSchema() == null && newValue == null) { // xsdImport.reset(); // xsdImport.importSchema(); // } // schema = xsdImport.getResolvedSchema(); // return schema; // } private void processAttributeStartingWithXMLNS(final INodeNotifier notifier, final Object oldValue, final Object newValue, final Attr attr) { if (attr.getName().startsWith(EmfXsdUtils.XMLNS_PREFIX)) { /* * here we are updating the "xmlns" attribute. if that's the case, * we are either updating the schema element of a XSD document, or a * WSDL definition element, or a WSDL types schema */ final EObject eObject = concreteComponentSource.getConcreteComponentFor((Element) notifier); XSDSchema schema = null; if (eObject instanceof XSDSchema) { schema = (XSDSchema) eObject; } else if (eObject instanceof XSDSchemaExtensibilityElement) { schema = ((XSDSchemaExtensibilityElement) eObject).getSchema(); } if (schema != null) { this.getModelReconcileRegistry().setNeedsReconciling(true); this.getModelReconcileRegistry().addChangedSchema(schema); fixSchemaForSchemaState(oldValue, newValue, attr, schema); } } } private void fixSchemaForSchemaState(final Object oldValue, final Object newValue, final Attr attr, final XSDSchema xsdSchema) { final String prefix = getPrefix(attr); if (newValue == null) { /* * The new value is null, so we need to remove the prefix/namespace * from the EMF model map. In some cases (on undo/redo) the EMF * didn't do that. * * We also need to check the number of times this attribute is * appearing in the schema element's body, because we don't want to * update the map if more than one attribute with the same name * exists. This is causing an unwanted DOM update of all the * attribute values with the same name, instead of just the * currently edited one. * * Please note here that the currently edited attribute is already * removed from DOM and that's why we check if the current count is * zero */ if (countAttributeWithSameName(attr.getName(), xsdSchema.getElement()) == 0 && xsdSchema.getQNamePrefixToNamespaceMap().containsKey(prefix)) { xsdSchema.getQNamePrefixToNamespaceMap().remove(prefix); } /* * notify for the change anyway, since we want to run the * validation. the EMF model didn't do that as well */ xsdSchema.eNotify(new ENotificationImpl((InternalEObject) xsdSchema, Notification.REMOVE, null, null, null)); } else { /* * we have non-null value, so we must be updating existing * namespace, or adding a new one */ final String newNamespaceValue = attr.getValue(); final String oldNamespaceValue = xsdSchema.getQNamePrefixToNamespaceMap().get(prefix); // we should not be updating the map when there are more than one // attribute with same name in the element if (countAttributeWithSameName(attr.getName(), xsdSchema.getElement()) <= 1 && !newNamespaceAndOldNamespaceAreTheSame(newNamespaceValue, oldNamespaceValue)) { xsdSchema.getQNamePrefixToNamespaceMap().put(prefix, newNamespaceValue); } /* * notify for the change anyway, since we want to run the * validation. the EMF model didn't do that as well */ xsdSchema.eNotify(new ENotificationImpl((InternalEObject) xsdSchema, Notification.ADD, null, null, null)); } } /** * @return the number of times the given attribute name is appearing in the * element attributes collection. */ private int countAttributeWithSameName(final String attributeName, final Element element) { final NamedNodeMap attributes = element.getAttributes(); final int attributesSize = attributes.getLength(); int count = 0; for (int i = 0; i < attributesSize; i++) { final Node item = attributes.item(i); if (attributeName.equals(item.getNodeName())) { count++; } } return count; } private boolean newNamespaceAndOldNamespaceAreTheSame(final String newNamespaceValue, final String oldNamespaceValue) { if (newNamespaceValue == null && oldNamespaceValue == null) { return true; } if (oldNamespaceValue == null && newNamespaceValue.isEmpty()) { // generally, "null" namespace and empty one will be treated the // same return true; } // XSDConstants.isSchemaForSchemaNamespace(attr.getValue()) && return newNamespaceValue.equals(oldNamespaceValue); } private void processParameterOrderAttribute(final INodeNotifier notifier, final String attributeName) { if (WSDLConstants.PARAMETER_ORDER_ATTRIBUTE.equals(attributeName)) { /* we need to check if we are updating a part name */ final EObject eObject = concreteComponentSource.getConcreteComponentFor((Element) notifier); if (eObject instanceof Operation) { /* * we need to tell the operation EMF object to update itself * from its corresponding DOM element. this updates the * parameterOrdering collection of the operation */ ((Operation) eObject).elementChanged((Element) notifier); } } } private void processTargetNamespaceAttribute(final INodeNotifier notifier, final String attributeName) { if (XSDConstants.TARGETNAMESPACE_ATTRIBUTE.equals(attributeName)) { /* * we need to prepare the reconciling information. setting * tagetNamespace "breaks" the EMF WSDL references */ this.getModelReconcileRegistry().setNeedsReconciling(true); if (notifier instanceof Element) { final EObject eObject = concreteComponentSource.getConcreteComponentFor((Element) notifier); if (eObject instanceof XSDSchema) { this.getModelReconcileRegistry().addChangedSchema((XSDSchema) eObject); } else if (eObject instanceof XSDSchemaExtensibilityElement) { this.getModelReconcileRegistry().addChangedSchema(((XSDSchemaExtensibilityElement) eObject).getSchema()); } } } } private void processNamespaceAttribute(final INodeNotifier notifier, final String attributeName) { if (WSDLConstants.NAMESPACE_ATTRIBUTE.equals(attributeName)) { this.getModelReconcileRegistry().setNeedsReconciling(true); /* * we are updating schema import. this breaks the schema references * to the changed import. we need to update the information for * resolve. */ if (notifier instanceof Element) { final EObject eObject = concreteComponentSource.getConcreteComponentFor((Element) notifier); if (eObject instanceof XSDImport) { this.getModelReconcileRegistry().addChangedSchema(((XSDImport) eObject).getSchema()); } } } } private void processElementAttribute(final INodeNotifier notifier, final String attributeName, final Object newValue) { if (WSDLConstants.ELEMENT_ATTRIBUTE.equals(attributeName)) { processPartAttribute(notifier, newValue, WSDLPackage.PART__ELEMENT_DECLARATION); } } private void processTypeAttribute(final INodeNotifier notifier, final String attributeName, final Object newValue) { if (WSDLConstants.TYPE_ATTRIBUTE.equals(attributeName)) { processPartAttribute(notifier, newValue, WSDLPackage.PART__TYPE_DEFINITION); processElementTypeAttribute(notifier, newValue); } } private void processPartAttribute(final INodeNotifier notifier, final Object newValue, final int eStructuralFeatureId) { if (notifier instanceof Element) { final EObject eObject = concreteComponentSource.getConcreteComponentFor((Element) notifier); if (eObject instanceof Part) { this.getModelReconcileRegistry().setNeedsReconciling(true); final Part part = (Part) eObject; if (newValue == null) { part .eNotify(new ENotificationImpl((InternalEObject) part, Notification.SET, eStructuralFeatureId, null, null)); } part.elementChanged(part.getElement()); } } } private void processElementTypeAttribute(final INodeNotifier notifier, final Object newValue) { if (notifier instanceof Element && newValue != null) { final EObject eObject = concreteComponentSource.getConcreteComponentFor((Element) notifier); if (eObject instanceof XSDElementDeclaration) { final XSDElementDeclaration elementDeclaration = (XSDElementDeclaration) eObject; if (elementDeclaration.getTypeDefinition() != null && elementDeclaration.getTypeDefinition().eContainer() == null) { getModelReconcileRegistry().setNeedsReconciling(true); getModelReconcileRegistry().addChangedSchema(elementDeclaration.getSchema()); } } } } private void processDefaultAttribute(final INodeNotifier notifier, final String attributeName, final Object newValue) { if (XSDConstants.DEFAULT_ATTRIBUTE.equals(attributeName)) { if (notifier instanceof Element) { final EObject eObject = concreteComponentSource.getConcreteComponentFor((Element) notifier); ((XSDFeature)eObject).setLexicalValue((String)newValue); eObject.eNotify(new ENotificationImpl((InternalEObject) eObject, Notification.SET, XSDPackage.XSD_FEATURE__VALUE, null, newValue)); } } } }