diff --git a/plugins/org.eclipse.emf.ecore/src/org/eclipse/emf/ecore/plugin/EcorePlugin.java b/plugins/org.eclipse.emf.ecore/src/org/eclipse/emf/ecore/plugin/EcorePlugin.java index 5fc4e3ab5..ba00cb7e8 100644 --- a/plugins/org.eclipse.emf.ecore/src/org/eclipse/emf/ecore/plugin/EcorePlugin.java +++ b/plugins/org.eclipse.emf.ecore/src/org/eclipse/emf/ecore/plugin/EcorePlugin.java @@ -69,6 +69,7 @@ import org.eclipse.emf.ecore.resource.URIConverter; import org.osgi.framework.BundleActivator; import org.osgi.framework.BundleContext; +import org.osgi.framework.BundleException; import org.osgi.framework.Constants; import org.osgi.framework.Filter; import org.osgi.framework.FrameworkUtil; @@ -605,6 +606,7 @@ public static Map getEPackageNsURIToDynamicModelLocationMap(boolean */ static public class Implementation extends EclipsePlugin { + private ManifestEPackageTracker ePackageTracker; /** * Creates the singleton instance. */ @@ -677,6 +679,18 @@ public void start(BundleContext context) throws Exception super.start(context); ExtensionProcessor.internalProcessExtensions(); + ePackageTracker = new ManifestEPackageTracker(context); + ePackageTracker.open(); + } + + @Override + public void stop(BundleContext context) throws Exception + { + super.stop(context); + if (ePackageTracker != null) + { + ePackageTracker.close(); + } } /** @@ -868,6 +882,8 @@ public IExtensionRegistry getRegistry() // Process the extension for the registry. // ExtensionProcessor.internalProcessExtensions(); + + ExtensionProcessor.internalProcessManifests(classLoader); } } @@ -938,12 +954,47 @@ protected boolean readElement(IConfigurationElement element) new ConversionDelegateFactoryRegistryReader().readRegistry(); new AnnotationValidatorRegistryReader().readRegistry(); } + + private static void internalProcessManifests(ClassLoader classLoader) + { + if (!IS_OSGI_RUNNING) + { // not in a OSGi runtime, process raw MANIFEST.MF + try + { + List manifests = getManifests(classLoader); + for (URI manifest : manifests) + { + ManifestEPackageTracker.registerManifestPackagesIfAbsent(manifest, classLoader); + } + } + catch (IOException | BundleException e) + { + e.printStackTrace(); //TODO: handle + } + } + } } /** * Determine all the available plugin.xml resources. */ private static List getPluginXMLs(ClassLoader classLoader) + { + return getClasspathResource(classLoader, "plugin.xml"); + } + + /** + * Determine all the available {@code META-INF/MANIFEST.MF} resources. + */ + private static List getManifests(ClassLoader classLoader) + { + return getClasspathResource(classLoader, "META-INF/MANIFEST.MF"); + } + + /** + * Determine all the available plugin.xml resources. + */ + private static List getClasspathResource(ClassLoader classLoader, String resourcePath) { List result = new ArrayList(); @@ -986,13 +1037,13 @@ private static List getPluginXMLs(ClassLoader classLoader) { // Determine if there is a plugin.xml at the root of the folder. // - File pluginXML = new File(file, "plugin.xml"); + File pluginXML = new File(file, resourcePath); if (!pluginXML.exists()) { // If not, check if there is one in the parent folder. // File parentFile = file.getParentFile(); - pluginXML = new File(parentFile, "plugin.xml"); + pluginXML = new File(parentFile, resourcePath); if (pluginXML.isFile()) { // If there is, then we have plugin.xml files that aren't on the classpath. @@ -1003,7 +1054,7 @@ else if (parentFile != null) { // The parent has a parent, check if there is one in the parent's parent folder. // - pluginXML = new File(parentFile.getParentFile(), "plugin.xml"); + pluginXML = new File(parentFile.getParentFile(), resourcePath); if (pluginXML.isFile()) { // If there is, then we have plugin.xml files that aren't on the classpath. @@ -1042,7 +1093,7 @@ else if (file.isFile()) // Look for a plugin.xml entry... // jarFile = new JarFile(classpathEntry); - ZipEntry entry = jarFile.getEntry("plugin.xml"); + ZipEntry entry = jarFile.getEntry(resourcePath); if (entry != null) { // If we find one, create a URI for it. @@ -1080,7 +1131,7 @@ else if (file.isFile()) result.clear(); try { - for (Enumeration resources = classLoader.getResources("plugin.xml"); resources.hasMoreElements(); ) + for (Enumeration resources = classLoader.getResources(resourcePath); resources.hasMoreElements(); ) { // Create a URI for each plugin.xml found by the class loader. // diff --git a/plugins/org.eclipse.emf.ecore/src/org/eclipse/emf/ecore/plugin/ManifestEPackageTracker.java b/plugins/org.eclipse.emf.ecore/src/org/eclipse/emf/ecore/plugin/ManifestEPackageTracker.java new file mode 100644 index 000000000..ff3471a9f --- /dev/null +++ b/plugins/org.eclipse.emf.ecore/src/org/eclipse/emf/ecore/plugin/ManifestEPackageTracker.java @@ -0,0 +1,223 @@ +/** + * Copyright (c) 2025-2025 IILS mbH 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 + * http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * Hannes Wellmann (IILS mbH) - Initial API and implementation + */ +package org.eclipse.emf.ecore.plugin; + + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +import org.eclipse.emf.common.util.URI; +import org.eclipse.emf.common.util.WrappedException; +import org.eclipse.emf.ecore.EFactory; +import org.eclipse.emf.ecore.EPackage; +import org.eclipse.emf.ecore.plugin.ManifestEPackageTracker.ManifestEPackageDescriptor; +import org.eclipse.emf.ecore.resource.URIConverter; +import org.eclipse.osgi.util.ManifestElement; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.framework.BundleEvent; +import org.osgi.framework.BundleException; +import org.osgi.framework.Constants; +import org.osgi.framework.wiring.BundleRevision; +import org.osgi.resource.Capability; +import org.osgi.resource.Resource; +import org.osgi.util.tracker.BundleTracker; + + +/** + * An {@link BundleTracker OSGi-BundleTracker} that reads and registers the EPackages generated into {@code META-INF/MANIFEST.MF} files as provided OSGi capabilities: + * + *
+ *  Provide-Capability:
+ *   org.eclipse.emf.ecore.generated_package;
+ *     uri=http://foo.bar/packA;
+ *     class=packA.PackAPackage;
+ *     genModel="model/packA.genmodel"
+ * 
+ */ +class ManifestEPackageTracker extends BundleTracker> +{ + public static final String GENERATED_PACKAGE_NAMESPACE = "org.eclipse.emf.ecore.generated_package"; + + public static final String ATTRIBUTE_URI = "uri"; + + public static final String ATTRIBUTE_CLASS = "class"; + + public static final String ATTRIBUTE_GEN_MODEL = "genModel"; + + static void registerManifestPackagesIfAbsent(URI manifest, ClassLoader classLoader) throws IOException, BundleException + { + try (InputStream content = URIConverter.INSTANCE.createInputStream(manifest)) + { + List descriptors = ManifestEPackageDescriptor.parse(content, classLoader); + for (ManifestEPackageDescriptor descriptor : descriptors) + { + registerIfAbsent(descriptor, genModel -> manifest.trimSegments(2).appendFileExtension(genModel)); + } + } + } + + ManifestEPackageTracker(BundleContext context) + { + // TODO: check if stopping and (re-)starting creates a new Classloader?! But re-wiring does?! + super(context, Bundle.RESOLVED | Bundle.STARTING | Bundle.ACTIVE | Bundle.STOPPING, null); + } + + @Override + public List addingBundle(Bundle bundle, BundleEvent event) + { + Resource resource = bundle.adapt(BundleRevision.class); + List descriptors = new ArrayList<>(); + for (Capability capability : resource.getCapabilities(GENERATED_PACKAGE_NAMESPACE)) + { + ManifestEPackageDescriptor descriptor = ManifestEPackageDescriptor.parse(bundle, capability); + if (registerIfAbsent(descriptor, genModel -> { + String path = bundle.getSymbolicName() + "/" + genModel; + return URI.createPlatformPluginURI(path, true); + })) + { + descriptors.add(descriptor); + } + } + return !descriptors.isEmpty() ? Collections.unmodifiableList(descriptors) : null; + } + + private static boolean registerIfAbsent(ManifestEPackageDescriptor descriptor, Function absoluteGenModelURIFactory) + { + String packageURI = descriptor.packageURI; + if (EPackage.Registry.INSTANCE.putIfAbsent(packageURI, descriptor) == null) + { + // Only add if absent to grant precedence for registrations through a plugin.xml + + String genModel = descriptor.genModel; + URI genModelURI = URI.createURI(genModel); + if (genModelURI.isRelative()) + { + genModelURI = absoluteGenModelURIFactory.apply(genModel); + } + EcorePlugin.getEPackageNsURIToGenModelLocationMap(false).put(packageURI, genModelURI); + return true; + } + return false; + } + + @Override + public void removedBundle(Bundle bundle, BundleEvent event, List packages) + { + for (ManifestEPackageDescriptor descriptor : packages) + { + String uri = descriptor.packageURI; + EPackage.Registry.INSTANCE.remove(uri); + EcorePlugin.getEPackageNsURIToGenModelLocationMap(false).remove(uri); + } + } + + private static interface ClassLoaderWrapper + { + Class loadClass(String name) throws ClassNotFoundException; + } + + static class ManifestEPackageDescriptor implements EPackage.Descriptor + { + + static ManifestEPackageDescriptor parse(Bundle bundle, Capability capability) + { + Map attributes = capability.getAttributes(); + return new ManifestEPackageDescriptor( + bundle::loadClass, + (String)attributes.get(ATTRIBUTE_URI), + (String)attributes.get(ATTRIBUTE_CLASS), + (String)attributes.get(ATTRIBUTE_GEN_MODEL)); + } + + static List parse(InputStream manifestContent, ClassLoader classLoader) throws IOException, BundleException + { + Map bundleManifest = ManifestElement.parseBundleManifest(manifestContent, null); + String value = bundleManifest.get(Constants.PROVIDE_CAPABILITY); + if (value == null || value.trim().isEmpty()) + { + return Collections.emptyList(); + } + ManifestElement[] elements = ManifestElement.parseHeader(Constants.PROVIDE_CAPABILITY, value.trim()); + if (elements == null) + { + return Collections.emptyList(); + } + List descriptors = new ArrayList<>(); + for (ManifestElement element : elements) + { + if (GENERATED_PACKAGE_NAMESPACE.equals(element.getValue())) + { + ManifestEPackageDescriptor descriptor = new ManifestEPackageDescriptor( + classLoader::loadClass, + element.getAttribute(ATTRIBUTE_URI), + element.getAttribute(ATTRIBUTE_CLASS), + element.getAttribute(ATTRIBUTE_GEN_MODEL)); + descriptors.add(descriptor); + } + } + return descriptors; + } + + final ClassLoaderWrapper classLoader; + + final String packageURI; + + final String packageClassName; + + final String genModel; + + ManifestEPackageDescriptor(Bundle bundle, String packageURI, String packageClassName, String genModel) + { // Don't use current Wiring ClassLoader directly to allow changes until the first EPackage is eventually created + this(bundle::loadClass, packageURI, packageClassName, genModel); + } + + ManifestEPackageDescriptor(ClassLoader classLoader, String packageURI, String packageClassName, String genModel) + { + this(classLoader::loadClass, packageURI, packageClassName, genModel); + } + + private ManifestEPackageDescriptor(ClassLoaderWrapper classLoader, String packageURI, String packageClassName, String genModel) + { + this.classLoader = classLoader; + this.packageURI = packageURI; + this.packageClassName = packageClassName; + this.genModel = genModel; + } + + @Override + public EPackage getEPackage() + { + try + { + Class packageClass = classLoader.loadClass(packageClassName); + return (EPackage)packageClass.getField("eINSTANCE").get(null); + } + catch (ReflectiveOperationException e) + { + throw new WrappedException(e); + } + } + + @Override + public EFactory getEFactory() + { + EPackage ePackage = getEPackage(); + return ePackage != null ? ePackage.getEFactoryInstance() : null; + } + } + +}