Skip to content

Commit 5b8cfc2

Browse files
committed
Register EMF-packages declared in MANIFEST.MF in EPackage-registry
if they are not already registered through an Eclipse Extension in a plugin.xml. Add an OSGi Bundle-Tracker that registers the EPackages declared in the MANIFEST.MF of a bundle by reading the provided capabilities in the 'org.eclipse.emf.ecore.generated_package' namespace. EMF can already generate these capabilities as of Bug 581348 [1] The EPackages declared in the MANIFEST are processed after the EPackages potentially declared in a plugin.xml and existing registrations with an equal URI are not overridden to grand registration in plugin.xml files precedence and to ensure backwards compatibility. If EMF is not executed in a OSGi runtime the corresponding header of all MANIFEST.MF files discoverable by the provided classloader are read directly to ensure EPackages are then registered too. [1] - https://bugs.eclipse.org/581348
1 parent 9a12e8b commit 5b8cfc2

File tree

2 files changed

+279
-5
lines changed

2 files changed

+279
-5
lines changed

plugins/org.eclipse.emf.ecore/src/org/eclipse/emf/ecore/plugin/EcorePlugin.java

Lines changed: 56 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
import org.eclipse.emf.ecore.resource.URIConverter;
7070
import org.osgi.framework.BundleActivator;
7171
import org.osgi.framework.BundleContext;
72+
import org.osgi.framework.BundleException;
7273
import org.osgi.framework.Constants;
7374
import org.osgi.framework.Filter;
7475
import org.osgi.framework.FrameworkUtil;
@@ -605,6 +606,7 @@ public static Map<String, URI> getEPackageNsURIToDynamicModelLocationMap(boolean
605606
*/
606607
static public class Implementation extends EclipsePlugin
607608
{
609+
private ManifestEPackageTracker ePackageTracker;
608610
/**
609611
* Creates the singleton instance.
610612
*/
@@ -677,6 +679,18 @@ public void start(BundleContext context) throws Exception
677679
super.start(context);
678680
ExtensionProcessor.internalProcessExtensions();
679681

682+
ePackageTracker = new ManifestEPackageTracker(context);
683+
ePackageTracker.open();
684+
}
685+
686+
@Override
687+
public void stop(BundleContext context) throws Exception
688+
{
689+
super.stop(context);
690+
if (ePackageTracker != null)
691+
{
692+
ePackageTracker.close();
693+
}
680694
}
681695

682696
/**
@@ -868,6 +882,8 @@ public IExtensionRegistry getRegistry()
868882
// Process the extension for the registry.
869883
//
870884
ExtensionProcessor.internalProcessExtensions();
885+
886+
ExtensionProcessor.internalProcessManifests(classLoader);
871887
}
872888
}
873889

@@ -938,12 +954,47 @@ protected boolean readElement(IConfigurationElement element)
938954
new ConversionDelegateFactoryRegistryReader().readRegistry();
939955
new AnnotationValidatorRegistryReader().readRegistry();
940956
}
957+
958+
private static void internalProcessManifests(ClassLoader classLoader)
959+
{
960+
if (!IS_OSGI_RUNNING)
961+
{ // not in a OSGi runtime, process raw MANIFEST.MF
962+
try
963+
{
964+
List<URI> manifests = getManifests(classLoader);
965+
for (URI manifest : manifests)
966+
{
967+
ManifestEPackageTracker.registerManifestPackagesIfAbsent(manifest, classLoader);
968+
}
969+
}
970+
catch (IOException | BundleException e)
971+
{
972+
e.printStackTrace(); //TODO: handle
973+
}
974+
}
975+
}
941976
}
942977

943978
/**
944979
* Determine all the available plugin.xml resources.
945980
*/
946981
private static List<URI> getPluginXMLs(ClassLoader classLoader)
982+
{
983+
return getClasspathResource(classLoader, "plugin.xml");
984+
}
985+
986+
/**
987+
* Determine all the available {@code META-INF/MANIFEST.MF} resources.
988+
*/
989+
private static List<URI> getManifests(ClassLoader classLoader)
990+
{
991+
return getClasspathResource(classLoader, "META-INF/MANIFEST.MF");
992+
}
993+
994+
/**
995+
* Determine all the available plugin.xml resources.
996+
*/
997+
private static List<URI> getClasspathResource(ClassLoader classLoader, String resourcePath)
947998
{
948999
List<URI> result = new ArrayList<URI>();
9491000

@@ -986,13 +1037,13 @@ private static List<URI> getPluginXMLs(ClassLoader classLoader)
9861037
{
9871038
// Determine if there is a plugin.xml at the root of the folder.
9881039
//
989-
File pluginXML = new File(file, "plugin.xml");
1040+
File pluginXML = new File(file, resourcePath);
9901041
if (!pluginXML.exists())
9911042
{
9921043
// If not, check if there is one in the parent folder.
9931044
//
9941045
File parentFile = file.getParentFile();
995-
pluginXML = new File(parentFile, "plugin.xml");
1046+
pluginXML = new File(parentFile, resourcePath);
9961047
if (pluginXML.isFile())
9971048
{
9981049
// If there is, then we have plugin.xml files that aren't on the classpath.
@@ -1003,7 +1054,7 @@ else if (parentFile != null)
10031054
{
10041055
// The parent has a parent, check if there is one in the parent's parent folder.
10051056
//
1006-
pluginXML = new File(parentFile.getParentFile(), "plugin.xml");
1057+
pluginXML = new File(parentFile.getParentFile(), resourcePath);
10071058
if (pluginXML.isFile())
10081059
{
10091060
// If there is, then we have plugin.xml files that aren't on the classpath.
@@ -1042,7 +1093,7 @@ else if (file.isFile())
10421093
// Look for a plugin.xml entry...
10431094
//
10441095
jarFile = new JarFile(classpathEntry);
1045-
ZipEntry entry = jarFile.getEntry("plugin.xml");
1096+
ZipEntry entry = jarFile.getEntry(resourcePath);
10461097
if (entry != null)
10471098
{
10481099
// If we find one, create a URI for it.
@@ -1080,7 +1131,7 @@ else if (file.isFile())
10801131
result.clear();
10811132
try
10821133
{
1083-
for (Enumeration<URL> resources = classLoader.getResources("plugin.xml"); resources.hasMoreElements(); )
1134+
for (Enumeration<URL> resources = classLoader.getResources(resourcePath); resources.hasMoreElements(); )
10841135
{
10851136
// Create a URI for each plugin.xml found by the class loader.
10861137
//
Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
/**
2+
* Copyright (c) 2025-2025 IILS mbH and others.
3+
* All rights reserved. This program and the accompanying materials
4+
* are made available under the terms of the Eclipse Public License v2.0
5+
* which accompanies this distribution, and is available at
6+
* http://www.eclipse.org/legal/epl-v20.html
7+
*
8+
* Contributors:
9+
* Hannes Wellmann (IILS mbH) - Initial API and implementation
10+
*/
11+
package org.eclipse.emf.ecore.plugin;
12+
13+
14+
import java.io.IOException;
15+
import java.io.InputStream;
16+
import java.util.ArrayList;
17+
import java.util.Collections;
18+
import java.util.List;
19+
import java.util.Map;
20+
import java.util.function.Function;
21+
22+
import org.eclipse.emf.common.util.URI;
23+
import org.eclipse.emf.common.util.WrappedException;
24+
import org.eclipse.emf.ecore.EFactory;
25+
import org.eclipse.emf.ecore.EPackage;
26+
import org.eclipse.emf.ecore.plugin.ManifestEPackageTracker.ManifestEPackageDescriptor;
27+
import org.eclipse.emf.ecore.resource.URIConverter;
28+
import org.eclipse.osgi.util.ManifestElement;
29+
import org.osgi.framework.Bundle;
30+
import org.osgi.framework.BundleContext;
31+
import org.osgi.framework.BundleEvent;
32+
import org.osgi.framework.BundleException;
33+
import org.osgi.framework.Constants;
34+
import org.osgi.framework.wiring.BundleRevision;
35+
import org.osgi.resource.Capability;
36+
import org.osgi.resource.Resource;
37+
import org.osgi.util.tracker.BundleTracker;
38+
39+
40+
/**
41+
* An {@link BundleTracker OSGi-BundleTracker} that reads and registers the EPackages generated into {@code META-INF/MANIFEST.MF} files as provided OSGi capabilities:
42+
*
43+
* <pre>
44+
* Provide-Capability:
45+
* org.eclipse.emf.ecore.generated_package;
46+
* uri=http://foo.bar/packA;
47+
* class=packA.PackAPackage;
48+
* genModel="model/packA.genmodel"
49+
* </pre>
50+
*/
51+
class ManifestEPackageTracker extends BundleTracker<List<ManifestEPackageDescriptor>>
52+
{
53+
public static final String GENERATED_PACKAGE_NAMESPACE = "org.eclipse.emf.ecore.generated_package";
54+
55+
public static final String ATTRIBUTE_URI = "uri";
56+
57+
public static final String ATTRIBUTE_CLASS = "class";
58+
59+
public static final String ATTRIBUTE_GEN_MODEL = "genModel";
60+
61+
static void registerManifestPackagesIfAbsent(URI manifest, ClassLoader classLoader) throws IOException, BundleException
62+
{
63+
try (InputStream content = URIConverter.INSTANCE.createInputStream(manifest))
64+
{
65+
List<ManifestEPackageDescriptor> descriptors = ManifestEPackageDescriptor.parse(content, classLoader);
66+
for (ManifestEPackageDescriptor descriptor : descriptors)
67+
{
68+
registerIfAbsent(descriptor, genModel -> manifest.trimSegments(2).appendFileExtension(genModel));
69+
}
70+
}
71+
}
72+
73+
ManifestEPackageTracker(BundleContext context)
74+
{
75+
// TODO: check if stopping and (re-)starting creates a new Classloader?! But re-wiring does?!
76+
super(context, Bundle.RESOLVED | Bundle.STARTING | Bundle.ACTIVE | Bundle.STOPPING, null);
77+
}
78+
79+
@Override
80+
public List<ManifestEPackageDescriptor> addingBundle(Bundle bundle, BundleEvent event)
81+
{
82+
Resource resource = bundle.adapt(BundleRevision.class);
83+
List<ManifestEPackageDescriptor> descriptors = new ArrayList<>();
84+
for (Capability capability : resource.getCapabilities(GENERATED_PACKAGE_NAMESPACE))
85+
{
86+
ManifestEPackageDescriptor descriptor = ManifestEPackageDescriptor.parse(bundle, capability);
87+
if (registerIfAbsent(descriptor, genModel -> {
88+
String path = bundle.getSymbolicName() + "/" + genModel;
89+
return URI.createPlatformPluginURI(path, true);
90+
}))
91+
{
92+
descriptors.add(descriptor);
93+
}
94+
}
95+
return !descriptors.isEmpty() ? Collections.unmodifiableList(descriptors) : null;
96+
}
97+
98+
private static boolean registerIfAbsent(ManifestEPackageDescriptor descriptor, Function<String, URI> absoluteGenModelURIFactory)
99+
{
100+
String packageURI = descriptor.packageURI;
101+
if (EPackage.Registry.INSTANCE.putIfAbsent(packageURI, descriptor) == null)
102+
{
103+
// Only add if absent to grant precedence for registrations through a plugin.xml
104+
105+
String genModel = descriptor.genModel;
106+
URI genModelURI = URI.createURI(genModel);
107+
if (genModelURI.isRelative())
108+
{
109+
genModelURI = absoluteGenModelURIFactory.apply(genModel);
110+
}
111+
EcorePlugin.getEPackageNsURIToGenModelLocationMap(false).put(packageURI, genModelURI);
112+
return true;
113+
}
114+
return false;
115+
}
116+
117+
@Override
118+
public void removedBundle(Bundle bundle, BundleEvent event, List<ManifestEPackageDescriptor> packages)
119+
{
120+
for (ManifestEPackageDescriptor descriptor : packages)
121+
{
122+
String uri = descriptor.packageURI;
123+
EPackage.Registry.INSTANCE.remove(uri);
124+
EcorePlugin.getEPackageNsURIToGenModelLocationMap(false).remove(uri);
125+
}
126+
}
127+
128+
private static interface ClassLoaderWrapper
129+
{
130+
Class<?> loadClass(String name) throws ClassNotFoundException;
131+
}
132+
133+
static class ManifestEPackageDescriptor implements EPackage.Descriptor
134+
{
135+
136+
static ManifestEPackageDescriptor parse(Bundle bundle, Capability capability)
137+
{
138+
Map<String, Object> attributes = capability.getAttributes();
139+
return new ManifestEPackageDescriptor(
140+
bundle::loadClass,
141+
(String)attributes.get(ATTRIBUTE_URI),
142+
(String)attributes.get(ATTRIBUTE_CLASS),
143+
(String)attributes.get(ATTRIBUTE_GEN_MODEL));
144+
}
145+
146+
static List<ManifestEPackageDescriptor> parse(InputStream manifestContent, ClassLoader classLoader) throws IOException, BundleException
147+
{
148+
Map<String, String> bundleManifest = ManifestElement.parseBundleManifest(manifestContent, null);
149+
String value = bundleManifest.get(Constants.PROVIDE_CAPABILITY);
150+
if (value == null || value.trim().isEmpty())
151+
{
152+
return Collections.emptyList();
153+
}
154+
ManifestElement[] elements = ManifestElement.parseHeader(Constants.PROVIDE_CAPABILITY, value.trim());
155+
if (elements == null)
156+
{
157+
return Collections.emptyList();
158+
}
159+
List<ManifestEPackageDescriptor> descriptors = new ArrayList<>();
160+
for (ManifestElement element : elements)
161+
{
162+
if (GENERATED_PACKAGE_NAMESPACE.equals(element.getValue()))
163+
{
164+
ManifestEPackageDescriptor descriptor = new ManifestEPackageDescriptor(
165+
classLoader::loadClass,
166+
element.getAttribute(ATTRIBUTE_URI),
167+
element.getAttribute(ATTRIBUTE_CLASS),
168+
element.getAttribute(ATTRIBUTE_GEN_MODEL));
169+
descriptors.add(descriptor);
170+
}
171+
}
172+
return descriptors;
173+
}
174+
175+
final ClassLoaderWrapper classLoader;
176+
177+
final String packageURI;
178+
179+
final String packageClassName;
180+
181+
final String genModel;
182+
183+
ManifestEPackageDescriptor(Bundle bundle, String packageURI, String packageClassName, String genModel)
184+
{ // Don't use current Wiring ClassLoader directly to allow changes until the first EPackage is eventually created
185+
this(bundle::loadClass, packageURI, packageClassName, genModel);
186+
}
187+
188+
ManifestEPackageDescriptor(ClassLoader classLoader, String packageURI, String packageClassName, String genModel)
189+
{
190+
this(classLoader::loadClass, packageURI, packageClassName, genModel);
191+
}
192+
193+
private ManifestEPackageDescriptor(ClassLoaderWrapper classLoader, String packageURI, String packageClassName, String genModel)
194+
{
195+
this.classLoader = classLoader;
196+
this.packageURI = packageURI;
197+
this.packageClassName = packageClassName;
198+
this.genModel = genModel;
199+
}
200+
201+
@Override
202+
public EPackage getEPackage()
203+
{
204+
try
205+
{
206+
Class<?> packageClass = classLoader.loadClass(packageClassName);
207+
return (EPackage)packageClass.getField("eINSTANCE").get(null);
208+
}
209+
catch (ReflectiveOperationException e)
210+
{
211+
throw new WrappedException(e);
212+
}
213+
}
214+
215+
@Override
216+
public EFactory getEFactory()
217+
{
218+
EPackage ePackage = getEPackage();
219+
return ePackage != null ? ePackage.getEFactoryInstance() : null;
220+
}
221+
}
222+
223+
}

0 commit comments

Comments
 (0)