FELIX-1771: Feature deployer is broken on windows

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@826649 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/karaf/deployer/blueprint/src/main/resources/OSGI-INF/blueprint/blueprint-deployer.xml b/karaf/deployer/blueprint/src/main/resources/OSGI-INF/blueprint/blueprint-deployer.xml
index b8d3aac..4726e44 100644
--- a/karaf/deployer/blueprint/src/main/resources/OSGI-INF/blueprint/blueprint-deployer.xml
+++ b/karaf/deployer/blueprint/src/main/resources/OSGI-INF/blueprint/blueprint-deployer.xml
@@ -19,11 +19,11 @@
 -->
 <blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0">
 
-    <service auto-export="interfaces">
-        <bean class="org.apache.felix.karaf.deployer.blueprint.BlueprintDeploymentListener"/>
+    <service auto-export="interfaces" depends-on="blueprintUrlHandler">
+        <bean class="org.apache.felix.karaf.deployer.blueprint.BlueprintDeploymentListener" />
     </service>
 
-    <service interface="org.osgi.service.url.URLStreamHandlerService">
+    <service id="blueprintUrlHandler" interface="org.osgi.service.url.URLStreamHandlerService">
     	<service-properties>
             <entry key="url.handler.protocol" value="blueprint"/>
         </service-properties>
diff --git a/karaf/deployer/features/src/main/java/org/apache/felix/karaf/deployer/features/FeatureDeploymentListener.java b/karaf/deployer/features/src/main/java/org/apache/felix/karaf/deployer/features/FeatureDeploymentListener.java
index dfefe60..51f6d0a 100644
--- a/karaf/deployer/features/src/main/java/org/apache/felix/karaf/deployer/features/FeatureDeploymentListener.java
+++ b/karaf/deployer/features/src/main/java/org/apache/felix/karaf/deployer/features/FeatureDeploymentListener.java
@@ -16,27 +16,13 @@
  */
 package org.apache.felix.karaf.deployer.features;
 
-import java.io.BufferedInputStream;
-import java.io.BufferedOutputStream;
 import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
 import java.net.URL;
 import java.util.Arrays;
-import java.util.Collections;
 import java.util.EnumSet;
 import java.util.Enumeration;
 import java.util.HashSet;
 import java.util.Set;
-import java.util.jar.JarFile;
-import java.util.jar.JarOutputStream;
-import java.util.jar.Manifest;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-import java.util.zip.ZipEntry;
 
 import javax.xml.parsers.DocumentBuilder;
 import javax.xml.parsers.DocumentBuilderFactory;
@@ -45,23 +31,22 @@
 
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
+import org.apache.felix.fileinstall.ArtifactUrlTransformer;
 import org.apache.felix.karaf.features.Feature;
 import org.apache.felix.karaf.features.FeaturesService;
 import org.apache.felix.karaf.features.Repository;
-import org.apache.felix.fileinstall.ArtifactTransformer;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.BundleEvent;
-import org.osgi.framework.Constants;
 import org.osgi.framework.SynchronousBundleListener;
 import org.xml.sax.ErrorHandler;
-import org.xml.sax.SAXParseException;
 import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
 
 /**
  * A deployment listener able to hot deploy a feature descriptor
  */
