blob: 8ea7ab8553d917d745a8831de436520d75735dbd [file] [log] [blame]
/*****************************************************************************
* Copyright (c) 2011, 2016 CEA LIST, Christian W. Damus, and others.
*
* 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:
* Ernest Wozniak (CEA LIST) ernest.wozniak@cea.fr - Initial API and implementation
* Patrick Tessier (CEA LIST) patrick.tessier@cea.fr - modification
* Christian W. Damus - bug 485220
*
*****************************************************************************/
package org.eclipse.papyrus.dsml.validation.wizard;
import java.io.File;
import java.io.IOException;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.papyrus.dsml.validation.PapyrusDSMLValidationRule.MessageHandling;
import org.eclipse.papyrus.dsml.validation.PapyrusDSMLValidationRule.NameBasedMsgMode;
import org.eclipse.papyrus.dsml.validation.generator.xtend.Generate;
import org.eclipse.papyrus.dsml.validation.model.elements.interfaces.Category;
import org.eclipse.papyrus.dsml.validation.model.elements.interfaces.IConstraintProvider;
import org.eclipse.papyrus.dsml.validation.model.elements.interfaces.IConstraintsCategory;
import org.eclipse.papyrus.dsml.validation.model.elements.interfaces.IConstraintsManager;
import org.eclipse.papyrus.dsml.validation.model.elements.interfaces.IValidationRule;
import org.eclipse.papyrus.dsml.validation.model.profilenames.Utils;
import org.eclipse.papyrus.eclipse.project.editors.file.ManifestEditor;
import org.eclipse.papyrus.eclipse.project.editors.interfaces.IPluginEditor;
import org.eclipse.papyrus.eclipse.project.editors.interfaces.IPluginProjectEditor;
import org.eclipse.papyrus.eclipse.project.editors.interfaces.ProjectEditors;
import org.eclipse.uml2.uml.Constraint;
import org.eclipse.uml2.uml.Package;
import org.eclipse.uml2.uml.Stereotype;
import org.eclipse.uml2.uml.UMLPackage;
import org.eclipse.uml2.uml.util.UMLUtil;
import org.w3c.dom.CDATASection;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xml.sax.SAXException;
/**
* This is the main class that generates the EMF Validation plugin. It creates
* extension points and adds required dependencies.
*
*
*/
public class ValidationPluginGenerator {
/**
*
*/
private static final String CONSTRAINT_S_IS_VIOLATED = "Constraint '%s' is violated"; //$NON-NLS-1$
private static final String XML_CONSTRAINT_TARGET = "target"; //$NON-NLS-1$
private static final String XML_CONSTRAINT_MESSAGE = "message"; //$NON-NLS-1$
private static final String XML_CONSTRAINT_CLASS = "class"; //$NON-NLS-1$
private static final String XML_CONSTRAINT_IS_ENABLED_BY_DEFAULT = "isEnabledByDefault"; //$NON-NLS-1$
private static final String JAVA_LANGUAGE = "Java"; //$NON-NLS-1$
/**
* Do not use the standard tag OCL but OCLpivot to enforce validation with the pivot variant of OCL.
* OCLpivot constraint validation is provided by the org.eclipse.papyrus.dsml.validation plugin
*/
private static final String OCL_LANGUAGE = "OCLpivot"; //$NON-NLS-1$
private static final String XML_CONSTRAINT_MODE = "mode"; //$NON-NLS-1$
private static final String XML_CONSTRAINT_SEVERITY = "severity"; //$NON-NLS-1$
private static final String XML_CONSTRAINT_STATUS_CODE = "statusCode"; //$NON-NLS-1$
private static final String XML_CONSTRAINT_LANG = "lang"; //$NON-NLS-1$
private static final String XML_CONSTRAINT_NAME = "name"; //$NON-NLS-1$
private static final String SEPARATOR = "."; //$NON-NLS-1$
private static final String EMF_VALIDATION_CONSTRAINT_CHILD = "constraint"; //$NON-NLS-1$
private static final String ATTRIB_ID = "id"; //$NON-NLS-1$
private static final String ATTRIB_NAME = "name"; //$NON-NLS-1$
private static final String EMF_VALIDATION_CONSTRAINT_PROVIDERS_EXTENSIONPOINT = "org.eclipse.emf.validation.constraintProviders"; //$NON-NLS-1$
private static final String EMF_VALIDATION_CONSTRAINT_BINDINGS_EXTENSIONPOINT = "org.eclipse.emf.validation.constraintBindings"; //$NON-NLS-1$
private static final String UML_PLUGIN = "org.eclipse.uml2.uml"; //$NON-NLS-1$
private static final String EMF_VALIDATION_PLUGIN = "org.eclipse.emf.validation"; //$NON-NLS-1$
private static final String UML_VALIDATION_PLUGIN = "org.eclipse.papyrus.uml.service.validation"; //$NON-NLS-1$
// A generated plugin will depend on the validation profile, since it typically contains the profile as well.
// [well, that's not always the case]
public static final String UML_DSML_VALIDATION_PROFILE_PLUGIN = "org.eclipse.papyrus.dsml.validation"; //$NON-NLS-1$
/**
* singleton
*/
public static ValidationPluginGenerator instance = new ValidationPluginGenerator();
private final static String PLUGIN_NATURE_ID = "org.eclipse.pde.PluginNature"; //$NON-NLS-1$
private IConstraintsManager constraintsManager;
/**
* Add a dependency to the plugin hosting the DSML profile.
*
* @param project
* The Eclipse project
* @return true, if dependency has been added
* @throws CoreException
* @throws IOException
*/
public boolean addDependency(IProject project, String pluginName) throws CoreException, IOException {
if ((project != null) && project.exists() && !pluginName.equals(project.getName())) {
ManifestEditor manifest = new ManifestEditor(project);
manifest.init();
if (manifest.initOk() && !manifest.hasDependency(pluginName)) {
manifest.addDependency(pluginName);
manifest.save();
return true;
}
}
return false;
}
/**
* Generate the java code form constraints contained in the profile
*
* @param project
* the Eclipse project
* @param constraintsManager
* the class in charge to collect all information from the model
* @param definition
* The definition of the profile for which a plugin is generated
* @throws CoreException
* @throws IOException
* @throws SAXException
* @throws ParserConfigurationException
*/
public void generate(IProject project, IConstraintsManager constraintsManager, EPackage definition) throws CoreException, IOException, SAXException, ParserConfigurationException {
IPluginEditor editor;
this.constraintsManager = constraintsManager;
// prepare the plugin
editor = ProjectEditors.getPluginEditor(project);
if (!editor.exists()) {
editor.create();
}
editor.init();
editor.registerSourceFolder(Generate.SRC);
// it's possible that editor.getManifestEditor() logs an exception due to resource out of sync.
String pluginID = editor.getSymbolicBundleName();
if (editor.getBundleName() == null) {
// initialize bundle name with symbolic name, if not name is given
editor.setBundleName(pluginID);
}
editor.setSingleton(true);
editor.addToBuild(IPluginProjectEditor.PLUGIN_XML_FILE);
Set<String> natures = new HashSet<String>();
natures.add(PLUGIN_NATURE_ID);
editor.addNatures(natures);
editor.addDependency(EMF_VALIDATION_PLUGIN);
editor.addDependency(UML_VALIDATION_PLUGIN);
editor.addDependency(UML_PLUGIN);
String filterName = "dsml.validation.generated" + SEPARATOR + this.constraintsManager.getPrimeCategory().getID(); //$NON-NLS-1$
Element constraintProviderExtension = createOrCleanExtension(editor, EMF_VALIDATION_CONSTRAINT_PROVIDERS_EXTENSIONPOINT, filterName);
// creation of categories extension point
// that corresponds to profile and sub-profiles
this.createExtensionForCategory(pluginID, this.constraintsManager.getPrimeCategory(), constraintProviderExtension, editor);
// add the constraint provider extension point, normally it exist only per profile so per category
for (IConstraintProvider constraintProvider : constraintsManager.getConstraintsProviders()) {
// create the extension point for the provider
Element extElForConstraintsProvider = createExtensionForConstraintsProvider(constraintProvider, constraintProviderExtension, editor, definition);
// go though category (profile)
for (IConstraintsCategory constraintCategory : constraintProvider.getConstraintsCategories()) {
// create the extension point validationRule for category
Element extElForConstraintsCategory = createExtensionForConstraintsCategory(pluginID, constraintCategory, extElForConstraintsProvider, editor, constraintsManager);
for (IValidationRule constraint : constraintCategory.getConstraints()) {
// is this a Java constraint?
if (Utils.hasSpecificationForJava(constraint.getConstraint())) {
createJavaExtensionForConstraint(pluginID, constraint, extElForConstraintsCategory, editor);
}
// is this an OCL constraint?
if (Utils.hasSpecificationForOCL(constraint.getConstraint())) {
createOCLExtensionForConstraint(constraint, extElForConstraintsCategory, editor);
}
// ((ValidationRuleImpl) constraint).setParentID();
}
}
}
generateBindings(pluginID, editor, this.constraintsManager, filterName);
try {
editor.save();
editor.getProject().refreshLocal(0, null);
} catch (Throwable ex) {
return;
}
}
private Element createExtensionForConstraint(IValidationRule validationRule,
Element parentElement, IPluginEditor editor) {
Element extElForConstraint = editor.addChild(parentElement, EMF_VALIDATION_CONSTRAINT_CHILD);
extElForConstraint.setAttribute(ATTRIB_ID, validationRule.getID());
extElForConstraint.setAttribute(XML_CONSTRAINT_NAME, validationRule.getName());
extElForConstraint.setAttribute(XML_CONSTRAINT_STATUS_CODE, validationRule.getStatusCode().toString());
extElForConstraint.setAttribute(XML_CONSTRAINT_SEVERITY, validationRule.getSeverity().name());
extElForConstraint.setAttribute(XML_CONSTRAINT_MODE, validationRule.getMode().name());
extElForConstraint.setAttribute(XML_CONSTRAINT_IS_ENABLED_BY_DEFAULT,
String.valueOf(validationRule.isEnabledByDefault()));
if (validationRule.getTargets() != null) {
for (String target : validationRule.getTargets()) {
Element targetExtension = editor.addChild(extElForConstraint, XML_CONSTRAINT_TARGET);
targetExtension.setAttribute(XML_CONSTRAINT_CLASS, target);
}
}
String validationMsg = validationRule.getMessage();
Element message = editor.addChild(extElForConstraint, XML_CONSTRAINT_MESSAGE);
if ((validationMsg != null) && (validationMsg.length() > 0)) {
message.setTextContent(validationMsg);
} else {
Package constraintPkg = validationRule.getConstraint().getNearestPackage();
boolean set = false;
if (constraintPkg != null) {
MessageHandling messageHandling = UMLUtil.getStereotypeApplication(constraintPkg, MessageHandling.class);
if (messageHandling != null) {
if (messageHandling.getMessageMode() == NameBasedMsgMode.NAME_IS_MESSAGE) {
message.setTextContent(validationRule.getName());
set = true;
} else if (messageHandling.getMessageMode() == NameBasedMsgMode.CUSTOM_TEMPLATE) {
String template = messageHandling.getCustomTemplate();
if (template != null && template.length() > 0) {
message.setTextContent(String.format(template, validationRule.getName()));
set = true;
}
}
}
}
if (!set) {
message.setTextContent(String.format(CONSTRAINT_S_IS_VIOLATED, validationRule.getName()));
}
}
if (validationRule.getDescription() != null) {
Element description = editor.addChild(extElForConstraint, "description"); //$NON-NLS-1$
description.setTextContent(validationRule.getDescription());
}
return extElForConstraint;
}
private Element createJavaExtensionForConstraint(String pluginID, IValidationRule validationRule,
Element parentElement, IPluginEditor editor) {
Element extElForConstraint = createExtensionForConstraint(validationRule, parentElement, editor);
extElForConstraint.setAttribute(XML_CONSTRAINT_LANG, JAVA_LANGUAGE);
extElForConstraint.setAttribute(XML_CONSTRAINT_CLASS, pluginID + SEPARATOR + validationRule.getImplementingClass());
return extElForConstraint;
}
/**
* create the extension point for constraint of emf validation
*
* @param validationRule
* the validation rule
* @param parentElement
* @param editor
* @return the extension point
*/
private Element createOCLExtensionForConstraint(IValidationRule validationRule,
Element parentElement, IPluginEditor editor) {
Element extElForConstraint = createExtensionForConstraint(validationRule, parentElement, editor);
extElForConstraint.setAttribute(XML_CONSTRAINT_LANG, OCL_LANGUAGE);
extElForConstraint.setAttribute(XML_CONSTRAINT_STATUS_CODE, validationRule.getStatusCode().toString());
extElForConstraint.setAttribute(XML_CONSTRAINT_SEVERITY, validationRule.getSeverity().name());
if (Utils.getOCLConstraintBody(validationRule.getConstraint()) != null) {
Document doc = editor.getDocument();
CDATASection cdata = doc.createCDATASection(Utils
.getOCLConstraintBody(validationRule.getConstraint()));
extElForConstraint.appendChild(cdata);
}
return extElForConstraint;
}
public static Document parseXmlFile(String filename, boolean validating) {
try {
// Create a builder factory
DocumentBuilderFactory factory = DocumentBuilderFactory
.newInstance();
factory.setValidating(validating);
// Create the builder and parse the file
Document doc = factory.newDocumentBuilder().parse(
new File(filename));
return doc;
} catch (SAXException e) {
// A parsing error occurred; the xml input is not valid
} catch (ParserConfigurationException e) {
} catch (IOException e) {
}
return null;
}
/**
* create the extension point categories
*
* @param projectName
* the name of the project
* @param category
* a category
* @param parentElement
* a ConstraintProviders extension
* @param editor
*/
private Element createExtensionForCategory(String pluginID, Category category,
Element parentElement, IPluginEditor editor) {
Element extElForCategory = editor.addChild(parentElement, "category"); //$NON-NLS-1$
extElForCategory.setAttribute(ATTRIB_ID, pluginID + SEPARATOR + category.getID());
extElForCategory.setAttribute(XML_CONSTRAINT_NAME, pluginID + SEPARATOR + category.getName());
extElForCategory.setAttribute("mandatory", String.valueOf(category.isMandatory())); //$NON-NLS-1$
return extElForCategory;
}
/**
* create the constraint provider extension point
*
* @param constraintProvider
* @param parentElement
* @param editor
* @param definition
* @return the extension point
*/
@SuppressWarnings("nls")
private Element createExtensionForConstraintsProvider(
IConstraintProvider constraintProvider, Element parentElement,
IPluginEditor editor, EPackage definition) {
Element extElForConstraintsProvider = editor.addChild(parentElement, "constraintProvider");
extElForConstraintsProvider.setAttribute(XML_CONSTRAINT_MODE, constraintProvider.getMode().name());
extElForConstraintsProvider.setAttribute("cache", String.valueOf(constraintProvider.getCache()));
Element pcg = editor.addChild(extElForConstraintsProvider, "package");
if (constraintProvider.getEPackage() == null) {
pcg.setAttribute("namespaceUri", UMLPackage.eNS_URI);
} else {
pcg.setAttribute("namespaceUri", constraintProvider.getEPackage().getNsURI());
}
return extElForConstraintsProvider;
}
@SuppressWarnings("nls")
private Element createExtensionForConstraintsCategory(String pluginID, IConstraintsCategory constraintsCategory, Element parentElement, IPluginEditor editor, IConstraintsManager constraintManager) {
Element extElForConstraintsCategory = editor.addChild(parentElement, "constraints"); //$NON-NLS-1$
extElForConstraintsCategory.setAttribute("categories", pluginID + SEPARATOR + constraintManager.getPrimeCategory().getName());
return extElForConstraintsCategory;
}
@SuppressWarnings("nls")
private void generateBindings(String pluginID, IPluginEditor editor, IConstraintsManager constraintsManager, String filterName) {
Element extension = createOrCleanExtension(editor, EMF_VALIDATION_CONSTRAINT_BINDINGS_EXTENSIONPOINT, filterName);
// create a client context per stereotype
Set<Stereotype> constrainedStereotype = constraintsManager.getConstraintsOfStereotype().keySet();
for (Iterator<Stereotype> iterator = constrainedStereotype.iterator(); iterator.hasNext();) {
Stereotype stereotype = iterator.next();
// ("+--> create clientContext for the stereotype "+stereotype.getName());
Element clientContextElement = editor.addChild(extension, "clientContext");
clientContextElement.setAttribute(ATTRIB_ID, stereotype.getName() + "ClientContext");
Element selectorElement = editor.addChild(clientContextElement, "selector");
selectorElement.setAttribute(XML_CONSTRAINT_CLASS, pluginID + ".selectors." + stereotype.getName() + "ClientSelector");
// create binding
List<Constraint> constraints = constraintsManager.getConstraintsOfStereotype().get(stereotype);
Element bindingelement = editor.addChild(extension, "binding");
bindingelement.setAttribute("context", stereotype.getName() + "ClientContext");
for (Iterator<Constraint> iteratorConstraint = constraints.iterator(); iteratorConstraint.hasNext();) {
Constraint constraint = iteratorConstraint.next();
constraintsManager.getValidationRuleMap().get(constraint).getID();
Element constraintElement = editor.addChild(bindingelement, "constraint");
// The pluginID must be part of the fully qualified constraint reference
constraintElement.setAttribute("ref", pluginID + SEPARATOR + constraintsManager.getValidationRuleMap().get(constraint).getID());
// ("+----> create binding for the constraint "+constraintsManager.getValidationRuleMap().get(constraint).getID());
}
}
}
/**
* Create a new extension with a given name or reuse an existing extension if an extension with the given
* name already exists. In case of the latter, the existing extensions will be cleaned first, i.e. all of its
* children are removed. This function enables multiple generation phases without duplicating elements.
*
* CAVEAT: This function has been changed: filtering is now based on its name instead of the ID. Using an
* ID on the extension point level, unifies the associated contexts, see bug 467692 - [UML-RT] Multiple
* issues when validating a UML-RT model
*
* @param editor
* the plugin editor
* @param extensionName
* the name of the extension
* @return
*/
protected Element createOrCleanExtension(IPluginEditor editor, String extensionName, String filterName) {
List<Node> existingExtensions = editor.getExtensions(extensionName);
for (Node extension : existingExtensions) {
if (extension instanceof Element) {
Element extensionElement = (Element) extension;
// use optional name field to identify an item that has been generated by this plugin.
// Context: bug 464363 - The DSML plugin generator removes all existing constraints from the plugin.xml
String name = extensionElement.getAttribute(ATTRIB_NAME);
// TODO: temporary accept entries without ID (ID == "") to avoid migration issues (bug 464363)
if (name.equals("")) { //$NON-NLS-1$
extensionElement.setAttribute(ATTRIB_NAME, filterName);
name = filterName;
}
if (name.equals(filterName)) {
// use existing extension and remove all children
for (;;) {
Node child = extension.getFirstChild();
if (child == null) {
break;
}
extension.removeChild(child);
}
return extensionElement;
}
}
}
// create new extension
Element newExtension = editor.addExtension(extensionName);
newExtension.setAttribute(ATTRIB_NAME, filterName);
return newExtension;
}
public static String getContextprefix() {
return ""; //$NON-NLS-1$
}
}