blob: 55c3f512f6348dff9e5f598cd99b12cc0a0776c1 [file] [log] [blame]
/**
* <copyright>
*
* Copyright (c) 2014-2015 itemis and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.html
*
* Contributors:
* itemis - Initial API and implementation
* itemis - [455185] Check Framework incompatible with QVTO metamodel
* itemis - [458403] CheckValidatorRegistry.getCheckModelURI(String) should be null-safe
* itemis - [458405] CheckValidatorRegistry.register(*) should be public
* itemis - [458921] Newly introduced registries for metamodel serives, check validators and workflow contributors are not standalone-safe
* itemis - [458976] Validators are not singleton when they implement checks for different EPackages
* itemis - [461051] API to get all registered check validators
* itemis - [478811] Check validation may compromise EMF Validation-based validation
*
* </copyright>
*/
package org.eclipse.sphinx.emf.check;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExtensionRegistry;
import org.eclipse.core.runtime.ILog;
import org.eclipse.core.runtime.Platform;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EValidator;
import org.eclipse.osgi.util.NLS;
import org.eclipse.sphinx.emf.check.catalog.Catalog;
import org.eclipse.sphinx.emf.check.internal.Activator;
import org.eclipse.sphinx.emf.check.internal.CheckValidatorDescriptor;
import org.eclipse.sphinx.emf.check.internal.EPackageMappings;
import org.eclipse.sphinx.emf.check.util.CheckValidationUtil;
import org.eclipse.sphinx.emf.util.EObjectUtil;
import org.eclipse.sphinx.emf.util.EcoreResourceUtil;
import org.eclipse.sphinx.emf.validation.ICompositeValidator;
import org.eclipse.sphinx.platform.util.PlatformLogUtil;
import org.eclipse.sphinx.platform.util.StatusUtil;
/**
* A validation registry singleton which backs a standard EMF validation
* {@link org.eclipse.emf.ecore.EValidator.Registry registry}. When a check validator is called for the first time, the
* registry reads all the contributed check validators and initializes its internal map, which is then used to retrieve
* check catalogs from validator class names. Check validators are contributed through the
* <code>org.eclipse.sphinx.emf.check.chekvalidators</code> extension point. A check validator contribution is a couple
* of a validator class and optionally a check catalog. By default, the {@link org.eclipse.emf.ecore.EPackage ePackage}
* affected by the validator is inferred from the set of the annotated method parameters provided by the validator.
*/
public class CheckValidatorRegistry {
private static final String EXTP_CHECK_VALIDATORS = "org.eclipse.sphinx.emf.check.checkvalidators"; //$NON-NLS-1$
private static final String NODE_VALIDATOR = "validator"; //$NON-NLS-1$
private static final String NODE_EPACKAGE_MAPPING = "ePackageMapping"; //$NON-NLS-1$
private static final String ATTR_EOBJECT_WRAPPER_PACKAGE_NAME = "eObjectWrapperPackageName"; //$NON-NLS-1$
private static final String ATTR_EPACKAGE_NS_URI = "ePackageNsURI"; //$NON-NLS-1$
/**
* The singleton instance of this registry.
*/
public static final CheckValidatorRegistry INSTANCE = new CheckValidatorRegistry(Platform.getExtensionRegistry(), EValidator.Registry.INSTANCE,
PlatformLogUtil.getLog(Activator.getPlugin()));
protected Map<ICheckValidator, URI> checkValidatorToCheckCatalogURIMap = null;
protected Map<URI, Set<ICheckValidator>> checkCatalogURIToCheckValidatorsMap = null;
protected Map<URI, Catalog> uriToCheckCatalogMap = new HashMap<URI, Catalog>();
protected IExtensionRegistry extensionRegistry;
protected EValidator.Registry eValidatorRegistry;
private ILog logger;
protected CheckValidatorRegistry(IExtensionRegistry extensionRegistry, EValidator.Registry eValidatorRegistry, ILog logger) {
Assert.isNotNull(extensionRegistry);
Assert.isNotNull(eValidatorRegistry);
Assert.isNotNull(logger);
this.extensionRegistry = extensionRegistry;
this.eValidatorRegistry = eValidatorRegistry;
this.logger = logger;
}
private EValidator.Registry getEValidatorRegistry() {
initialize();
return eValidatorRegistry;
}
private Map<ICheckValidator, URI> getCheckValidatorToCheckCatalogURIMap() {
initialize();
return checkValidatorToCheckCatalogURIMap != null ? checkValidatorToCheckCatalogURIMap : Collections.<ICheckValidator, URI> emptyMap();
}
private Map<URI, Set<ICheckValidator>> getCheckCatalogURIToCheckValidatorsMap() {
initialize();
return checkCatalogURIToCheckValidatorsMap != null ? checkCatalogURIToCheckValidatorsMap : Collections.<URI, Set<ICheckValidator>> emptyMap();
}
private void initialize() {
if (extensionRegistry == null) {
return;
}
if (checkValidatorToCheckCatalogURIMap == null || checkCatalogURIToCheckValidatorsMap == null) {
checkValidatorToCheckCatalogURIMap = new HashMap<ICheckValidator, URI>();
checkCatalogURIToCheckValidatorsMap = new HashMap<URI, Set<ICheckValidator>>();
// Create a temporary objects
Map<String, CheckValidatorDescriptor> checkValidatorClassNameToCheckValidatorDescriptorMap = new HashMap<String, CheckValidatorDescriptor>();
EPackageMappings ePackageMappings = new EPackageMappings();
// First iteration to detect duplicate check validator contributions and initialize check validator
// descriptor map
for (IConfigurationElement checkValidatorConfigurationElement : extensionRegistry.getConfigurationElementsFor(EXTP_CHECK_VALIDATORS)) {
try {
if (NODE_VALIDATOR.equals(checkValidatorConfigurationElement.getName())) {
CheckValidatorDescriptor checkValidatorDescriptor = new CheckValidatorDescriptor(checkValidatorConfigurationElement);
String checkValidatorClassName = checkValidatorDescriptor.getClassName();
if (checkValidatorClassNameToCheckValidatorDescriptorMap.containsKey(checkValidatorClassName)) {
logWarning("Duplicate validator contribution found for: " + checkValidatorClassName); //$NON-NLS-1$
continue;
}
checkValidatorClassNameToCheckValidatorDescriptorMap.put(checkValidatorClassName, checkValidatorDescriptor);
} else if (NODE_EPACKAGE_MAPPING.equals(checkValidatorConfigurationElement.getName())) {
String javaPackageName = checkValidatorConfigurationElement.getAttribute(ATTR_EOBJECT_WRAPPER_PACKAGE_NAME);
String ePackageNsURI = checkValidatorConfigurationElement.getAttribute(ATTR_EPACKAGE_NS_URI);
Object ePackageObject = EPackage.Registry.INSTANCE.get(ePackageNsURI);
if (ePackageObject == null) {
logError("Unable to find EPackage for ", ePackageNsURI); //$NON-NLS-1$
}
ePackageMappings.put(javaPackageName, ePackageObject);
}
} catch (Exception ex) {
logError(ex);
}
}
// Second iteration to register the contributed check validators with EMF's EValidator registry and check
// catalog URI map
for (CheckValidatorDescriptor checkValidatorDescriptor : checkValidatorClassNameToCheckValidatorDescriptorMap.values()) {
try {
// Create instance of contributed check validator
ICheckValidator validator = checkValidatorDescriptor.newInstance();
// Register check validator upon all EPackages that are affected by the former
Set<Class<?>> classesUnderCheck = findClassesUnderCheck(validator.getClass());
Set<EPackage> affectedEPackages = findAffectedEPackages(classesUnderCheck, ePackageMappings);
for (EPackage affectedEPackage : affectedEPackages) {
addValidator(affectedEPackage, validator);
}
URI catalogURI = checkValidatorDescriptor.getCatalogURI();
checkValidatorToCheckCatalogURIMap.put(validator, catalogURI);
if (catalogURI != null) {
Set<ICheckValidator> checkValidators = checkCatalogURIToCheckValidatorsMap.get(catalogURI);
if (checkValidators == null) {
checkValidators = new HashSet<ICheckValidator>();
checkCatalogURIToCheckValidatorsMap.put(catalogURI, checkValidators);
}
checkValidators.add(validator);
}
} catch (Exception ex) {
logError(ex);
}
}
// Clear temporary objects
checkValidatorClassNameToCheckValidatorDescriptorMap.clear();
ePackageMappings.clear();
}
}
/**
* Retrieves the classes (typically metamodel element types) from the arguments of the check validator methods that
* are annotated with @Check.
*
* @param validatorClass
* @return
*/
private Set<Class<?>> findClassesUnderCheck(Class<? extends ICheckValidator> validatorClass) {
Assert.isNotNull(validatorClass);
Set<Class<?>> classesUnderCheck = new HashSet<Class<?>>();
Collection<Method> methods = CheckValidationUtil.getDeclaredCheckMethods(validatorClass);
for (Method method : methods) {
Annotation[] annotations = method.getAnnotations();
for (Annotation annotation : annotations) {
Class<? extends Annotation> annotationType = annotation.annotationType();
if (annotationType.equals(Check.class)) {
Class<?>[] parameterTypes = method.getParameterTypes();
if (parameterTypes.length > 0) {
classesUnderCheck.add(parameterTypes[0]);
}
}
}
}
return classesUnderCheck;
}
/**
* Retrieves the set {@link EPackage}s behind the given set of classes to be checked.
*
* @param classesUnderCheck
* @return
*/
private Set<EPackage> findAffectedEPackages(Set<Class<?>> classesUnderCheck, EPackageMappings ePackageMappings) {
Assert.isNotNull(classesUnderCheck);
Set<EPackage> affectedEPackages = new HashSet<EPackage>();
for (Class<?> classUnderCheck : classesUnderCheck) {
EPackage ePackage = findAffectedEPackage(classUnderCheck, ePackageMappings);
if (ePackage == null) {
logError("Unable to find EPackage for ", classUnderCheck.getName()); //$NON-NLS-1$
continue;
}
affectedEPackages.add(ePackage);
}
return affectedEPackages;
}
private EPackage findAffectedEPackage(Class<?> classUnderCheck, EPackageMappings ePackageMappings) {
Assert.isNotNull(ePackageMappings);
EPackage ePackage = ePackageMappings.getEPackageFor(classUnderCheck);
if (ePackage != null) {
return ePackage;
}
ePackage = EObjectUtil.findEPackage(classUnderCheck);
if (ePackage != null) {
ePackageMappings.put(classUnderCheck.getName(), ePackage);
}
return ePackage;
}
public void addValidator(EPackage ePackage, EValidator validator) {
// Retrieve existing validator for given EPackage if any
EValidator existingValidator = eValidatorRegistry.getEValidator(ePackage);
// No validator for given EPackage so far?
if (existingValidator == null) {
// Register given check validator as is
eValidatorRegistry.put(ePackage, validator);
}
// Existing validator being a composite validator?
else if (existingValidator instanceof ICompositeValidator) {
// Add given check validator as additional child validator
((ICompositeValidator) existingValidator).addValidator(validator);
}
// Existing validator is an EValidator or another check validator
else {
// Replace existing validator by a composite validator containing existing validator and given check
// validator as child validators
ICompositeValidator compositeValidator = new CompositeValidator();
compositeValidator.addValidator(existingValidator);
compositeValidator.addValidator(validator);
eValidatorRegistry.put(ePackage, compositeValidator);
}
}
/**
* Retrieve a check-based validator contributed through the <code>org.eclipse.sphinx.emf.check.chekvalidators</code>
* extension point for the given package.
*
* @param ePackage
* @return
* @throws CoreException
*/
public EValidator getValidator(EPackage ePackage) {
EValidator eValidator = getEValidatorRegistry().getEValidator(ePackage);
if (eValidator instanceof ICheckValidator || eValidator instanceof ICompositeValidator) {
return eValidator;
}
return null;
}
/**
* Returns the URI of a check catalog associated with given check validator.
*
* @param checkValidator
* @return
*/
public URI getCheckCatalogURI(ICheckValidator checkValidator) {
return getCheckValidatorToCheckCatalogURIMap().get(checkValidator);
}
public Catalog getCheckCatalog(ICheckValidator checkValidator) {
URI checkCatalogURI = getCheckCatalogURI(checkValidator);
if (checkCatalogURI != null) {
return loadCheckCatalog(checkCatalogURI);
}
return null;
}
private Catalog loadCheckCatalog(URI checkCatalogURI) {
Catalog catalog = uriToCheckCatalogMap.get(checkCatalogURI);
if (catalog == null) {
EObject eObject = EcoreResourceUtil.loadEObject(null, checkCatalogURI.appendFragment("/")); //$NON-NLS-1$
if (!(eObject instanceof Catalog)) {
throw new IllegalStateException("Unable to find the check catalog for URI '" + checkCatalogURI + "'"); //$NON-NLS-1$ //$NON-NLS-2$
}
catalog = (Catalog) eObject;
uriToCheckCatalogMap.put(checkCatalogURI, catalog);
}
return catalog;
}
public Collection<Catalog> getCheckCatalogs() {
Set<URI> allCalalogURIs = getCheckCatalogURIToCheckValidatorsMap().keySet();
for (URI uri : allCalalogURIs) {
if (uriToCheckCatalogMap.get(uri) == null) {
loadCheckCatalog(uri);
}
}
return uriToCheckCatalogMap.values();
}
public Collection<ICheckValidator> getCheckValidators() {
return getCheckValidatorToCheckCatalogURIMap().keySet();
}
private void logWarning(String msgId, Object... objects) {
logWarning(new RuntimeException(NLS.bind(msgId, objects)));
}
private void logWarning(Throwable throwable) {
logger.log(StatusUtil.createWarningStatus(Activator.getDefault(), throwable));
}
private void logError(String msgId, Object... objects) {
logError(new RuntimeException(NLS.bind(msgId, objects)));
}
private void logError(Throwable throwable) {
logger.log(StatusUtil.createErrorStatus(Activator.getDefault(), throwable));
}
}