-public class FeatureDeploymentListener implements ArtifactTransformer, SynchronousBundleListener {
+public class FeatureDeploymentListener implements ArtifactUrlTransformer, SynchronousBundleListener {
 
     public static final String FEATURE_PATH = "org.apache.felix.karaf.shell.features";
 
@@ -114,7 +99,7 @@
         return false;
     }
 
-    public File transform(File artifact, File tmpDir) {
+    public URL transform(URL artifact) {
         // We can't really install the feature right now and just return nothing.
         // We would not be aware of the fact that the bundle has been uninstalled
         // and therefore require the feature to be uninstalled.
@@ -122,43 +107,9 @@
         // this deployer: installation / uninstallation of the feature will be done
         // while the bundle is installed / uninstalled.
         try {
-            File destFile = new File(tmpDir, artifact.getName() + ".jar");
-            OutputStream os = new BufferedOutputStream(new FileOutputStream(destFile));
-
-            String name = artifact.getCanonicalPath();
-            int idx = name.lastIndexOf('/');
-            if (idx >= 0) {
-                name = name.substring(idx + 1);
-            }
-            String[] str = extractNameVersionType(name);
-            // Create manifest
-            Manifest m = new Manifest();
-            m.getMainAttributes().putValue("Manifest-Version", "2");
-            m.getMainAttributes().putValue(Constants.BUNDLE_MANIFESTVERSION, "2");
-            m.getMainAttributes().putValue(Constants.BUNDLE_SYMBOLICNAME, str[0]);
-            m.getMainAttributes().putValue(Constants.BUNDLE_VERSION, str[1]);
-            // Put content
-            JarOutputStream out = new JarOutputStream(os);
-            ZipEntry e = new ZipEntry(JarFile.MANIFEST_NAME);
-            out.putNextEntry(e);
-            m.write(out);
-            out.closeEntry();
-            e = new ZipEntry("META-INF/");
-            out.putNextEntry(e);
-            e = new ZipEntry("META-INF/" + FEATURE_PATH + "/");
-            out.putNextEntry(e);
-            out.closeEntry();
-            e = new ZipEntry("META-INF/" + FEATURE_PATH + "/" + name);
-            out.putNextEntry(e);
-            InputStream fis = new BufferedInputStream(new FileInputStream(artifact));
-            copyInputStream(fis, out);
-            fis.close();
-            out.closeEntry();
-            out.close();
-            os.close();
-            return destFile;
+            return new URL("feature", null, artifact.toString());
         } catch (Exception e) {
-            LOGGER.error("Unable to build spring application bundle", e);
+            LOGGER.error("Unable to build feature bundle", e);
             return null;
         }
     }
@@ -223,73 +174,4 @@
         return db.parse(artifact);
     }
 
-    private static void copyInputStream(InputStream in, OutputStream out) throws IOException {
-        byte[] buffer = new byte[8192];
-        int len = in.read(buffer);
-        while (len >= 0) {
-            out.write(buffer, 0, len);
-            len = in.read(buffer);
-        }
-    }
-    private static final String DEFAULT_VERSION = "0.0.0";
-
-    private static final Pattern ARTIFACT_MATCHER = Pattern.compile("(.+)(?:-(\\d+)(?:\\.(\\d+)(?:\\.(\\d+))?)?(?:[^a-zA-Z0-9](.*))?)(?:\\.([^\\.]+))", Pattern.DOTALL);
-    private static final Pattern FUZZY_MODIFIDER = Pattern.compile("(?:\\d+[.-])*(.*)", Pattern.DOTALL);
-
-    public static String[] extractNameVersionType(String url) {
-        Matcher m = ARTIFACT_MATCHER.matcher(url);
-        if (!m.matches()) {
-            return new String[] { url, DEFAULT_VERSION };
-        }
-        else {
-            //System.err.println(m.groupCount());
-            //for (int i = 1; i <= m.groupCount(); i++) {
-            //    System.err.println("Group " + i + ": " + m.group(i));
-            //}
-
-            StringBuffer v = new StringBuffer();
-            String d1 = m.group(1);
-            String d2 = m.group(2);
-            String d3 = m.group(3);
-            String d4 = m.group(4);
-            String d5 = m.group(5);
-            String d6 = m.group(6);
-            if (d2 != null) {
-                v.append(d2);
-                if (d3 != null) {
-                    v.append('.');
-                    v.append(d3);
-                    if (d4 != null) {
-                        v.append('.');
-                        v.append(d4);
-                        if (d5 != null) {
-                            v.append(".");
-                            cleanupModifier(v, d5);
-                        }
-                    } else if (d5 != null) {
-                        v.append(".0.");
-                        cleanupModifier(v, d5);
-                    }
-                } else if (d5 != null) {
-                    v.append(".0.0.");
-                    cleanupModifier(v, d5);
-                }
-            }
-            return new String[] { d1, v.toString(), d6 };
-        }
-    }
-
-    private static void cleanupModifier(StringBuffer result, String modifier) {
-        Matcher m = FUZZY_MODIFIDER.matcher(modifier);
-        if (m.matches()) {
-            modifier = m.group(1);
-        }
-        for (int i = 0; i < modifier.length(); i++) {
-            char c = modifier.charAt(i);
-            if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' || c == '-') {
-                result.append(c);
-            }
-        }
-    }
-
 }
