blob: be51f197420c7a04647e9c851e82088ba0a94a55 [file] [log] [blame]
/**
* <copyright>
*
* 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:
* Reiner Hille-Doering (SAP AG) - initial API and implementation and/or initial documentation
*
* </copyright>
*/
package org.eclipse.bpmn2.util;
import java.util.Iterator;
import java.util.Map;
import javax.xml.XMLConstants;
import org.eclipse.bpmn2.Bpmn2Package;
import org.eclipse.bpmn2.Definitions;
import org.eclipse.bpmn2.DocumentRoot;
import org.eclipse.bpmn2.Import;
import org.eclipse.emf.common.notify.NotificationChain;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.ecore.xmi.XMLHelper;
import org.eclipse.emf.ecore.xmi.XMLLoad;
import org.eclipse.emf.ecore.xmi.XMLResource;
import org.eclipse.emf.ecore.xmi.XMLSave;
import org.eclipse.emf.ecore.xmi.impl.SAXXMLHandler;
import org.eclipse.emf.ecore.xmi.impl.XMLHelperImpl;
import org.eclipse.emf.ecore.xmi.impl.XMLLoadImpl;
import org.eclipse.emf.ecore.xmi.impl.XMLResourceImpl;
import org.eclipse.emf.ecore.xmi.impl.XMLSaveImpl;
import org.xml.sax.helpers.DefaultHandler;
/**
* <!-- begin-user-doc -->
* The <b>Resource </b> associated with the package.
* @implements Bpmn2Resource
* <!-- end-user-doc -->
* @see org.eclipse.bpmn2.util.Bpmn2ResourceFactoryImpl
* @generated
*/
public class Bpmn2ResourceImpl extends XMLResourceImpl implements Bpmn2Resource {
private QNameURIHandler uriHandler;
private BpmnXmlHelper xmlHelper;
// CHECK: make this optional (as it adds notification overhead)
// ... or lazy (also works if added later on, because it attaches itself to the whole tree at once)
protected Bpmn2OppositeReferenceAdapter oppositeReferenceAdapter = new Bpmn2OppositeReferenceAdapter();
public Bpmn2OppositeReferenceAdapter getOppositeReferenceAdapter() {
return oppositeReferenceAdapter;
}
/**
* Creates an instance of the resource.
* <!-- begin-user-doc -->
* <!-- end-user-doc -->
* @param uri the URI of the new resource.
* @generated NOT
*/
public Bpmn2ResourceImpl(URI uri) {
super(uri);
this.xmlHelper = new BpmnXmlHelper(this);
this.uriHandler = new QNameURIHandler(xmlHelper);
this.getDefaultLoadOptions().put(XMLResource.OPTION_URI_HANDLER, uriHandler);
this.getDefaultSaveOptions().put(XMLResource.OPTION_URI_HANDLER, uriHandler);
// only necessary if this resource will not be added to a ResourceSet instantly
this.eAdapters().add(oppositeReferenceAdapter);
}
@Override
public NotificationChain basicSetResourceSet(ResourceSet resourceSet,
NotificationChain notifications) {
if (resourceSet != null)
resourceSet.eAdapters().add(oppositeReferenceAdapter);
return super.basicSetResourceSet(resourceSet, notifications);
}
// This method is called by all save methods - save(Document,...), doSave(Writer/OutputStream, ...) - in superclasses.
@Override
protected XMLSave createXMLSave() {
prepareSave();
return new XMLSaveImpl(createXMLHelper()) {
@Override
protected boolean shouldSaveFeature(EObject o, EStructuralFeature f) {
if (Bpmn2Package.eINSTANCE.getDocumentation_Text().equals(f))
return false;
return super.shouldSaveFeature(o, f);
}
};
}
/**
* Prepares this resource for saving.
*
* Sets all ID attributes of contained and referenced objects
* that are not yet set, to a generated UUID.
*/
protected void prepareSave() {
EObject cur;
for (Iterator<EObject> iter = getAllContents(); iter.hasNext();) {
cur = iter.next();
setIdIfNotSet(cur);
for (EObject referenced : cur.eCrossReferences()) {
setIdIfNotSet(referenced);
}
}
}
/**
* Set the ID attribute of cur to a generated ID, if it is not already set.
* @param obj The object whose ID should be set.
*/
protected static void setIdIfNotSet(EObject obj) {
if (obj.eClass() != null) {
EStructuralFeature idAttr = obj.eClass().getEIDAttribute();
if (idAttr != null && !obj.eIsSet(idAttr)) {
obj.eSet(idAttr, EcoreUtil.generateUUID());
}
}
}
/**
* We must override this method for having an own XMLHandler
*/
@Override
protected XMLLoad createXMLLoad() {
return new XMLLoadImpl(createXMLHelper()) {
@Override
protected DefaultHandler makeDefaultHandler() {
return new BpmnXmlHandler(resource, helper, options);
}
};
}
@Override
protected XMLHelper createXMLHelper() {
return this.xmlHelper;
}
/**
* We need extend the standard SAXXMLHandler to hook into the handling of attribute references - which are no URIs but QNames.
* @author Reiner Hille
*
*/
protected static class BpmnXmlHandler extends SAXXMLHandler {
public BpmnXmlHandler(XMLResource xmiResource, XMLHelper helper, Map<?, ?> options) {
super(xmiResource, helper, options);
}
/**
* Overridden to be able to convert QName references in attributes to URIs during load.
* @param ids
* In our case the parameter will contain exactly one QName that we resolve to URI.
*/
@Override
protected void setValueFromId(EObject object, EReference eReference, String ids) {
super.setValueFromId(
object,
eReference,
eReference.isResolveProxies() ? ((QNameURIHandler) uriHandler)
.convertQNameToUri(ids) : ids);
}
}
/**
* Extend XML Helper to gain access to the different XSD namespace handling features.
* @author Reiner Hille
*
*/
protected static class BpmnXmlHelper extends XMLHelperImpl {
public BpmnXmlHelper(Bpmn2ResourceImpl resource) {
super(resource);
}
private Definitions getDefinitions() {
for (EObject eobj : getResource().getContents()) {
if (eobj instanceof Definitions) {
return (Definitions) eobj;
} else if (eobj instanceof DocumentRoot) {
return ((DocumentRoot) eobj).getDefinitions();
}
}
return null;
}
/**
* Checks if the given prefix is pointing to the current target namespace and thus is optional.
* The method is called during load.
* @param prefix The prefix or null, if no prefix is given (interpreted as default namespace).
* @return <code>true</code>, if the namespace associated with the prefix equals the target namespace
* of this Definitions.
* If prefix is null or the empty string, then the default namespace is compared with the target namespace.
* If prefix is null/empty and default namespace is not defined, <code>true</code> if the target namespace
* is not defined either.
*
* <p>
* The above rules describe a strict interpretation of the rules for QName resolution.
* This method relaxes these rules and additionally returns <code>true</code> in the following cases:
* <ul>
* <li>prefix is null/empty, no default namespace (regardless of target namespace)</li>
* <li>prefix is null/empty, default namespace is not {@linkplain #findImportForNamespace(String) mapped by an import element}.</li>
* </ul>
* </p>
*/
public boolean isTargetNamespace(String prefix) {
if (prefix == null)
prefix = XMLConstants.DEFAULT_NS_PREFIX;
final String prefixNs = this.getNamespaceURI(prefix);
if (prefixNs == null) {
if (XMLConstants.DEFAULT_NS_PREFIX.equals(prefix))
/*
* The (empty) prefix points to {no namespace}, because no default namespace is defined.
* This would be OK if target namespace is undefined as well (meaning {no namespace}).
*
* However, we employ a relaxed interpretation and do not require that
* getDefinitions().getTargetNamespace() == null (i.e. target namespace == {no namespace})
* Every unprefixed QName is interpreted as local reference, if the default namespace is not defined.
*/
return true;
// the non-empty prefix is not mapped to a namespace
throw new IllegalArgumentException(String.format("The prefix '%s' is not valid.",
prefix));
}
// result with strict evaluation: return prefixNs.equals(getDefinitions().getTargetNamespace())
if (prefixNs.equals(getDefinitions().getTargetNamespace()))
return true;
else if (XMLConstants.DEFAULT_NS_PREFIX.equals(prefix)
&& findImportForNamespace(prefixNs) == null) {
// The default namespace is not mapped to a location by an import element.
// Guess that the unprefixed QName should point to a local element (relaxed interpretation)
// TODO: emit warning
return true;
} else
return false;
}
/**
* Looks up the given prefix in the list of BPMN import elements and returns - if found - the corresponding file location.
* The method is called during load.
* @param prefix
* @return
*/
public String getPathForPrefix(String prefix) {
String ns = this.getNamespaceURI(prefix == null ? XMLConstants.DEFAULT_NS_PREFIX
: prefix);
if (ns != null) {
Import imp = findImportForNamespace(ns);
if (imp != null)
return imp.getLocation();
}
return "";
}
/**
* Looks up the list of import elements in this definitions for an import of the given namespace.
* @param namespace The namespace to look for in {@link Import#getNamespace()}.
* @return The first import element in {@link Definitions#getImports()} with {@link Import#getNamespace()}
* equal to the given namespace.
*/
protected Import findImportForNamespace(String namespace) {
for (Import imp : getDefinitions().getImports()) {
if (namespace.equals(imp.getNamespace())) {
// TODO: Also check that imp.getType() is BPMN
return imp;
}
}
return null;
}
/**
* Partly stolen from XmlHelperImpl.setPrefixToNamespaceMap().
* Ensuring that namespace declaration is saved seems to be really tricky.
* We will necessarily create a dummy package to ensure that later XmlSaveImpl.addNamespaceDeclarations() writes the ns declaration for us
* @param namespace
* @return
*/
private String getPrefixDuringSave(String namespace) {
if (urisToPrefixes.containsKey(namespace))
return urisToPrefixes.get(namespace).get(0);
EPackage ePackage = extendedMetaData.getPackage(namespace);
if (ePackage == null) {
ePackage = extendedMetaData.demandPackage(namespace);
// This will internally create a nice prefix
}
String prefix;
if (namespace.equals(getDefinitions().getTargetNamespace()))
// try to use the default namespace (xmlns="...") for local references
prefix = XMLConstants.DEFAULT_NS_PREFIX;
else
prefix = ePackage.getNsPrefix();
// Make prefix unique
String originalPrefix = prefix + "_";
int discr = 0;
while (prefixesToURIs.containsKey(prefix)
&& !prefixesToURIs.get(prefix).equals(namespace))
prefix = originalPrefix + discr++;
// I'm not sure if the following code is needed, but I keep it to avoid inconsistencies
if (!packages.containsKey(ePackage)) {
packages.put(ePackage, prefix);
}
prefixesToURIs.put(prefix, namespace);
return prefix;
}
/**
* This is called on save to convert from a file-based URI to a namespace prefix.
* It might be necessary to add a new namespace declaration to the file, if the
* namespace was not known to far.
* @param filePath
* @return
*/
public String getNsPrefix(String filePath) {
String ns = null;
String prefix = "";
if (filePath.equals(getResource().getURI().toString())) // TODO: this needs to operate on "canonical paths"
// reference to local element
ns = getDefinitions().getTargetNamespace();
else {
for (Import imp : getDefinitions().getImports()) {
if (filePath.equals(imp.getLocation())) {
// TODO: Also check that imp.getType() is BPMN
ns = imp.getNamespace();
break;
}
}
}
if (ns != null) {
prefix = getPrefixDuringSave(ns);
}
return prefix;
}
}
} //Bpmn2ResourceImpl