/** | |
* Copyright (c) 2009, 2011 Mia-Software. | |
* 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: | |
* Gregoire DUPE (Mia-Software) - initial API and implementation | |
* Nicolas Bros (Mia-Software) | |
*/ | |
package org.eclipse.gmt.modisco.infra.query.core.java.internal; | |
import java.lang.reflect.Field; | |
import java.util.HashMap; | |
import java.util.Properties; | |
import java.util.Set; | |
import org.eclipse.core.resources.IProject; | |
import org.eclipse.core.resources.IResource; | |
import org.eclipse.core.resources.IResourceChangeEvent; | |
import org.eclipse.core.resources.IResourceChangeListener; | |
import org.eclipse.core.resources.IResourceDelta; | |
import org.eclipse.core.resources.IWorkspace; | |
import org.eclipse.core.resources.ResourcesPlugin; | |
import org.eclipse.core.runtime.IPath; | |
import org.eclipse.core.runtime.Path; | |
import org.eclipse.core.runtime.Platform; | |
import org.eclipse.emf.common.util.URI; | |
import org.eclipse.emf.ecore.EClass; | |
import org.eclipse.emf.ecore.EObject; | |
import org.eclipse.emf.ecore.EPackage; | |
import org.eclipse.emf.ecore.plugin.EcorePlugin; | |
import org.eclipse.gmt.modisco.infra.common.core.internal.resource.MoDiscoResourceSet; | |
import org.eclipse.gmt.modisco.infra.common.core.logging.MoDiscoLogger; | |
import org.eclipse.gmt.modisco.infra.query.JavaModelQuery; | |
import org.eclipse.gmt.modisco.infra.query.ModelQuery; | |
import org.eclipse.gmt.modisco.infra.query.ModelQuerySet; | |
import org.eclipse.gmt.modisco.infra.query.QueryPackage; | |
import org.eclipse.gmt.modisco.infra.query.core.AbstractModelQuery; | |
import org.eclipse.gmt.modisco.infra.query.core.IModelQueryFactory; | |
import org.eclipse.gmt.modisco.infra.query.core.ModelQuerySetCatalog; | |
import org.eclipse.gmt.modisco.infra.query.core.exception.BundleClassPathException; | |
import org.eclipse.gmt.modisco.infra.query.core.exception.ExecutionEnvironmentException; | |
import org.eclipse.gmt.modisco.infra.query.core.exception.MissingBundleException; | |
import org.eclipse.gmt.modisco.infra.query.core.exception.ModelQueryException; | |
import org.eclipse.gmt.modisco.infra.query.core.exception.ModelQueryExecutionException; | |
import org.eclipse.gmt.modisco.infra.query.core.exception.ProjectDisabledException; | |
import org.eclipse.gmt.modisco.infra.query.core.internal.Activator; | |
import org.eclipse.gmt.modisco.infra.query.core.java.IJavaModelQuery; | |
import org.eclipse.jdt.core.IJavaProject; | |
import org.eclipse.jdt.core.JavaCore; | |
import org.eclipse.osgi.util.ManifestElement; | |
import org.eclipse.pde.core.plugin.IPluginAttribute; | |
import org.eclipse.pde.core.plugin.IPluginElement; | |
import org.eclipse.pde.core.plugin.IPluginExtension; | |
import org.eclipse.pde.core.plugin.IPluginModelBase; | |
import org.eclipse.pde.core.plugin.IPluginObject; | |
import org.eclipse.pde.core.plugin.PluginRegistry; | |
import org.osgi.framework.Bundle; | |
import org.osgi.framework.BundleException; | |
public class JavaModelQueryFactory implements IModelQueryFactory { | |
private static final long DELAY = 1000; | |
private static IResourceChangeListener changeListener = null; | |
private static HashMap<IProject, Bundle> bundleMap = new HashMap<IProject, Bundle>(); | |
private static HashMap<IProject, Long> disabledProjects = new HashMap<IProject, Long>(); | |
private static HashMap<IProject, ModelQueryException> disabledProjectsCause = new HashMap<IProject, ModelQueryException>(); | |
public JavaModelQueryFactory() { | |
if (JavaModelQueryFactory.changeListener == null) { | |
JavaModelQueryFactory.changeListener = JavaModelQueryFactory.initChangeListener(); | |
} | |
} | |
public AbstractModelQuery create(final ModelQuery modelQuery, final Bundle bundle) | |
throws ModelQueryException { | |
if (!(modelQuery instanceof JavaModelQuery)) { | |
throw new ModelQueryException("Wrong kind of ModelQuery: " //$NON-NLS-1$ | |
+ modelQuery.getClass().getSimpleName() + " found, " //$NON-NLS-1$ | |
+ JavaModelQuery.class.getSimpleName() + " expected."); //$NON-NLS-1$ | |
} | |
AbstractModelQuery javaModelQueryInst = null; | |
IProject project = null; | |
try { | |
Bundle localBundle; | |
JavaModelQuery javaModelQuery = (JavaModelQuery) modelQuery; | |
if (javaModelQuery.getModelQuerySet() == null) { | |
throw new IllegalStateException("JavaModelQuery '" //$NON-NLS-1$ | |
+ javaModelQuery.getName() + " 'is not in any query set"); //$NON-NLS-1$ | |
} | |
if (bundle == null) { | |
final IWorkspace ws = ResourcesPlugin.getWorkspace(); | |
final String querySetName = modelQuery.getModelQuerySet().getName(); | |
URI uri = ModelQuerySetCatalog.getSingleton().getURI(querySetName); | |
if (uri == null) { | |
throw new IllegalStateException("QuerySet not found: " + querySetName); //$NON-NLS-1$ | |
} | |
Path querySetPath = new Path(uri.toPlatformString(true)); | |
IResource file = ws.getRoot().findMember(querySetPath); | |
if (file == null) { | |
throw new IllegalStateException( | |
"QuerySet file not found: " + querySetPath.toString()); //$NON-NLS-1$ | |
} | |
project = file.getProject(); | |
localBundle = JavaModelQueryFactory.bundleMap.get(project); | |
checkProjectEnabled(project); | |
if (localBundle == null) { | |
localBundle = Activator.getDefault().installBundle(project); | |
loadMetamodelsFromPluginXml(project, localBundle); | |
JavaModelQueryFactory.bundleMap.put(project, localBundle); | |
// reload the query | |
ModelQuerySet oldModelQuerySet = javaModelQuery.getModelQuerySet(); | |
if (oldModelQuerySet == null) { | |
throw new IllegalStateException("JavaModelQuery '" //$NON-NLS-1$ | |
+ javaModelQuery.getName() + " 'is not in any query set"); //$NON-NLS-1$ | |
} | |
ModelQuerySet modelQuerySet = ModelQuerySetCatalog.getSingleton() | |
.getModelQuerySet(querySetName); | |
if (modelQuerySet == null) { | |
throw new IllegalStateException("Couln't find a QuerySet named '" //$NON-NLS-1$ | |
+ querySetName + "'"); //$NON-NLS-1$ | |
} | |
javaModelQuery = (JavaModelQuery) modelQuerySet.getQuery(javaModelQuery | |
.getName()); | |
if (javaModelQuery == null) { | |
throw new IllegalStateException("Couldn't reload the JavaModelQuery"); //$NON-NLS-1$ | |
} | |
} | |
checkBundleClassPath(project, localBundle); | |
checkRequiredExecutionEnvironment(localBundle); | |
checkDependencies(localBundle); | |
} else { | |
localBundle = bundle; | |
} | |
String className = javaModelQuery.getImplementationClassName(); | |
if (className == null || className.length() == 0) { | |
throw new ModelQueryExecutionException("implementationClassName is empty"); //$NON-NLS-1$ | |
} | |
Class<?> javaModelQueryClass = localBundle.loadClass(className); | |
if (!IJavaModelQuery.class.isAssignableFrom(javaModelQueryClass)) { | |
throw new Exception(className + " does not implement " //$NON-NLS-1$ | |
+ IJavaModelQuery.class.getSimpleName() + "."); //$NON-NLS-1$ | |
} | |
javaModelQueryInst = createJavaModelQueryImpl(javaModelQuery, javaModelQueryClass); | |
javaModelQueryInst.setCheckResult(bundle != null); | |
} catch (ExecutionEnvironmentException e) { | |
if (project != null) { | |
synchronized (JavaModelQueryFactory.disabledProjects) { | |
JavaModelQueryFactory.disabledProjects.put(project, | |
new Long(System.currentTimeMillis())); | |
} | |
} | |
throw e; | |
} catch (MissingBundleException e) { | |
if (project != null) { | |
synchronized (JavaModelQueryFactory.disabledProjects) { | |
JavaModelQueryFactory.disabledProjects.put(project, | |
new Long(System.currentTimeMillis())); | |
} | |
} | |
throw e; | |
} catch (BundleClassPathException e) { | |
if (project != null) { | |
synchronized (JavaModelQueryFactory.disabledProjects) { | |
JavaModelQueryFactory.disabledProjects.put(project, | |
new Long(System.currentTimeMillis())); | |
} | |
} | |
throw e; | |
} catch (ProjectDisabledException e) { | |
throw e; | |
} catch (Exception e) { | |
ModelQueryException mqe = new ModelQueryException("Failed to load the model query: " //$NON-NLS-1$ | |
+ modelQuery.getModelQuerySet().getName() + "::" //$NON-NLS-1$ | |
+ modelQuery.getName(), e); | |
MoDiscoLogger.logError(mqe, Activator.getDefault()); | |
throw mqe; | |
} | |
return javaModelQueryInst; | |
} | |
private void checkProjectEnabled(final IProject project) throws ProjectDisabledException { | |
synchronized (JavaModelQueryFactory.disabledProjects) { | |
Long timestamp = JavaModelQueryFactory.disabledProjects.get(project); | |
if (timestamp != null) { | |
long sinceLastTry = System.currentTimeMillis() - timestamp.longValue(); | |
if (sinceLastTry < JavaModelQueryFactory.DELAY) { | |
throw new ProjectDisabledException( | |
JavaModelQueryFactory.disabledProjectsCause.get(project)); | |
} | |
JavaModelQueryFactory.disabledProjectsCause.remove(project); | |
JavaModelQueryFactory.disabledProjects.remove(project); | |
} | |
} | |
} | |
private void checkDependencies(final Bundle localBundle) throws BundleException, | |
MissingBundleException { | |
String dependencies = (String) localBundle.getHeaders().get( | |
org.osgi.framework.Constants.REQUIRE_BUNDLE); | |
ManifestElement[] manifestElements = ManifestElement.parseHeader( | |
org.osgi.framework.Constants.REQUIRE_BUNDLE, dependencies); | |
for (ManifestElement manifestElement : manifestElements) { | |
String bundleName = manifestElement.getValue(); | |
if (Platform.getBundle(bundleName) == null) { | |
throw new MissingBundleException(bundleName); | |
} | |
} | |
} | |
private void checkRequiredExecutionEnvironment(final Bundle localBundle) | |
throws ExecutionEnvironmentException { | |
String requiredExecEnv = (String) localBundle.getHeaders().get( | |
org.osgi.framework.Constants.BUNDLE_REQUIREDEXECUTIONENVIRONMENT); | |
Properties p = System.getProperties(); | |
String availableExecEnv = (String) p | |
.get(org.osgi.framework.Constants.FRAMEWORK_EXECUTIONENVIRONMENT); | |
if (!availableExecEnv.contains(requiredExecEnv)) { | |
throw new ExecutionEnvironmentException(requiredExecEnv, availableExecEnv); | |
} | |
} | |
/** | |
* Check that the output location of the Java project containing the query | |
* implementation is also on the Bundle-ClassPath of the project. Otherwise, | |
* The Java queries implementations it contains will not be found. | |
*/ | |
private void checkBundleClassPath(final IProject project, final Bundle localBundle) | |
throws BundleClassPathException { | |
boolean found = false; | |
IPath outputLocation = null; | |
try { | |
IJavaProject javaProject = JavaCore.create(project); | |
if (javaProject != null) { | |
outputLocation = javaProject.getOutputLocation(); | |
Object header = localBundle.getHeaders().get( | |
org.osgi.framework.Constants.BUNDLE_CLASSPATH); | |
if (header instanceof String) { | |
String bundleClasspath = (String) header; | |
String[] bundleClassPathElements = bundleClasspath.split(","); //$NON-NLS-1$ | |
for (String bundleClassPathEntry : bundleClassPathElements) { | |
if (project.getFullPath().append(bundleClassPathEntry.trim()) | |
.equals(outputLocation)) { | |
found = true; | |
} | |
} | |
} | |
} | |
} catch (Exception e) { | |
MoDiscoLogger.logError(e, "Error checking bundle classpath", Activator.getDefault()); //$NON-NLS-1$ | |
return; | |
} | |
if (!found) { | |
throw new BundleClassPathException(project, outputLocation, localBundle); | |
} | |
} | |
/** This methods is dedicated to isolate the "Unchecked cast" warning. */ | |
@SuppressWarnings("unchecked") | |
private AbstractModelQuery createJavaModelQueryImpl(final JavaModelQuery javaModelQuery, | |
final Class<?> javaModelQueryClass) throws InstantiationException, | |
IllegalAccessException { | |
IJavaModelQuery<EObject, ?> javaModelQueryInst; | |
javaModelQueryInst = (IJavaModelQuery<EObject, ?>) javaModelQueryClass.newInstance(); | |
AbstractModelQuery adapter = new JavaModelQueryAdapter(javaModelQuery, javaModelQueryInst); | |
return adapter; | |
} | |
public EClass getManagedModelQueryType() { | |
return QueryPackage.eINSTANCE.getJavaModelQuery(); | |
} | |
static HashMap<IProject, Bundle> getBundleMap() { | |
return JavaModelQueryFactory.bundleMap; | |
} | |
/** | |
* Add a change listener that when a class is modified, removes the | |
* containing project from the bundle map, so that it gets re-installed when | |
* a query is executed. | |
*/ | |
private static IResourceChangeListener initChangeListener() { | |
IWorkspace workspace = ResourcesPlugin.getWorkspace(); | |
IResourceChangeListener listener = new IResourceChangeListener() { | |
public void resourceChanged(final IResourceChangeEvent event) { | |
if (event != null) { | |
checkDelta(event.getDelta()); | |
} | |
} | |
private void checkDelta(final IResourceDelta delta) { | |
if (delta != null) { | |
for (IResourceDelta subdelta : delta.getAffectedChildren()) { | |
checkDelta(subdelta); | |
} | |
IResource resource = delta.getResource(); | |
if (resource != null) { | |
if ("class".equals(resource.getFileExtension())) { //$NON-NLS-1$ | |
IProject project = resource.getProject(); | |
Set<IProject> listened = JavaModelQueryFactory.getBundleMap().keySet(); | |
if (listened.contains(project)) { | |
if (project.getLocation() != null) { | |
JavaModelQueryFactory.getBundleMap().remove(project); | |
} | |
} | |
} | |
} | |
} | |
} | |
}; | |
workspace.addResourceChangeListener(listener); | |
return listener; | |
// workspace.removeResourceChangeListener(listener); | |
} | |
/** | |
* Load the {@link EPackage}s containing the metamodel implementations | |
* defined in the <code>plugin.xml</code> of the given project. | |
*/ | |
void loadMetamodelsFromPluginXml(final IProject project, final Bundle bundle) { | |
final String generatedPackageExtensionPoint = EcorePlugin.getPlugin().getSymbolicName() | |
+ "." //$NON-NLS-1$ | |
+ EcorePlugin.GENERATED_PACKAGE_PPID; | |
IPluginModelBase pluginModel = PluginRegistry.findModel(project); | |
IPluginExtension[] extensions = pluginModel.getExtensions().getExtensions(); | |
for (IPluginExtension pluginExtension : extensions) { | |
if (generatedPackageExtensionPoint.equals(pluginExtension.getPoint())) { | |
IPluginObject[] children = pluginExtension.getChildren(); | |
for (IPluginObject child : children) { | |
if (child instanceof IPluginElement) { | |
IPluginElement pluginElement = (IPluginElement) child; | |
IPluginAttribute[] attributes = pluginElement.getAttributes(); | |
String uri = null; | |
String className = null; | |
for (IPluginAttribute pluginAttribute : attributes) { | |
if ("class".equalsIgnoreCase(pluginAttribute.getName())) { //$NON-NLS-1$ | |
className = pluginAttribute.getValue(); | |
} else if ("uri".equalsIgnoreCase(pluginAttribute.getName())) { //$NON-NLS-1$ | |
uri = pluginAttribute.getValue(); | |
} | |
} | |
if (className == null || uri == null) { | |
MoDiscoLogger.logError("Error in extension " //$NON-NLS-1$ | |
+ EcorePlugin.GENERATED_PACKAGE_PPID | |
+ ": couldn't hotload metamodel", Activator.getDefault()); //$NON-NLS-1$ | |
} | |
loadEPackage(bundle, uri, className); | |
} | |
} | |
} | |
} | |
} | |
private void loadEPackage(final Bundle bundle, final String uri, final String className) { | |
try { | |
Class<?> ePackageClass = bundle.loadClass(className); | |
Field instanceField = ePackageClass.getField("eINSTANCE"); //$NON-NLS-1$ | |
EPackage ePackage = (EPackage) instanceField.get(null); | |
EPackage.Registry.INSTANCE.put(ePackage.getNsURI(), ePackage); | |
MoDiscoResourceSet.getResourceSetSingleton().aResourceHasBeenLoaded( | |
ePackage.eResource()); | |
} catch (Exception e) { | |
MoDiscoLogger.logError(e, "Failed to load an EPackage: " + uri, //$NON-NLS-1$ | |
Activator.getDefault()); | |
} | |
} | |
} |