diff --git a/karaf/deployer/features/src/main/java/org/apache/felix/karaf/deployer/features/FeatureTransformer.java b/karaf/deployer/features/src/main/java/org/apache/felix/karaf/deployer/features/FeatureTransformer.java
new file mode 100644
index 0000000..050bec3
--- /dev/null
+++ b/karaf/deployer/features/src/main/java/org/apache/felix/karaf/deployer/features/FeatureTransformer.java
@@ -0,0 +1,146 @@
+/**
+ *
+ * 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.felix.karaf.deployer.features;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URL;
+import java.util.jar.JarFile;
+import java.util.jar.JarOutputStream;
+import java.util.jar.Manifest;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.zip.ZipEntry;
+
+import org.osgi.framework.Constants;
+
+/**
+ * Transform a feature descriptor into an osgi bundle
+ */
+public class FeatureTransformer {
+
+    public static void transform(URL url, OutputStream os) throws Exception {
+        // Heuristicly retrieve name and version
+        String name = url.getPath();
+        int idx = name.lastIndexOf('/');
+        if (idx >= 0) {
+            name = name.substring(idx + 1);
+        }
+        String[] str = extractNameVersionType(name);
+        // Create manifest
+        Manifest m = new Manifest();
+        m.getMainAttributes().putValue("Manifest-Version", "2");
+        m.getMainAttributes().putValue(Constants.BUNDLE_MANIFESTVERSION, "2");
+        m.getMainAttributes().putValue(Constants.BUNDLE_SYMBOLICNAME, str[0]);
+        m.getMainAttributes().putValue(Constants.BUNDLE_VERSION, str[1]);
+        // Put content
+        JarOutputStream out = new JarOutputStream(os);
+        ZipEntry e = new ZipEntry(JarFile.MANIFEST_NAME);
+        out.putNextEntry(e);
+        m.write(out);
+        out.closeEntry();
+        e = new ZipEntry("META-INF/");
+        out.putNextEntry(e);
+        e = new ZipEntry("META-INF/" + FeatureDeploymentListener.FEATURE_PATH + "/");
+        out.putNextEntry(e);
+        out.closeEntry();
+        e = new ZipEntry("META-INF/" + FeatureDeploymentListener.FEATURE_PATH + "/" + name);
+        out.putNextEntry(e);
+        InputStream fis = url.openStream();
+        try {
+            copyInputStream(fis, out);
+        } finally {
+            fis.close();
+        }
+        out.closeEntry();
+        out.close();
+        os.close();
+    }
+
+    private static void copyInputStream(InputStream in, OutputStream out) throws IOException {
+        byte[] buffer = new byte[8192];
+        int len = in.read(buffer);
+        while (len >= 0) {
+            out.write(buffer, 0, len);
+            len = in.read(buffer);
+        }
+    }
+
+    private static final String DEFAULT_VERSION = "0.0.0";
+
+    private static final Pattern ARTIFACT_MATCHER = Pattern.compile("(.+)(?:-(\\d+)(?:\\.(\\d+)(?:\\.(\\d+))?)?(?:[^a-zA-Z0-9](.*))?)(?:\\.([^\\.]+))", Pattern.DOTALL);
+    private static final Pattern FUZZY_MODIFIDER = Pattern.compile("(?:\\d+[.-])*(.*)", Pattern.DOTALL);
+
+    public static String[] extractNameVersionType(String url) {
+        Matcher m = ARTIFACT_MATCHER.matcher(url);
+        if (!m.matches()) {
+            return new String[] { url, DEFAULT_VERSION };
+        }
+        else {
+            //System.err.println(m.groupCount());
+            //for (int i = 1; i <= m.groupCount(); i++) {
+            //    System.err.println("Group " + i + ": " + m.group(i));
+            //}
+
+            StringBuffer v = new StringBuffer();
+            String d1 = m.group(1);
+            String d2 = m.group(2);
+            String d3 = m.group(3);
+            String d4 = m.group(4);
+            String d5 = m.group(5);
+            String d6 = m.group(6);
+            if (d2 != null) {
+                v.append(d2);
+                if (d3 != null) {
+                    v.append('.');
+                    v.append(d3);
+                    if (d4 != null) {
+                        v.append('.');
+                        v.append(d4);
+                        if (d5 != null) {
+                            v.append(".");
+                            cleanupModifier(v, d5);
+                        }
+                    } else if (d5 != null) {
+                        v.append(".0.");
+                        cleanupModifier(v, d5);
+                    }
+                } else if (d5 != null) {
+                    v.append(".0.0.");
+                    cleanupModifier(v, d5);
+                }
+            }
+            return new String[] { d1, v.toString(), d6 };
+        }
+    }
+
+    private static void cleanupModifier(StringBuffer result, String modifier) {
+        Matcher m = FUZZY_MODIFIDER.matcher(modifier);
+        if (m.matches()) {
+            modifier = m.group(1);
+        }
+        for (int i = 0; i < modifier.length(); i++) {
+            char c = modifier.charAt(i);
+            if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' || c == '-') {
+                result.append(c);
+            }
+        }
+    }
+
+}
diff --git a/karaf/deployer/features/src/main/java/org/apache/felix/karaf/deployer/features/FeatureURLHandler.java b/karaf/deployer/features/src/main/java/org/apache/felix/karaf/deployer/features/FeatureURLHandler.java
new file mode 100644
index 0000000..dfa1afb
--- /dev/null
+++ b/karaf/deployer/features/src/main/java/org/apache/felix/karaf/deployer/features/FeatureURLHandler.java
@@ -0,0 +1,73 @@
+package org.apache.felix.karaf.deployer.features;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLConnection;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.osgi.service.url.AbstractURLStreamHandlerService;
+
+/**
+ * URL handler for features
+ */
+public class FeatureURLHandler extends AbstractURLStreamHandlerService {
+
+    private static Log logger = LogFactory.getLog(FeatureURLHandler.class);
+
+    private static String SYNTAX = "feature: xml-uri";
+
+    private URL featureXmlURL;
+
+    /**
+     * Open the connection for the given URL.
+     *
+     * @param url the url from which to open a connection.
+     * @return a connection on the specified URL.
+     * @throws java.io.IOException if an error occurs or if the URL is malformed.
+     */
+    @Override
+    public URLConnection openConnection(URL url) throws IOException {
+        if (url.getPath() == null || url.getPath().trim().length() == 0) {
+            throw new MalformedURLException("Path can not be null or empty. Syntax: " + SYNTAX );
+        }
+        featureXmlURL = new URL(url.getPath());
+
+        logger.debug("Blueprint xml URL is: [" + featureXmlURL + "]");
+        return new Connection(url);
+    }
+
+    public URL getFeatureXmlURL() {
+        return featureXmlURL;
+    }
+
+    public class Connection extends URLConnection {
+
+        public Connection(URL url) {
+            super(url);
+        }
+
+        @Override
+        public void connect() throws IOException {
+        }
+
+        @Override
+        public InputStream getInputStream() throws IOException {
+            try {
+                ByteArrayOutputStream os = new ByteArrayOutputStream();
+                FeatureTransformer.transform(featureXmlURL, os);
+                os.close();
+                return new ByteArrayInputStream(os.toByteArray());
+            } catch (Exception e) {
+                logger.error("Error opening blueprint xml url", e);
+                throw (IOException) new IOException("Error opening blueprint xml url").initCause(e);
+            }
+        }
+    }
+
+
+}
diff --git a/karaf/deployer/features/src/main/resources/OSGI-INF/blueprint/features-deployer.xml b/karaf/deployer/features/src/main/resources/OSGI-INF/blueprint/features-deployer.xml
index 12ba2d9..faad6b0 100644
--- a/karaf/deployer/features/src/main/resources/OSGI-INF/blueprint/features-deployer.xml
+++ b/karaf/deployer/features/src/main/resources/OSGI-INF/blueprint/features-deployer.xml
@@ -20,7 +20,7 @@
 <blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
            default-activation="lazy">
 
