Skip to content
This repository was archived by the owner on May 28, 2018. It is now read-only.

Commit f879e98

Browse files
Michal GajdosGerrit Code Review
authored andcommitted
Merge "Fixed and enabled recursive OSGi bundle scanning. Parametrized via 'jersey.config.server.provider.scanning.recursive' (defaults to true)."
2 parents deef773 + 32c5efd commit f879e98

File tree

9 files changed

+583
-41
lines changed

9 files changed

+583
-41
lines changed

core-common/src/main/java/org/glassfish/jersey/internal/OsgiRegistry.java

Lines changed: 89 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@
9494
*/
9595
public final class OsgiRegistry implements SynchronousBundleListener {
9696

97+
private static final String WEB_INF_CLASSES = "WEB-INF/classes/";
9798
private static final String CoreBundleSymbolicNAME = "org.glassfish.jersey.core.jersey-common";
9899
private static final Logger LOGGER = Logger.getLogger(OsgiRegistry.class.getName());
99100

@@ -299,31 +300,97 @@ public void bundleChanged(final BundleEvent event) {
299300
}
300301
}
301302

303+
/**
304+
* Translates bundle entry path as returned from {@link org.osgi.framework.Bundle#findEntries(String, String, boolean)} to
305+
* fully qualified class name that resides in given package path (directly or indirectly in its subpackages).
306+
*
307+
* @param packagePath The package path where the class is located (even recursively)
308+
* @param bundleEntryPath The bundle path to translate.
309+
* @return Fully qualified class name.
310+
*/
311+
public static String bundleEntryPathToClassName(String packagePath, String bundleEntryPath) {
312+
// normalize packagePath
313+
packagePath = normalizedPackagePath(packagePath);
314+
315+
// remove WEB-INF/classes from bundle entry path
316+
if (bundleEntryPath.contains(WEB_INF_CLASSES)) {
317+
bundleEntryPath = bundleEntryPath.substring(bundleEntryPath.indexOf(WEB_INF_CLASSES) + WEB_INF_CLASSES.length());
318+
}
319+
320+
final int packageIndex = bundleEntryPath.indexOf(packagePath);
321+
322+
String normalizedClassNamePath = packageIndex > -1
323+
// the package path was found in the bundle path
324+
? bundleEntryPath.substring(packageIndex)
325+
// the package path is not included in the bundle entry path
326+
// fall back to the original implementation of the translation which does not consider recursion
327+
: packagePath + bundleEntryPath.substring(bundleEntryPath.lastIndexOf('/') + 1);
328+
329+
return (normalizedClassNamePath.startsWith("/") ? normalizedClassNamePath.substring(1) : normalizedClassNamePath)
330+
.replace('/', '.').replace(".class", "");
331+
}
332+
333+
/**
334+
* Returns whether the given entry path is located directly in the provided package path. That is,
335+
* if the entry is located in a sub-package, then {@code false} is returned.
336+
*
337+
* @param packagePath Package path which the entry is compared to
338+
* @param entryPath Entry path
339+
* @return Whether the given entry path is located directly in the provided package path.
340+
*/
341+
public static boolean isPackageLevelEntry(String packagePath, final String entryPath) {
342+
// normalize packagePath
343+
packagePath = normalizedPackagePath(packagePath);
344+
345+
// if the package path is contained in the jar entry name, subtract it
346+
String entryWithoutPackagePath = entryPath.contains(packagePath)
347+
? entryPath.substring(entryPath.indexOf(packagePath) + packagePath.length())
348+
: entryPath;
349+
350+
return !(entryWithoutPackagePath.startsWith("/") ? entryWithoutPackagePath.substring(1)
351+
: entryWithoutPackagePath)
352+
.contains("/");
353+
}
354+
355+
/**
356+
* Normalized package returns path that does not start with '/' character and ends with '/' character.
357+
* If the argument is '/' then returned value is empty string "".
358+
*
359+
* @param packagePath package path to normalize.
360+
* @return Normalized package path.
361+
*/
362+
public static String normalizedPackagePath(String packagePath) {
363+
packagePath = packagePath.startsWith("/") ? packagePath.substring(1) : packagePath;
364+
packagePath = packagePath.endsWith("/") ? packagePath : packagePath + "/";
365+
packagePath = "/".equals(packagePath) ? "" : packagePath;
366+
return packagePath;
367+
}
368+
302369
/**
303370
* Get URLs of resources from a given package.
304371
*
305372
* @param packagePath package.
306373
* @param classLoader resource class loader.
374+
* @param recursive whether the given package path should be scanned recursively by OSGi
307375
* @return URLs of the located resources.
308376
*/
309377
@SuppressWarnings("unchecked")
310-
public Enumeration<URL> getPackageResources(final String packagePath, final ClassLoader classLoader) {
378+
public Enumeration<URL> getPackageResources(final String packagePath,
379+
final ClassLoader classLoader,
380+
final boolean recursive) {
311381
final List<URL> result = new LinkedList<URL>();
312382

313383
for (final Bundle bundle : bundleContext.getBundles()) {
314384
// Look for resources at the given <packagePath> and at WEB-INF/classes/<packagePath> in case a WAR is being examined.
315-
for (final String bundlePackagePath : new String[] {packagePath, "WEB-INF/classes/" + packagePath}) {
316-
final Enumeration<URL> enumeration = findEntries(bundle, bundlePackagePath, "*.class", false);
385+
for (final String bundlePackagePath : new String[] {packagePath, WEB_INF_CLASSES + packagePath}) {
386+
final Enumeration<URL> enumeration = findEntries(bundle, bundlePackagePath, "*.class", recursive);
317387

318388
if (enumeration != null) {
319389
while (enumeration.hasMoreElements()) {
320390
final URL url = enumeration.nextElement();
321391
final String path = url.getPath();
322392

323-
final String className = (packagePath + path.substring(path.lastIndexOf('/')))
324-
.replace('/', '.').replace(".class", "");
325-
326-
classToBundleMapping.put(className, bundle);
393+
classToBundleMapping.put(bundleEntryPathToClassName(packagePath, path), bundle);
327394
result.add(url);
328395
}
329396
}
@@ -356,8 +423,21 @@ public Enumeration<URL> getPackageResources(final String packagePath, final Clas
356423
JarEntry jarEntry;
357424
while ((jarEntry = jarInputStream.getNextJarEntry()) != null) {
358425
final String jarEntryName = jarEntry.getName();
359-
360-
if (jarEntryName.endsWith(".class") && jarEntryName.contains(packagePath)) {
426+
final String jarEntryNameLeadingSlash = jarEntryName.startsWith("/")
427+
? jarEntryName : "/" + jarEntryName;
428+
429+
if (jarEntryName.endsWith(".class")
430+
// Added leading and trailing slashes '/' to package path (e.g. '/com/') helps us to not
431+
// accidentally match sub-strings of the package path (e.g., if package path 'com' was used
432+
// for scanning, package 'whatever.foo.telecom' would be matched because of word 'tele[com]').
433+
// Note that we cannot avoid all corner cases with accidental matches since jar
434+
// entry name might be almost anything (e.g., if package path 'telecom' was used, package
435+
// 'whatever.foo.telecom' will be matched and there is no way to avoid it unless user
436+
// explicitly instructs us to do so somehow (not implemented)
437+
&& jarEntryNameLeadingSlash.contains("/" + normalizedPackagePath(packagePath))) {
438+
if (!recursive && !isPackageLevelEntry(packagePath, jarEntryName)) {
439+
continue;
440+
}
361441
classToBundleMapping.put(jarEntryName.replace(".class", "").replace('/', '.'), bundle);
362442
result.add(bundle.getResource(jarEntryName));
363443
}
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
/*
2+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3+
*
4+
* Copyright (c) 2015 Oracle and/or its affiliates. All rights reserved.
5+
*
6+
* The contents of this file are subject to the terms of either the GNU
7+
* General Public License Version 2 only ("GPL") or the Common Development
8+
* and Distribution License("CDDL") (collectively, the "License"). You
9+
* may not use this file except in compliance with the License. You can
10+
* obtain a copy of the License at
11+
* http://glassfish.java.net/public/CDDL+GPL_1_1.html
12+
* or packager/legal/LICENSE.txt. See the License for the specific
13+
* language governing permissions and limitations under the License.
14+
*
15+
* When distributing the software, include this License Header Notice in each
16+
* file and include the License file at packager/legal/LICENSE.txt.
17+
*
18+
* GPL Classpath Exception:
19+
* Oracle designates this particular file as subject to the "Classpath"
20+
* exception as provided by Oracle in the GPL Version 2 section of the License
21+
* file that accompanied this code.
22+
*
23+
* Modifications:
24+
* If applicable, add the following below the License Header, with the fields
25+
* enclosed by brackets [] replaced by your own identifying information:
26+
* "Portions Copyright [year] [name of copyright owner]"
27+
*
28+
* Contributor(s):
29+
* If you wish your version of this file to be governed by only the CDDL or
30+
* only the GPL Version 2, indicate your decision by adding "[Contributor]
31+
* elects to include this software in this distribution under the [CDDL or GPL
32+
* Version 2] license." If you don't indicate a single choice of license, a
33+
* recipient has the option to distribute your version of this file under
34+
* either the CDDL, the GPL Version 2 or to extend the choice of license to
35+
* its licensees as provided above. However, if you add GPL Version 2 code
36+
* and therefore, elected the GPL Version 2 license, then the option applies
37+
* only if the new code is made subject to such option by the copyright
38+
* holder.
39+
*/
40+
package org.glassfish.jersey.internal.util;
41+
42+
import org.glassfish.jersey.internal.OsgiRegistry;
43+
44+
import org.junit.Assert;
45+
import org.junit.Test;
46+
47+
/**
48+
* Utility class {@ling OsgiRegistry} tests.
49+
*
50+
* @author Stepan Vavra (stepan.vavra at oracle.com)
51+
*/
52+
public class OsgiRegistryTest {
53+
54+
@Test
55+
public void testWebInfClassesBundleEntryPathTranslation() {
56+
String className = OsgiRegistry
57+
.bundleEntryPathToClassName("org/glassfish/jersey", "/WEB-INF/classes/org/glassfish/jersey/Test.class");
58+
Assert.assertEquals("org.glassfish.jersey.Test", className);
59+
}
60+
61+
@Test
62+
public void testWebInfClassesBundleEntryPathTranslationPackageTrailingSlash() {
63+
String className = OsgiRegistry
64+
.bundleEntryPathToClassName("org/glassfish/jersey/", "/WEB-INF/classes/org/glassfish/jersey/Test.class");
65+
Assert.assertEquals("org.glassfish.jersey.Test", className);
66+
}
67+
68+
@Test
69+
public void testWebInfClassesBundleEntryPathTranslationPackageLeadingSlash() {
70+
String className = OsgiRegistry
71+
.bundleEntryPathToClassName("/org/glassfish/jersey", "/WEB-INF/classes/org/glassfish/jersey/Test.class");
72+
Assert.assertEquals("org.glassfish.jersey.Test", className);
73+
}
74+
75+
@Test
76+
public void testWebInfClassesBundleEntryPathTranslationBundleNoLeadingSlash() {
77+
String className = OsgiRegistry
78+
.bundleEntryPathToClassName("/org/glassfish/jersey", "WEB-INF/classes/org/glassfish/jersey/Test.class");
79+
Assert.assertEquals("org.glassfish.jersey.Test", className);
80+
}
81+
82+
@Test
83+
public void testOsgiInfClassesBundleEntryPathTranslation() {
84+
String className = OsgiRegistry
85+
.bundleEntryPathToClassName("/org/glassfish/jersey", "OSGI-INF/directory/org/glassfish/jersey/Test.class");
86+
Assert.assertEquals("org.glassfish.jersey.Test", className);
87+
}
88+
89+
@Test
90+
public void testBundleEntryPathTranslation() {
91+
String className = OsgiRegistry.bundleEntryPathToClassName("/org/glassfish/jersey", "/org/glassfish/jersey/Test.class");
92+
Assert.assertEquals("org.glassfish.jersey.Test", className);
93+
}
94+
95+
@Test
96+
public void testBundleEntryPathTranslationBundleNoLeadingSlash() {
97+
String className = OsgiRegistry.bundleEntryPathToClassName("/org/glassfish/jersey", "org/glassfish/jersey/Test.class");
98+
Assert.assertEquals("org.glassfish.jersey.Test", className);
99+
}
100+
101+
@Test
102+
public void testBundleEntryPathTranslationNotMatching() {
103+
String className = OsgiRegistry.bundleEntryPathToClassName("/com/oracle", "org/glassfish/jersey/Test.class");
104+
Assert.assertEquals("com.oracle.Test", className);
105+
}
106+
107+
@Test
108+
public void testWebInfClassesBundleEntryPathTranslationNotMatching() {
109+
String className = OsgiRegistry
110+
.bundleEntryPathToClassName("/com/oracle/", "/WEB-INF/classes/org/glassfish/jersey/Test.class");
111+
Assert.assertEquals("com.oracle.Test", className);
112+
}
113+
114+
@Test
115+
public void testWebInfClassesBundleEntryPathTranslationNotMatching2() {
116+
String className = OsgiRegistry.bundleEntryPathToClassName("com/oracle", "/org/glassfish/jersey/Test.class");
117+
Assert.assertEquals("com.oracle.Test", className);
118+
}
119+
120+
@Test
121+
public void testRootBundleEntryPathTranslation() {
122+
String className = OsgiRegistry.bundleEntryPathToClassName("/", "/org/glassfish/jersey/Test.class");
123+
Assert.assertEquals("org.glassfish.jersey.Test", className);
124+
}
125+
126+
@Test
127+
public void testRootBundleEntryPathTranslationNoLeadingSlash() {
128+
String className = OsgiRegistry.bundleEntryPathToClassName("/", "org/glassfish/jersey/Test.class");
129+
Assert.assertEquals("org.glassfish.jersey.Test", className);
130+
}
131+
132+
@Test
133+
public void testRootWebInfClassesBundleEntryPathTranslationNoLeadingSlash() {
134+
String className = OsgiRegistry.bundleEntryPathToClassName("/", "/WEB-INF/classes/org/glassfish/jersey/Test.class");
135+
Assert.assertEquals("org.glassfish.jersey.Test", className);
136+
}
137+
138+
@Test
139+
public void testRootWebInfClassesBundleEntryPathEsTranslation() {
140+
String className = OsgiRegistry.bundleEntryPathToClassName("es", "/WEB-INF/classes/es/a/Test.class");
141+
Assert.assertEquals("es.a.Test", className);
142+
}
143+
144+
@Test
145+
public void testIsTopLevelEntry() {
146+
Assert.assertTrue(OsgiRegistry.isPackageLevelEntry("a", "/a/Foo.class"));
147+
}
148+
149+
@Test
150+
public void testIsTopLevelEntrySequenceRepeats() {
151+
Assert.assertFalse(OsgiRegistry.isPackageLevelEntry("o", "/a/Foo.class"));
152+
}
153+
154+
@Test
155+
public void testIsTopLevelEntryNoSlash() {
156+
Assert.assertTrue(OsgiRegistry.isPackageLevelEntry("a", "a/Foo.class"));
157+
}
158+
159+
@Test
160+
public void testIsTopLevelEntrySequenceRepeatsNoSlash() {
161+
Assert.assertFalse(OsgiRegistry.isPackageLevelEntry("o", "a/Foo.class"));
162+
}
163+
164+
@Test
165+
public void testIsTopLevelEntrySlash() {
166+
Assert.assertTrue(OsgiRegistry.isPackageLevelEntry("a/", "/a/Foo.class"));
167+
}
168+
169+
@Test
170+
public void testIsTopLevelEntrySequenceRepeatsSlash() {
171+
Assert.assertFalse(OsgiRegistry.isPackageLevelEntry("o/", "/a/Foo.class"));
172+
}
173+
174+
@Test
175+
public void testIsTopLevelEntryRoot() {
176+
Assert.assertTrue(OsgiRegistry.isPackageLevelEntry("/", "/Foo.class"));
177+
}
178+
179+
@Test
180+
public void testIsTopLevelEntryRootFalse() {
181+
Assert.assertFalse(OsgiRegistry.isPackageLevelEntry("/", "/a/Foo.class"));
182+
}
183+
184+
}

core-server/src/main/java/org/glassfish/jersey/server/internal/scanning/PackageNamesScanner.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ public PackageNamesScanner(final ClassLoader classLoader, final String[] package
146146

147147
@Override
148148
public Enumeration<URL> getResources(String packagePath, ClassLoader classLoader) throws IOException {
149-
return osgiRegistry.getPackageResources(packagePath, classLoader);
149+
return osgiRegistry.getPackageResources(packagePath, classLoader, recursive);
150150
}
151151
});
152152
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3+
*
4+
* Copyright (c) 2015 Oracle and/or its affiliates. All rights reserved.
5+
*
6+
* The contents of this file are subject to the terms of either the GNU
7+
* General Public License Version 2 only ("GPL") or the Common Development
8+
* and Distribution License("CDDL") (collectively, the "License"). You
9+
* may not use this file except in compliance with the License. You can
10+
* obtain a copy of the License at
11+
* http://glassfish.java.net/public/CDDL+GPL_1_1.html
12+
* or packager/legal/LICENSE.txt. See the License for the specific
13+
* language governing permissions and limitations under the License.
14+
*
15+
* When distributing the software, include this License Header Notice in each
16+
* file and include the License file at packager/legal/LICENSE.txt.
17+
*
18+
* GPL Classpath Exception:
19+
* Oracle designates this particular file as subject to the "Classpath"
20+
* exception as provided by Oracle in the GPL Version 2 section of the License
21+
* file that accompanied this code.
22+
*
23+
* Modifications:
24+
* If applicable, add the following below the License Header, with the fields
25+
* enclosed by brackets [] replaced by your own identifying information:
26+
* "Portions Copyright [year] [name of copyright owner]"
27+
*
28+
* Contributor(s):
29+
* If you wish your version of this file to be governed by only the CDDL or
30+
* only the GPL Version 2, indicate your decision by adding "[Contributor]
31+
* elects to include this software in this distribution under the [CDDL or GPL
32+
* Version 2] license." If you don't indicate a single choice of license, a
33+
* recipient has the option to distribute your version of this file under
34+
* either the CDDL, the GPL Version 2 or to extend the choice of license to
35+
* its licensees as provided above. However, if you add GPL Version 2 code
36+
* and therefore, elected the GPL Version 2 license, then the option applies
37+
* only if the new code is made subject to such option by the copyright
38+
* holder.
39+
*/
40+
package org.glassfish.jersey.examples.osgi.helloworld.additional.resource.subpackage;
41+
42+
import javax.ws.rs.GET;
43+
import javax.ws.rs.Path;
44+
import javax.ws.rs.Produces;
45+
46+
/**
47+
* This resource is located in a sub-package and will be detected by OSGI framework only if recursive scanning is turned on.
48+
*
49+
* @author Stepan Vavra (stepan.vavra at oracle.com)
50+
*/
51+
@Path("/subadditional")
52+
public class AdditionalSubPackagedResource {
53+
54+
@GET
55+
@Produces("text/plain")
56+
public String getSubPackagedAdditionalMessage() {
57+
return "Sub-packaged Additional Bundle!";
58+
}
59+
60+
}

0 commit comments

Comments
 (0)