You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@karaf.apache.org by jb...@apache.org on 2019/11/01 15:29:28 UTC

[karaf] branch master updated: [KARAF-6460] Create ClassLoader that recognize bundle-classpath in case of no OSGi usage e.g. shell/shell.bat

This is an automated email from the ASF dual-hosted git repository.

jbonofre pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/karaf.git


The following commit(s) were added to refs/heads/master by this push:
     new 9d652bc  [KARAF-6460] Create ClassLoader that recognize bundle-classpath in case of no OSGi usage e.g. shell/shell.bat
     new bb74195  Merge pull request #973 from avasilev/KARAF-6460
9d652bc is described below

commit 9d652bcfa0a05a7dec018b7cc842e8f8142363fc
Author: Aleksandar Vasilev <a....@seeburger.com>
AuthorDate: Thu Oct 24 09:37:13 2019 +0300

    [KARAF-6460] Create ClassLoader that recognize bundle-classpath in case of no OSGi usage e.g. shell/shell.bat
---
 .../impl/console/loader/JarInJarConstants.java     | 30 +++++++++++++
 .../impl/console/loader/JarInJarStreamHandler.java | 51 ++++++++++++++++++++++
 .../impl/console/loader/JarInJarURLConnection.java | 50 +++++++++++++++++++++
 .../loader/JarInJarURLStreamHandlerFactory.java    | 41 +++++++++++++++++
 .../karaf/shell/impl/console/standalone/Main.java  | 35 ++++++++++++++-
 5 files changed, 206 insertions(+), 1 deletion(-)

