diff --git a/pom.xml b/pom.xml index e5c1ae2d2..e9ecc1151 100644 --- a/pom.xml +++ b/pom.xml @@ -96,15 +96,22 @@ true org.osgi osgi.annotation - 7.0.0 + 8.0.0 provided + + org.osgi + osgi.core + 8.0.0 + provided + + diff --git a/src/main/java/org/bytedeco/javacpp/Loader.java b/src/main/java/org/bytedeco/javacpp/Loader.java index 9015b9e33..cd8ce94ea 100644 --- a/src/main/java/org/bytedeco/javacpp/Loader.java +++ b/src/main/java/org/bytedeco/javacpp/Loader.java @@ -22,6 +22,10 @@ package org.bytedeco.javacpp; +import static org.bytedeco.javacpp.tools.OSGiBundleResourceLoader.getOSGiClassLoaderResources; +import static org.bytedeco.javacpp.tools.OSGiBundleResourceLoader.getOSGiClassResource; +import static org.bytedeco.javacpp.tools.OSGiBundleResourceLoader.isOSGiRuntime; + import java.io.File; import java.io.FileOutputStream; import java.io.IOException; @@ -55,12 +59,14 @@ import java.util.WeakHashMap; import java.util.jar.JarEntry; import java.util.jar.JarFile; + import org.bytedeco.javacpp.annotation.Cast; import org.bytedeco.javacpp.annotation.Name; import org.bytedeco.javacpp.annotation.Platform; import org.bytedeco.javacpp.annotation.Raw; import org.bytedeco.javacpp.tools.Builder; import org.bytedeco.javacpp.tools.Logger; +import org.bytedeco.javacpp.tools.OSGiBundleResourceLoader; /** * The Loader contains functionality to load native libraries, but also has a bit @@ -522,6 +528,21 @@ public static File cacheResource(URL resourceURL, String target) throws IOExcept if (!noSubdir) { cacheSubdir = new File(cacheSubdir, urlFile.getParentFile().getName()); } + } else if (isOSGiRuntime()) { + // TODO: what happens if this is another URL in a OSGi environment? + // I think it is unlikely that is called with URL-schema?! + if (!noSubdir) { + String subdirName = OSGiBundleResourceLoader.getOSGiContainerBundleName(resourceURL); + if (subdirName != null) { + String parentName = urlFile.getParentFile().toString(); + if (parentName != null) { + subdirName = subdirName + File.separator + parentName; + } + cacheSubdir = new File(cacheSubdir, subdirName); + } + size = urlConnection.getContentLengthLong(); + timestamp = urlConnection.getLastModified(); + } } else { if (urlFile.exists()) { size = urlFile.length(); @@ -745,10 +766,10 @@ public static File extractResource(URL resourceURL, File directoryOrFile, public static File extractResource(URL resourceURL, File directoryOrFile, String prefix, String suffix, boolean cacheDirectory) throws IOException { URLConnection urlConnection = resourceURL != null ? resourceURL.openConnection() : null; + long start = System.currentTimeMillis(); if (urlConnection instanceof JarURLConnection) { - JarFile jarFile = ((JarURLConnection)urlConnection).getJarFile(); - JarEntry jarEntry = ((JarURLConnection)urlConnection).getJarEntry(); - String jarFileName = jarFile.getName(); + JarFile jarFile = ((JarURLConnection) urlConnection).getJarFile(); + JarEntry jarEntry = ((JarURLConnection) urlConnection).getJarEntry(); String jarEntryName = jarEntry.getName(); if (!jarEntryName.endsWith("/")) { jarEntryName += "/"; @@ -765,20 +786,47 @@ public static File extractResource(URL resourceURL, File directoryOrFile, File file = new File(directoryOrFile, entryName.substring(jarEntryName.length())); if (entry.isDirectory()) { file.mkdirs(); - } else if (!cacheDirectory || !file.exists() || file.length() != entrySize - || file.lastModified() != entryTimestamp || !file.equals(file.getCanonicalFile())) { + } else if (!cacheDirectory || isCacheFileCurrent(file, entrySize, entryTimestamp)) { // ... extract it from our resources ... file.delete(); String s = resourceURL.toString(); URL u = new URL(s.substring(0, s.indexOf("!/") + 2) + entryName); + // FIXME: check if directories have to be extracted again?! file = extractResource(u, file, prefix, suffix); } file.setLastModified(entryTimestamp); } } + System.out.println("Extract took " + (System.currentTimeMillis() - start) + "ms, for:" + resourceURL); return directoryOrFile; } } + if (isOSGiRuntime()) { + Enumeration directoryEntries = OSGiBundleResourceLoader.getOSGiBundleDirectoryContent(resourceURL); + if (directoryEntries != null && directoryEntries.hasMoreElements()) { // a not empty directory + String directoryName = resourceURL.getPath(); + while (directoryEntries.hasMoreElements()) { + URL entry = directoryEntries.nextElement(); + String entryName = entry.getPath(); + URLConnection entryConnection = entry.openConnection(); + long entrySize = entryConnection.getContentLengthLong(); + long entryTimestamp = entryConnection.getLastModified(); + File file = new File(directoryOrFile, entryName.substring(directoryName.length())); + if (entryName.endsWith("/")) { // is directory + file.mkdirs(); + } else if (!cacheDirectory || isCacheFileCurrent(file, entrySize, entryTimestamp)) { + // ... extract it from our resources ... + file.delete(); + // FIXME: check if directories have to be extracted again?! + file = extractResource(entry, file, prefix, suffix); + } + file.setLastModified(entryTimestamp); + } + System.out.println("Extract took " + (System.currentTimeMillis() - start) + "ms, for:" + resourceURL); + return directoryOrFile; + } + } + InputStream is = urlConnection != null ? urlConnection.getInputStream() : null; OutputStream os = null; if (is == null) { @@ -810,6 +858,7 @@ public static File extractResource(URL resourceURL, File directoryOrFile, } else { file = File.createTempFile(prefix, suffix, directoryOrFile); } + System.out.println("Extract resource (" + resourceURL + ") to " + file); file.delete(); os = new FileOutputStream(file); byte[] buffer = new byte[64 * 1024]; @@ -831,6 +880,11 @@ public static File extractResource(URL resourceURL, File directoryOrFile, return file; } + private static boolean isCacheFileCurrent(File file, long entrySize, long entryTimestamp) throws IOException { + return !file.exists() || file.length() != entrySize || file.lastModified() != entryTimestamp + || !file.equals(file.getCanonicalFile()); + } + /** Returns {@code findResources(cls, name, 1)[0]} or null if none. */ public static URL findResource(Class cls, String name) throws IOException { URL[] url = findResources(cls, name, 1); @@ -862,7 +916,7 @@ public static URL[] findResources(Class cls, String name, int maxLength) throws } // Under JPMS, Class.getResource() and ClassLoader.getResources() do not return the same URLs - URL url = cls.getResource(name); + URL url = !isOSGiRuntime() ? cls.getResource(name) : getOSGiClassResource(cls, name); if (url != null && maxLength == 1) { return new URL[] {url}; } @@ -882,7 +936,7 @@ public static URL[] findResources(Class cls, String name, int maxLength) throws // This is the bootstrap class loader, let's try the system class loader instead classLoader = ClassLoader.getSystemClassLoader(); } - Enumeration urls = classLoader.getResources(path + name); + Enumeration urls = !isOSGiRuntime() ? classLoader.getResources(path + name) : getOSGiClassLoaderResources(classLoader, path + name); ArrayList array = new ArrayList(); if (url != null) { array.add(url); @@ -894,7 +948,7 @@ public static URL[] findResources(Class cls, String name, int maxLength) throws } else { path = ""; } - urls = classLoader.getResources(path + name); + urls = !isOSGiRuntime() ? classLoader.getResources(path + name) : getOSGiClassLoaderResources(classLoader, path + name); } while (urls.hasMoreElements() && (maxLength < 0 || array.size() < maxLength)) { url = urls.nextElement(); diff --git a/src/main/java/org/bytedeco/javacpp/tools/OSGiBundleResourceLoader.java b/src/main/java/org/bytedeco/javacpp/tools/OSGiBundleResourceLoader.java new file mode 100644 index 000000000..a451fce37 --- /dev/null +++ b/src/main/java/org/bytedeco/javacpp/tools/OSGiBundleResourceLoader.java @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2021-2021 Hannes Wellmann + * + * Licensed either under the Apache License, Version 2.0, or (at your option) + * under the terms of the GNU General Public License as published by + * the Free Software Foundation (subject to the "Classpath" exception), + * either version 2, or any later version (collectively, the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.gnu.org/licenses/ + * http://www.gnu.org/software/classpath/license.html + * + * or as provided in the LICENSE.txt file that accompanied this code. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.bytedeco.javacpp.tools; + +import java.io.IOException; +import java.lang.ref.WeakReference; +import java.net.URL; +import java.util.Collections; +import java.util.Enumeration; +import java.util.List; +import java.util.Optional; +import java.util.WeakHashMap; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +import org.osgi.framework.Bundle; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.Version; + +public class OSGiBundleResourceLoader { + + private OSGiBundleResourceLoader() { // static use only + } + + private static final boolean IS_OSGI_RUNTIME; + + static { + boolean isOSGI; + try { + Bundle.class.getName(); + isOSGI = true; + } catch (NoClassDefFoundError e) { + isOSGI = false; + } + IS_OSGI_RUNTIME = isOSGI; + } + + public static boolean isOSGiRuntime() { + return IS_OSGI_RUNTIME; + } + + public static String getOSGiContainerBundleName(URL resourceURL) { + requireOSGi(); + return OSGiEnvironmentLoader.getContainerBundleName(resourceURL); + } + + public static Enumeration getOSGiBundleDirectoryContent(URL resourceURL) { + requireOSGi(); + return OSGiEnvironmentLoader.getBundleDirectoryContent(resourceURL); + } + + public static URL getOSGiClassResource(Class c, String name) { + requireOSGi(); + return OSGiEnvironmentLoader.getClassResource(c, name); + } + + public static Enumeration getOSGiClassLoaderResources(ClassLoader cl, String name) throws IOException { + requireOSGi(); + return OSGiEnvironmentLoader.getClassLoaderResources(cl, name); + } + + private static void requireOSGi() { + if (!IS_OSGI_RUNTIME) { + throw new IllegalStateException(OSGiBundleResourceLoader.class.getSimpleName() + " must only be used within a OSGi runtime"); + } + } + + private static class OSGiEnvironmentLoader { + // Code using OSGi APIs has to be encapsulated into own class + // to prevent NoClassDefFoundErrors in OSGi environments + + private static final ReadWriteLock LOCK = new ReentrantReadWriteLock(); + private static final WeakHashMap> URL_TO_BUNDLE = new WeakHashMap<>(); + + private static void associateURLtoBundle(URL resource, Bundle bundle) { + URL_TO_BUNDLE.put(resource, new WeakReference<>(bundle)); + } + + private static Bundle getContainerBundle(URL url) { + WeakReference bundle; + LOCK.readLock().lock(); + try { + bundle = URL_TO_BUNDLE.get(url); + } finally { + LOCK.readLock().unlock(); + } + return bundle != null ? bundle.get() : null; + } + + private static URL getClassResource(Class c, String name) { + URL resource = c.getResource(name); + if (resource != null) { + Bundle bundle = FrameworkUtil.getBundle(c); + LOCK.writeLock().lock(); + try { + associateURLtoBundle(resource, bundle); + } finally { + LOCK.writeLock().unlock(); + } + } + return resource; + } + + private static Enumeration getClassLoaderResources(ClassLoader cl, String name) throws IOException { + Enumeration resources = cl.getResources(name); + if (resources != null && resources.hasMoreElements()) { + Optional bundleOpt = FrameworkUtil.getBundle(cl); + if (bundleOpt.isPresent()) { + Bundle bundle = bundleOpt.get(); + List resourcesList = Collections.list(resources); + LOCK.writeLock().lock(); + try { + for (URL url : resourcesList) { + associateURLtoBundle(url, bundle); + } + } finally { + LOCK.writeLock().unlock(); + } + return Collections.enumeration(resourcesList); + } + } + return resources; + } + + private static String getContainerBundleName(URL resourceURL) { + Bundle bundle = getContainerBundle(resourceURL); + if (bundle != null) { + Version v = bundle.getVersion(); + String version = v.getMajor() + "." + v.getMinor() + "." + v.getMicro(); // skip qualifier + return bundle.getSymbolicName() + "_" + version; + } + return null; + } + + private static Enumeration getBundleDirectoryContent(URL resourceURL) { + Bundle bundle = getContainerBundle(resourceURL); + if (bundle != null) { + return bundle.findEntries(resourceURL.getPath(), null, true); + } + return null; + } + } +}