-    <service ref="featureDeploymentListener" auto-export="interfaces"/>
+    <service ref="featureDeploymentListener" auto-export="interfaces" depends-on="featureUrlHandler"/>
 
     <bean id="featureDeploymentListener" class="org.apache.felix.karaf.deployer.features.FeatureDeploymentListener"
           init-method="init" destroy-method="destroy" activation="lazy">
@@ -30,4 +30,11 @@
         </property>
     </bean>
 
+    <service id="featureUrlHandler" interface="org.osgi.service.url.URLStreamHandlerService">
+    	<service-properties>
+            <entry key="url.handler.protocol" value="feature"/>
+        </service-properties>
+        <bean class="org.apache.felix.karaf.deployer.features.FeatureURLHandler"/>
+    </service>
+
 </blueprint>
diff --git a/karaf/deployer/spring/src/main/resources/OSGI-INF/blueprint/spring-deployer.xml b/karaf/deployer/spring/src/main/resources/OSGI-INF/blueprint/spring-deployer.xml
index 3e04d1e..558e003 100644
--- a/karaf/deployer/spring/src/main/resources/OSGI-INF/blueprint/spring-deployer.xml
+++ b/karaf/deployer/spring/src/main/resources/OSGI-INF/blueprint/spring-deployer.xml
@@ -19,11 +19,11 @@
 -->
 <blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0">
 
-    <service auto-export="interfaces">
+    <service auto-export="interfaces" depends-on="springUrlHandler">
         <bean class="org.apache.felix.karaf.deployer.spring.SpringDeploymentListener"/>
     </service>
 
-    <service interface="org.osgi.service.url.URLStreamHandlerService">
+    <service id="springUrlHandler" interface="org.osgi.service.url.URLStreamHandlerService">
     	<service-properties>
             <entry key="url.handler.protocol" value="spring"/>
         </service-properties>