diff --git a/shell/core/src/main/java/org/apache/karaf/shell/impl/console/loader/JarInJarConstants.java b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/loader/JarInJarConstants.java
new file mode 100644
index 0000000..558c018
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/loader/JarInJarConstants.java
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (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
+ *
+ * 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.apache.karaf.shell.impl.console.loader;
+
+public interface JarInJarConstants {
+    String REDIRECTED_CLASS_PATH_MANIFEST_NAME  = "Bundle-ClassPath";
+    String JAR_INTERNAL_URL_PROTOCOL_WITH_COLON = "jar:jinj:";
+    String JAR_INTERNAL_SEPARATOR               = "!/";
+    String INTERNAL_URL_PROTOCOL_WITH_COLON     = "jinj:";
+    String INTERNAL_URL_PROTOCOL                = "jinj";
+    String PATH_SEPARATOR                       = "/";
+    String CURRENT_DIR                          = "./";
+    String JAR_EXTENSION                        = ".jar";
+}
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/impl/console/loader/JarInJarStreamHandler.java b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/loader/JarInJarStreamHandler.java
new file mode 100644
index 0000000..47f5f69
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/loader/JarInJarStreamHandler.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (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
+ *
+ * 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.apache.karaf.shell.impl.console.loader;
+
+import java.io.IOException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLStreamHandler;
+
+public class JarInJarStreamHandler extends URLStreamHandler {
+    private ClassLoader classLoader;
+
+    public JarInJarStreamHandler(ClassLoader classLoader) {
+        this.classLoader = classLoader;
+    }
+
+    @Override
+    protected URLConnection openConnection( URL u) throws IOException {
+        return new JarInJarURLConnection(u, classLoader);
+    }
+
+    @Override
+    protected void parseURL(URL url, String spec, int start, int limit) {
+        String file;
+        if (spec.startsWith(JarInJarConstants.INTERNAL_URL_PROTOCOL_WITH_COLON))
+            file = spec.substring(5);
+        else if (url.getFile().equals(JarInJarConstants.CURRENT_DIR))
+            file = spec;
+        else if (url.getFile().endsWith(JarInJarConstants.PATH_SEPARATOR))
+            file = url.getFile() + spec;
+        else
+            file = spec;
+        setURL(url, JarInJarConstants.INTERNAL_URL_PROTOCOL, "", -1, null, null, file, null, null);	 //$NON-NLS-1$
+    }
+}
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/impl/console/loader/JarInJarURLConnection.java b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/loader/JarInJarURLConnection.java
new file mode 100644
index 0000000..d1d56c1
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/loader/JarInJarURLConnection.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (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
+ *
+ * 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.apache.karaf.shell.impl.console.loader;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLDecoder;
+import java.nio.charset.StandardCharsets;
+
+public class JarInJarURLConnection extends URLConnection {
+    private ClassLoader classLoader;
+
+    public JarInJarURLConnection( URL url, ClassLoader classLoader) {
+        super(url);
+        this.classLoader= classLoader;
+    }
+
+    @Override
+    public void connect() throws IOException {
+    }
+
+    @Override
+    public InputStream getInputStream() throws IOException {
+        String file= URLDecoder.decode(url.getFile(), StandardCharsets.UTF_8.name() );
+        InputStream result= classLoader.getResourceAsStream(file);
+        if (result == null) {
+            throw new MalformedURLException("Could not open InputStream for URL '" + url + "'");
+        }
+        return result;
+    }
+}
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/impl/console/loader/JarInJarURLStreamHandlerFactory.java b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/loader/JarInJarURLStreamHandlerFactory.java
new file mode 100644
index 0000000..8335f5c
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/loader/JarInJarURLStreamHandlerFactory.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (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
+ *
+ * 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.apache.karaf.shell.impl.console.loader;
+
+import java.net.URLStreamHandler;
+import java.net.URLStreamHandlerFactory;
+
+public class JarInJarURLStreamHandlerFactory implements URLStreamHandlerFactory {
+
+    private ClassLoader classLoader;
+    private URLStreamHandlerFactory chainFac;
+
+    public JarInJarURLStreamHandlerFactory(ClassLoader cl) {
+        this.classLoader = cl;
+    }
+
+    @Override
+    public URLStreamHandler createURLStreamHandler( String protocol) {
+        if (JarInJarConstants.INTERNAL_URL_PROTOCOL.equals(protocol))
+            return new JarInJarStreamHandler( classLoader);
+        if (chainFac != null)
+            return chainFac.createURLStreamHandler(protocol);
+        return null;
+    }
+}
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/impl/console/standalone/Main.java b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/standalone/Main.java
index 322b986..631e5a8 100644
--- a/shell/core/src/main/java/org/apache/karaf/shell/impl/console/standalone/Main.java
+++ b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/standalone/Main.java
@@ -32,7 +32,8 @@ import java.net.URLClassLoader;
 import java.util.ArrayList;
 import java.util.Enumeration;
 import java.util.List;
-
+import java.util.jar.JarFile;
+import java.util.jar.Manifest;
 import org.apache.felix.gogo.runtime.threadio.ThreadIOImpl;
 import org.apache.felix.service.threadio.ThreadIO;
 import org.apache.karaf.shell.api.action.lifecycle.Manager;
@@ -42,6 +43,8 @@ import org.apache.karaf.shell.api.console.Terminal;
 import org.apache.karaf.shell.impl.action.command.ManagerImpl;
 import org.apache.karaf.shell.impl.console.JLineTerminal;
 import org.apache.karaf.shell.impl.console.SessionFactoryImpl;
+import org.apache.karaf.shell.impl.console.loader.JarInJarConstants;
+import org.apache.karaf.shell.impl.console.loader.JarInJarURLStreamHandlerFactory;
 import org.apache.karaf.shell.support.NameScoping;
 import org.apache.karaf.shell.support.ShellUtil;
 import org.jline.terminal.TerminalBuilder;
@@ -127,6 +130,16 @@ public class Main {
         ClassLoader cl = Main.class.getClassLoader();
         if (classpath != null) {
             List<URL> urls = getFiles(new File(classpath));
+            // Load jars in class path to be able to load jars inside them
+            ClassLoader tmpClassLoader = new URLClassLoader(urls.toArray(new URL[urls.size()]), cl);
+            URL.setURLStreamHandlerFactory(new JarInJarURLStreamHandlerFactory( tmpClassLoader));
+            List<URL> jarsInJars = getJarsInJars(urls);
+            // Create ClassLoader with jars in jars and parent main ClassLoader
+            cl = new URLClassLoader(jarsInJars.toArray(new URL[jarsInJars.size()]), cl);
+            // Load original Jars with jarsInJars ClassLoader as parent.
+            // This is needed cause if you load Class from main jar which depends on class in inner jar.
+            // The loaded class has its class loader and resolve dependant classes in its or parent classloaders
+            // which exclude jarInJar classloader.
             cl = new URLClassLoader(urls.toArray(new URL[urls.size()]), cl);
         }
 
@@ -135,6 +148,26 @@ public class Main {
         run(sessionFactory, sb.toString(), in, out, err, cl);
     }
 
+    private List<URL> getJarsInJars(List<URL> urls) throws IOException {
+        List<URL> result = new ArrayList<>();
+        for (URL url : urls) {
+            try (JarFile jarFile = new JarFile(url.getFile())) {
+                Manifest manifest = jarFile.getManifest();
+                String embeddedArtifacts = manifest.getMainAttributes().getValue(JarInJarConstants.REDIRECTED_CLASS_PATH_MANIFEST_NAME);
+                if (embeddedArtifacts != null) {
+                    String[] artifacts = embeddedArtifacts.split( "," );
+                    for ( String artifact : artifacts ) {
+                        if (!artifact.endsWith(JarInJarConstants.JAR_EXTENSION )) {
+                            continue;
+                        }
+                        result.add(new URL(JarInJarConstants.JAR_INTERNAL_URL_PROTOCOL_WITH_COLON + artifact + JarInJarConstants.JAR_INTERNAL_SEPARATOR));
+                    }
+                }
+            }
+        }
+        return result;
+    }
+
     private void run(final SessionFactory sessionFactory, String command, final InputStream in, final PrintStream out, final PrintStream err, ClassLoader cl) throws Exception {
 
         try (org.jline.terminal.Terminal jlineTerminal = TerminalBuilder.terminal()) {