/*******************************************************************************
* Copyright (c) 2011, 2012 Red Hat, Inc.
* All rights reserved.
* This program is 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:
* Red Hat, Inc. - initial API and implementation
*
* @author Bob Brodt
******************************************************************************/
package org.eclipse.bpmn2.modeler.core.model;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;
import org.eclipse.bpmn2.Bpmn2Package;
import org.eclipse.bpmn2.DocumentRoot;
import org.eclipse.bpmn2.di.BpmnDiFactory;
import org.eclipse.bpmn2.di.BpmnDiPackage;
import org.eclipse.bpmn2.impl.Bpmn2FactoryImpl;
import org.eclipse.bpmn2.impl.DocumentRootImpl;
import org.eclipse.bpmn2.modeler.core.LifecycleEvent;
import org.eclipse.bpmn2.modeler.core.LifecycleEvent.EventType;
import org.eclipse.bpmn2.modeler.core.adapters.ExtendedPropertiesAdapter;
import org.eclipse.bpmn2.modeler.core.features.GraphitiConstants;
import org.eclipse.bpmn2.modeler.core.runtime.CustomTaskDescriptor;
import org.eclipse.bpmn2.modeler.core.runtime.ModelExtensionDescriptor;
import org.eclipse.bpmn2.modeler.core.runtime.TargetRuntime;
import org.eclipse.bpmn2.modeler.core.runtime.TargetRuntimeAdapter;
import org.eclipse.bpmn2.modeler.core.utils.ModelUtil;
import org.eclipse.core.runtime.Assert;
import org.eclipse.dd.dc.DcPackage;
import org.eclipse.dd.di.DiPackage;
import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EFactory;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.EcorePackage;
import org.eclipse.emf.ecore.impl.EStringToStringMapEntryImpl;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.util.EcoreEMap;
import org.eclipse.osgi.util.NLS;
/**
* This Factory will invoke the super factory to create a "bare bones"
* model object, and then "decorate" it with model extensions defined
* by the Target Runtime plugin extension.
*
* Note the create(EClass) and create(Resource,EClass) methods override default Bpmn2Factory behavior by
* decorating newly constructed objects with optional artifacts as defined by the Target Runtime extension
* plugin. These also add adapters to the new EObjects to help identify the Target Runtime and Resource (among other things).
*
* The createObject(Resource,...) and createFeature(EObject,EStructuralFeature,...) methods route object creation through
* the ExtensionPropertiesAdapter attached to the EObject. This allows Target Runtime extension plugins to override object
* creation. These methods should eventually invoke this factory's create(Resource,EClass) method.
*
* @author Bob Brodt
*
*/
public class Bpmn2ModelerFactory extends Bpmn2FactoryImpl {
public static final String TARGET_RUNTIME = "TargetRuntime";
public static final String INITIALIZE_OBJECT = "InitializeObject";
/**
* A simple class for passing name/value pairs to create() method.
*/
public static class KeyValue implements java.util.Map.Entry<String,Object> {
private String key;
private Object value;
public KeyValue(String key, Object value) {
this.key = key;
this.value = value;
}
@Override
public String getKey() {
return key;
}
@Override
public Object getValue() {
return value;
}
@Override
public Object setValue(Object value) {
this.value = value;
return value;
}
}
/**
* We provide our own DocumentRoot (since we can't modify the one in org.eclipse.bpmn2)
* which prevents forwarding change notifications to the XML Namespace Prefix map AFTER
* the document has been saved. This avoids the nasty "Cannot modify resource set without
* a write transaction" error.
*
* See https://bugs.eclipse.org/bugs/show_bug.cgi?id=392427
*/
public class Bpmn2ModelerDocumentRootImpl extends DocumentRootImpl {
private boolean deliver = true;
public Bpmn2ModelerDocumentRootImpl() {
super();
}
public void setDeliver(boolean deliver) {
this.deliver = deliver;
}
public Map<String, String> getXMLNSPrefixMap() {
if (xMLNSPrefixMap == null) {
xMLNSPrefixMap = new EcoreEMap<String, String>(
EcorePackage.Literals.ESTRING_TO_STRING_MAP_ENTRY,
EStringToStringMapEntryImpl.class, this,
Bpmn2Package.DOCUMENT_ROOT__XMLNS_PREFIX_MAP) {
{
initializeDelegateEList();
}
@Override
protected void initializeDelegateEList() {
delegateEList = new DelegateEObjectContainmentEList<Entry<String, String>>(entryClass,
Bpmn2ModelerDocumentRootImpl.this, Bpmn2Package.DOCUMENT_ROOT__XMLNS_PREFIX_MAP) {
@Override
protected void dispatchNotification(Notification notification) {
if (deliver)
super.dispatchNotification(notification);
}
};
}
};
// To remove the "bpmn2:" namespace prefix from all elements, just add this
// as the default namespace to the <definitions> namespace prefix map
// xMLNSPrefixMap.map().put("", Bpmn2Package.eNS_URI);
}
return xMLNSPrefixMap.map();
}
}
// Allows the XML loader for a particular target runtime to temporarily disable
// model extensions. This prevents extensions being added multiple times by
// ModelExtensionDescriptor.populateObject() every time a file is loaded.
protected static boolean enableModelExtensions = true;
protected static Resource resource;
private static ReentrantLock lock = new ReentrantLock();
public static void lock() {
lock.lock();
}
public static void unlock() {
lock.unlock();
}
public static Bpmn2ModelerFactory getInstance() {
return (Bpmn2ModelerFactory) Bpmn2ModelerFactory.eINSTANCE;
}
@Override
public DocumentRoot createDocumentRoot() {
DocumentRootImpl documentRoot = new Bpmn2ModelerDocumentRootImpl();
return documentRoot;
}
@Override
public EObject create(EClass eClass) {
if (resource!=null) {
return create(resource, eClass);
}
return super.create(eClass);
}
public EObject createInternal(EClass eClass) {
return super.create(eClass);
}
// convenience methods
public static <T extends EObject> T create(Resource resource, Class<T> clazz) {
EClass eClass = getEClass(resource, clazz);
return (T) create(resource, eClass);
}
public static EObject create(Resource resource, EClass eClass, KeyValue... args) {
Map<String, Object> map = new Hashtable<String, Object>();
if (args!=null) {
for (KeyValue kv : args) {
if (kv.getValue()!=null)
map.put(kv.getKey(), kv.getValue());
}
}
return create(resource, eClass, map);
}
public static EObject create(Resource resource, EClass eClass, Map<String,Object> args) {
Assert.isTrue(eClass!=null);
Assert.isTrue(resource!=null);
EObject object = null;
lock();
try {
String customElementId = null;
TargetRuntime rt = null;
boolean initializeObject = enableModelExtensions;
if (args!=null) {
for (java.util.Map.Entry<String, Object> kv : args.entrySet()) {
if (GraphitiConstants.CUSTOM_ELEMENT_ID.equals(kv.getKey()))
customElementId = (String)kv.getValue();
if (TARGET_RUNTIME.equals(kv.getKey()))
rt = (TargetRuntime) kv.getValue();
if (INITIALIZE_OBJECT.equals(kv.getKey()))
initializeObject = (Boolean) kv.getValue();
}
}
if (rt==null)
rt = TargetRuntime.getRuntime(resource);
Assert.isTrue(rt!=null);
EFactory factory = eClass.getEPackage().getEFactoryInstance();
if (factory instanceof Bpmn2ModelerFactory)
object = ((Bpmn2ModelerFactory) factory).createInternal(eClass);
else
object = factory.create(eClass);
TargetRuntimeAdapter.adapt(object, rt);
String className = eClass.getName();
if (!className.equals(Bpmn2Package.eINSTANCE.getDocumentRoot().getName()) &&
rt.getModelDescriptor().getEPackage() != Bpmn2Package.eINSTANCE &&
rt.getModelDescriptor().getEPackage() != null &&
rt.getModelDescriptor().getEPackage().getEClassifier(className) != null ) {
EClass clazz = (EClass) rt.getModelDescriptor().getEPackage().getEClassifier(className);
object = rt.getModelDescriptor().getEFactory().create(clazz);
}
// first look for Model Extension Descriptors for this specific object type
if (customElementId!=null) {
CustomTaskDescriptor ctd = rt.getCustomTask(customElementId);
if (ctd!=null)
ctd.populateObject(object, resource, initializeObject);
}
else {
List<ModelExtensionDescriptor> list = rt.getModelExtensionDescriptors();
for (ModelExtensionDescriptor med : list) {
if (className.equals(med.getType())) {
med.populateObject(object, resource, initializeObject);
}
}
// then check if there are any MEDs for any supertypes of this object type
for (ModelExtensionDescriptor med : list) {
for (EClass st : eClass.getEAllSuperTypes()) {
if (st.getName().equals(med.getType())) {
med.populateObject(object, resource, initializeObject);
}
}
}
}
// TODO: should we set a default ID and "name" here?
if (initializeObject) {
String id = ModelUtil.setID(object,resource);
// also set a default name
EStructuralFeature feature = object.eClass().getEStructuralFeature("name"); //$NON-NLS-1$
if (feature!=null && !object.eIsSet(feature)) {
if (id!=null)
object.eSet(feature, ModelUtil.toCanonicalString(id));
else {
String name = ModelUtil.toCanonicalString(object.eClass().getName());
object.eSet(feature, NLS.bind(Messages.Bpmn2ModelerFactory_New_Name, name));
}
}
}
ExtendedPropertiesAdapter adapter = ExtendedPropertiesAdapter.adapt(object);
if (adapter!=null)
adapter.setResource(resource);
rt.notify(new LifecycleEvent(EventType.BUSINESSOBJECT_CREATED, object, rt));
}
catch (Exception e) {
e.printStackTrace();
}
finally {
unlock();
}
return object;
}
public static void setEnableModelExtensions(boolean enable) {
enableModelExtensions = enable;
}
public static boolean getEnableModelExtensions() {
return enableModelExtensions;
}
@SuppressWarnings("unchecked")
public static <T extends EObject> T createObject(Resource resource, Class<T> clazz) {
EClass eClass = getEClass(resource, clazz);
T newObject = null;
if (eClass!=null) {
newObject = (T) createObject(resource, eClass);
}
else {
// maybe it's a DI object type?
EClassifier eClassifier = BpmnDiPackage.eINSTANCE.getEClassifier(clazz.getSimpleName());
if (eClassifier instanceof EClass) {
newObject = (T) BpmnDiFactory.eINSTANCE.create((EClass)eClassifier);
}
}
return newObject;
}
public static EObject createObject(Resource resource, EClass eClass, KeyValue... args) {
Map<String, Object> map = new Hashtable<String, Object>();
if (args!=null) {
for (KeyValue kv : args) {
if (kv.getValue()!=null)
map.put(kv.getKey(), kv.getValue());
}
}
return createObject(resource, eClass, map);
}
public static EObject createObject(Resource resource, EClass eClass, Map<String, Object> args) {
Assert.isTrue(eClass!=null);
EObject newObject = null;
ExtendedPropertiesAdapter adapter = ExtendedPropertiesAdapter.adapt(resource, eClass);
if (adapter!=null) {
newObject = adapter.getObjectDescriptor().createObject(resource, eClass, args);
}
else {
// There is no properties adapter registered for this class. This can only happen if the object to
// be created is in an external package. If this is the case, simply construct an object using the
// registered model factory.
EPackage pkg = eClass.getEPackage();
if (!isBpmnPackage(pkg)) {
newObject = pkg.getEFactoryInstance().create(eClass);
}
}
if (newObject!=null) {
TargetRuntime rt = TargetRuntime.getRuntime(newObject);
rt.notify(new LifecycleEvent(EventType.BUSINESSOBJECT_CREATED, newObject, rt));
}
return newObject;
}
public static void setResource(Resource r) {
resource = r;
}
// public static EObject createFeature(EObject object, EStructuralFeature feature) {
// return createFeature(object.eResource(), object, feature, (Class<? extends EObject>)feature.getEType().getInstanceClass());
// }
// public static EObject createFeature(EObject object, String featureName) {
// return createFeature(object, object.eClass().getEStructuralFeature(featureName));
// }
//
// //
//
// public static <T extends EObject> T createFeature(EObject object, EStructuralFeature feature, Class<T> clazz) {
// return createFeature(object.eResource(), object, feature, clazz);
// }
// public static <T extends EObject> T createFeature(EObject object, String featureName, Class<T> clazz) {
// return createFeature(object, object.eClass().getEStructuralFeature(featureName), clazz);
// }
//
// //
//
// public static EObject createFeature(EObject object, EStructuralFeature feature, EClass eClass) {
// return createFeature(object.eResource(), object, feature, eClass);
// }
// public static EObject createFeature(EObject object, String featureName, EClass eClass) {
// return createFeature(object.eResource(), object, object.eClass().getEStructuralFeature(featureName), eClass);
// }
//
public static <T extends EObject> T createFeature(Resource resource, EObject object, String featureName, Class<T> clazz) {
return createFeature(resource, object, object.eClass().getEStructuralFeature(featureName), clazz);
}
@SuppressWarnings("unchecked")
public static <T extends EObject> T createFeature(Resource resource, EObject object, EStructuralFeature feature, Class<T> clazz) {
Assert.isTrue(feature.getEType().getInstanceClass().isAssignableFrom(clazz));
EClass eClass = getEClass(resource, clazz);
return (T)createFeature(resource, object, feature, eClass);
}
public static EObject createFeature(Resource resource, EObject object, EStructuralFeature feature, EClass eClass) {
if (eClass==null)
eClass = (EClass)feature.getEType();
Assert.isTrue(feature.getEType().getInstanceClass().isAssignableFrom( eClass.getInstanceClass() ));
EObject newObject = null;
if (resource==null)
resource = object.eResource();
ExtendedPropertiesAdapter adapter = ExtendedPropertiesAdapter.adapt(object);
if (adapter!=null) {
newObject = adapter.getFeatureDescriptor(feature).createFeature(resource, eClass);
}
else {
// There is no properties adapter registered for this class. This can only happen if the object to
// be created is in an external package. If this is the case, simply construct an object using the
// registered model factory.
EPackage pkg = eClass.getEPackage();
if (!isBpmnPackage(pkg)) {
newObject = pkg.getEFactoryInstance().create(eClass);
}
}
if (newObject!=null) {
TargetRuntime rt = TargetRuntime.getRuntime(newObject);
rt.notify(new LifecycleEvent(EventType.BUSINESSOBJECT_CREATED, newObject, rt));
}
return newObject;
}
private static EClass getEClass(Resource resource, Class clazz) {
String nsURI = null;
if (resource!=null) {
TargetRuntime rt = TargetRuntimeAdapter.getTargetRuntime(resource);
if (rt!=null)
nsURI = rt.getRuntimeExtension().getTargetNamespace(null);
}
EPackage pkg = ModelDecorator.getEPackage(nsURI);
EClassifier eClassifier = ModelDecorator.findEClassifier(pkg, clazz.getSimpleName());
if (eClassifier instanceof EClass) {
return (EClass)eClassifier;
}
return null;
}
public static boolean isBpmnPackage(EPackage pkg) {
return pkg == Bpmn2Package.eINSTANCE ||
pkg == BpmnDiPackage.eINSTANCE ||
pkg == DcPackage.eINSTANCE ||
pkg == DiPackage.eINSTANCE;
}
public static boolean isBpmnPackage(String nsURI) {
if (nsURI==null || nsURI.isEmpty())
return true;
if (!nsURI.endsWith("-XMI")) //$NON-NLS-1$
nsURI += "-XMI"; //$NON-NLS-1$
return Bpmn2Package.eINSTANCE.getNsURI().equals(nsURI) ||
BpmnDiPackage.eINSTANCE.getNsURI().equals(nsURI) ||
DcPackage.eINSTANCE.getNsURI().equals(nsURI) ||
DiPackage.eINSTANCE.getNsURI().equals(nsURI);
}
}