You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@karaf.apache.org by cs...@apache.org on 2012/06/22 17:50:22 UTC

svn commit: r1352937 - in /karaf/trunk: deployer/kar/src/main/java/org/apache/karaf/deployer/kar/ kar/command/src/main/java/org/apache/karaf/kar/command/ kar/command/src/main/java/org/apache/karaf/kar/command/completers/ kar/core/ kar/core/src/main/jav...

Author: cschneider
Date: Fri Jun 22 15:50:19 2012
New Revision: 1352937

URL: http://svn.apache.org/viewvc?rev=1352937&view=rev
Log:
KARAF-1551 Install kar repos into separate folders

Added:
    karaf/trunk/kar/core/src/main/java/org/apache/karaf/kar/internal/FeatureDetector.java
    karaf/trunk/kar/core/src/main/java/org/apache/karaf/kar/internal/Kar.java
    karaf/trunk/kar/core/src/main/java/org/apache/karaf/kar/internal/MavenRepoManager.java
    karaf/trunk/kar/core/src/main/java/org/apache/karaf/kar/internal/NoSubstProperties.java
    karaf/trunk/kar/core/src/test/
    karaf/trunk/kar/core/src/test/java/
    karaf/trunk/kar/core/src/test/java/org/
    karaf/trunk/kar/core/src/test/java/org/apache/
    karaf/trunk/kar/core/src/test/java/org/apache/karaf/
    karaf/trunk/kar/core/src/test/java/org/apache/karaf/kar/
    karaf/trunk/kar/core/src/test/java/org/apache/karaf/kar/internal/
    karaf/trunk/kar/core/src/test/java/org/apache/karaf/kar/internal/MavenRepoManagerTest.java
Modified:
    karaf/trunk/deployer/kar/src/main/java/org/apache/karaf/deployer/kar/KarArtifactInstaller.java
    karaf/trunk/kar/command/src/main/java/org/apache/karaf/kar/command/UninstallKarCommand.java
    karaf/trunk/kar/command/src/main/java/org/apache/karaf/kar/command/completers/KarCompleter.java
    karaf/trunk/kar/core/pom.xml
    karaf/trunk/kar/core/src/main/java/org/apache/karaf/kar/KarService.java
    karaf/trunk/kar/core/src/main/java/org/apache/karaf/kar/KarsMBean.java
    karaf/trunk/kar/core/src/main/java/org/apache/karaf/kar/internal/KarServiceImpl.java
    karaf/trunk/kar/core/src/main/java/org/apache/karaf/kar/internal/Kars.java
    karaf/trunk/kar/core/src/main/resources/OSGI-INF/blueprint/kar-core.xml
    karaf/trunk/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/features/InstallKarsMojo.java

Modified: karaf/trunk/deployer/kar/src/main/java/org/apache/karaf/deployer/kar/KarArtifactInstaller.java
URL: http://svn.apache.org/viewvc/karaf/trunk/deployer/kar/src/main/java/org/apache/karaf/deployer/kar/KarArtifactInstaller.java?rev=1352937&r1=1352936&r2=1352937&view=diff
==============================================================================
--- karaf/trunk/deployer/kar/src/main/java/org/apache/karaf/deployer/kar/KarArtifactInstaller.java (original)
+++ karaf/trunk/deployer/kar/src/main/java/org/apache/karaf/deployer/kar/KarArtifactInstaller.java Fri Jun 22 15:50:19 2012
@@ -50,15 +50,22 @@ public class KarArtifactInstaller implem
 	}
 
     public void uninstall(File file) throws Exception {
-        LOGGER.info("Uninstalling KAR {}", file.getName());
-        karService.uninstall(file.getName(), true);
+        String karName = getKarName(file);
+        LOGGER.info("Uninstalling KAR {}", karName);
+        karService.uninstall(karName);
 	}
 
 	public void update(File file) throws Exception {
         LOGGER.warn("Karaf archive {}' has been updated; redeploying.", file);
-        karService.uninstall(file.getName(), true);
+        karService.uninstall(getKarName(file));
         karService.install(file.toURI());
 	}
+	
+	private String getKarName(File karFile) {
+	    String karName = karFile.getName();
+        karName = karName.substring(0, karName.lastIndexOf("."));
+        return karName;
+	}
 
     public boolean canHandle(File file) {
 		// If the file ends with .kar, then we can handle it!

Modified: karaf/trunk/kar/command/src/main/java/org/apache/karaf/kar/command/UninstallKarCommand.java
URL: http://svn.apache.org/viewvc/karaf/trunk/kar/command/src/main/java/org/apache/karaf/kar/command/UninstallKarCommand.java?rev=1352937&r1=1352936&r2=1352937&view=diff
==============================================================================
--- karaf/trunk/kar/command/src/main/java/org/apache/karaf/kar/command/UninstallKarCommand.java (original)
+++ karaf/trunk/kar/command/src/main/java/org/apache/karaf/kar/command/UninstallKarCommand.java Fri Jun 22 15:50:19 2012
@@ -18,7 +18,6 @@ package org.apache.karaf.kar.command;
 
 import org.apache.karaf.shell.commands.Argument;
 import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.shell.commands.Option;
 
 @Command(scope = "kar", name = "uninstall", description = "Uninstall a KAR file.")
 public class UninstallKarCommand extends KarCommandSupport {
@@ -26,11 +25,8 @@ public class UninstallKarCommand extends
     @Argument(index = 0, name = "name", description = "The name of the KAR file to uninstall.", required = true, multiValued = false)
     private String name;
 
-    @Option(name = "-c", aliases = { "--clean" }, description = "Uninstall features and cleanup the system repository from the KAR content", required = false, multiValued = false)
-    private boolean clean = false;
-    
     public Object doExecute() throws Exception {
-        this.getKarService().uninstall(name, clean);
+        this.getKarService().uninstall(name);
         return null;
     }
     

Modified: karaf/trunk/kar/command/src/main/java/org/apache/karaf/kar/command/completers/KarCompleter.java
URL: http://svn.apache.org/viewvc/karaf/trunk/kar/command/src/main/java/org/apache/karaf/kar/command/completers/KarCompleter.java?rev=1352937&r1=1352936&r2=1352937&view=diff
==============================================================================
--- karaf/trunk/kar/command/src/main/java/org/apache/karaf/kar/command/completers/KarCompleter.java (original)
+++ karaf/trunk/kar/command/src/main/java/org/apache/karaf/kar/command/completers/KarCompleter.java Fri Jun 22 15:50:19 2012
@@ -29,7 +29,7 @@ public class KarCompleter implements Com
     
     private KarService karService;
     
-    public int complete(String buffer, int cursor, List candidates) {
+    public int complete(String buffer, int cursor, @SuppressWarnings("rawtypes") List candidates) {
         StringsCompleter delegate = new StringsCompleter();
         try {
             for (String karName : karService.list()) {

Modified: karaf/trunk/kar/core/pom.xml
URL: http://svn.apache.org/viewvc/karaf/trunk/kar/core/pom.xml?rev=1352937&r1=1352936&r2=1352937&view=diff
==============================================================================
--- karaf/trunk/kar/core/pom.xml (original)
+++ karaf/trunk/kar/core/pom.xml Fri Jun 22 15:50:19 2012
@@ -39,6 +39,20 @@
 
     <dependencies>
         <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.utils</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.karaf</groupId>
+            <artifactId>org.osgi.core</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.compendium</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
             <groupId>org.ops4j.pax.url</groupId>
             <artifactId>pax-url-aether</artifactId>
         </dependency>
@@ -80,7 +94,8 @@
                             org.apache.karaf.kar
                         </Export-Package>
                         <Private-Package>
-                            org.apache.karaf.kar.internal
+                            org.apache.karaf.kar.internal,
+                            org.apache.felix.utils.properties
                         </Private-Package>
                     </instructions>
                 </configuration>

Modified: karaf/trunk/kar/core/src/main/java/org/apache/karaf/kar/KarService.java
URL: http://svn.apache.org/viewvc/karaf/trunk/kar/core/src/main/java/org/apache/karaf/kar/KarService.java?rev=1352937&r1=1352936&r2=1352937&view=diff
==============================================================================
--- karaf/trunk/kar/core/src/main/java/org/apache/karaf/kar/KarService.java (original)
+++ karaf/trunk/kar/core/src/main/java/org/apache/karaf/kar/KarService.java Fri Jun 22 15:50:19 2012
@@ -16,6 +16,7 @@
  */
 package org.apache.karaf.kar;
 
+import java.io.File;
 import java.io.PrintStream;
 import java.net.URI;
 import java.util.List;
@@ -26,30 +27,35 @@ import java.util.List;
 public interface KarService {
 
     /**
-     * Install KAR from a given URL.
+     * Install KAR from a given URI
+     * 
+     * Resources will be copied to the karaf base dir
+     * Repository contents will be copied to a subdir in the 
+     * karaf data directory
      *
-     * @param url the KAR URL.
+     * @param karUri Uri of the kar to be installed
      * @throws Exception in case of installation failure.
      */
-    void install(URI url) throws Exception;
-
+    void install(URI karUri) throws Exception;
+    
     /**
-     * Uninstall the given KAR.
-     * NB: the system folder is not cleaned.
-     *
-     * @param name the name of the KAR.
-     * @throws Exception in case of uninstall failure.
+     * Install a kar with manually given repository and 
+     * resource directories.
+     * 
+     * @param karUri Uri of the kar to be installed
+     * @param repoDir destination for the repository contents of the kar
+     * @param resourceDir destination for the resource contents of the kar
+     * @throws Exception
      */
-    void uninstall(String name) throws Exception;
+    void install(URI karUri, File repoDir, File resourceDir) throws Exception;
 
     /**
-     * Uninstall the given KAR and, eventually, cleanup the repository from the KAR content.
+     * Uninstall the given KAR
      *
-     * @param name the name of the KAR.
-     * @param clean true to cleanup the repository folder, false else.
-     * @throws Exception in case of uninstall failure.
+     * @param name the name of the KAR
+     * @throws Exception in case of failure
      */
-    void uninstall(String name, boolean clean) throws Exception;
+    void uninstall(String name) throws Exception;
 
     /**
      * List the KAR stored in the data folder.
@@ -69,5 +75,6 @@ public interface KarService {
      * @param console
      */
     void create(String repoName, List<String> features, PrintStream console);
+
     
 }

Modified: karaf/trunk/kar/core/src/main/java/org/apache/karaf/kar/KarsMBean.java
URL: http://svn.apache.org/viewvc/karaf/trunk/kar/core/src/main/java/org/apache/karaf/kar/KarsMBean.java?rev=1352937&r1=1352936&r2=1352937&view=diff
==============================================================================
--- karaf/trunk/kar/core/src/main/java/org/apache/karaf/kar/KarsMBean.java (original)
+++ karaf/trunk/kar/core/src/main/java/org/apache/karaf/kar/KarsMBean.java Fri Jun 22 15:50:19 2012
@@ -44,13 +44,4 @@ public interface KarsMBean {
      */
     void uninstall(String name) throws Exception;
 
-    /**
-     * Uninstall a KAR file and clean the system repository with the KAR content.
-     *
-     * @param name the KAR name.
-     * @param clean true to cleanup the system repository, false else.
-     * @throws Exception in case of uninstall failure.
-     */
-    void uninstall(String name, boolean clean) throws Exception;
-
 }

Added: karaf/trunk/kar/core/src/main/java/org/apache/karaf/kar/internal/FeatureDetector.java
URL: http://svn.apache.org/viewvc/karaf/trunk/kar/core/src/main/java/org/apache/karaf/kar/internal/FeatureDetector.java?rev=1352937&view=auto
==============================================================================
--- karaf/trunk/kar/core/src/main/java/org/apache/karaf/kar/internal/FeatureDetector.java (added)
+++ karaf/trunk/kar/core/src/main/java/org/apache/karaf/kar/internal/FeatureDetector.java Fri Jun 22 15:50:19 2012
@@ -0,0 +1,70 @@
+package org.apache.karaf.kar.internal;
+
+import java.io.File;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.w3c.dom.Document;
+import org.xml.sax.ErrorHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+
+/**
+ * Simple helper to determine if a file is a feature repo
+ *
+ */
+class FeatureDetector {
+    private static final Logger LOGGER = LoggerFactory.getLogger(FeatureDetector.class);
+    
+    private DocumentBuilderFactory dbf;
+
+    FeatureDetector() {
+        dbf = DocumentBuilderFactory.newInstance();
+        dbf.setNamespaceAware(true);
+    }
+    /**
+     * Check if a file is a features XML.
+     *
+     * @param artifact the file to check.
+     * @return true if the artifact is a features XML, false else.
+     */
+    boolean isFeaturesRepository(File artifact) {
+        try {
+            if (artifact.isFile() && artifact.getName().endsWith(".xml")) {
+                Document doc = parse(artifact);
+                String name = doc.getDocumentElement().getLocalName();
+                String uri  = doc.getDocumentElement().getNamespaceURI();
+                if ("features".equals(name) && (uri == null || "".equals(uri) || uri.startsWith("http://karaf.apache.org/xmlns/features/v"))) {
+                    return true;
+                }
+            }
+        } catch (Exception e) {
+            LOGGER.debug("File '{}' is not a features file.", artifact.getName(), e);
+        }
+        return false;
+    }
+    
+    /**
+     * Parse a features XML.
+     *
+     * @param artifact the features XML to parse.
+     * @return the parsed document.
+     * @throws Exception in case of parsing failure.
+     */
+    private Document parse(File artifact) throws Exception {
+        DocumentBuilder db = dbf.newDocumentBuilder();
+        db.setErrorHandler(new ErrorHandler() {
+            public void warning(SAXParseException exception) throws SAXException {
+            }
+            public void error(SAXParseException exception) throws SAXException {
+            }
+            public void fatalError(SAXParseException exception) throws SAXException {
+                throw exception;
+            }
+        });
+        return db.parse(artifact);
+    }
+}

Added: karaf/trunk/kar/core/src/main/java/org/apache/karaf/kar/internal/Kar.java
URL: http://svn.apache.org/viewvc/karaf/trunk/kar/core/src/main/java/org/apache/karaf/kar/internal/Kar.java?rev=1352937&view=auto
==============================================================================
--- karaf/trunk/kar/core/src/main/java/org/apache/karaf/kar/internal/Kar.java (added)
+++ karaf/trunk/kar/core/src/main/java/org/apache/karaf/kar/internal/Kar.java Fri Jun 22 15:50:19 2012
@@ -0,0 +1,189 @@
+package org.apache.karaf.kar.internal;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.jar.Attributes;
+import java.util.jar.JarInputStream;
+import java.util.jar.Manifest;
+import java.util.zip.ZipEntry;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Representation of a Karaf Kar archive
+ * 
+ * A Kar archive is a jar file with a special structure that can be used
+ * to deploy feature repositories, maven repo contents and resources for the
+ * karaf installation.
+ * 
+ * meta-inf/Manifest: 
+ *   Karaf-Feature-Start: (true|false) Controls if the features in the feature repos should be started on deploy
+ *   Karaf-Feature-Repos: (uri)* If present then only the given feature repo urls are added to karaf if it is not
+ *      present then the karaf file is scanned for repo files
+ *      
+ * repository/
+ *   Everything below this directory is treated as a maven repository. On deploy the contents
+ *   will be copied to a directory below data. This directory will then be added to the 
+ *   maven repos of pax url maven
+ *   
+ * resource/
+ *   Everything below this directory will be copied to the karaf base dir on deploy
+ * 
+ */
+public class Kar {
+    public static final Logger LOGGER = LoggerFactory.getLogger(KarServiceImpl.class);
+    public static final String MANIFEST_ATTR_KARAF_FEATURE_START = "Karaf-Feature-Start";
+    public static final String MANIFEST_ATTR_KARAF_FEATURE_REPOS = "Karaf-Feature-Repos";
+    private final URI karUri;
+    private boolean shouldInstallFeatures;
+    private List<URI> featureRepos;
+
+    public Kar(URI karUri) {
+        this.karUri = karUri;
+    }
+
+    /**
+     * Extract a kar from a given URI into a repository dir and resource dir
+     * and populate shouldInstallFeatures and featureRepos
+     * 
+     * @param karUri uri to read the kar from
+     * @param repoDir directory to write the repository contents of the kar to
+     * @param resourceDir directory to write the resource contents of the kar to
+     */
+    public void extract(File repoDir, File resourceDir) {
+        InputStream is = null;
+        JarInputStream zipIs = null;
+        FeatureDetector featureDetector = new FeatureDetector();
+        this.featureRepos = new ArrayList<URI>();
+        this.shouldInstallFeatures = true;
+
+        try {
+            is = karUri.toURL().openStream();
+            repoDir.mkdirs();
+
+            if (!repoDir.isDirectory()) {
+                throw new RuntimeException("The KAR file " + karUri + " is already installed");
+            }
+
+            LOGGER.debug("Uncompress the KAR file {} into directory {}", karUri, repoDir);
+            zipIs = new JarInputStream(is);
+            boolean scanForRepos = true;
+
+            Manifest manifest = zipIs.getManifest();
+            if (manifest != null) {
+                Attributes attr = manifest.getMainAttributes();
+                String featureStartSt = (String)attr
+                    .get(new Attributes.Name(MANIFEST_ATTR_KARAF_FEATURE_START));
+                if ("false".equals(featureStartSt)) {
+                    shouldInstallFeatures = false;
+                }
+                String featureReposAttr = (String)attr
+                    .get(new Attributes.Name(MANIFEST_ATTR_KARAF_FEATURE_REPOS));
+                if (featureReposAttr != null) {
+                    featureRepos.add(new URI(featureReposAttr));
+                    scanForRepos = false;
+                }
+            }
+
+            ZipEntry entry = zipIs.getNextEntry();
+            while (entry != null) {
+                if (entry.getName().startsWith("repository")) {
+                    String path = entry.getName().substring("repository/".length());
+                    File destFile = new File(repoDir, path);
+                    extract(zipIs, entry, destFile);
+                    if (scanForRepos && featureDetector.isFeaturesRepository(destFile)) {
+                        featureRepos.add(destFile.toURI());
+                    }
+                }
+
+                if (entry.getName().startsWith("resource")) {
+                    String path = entry.getName().substring("resource/".length());
+                    File destFile = new File(resourceDir, path);
+                    extract(zipIs, entry, destFile);
+                }
+                entry = zipIs.getNextEntry();
+            }
+        } catch (Exception e) {
+            throw new RuntimeException("Error extracting kar file " + karUri + " into dir " + repoDir + ": " + e.getMessage(), e);
+        } finally {
+            closeStream(zipIs);
+            closeStream(is);
+        }
+    }
+
+    /**
+     * Extract an entry from a KAR file
+     * 
+     * @param is
+     * @param zipEntry
+     * @param dest
+     * @return
+     * @throws Exception
+     */
+    private static File extract(InputStream is, ZipEntry zipEntry, File dest) throws Exception {
+        if (zipEntry.isDirectory()) {
+            LOGGER.debug("Creating directory {}", dest.getName());
+            dest.mkdirs();
+        } else {
+            dest.getParentFile().mkdirs();
+            FileOutputStream out = new FileOutputStream(dest);
+            copyStream(is, out);
+            out.close();
+        }
+        return dest;
+    }
+
+    private static void closeStream(InputStream is) {
+        if (is != null) {
+            try {
+                is.close();
+            } catch (IOException e) {
+                LOGGER.warn("Error closing stream", e);
+            }
+        }
+    }
+
+    static long copyStream(InputStream input, OutputStream output) throws IOException {
+        byte[] buffer = new byte[10000];
+        long count = 0;
+        int n = 0;
+        while (-1 != (n = input.read(buffer))) {
+            output.write(buffer, 0, n);
+            count += n;
+        }
+        return count;
+    }
+    
+
+    public String getKarName() {
+        try {
+            String karName = new File(karUri.toURL().getFile()).getName();
+            karName = karName.substring(0, karName.lastIndexOf("."));
+            return karName;
+        } catch (MalformedURLException e) {
+            throw new RuntimeException("Invalid kar URI " + karUri, e);
+        }
+    }
+    
+    public URI getKarUri() {
+        return karUri;
+    }
+
+    public boolean isShouldInstallFeatures() {
+        return shouldInstallFeatures;
+    }
+
+    public List<URI> getFeatureRepos() {
+        return featureRepos;
+    } 
+
+    
+}

Modified: karaf/trunk/kar/core/src/main/java/org/apache/karaf/kar/internal/KarServiceImpl.java
URL: http://svn.apache.org/viewvc/karaf/trunk/kar/core/src/main/java/org/apache/karaf/kar/internal/KarServiceImpl.java?rev=1352937&r1=1352936&r2=1352937&view=diff
==============================================================================
--- karaf/trunk/kar/core/src/main/java/org/apache/karaf/kar/internal/KarServiceImpl.java (original)
+++ karaf/trunk/kar/core/src/main/java/org/apache/karaf/kar/internal/KarServiceImpl.java Fri Jun 22 15:50:19 2012
@@ -19,9 +19,11 @@
 package org.apache.karaf.kar.internal;
 
 import java.io.BufferedOutputStream;
+import java.io.BufferedReader;
 import java.io.ByteArrayInputStream;
 import java.io.File;
 import java.io.FileOutputStream;
+import java.io.FileReader;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
@@ -31,21 +33,14 @@ import java.net.URI;
 import java.net.URISyntaxException;
 import java.util.ArrayList;
 import java.util.EnumSet;
-import java.util.Enumeration;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.jar.Attributes;
 import java.util.jar.JarEntry;
 import java.util.jar.JarOutputStream;
 import java.util.jar.Manifest;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipFile;
-
-import javax.xml.parsers.DocumentBuilder;
-import javax.xml.parsers.DocumentBuilderFactory;
 
 import org.apache.karaf.features.BundleInfo;
 import org.apache.karaf.features.ConfigFileInfo;
@@ -57,297 +52,139 @@ import org.apache.karaf.kar.KarService;
 import org.ops4j.pax.url.mvn.Parser;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.w3c.dom.Document;
-import org.xml.sax.ErrorHandler;
-import org.xml.sax.SAXException;
-import org.xml.sax.SAXParseException;
 
 /**
  * Implementation of the KAR service.
  */
 public class KarServiceImpl implements KarService {
+    private static final String FEATURE_CONFIG_FILE = "features.cfg";
+    private static final Logger LOGGER = LoggerFactory.getLogger(KarServiceImpl.class);
 
-    private static final String MANIFEST_ATTR_KARAF_FEATURE_START = "Karaf-Feature-Start";
-
-    public static final Logger LOGGER = LoggerFactory.getLogger(KarServiceImpl.class);
-
-    private String storage = "./target/data/kar"; // ${KARAF.DATA}/kar
-    private String base = "./";
-    private String localRepo = "./target/local-repo"; // ${KARAF.BASE}/system
+    private File storage;
+    private File base;
     private FeaturesService featuresService;
-    
-    private DocumentBuilderFactory dbf;
+    private final MavenRepoManager mavenRepoManager;
 
-    /**
-     * Init method.
-     *
-     * @throws Exception in case of init failure.
-     */
-    public void init() throws Exception {
-        dbf = DocumentBuilderFactory.newInstance();
-        dbf.setNamespaceAware(true);
-        
-        File karStorageDir = new File(storage);
-        if (!karStorageDir.exists()) {
-            LOGGER.debug("Create the KAR storage directory");
-            karStorageDir.mkdirs();
-        }
-        if (!karStorageDir.isDirectory()) {
+    public KarServiceImpl(String karafBase, FeaturesService featuresService, MavenRepoManager mavenRepoManager) {
+        this.base = new File(karafBase);
+        this.storage = new File(this.base, "data" + File.separator + "kar");
+        this.featuresService = featuresService;
+        this.mavenRepoManager = mavenRepoManager;
+        this.storage.mkdirs();
+        if (!storage.isDirectory()) {
             throw new IllegalStateException("KAR storage " + storage + " is not a directory");
         }
     }
     
-    public void install(URI url) throws Exception {
-        File file = new File(url.toURL().getFile());
-        String karName = file.getName();
-        
-        LOGGER.debug("Installing KAR {} from {}", karName, url);
-        
-        LOGGER.debug("Check if the KAR file already exists in the storage directory");
-        File karStorage = new File(storage);
-        File karFile = new File(karStorage, karName);
-        
-        if (karFile.exists()) {
-            LOGGER.warn("The KAR file " + karName + " is already installed. Override it.");
-        }
-        
-        LOGGER.debug("Copy the KAR file from {} to {}", url, karFile.getParent());
-        copy(url, karFile);
-        
-        LOGGER.debug("Uncompress the KAR file {} into the system repository", karName);
-        ZipFile zipFile = new ZipFile(karFile);
-        
-        List<URI> featuresRepositoriesInKar = new ArrayList<URI>();
-        boolean shouldInstallFeatures = true;
-        
-        @SuppressWarnings("unchecked")
-        Enumeration<ZipEntry> entries = (Enumeration<ZipEntry>) zipFile.entries();
-        while (entries.hasMoreElements()) {
-            ZipEntry entry = entries.nextElement();
-
-            String repoEntryName = getRepoEntryName(entry);
-
-            if (repoEntryName != null) {
-                File extract = extract(zipFile, entry, repoEntryName, localRepo);
-                if (isFeaturesRepository(extract)) {
-                    addToFeaturesRepositories(extract.toURI());
-                    featuresRepositoriesInKar.add(extract.toURI());
-                }
-            }
-            
-            if ("meta-inf/manifest.mf".equals(entry.getName().toLowerCase())) {
-                InputStream is = zipFile.getInputStream(entry);
-                Manifest manifest = new Manifest(is);
-                Attributes attr = manifest.getMainAttributes();
-                String featureStartSt = (String)attr.get(new Attributes.Name(MANIFEST_ATTR_KARAF_FEATURE_START));
-                if ("false".equals(featureStartSt)) {
-                    shouldInstallFeatures = false;
-                }
-                is.close();
-            }
-
-            if (entry.getName().startsWith("resource")) {
-                String resourceEntryName = entry.getName().substring("resource/".length());
-                extract(zipFile, entry, resourceEntryName, base);
-            }
+    @Override
+    public void install(URI karUri) throws Exception {
+        String karName = new Kar(karUri).getKarName();
+        LOGGER.debug("Installing KAR {} from {}", karName, karUri);
+        File karDir = new File(storage, karName);
+        install(karUri, karDir, base);
+    }
+    
+    @Override
+    public void install(URI karUri, File repoDir, File resourceDir) throws Exception {
+        Kar kar = new Kar(karUri);
+        kar.extract(repoDir, resourceDir);
+        writeToFile(kar.getFeatureRepos(), new File(repoDir, FEATURE_CONFIG_FILE));
+        for (URI uri : kar.getFeatureRepos()) {
+            addToFeaturesRepositories(uri);
         }
-        
-        if (shouldInstallFeatures) {
-            installFeatures(featuresRepositoriesInKar);
+        if (kar.isShouldInstallFeatures()) {
+            installFeatures(kar.getFeatureRepos());
         }
-        
-        zipFile.close();
-    }
 
-    public void uninstall(String karName) throws Exception {
-        uninstall(karName, false);
+        if (mavenRepoManager != null) {
+            mavenRepoManager.addRepo(repoDir.toURI());
+        }
     }
 
-    public void uninstall(String karName, boolean clean) throws Exception {
-        File karStorage = new File(storage);
-        File karFile = new File(karStorage, karName);
-        
-        if (!karFile.exists()) {
-            throw new IllegalArgumentException("The KAR " + karName + " is not installed");
-        }
 
-        if (clean) {
-            LOGGER.debug("Looking for KAR entries to purge the local repository");
-            List<URI> featuresRepositories = new ArrayList<URI>();
-            ZipFile zipFile = new ZipFile(karFile);
-            @SuppressWarnings("unchecked")
-            Enumeration<ZipEntry> entries = (Enumeration<ZipEntry>) zipFile.entries();
-            while (entries.hasMoreElements()) {
-                ZipEntry entry = entries.nextElement();
-                String repoEntryName = getRepoEntryName(entry);
-                if (repoEntryName != null) {
-                    File toDelete = new File(localRepo + File.separator + repoEntryName);
-                    if (isFeaturesRepository(toDelete)) {
-                        featuresRepositories.add(toDelete.toURI());
-                    } else {
-                        if (toDelete.isFile() && toDelete.exists()) {
-                            toDelete.delete();
-                        }
-                    }
-                }
-                if (entry.getName().startsWith("resource")) {
-                    String resourceEntryName = entry.getName().substring("resource/".length());
-                    File toDelete = new File(base + File.separator + resourceEntryName);
-                    if (toDelete.isFile() && toDelete.exists()) {
-                        toDelete.delete();
-                    }
-                }
+    private List<URI> readFromFile(File repoListFile) {
+        ArrayList<URI> uriList = new ArrayList<URI>();
+        FileReader fr = null;
+        try {
+            fr = new FileReader(repoListFile);
+            BufferedReader br = new BufferedReader(fr);
+            String line;
+            while ((line = br.readLine()) != null) {
+                uriList.add(new URI(line));
             }
-            zipFile.close();
-
-            uninstallFeatures(featuresRepositories);
-            for (URI featuresRepository : featuresRepositories) {
-                featuresService.removeRepository(featuresRepository);
-                File toDelete = new File(featuresRepository);
-                if (toDelete.exists() && toDelete.isFile()) {
-                    toDelete.delete();
-                }
+        } catch (Exception e) {
+            throw new RuntimeException("Error reading repo list from file " + repoListFile.getAbsolutePath(), e);
+        } finally {
+            try {
+                fr.close();
+            } catch (IOException e) {
+                LOGGER.warn("Error closing reader for file " + repoListFile, e);
             }
         }
-        
-        karFile.delete();
+        return uriList;
     }
     
-    public List<String> list() throws Exception {
-        File karStorage = new File(storage);
-        List<String> kars = new ArrayList<String>();
-        for (File kar : karStorage.listFiles()) {
-            kars.add(kar.getName());
-        }
-        return kars;
-    }
-
-    /**
-     * Create a destination file using a source URL.
-     * 
-     * @param url the source URL. 
-     * @param file the destination file
-     * @throws Exception in case of copy failure
-     */
-    private void copy(URI url, File file) throws Exception {
-        InputStream is = null;
+    private void writeToFile(List<URI> featuresRepositoriesInKar, File repoListFile) {
         FileOutputStream fos = null;
+        PrintStream ps = null;
         try {
-            is = url.toURL().openStream();
-            fos = new FileOutputStream(file);
-            copyStream(is,  fos);
-        } finally {
-            if (is != null) {
-                is.close();
-            }
-            if (fos != null) {
-                fos.flush();
-                fos.close();
+            fos = new FileOutputStream(repoListFile);
+            ps = new PrintStream(fos);
+            for (URI uri : featuresRepositoriesInKar) {
+                ps.println(uri);
             }
+            ps.close();
+            fos.close();
+        } catch (Exception e) {
+            throw new RuntimeException("Error writing feature repo list to file " + repoListFile.getAbsolutePath(), e);
+        } finally {
+            closeStream(ps);
+            closeStream(fos);
         }
     }
 
-    /**
-     * Get the entry name filtering the repository  folder.
-     *
-     * @param entry the "normal" entry name.
-     * @return the entry with the repository folder filtered.
-     */
-    private String getRepoEntryName(ZipEntry entry) {
-        String entryName = entry.getName();
-        if (entryName.startsWith("repository")) {
-            return entryName.substring("repository/".length());
-        }
-        if (entryName.startsWith("META-INF") || entryName.startsWith("resources")) {
-            return null;
+    private void deleteRecursively(File dir) {
+        if (dir.isDirectory()) {
+            File[] children = dir.listFiles();
+            for (File child : children) {
+                deleteRecursively(child);
+            }
         }
-        return entryName;
+        dir.delete();
     }
 
-    /**
-     * Extract an entry from a KAR file.
-     * 
-     * @param zipFile the KAR file (zip file).
-     * @param zipEntry the entry in the KAR file.
-     * @param repoEntryName the target extract name.
-     * @param base the base directory where to extract the file.
-     * @return the extracted file.
-     * @throws Exception in the extraction fails.
-     */
-    private File extract(ZipFile zipFile, ZipEntry zipEntry, String repoEntryName, String base) throws Exception {
-        File extract;
-        if (zipEntry.isDirectory()) {
-            extract = new File(base + File.separator + repoEntryName);
-            LOGGER.debug("Creating directory {}", extract.getName());
-            extract.mkdirs();
-        } else {
-            extract = new File(base + File.separator + repoEntryName);
-            extract.getParentFile().mkdirs();
-            
-            InputStream in = zipFile.getInputStream(zipEntry);
-            FileOutputStream out = new FileOutputStream(extract);
-            
-            byte[] buffer = new byte[8192];
-            int count = in.read(buffer);
-            int totalBytes = 0;
-            
-            while (count >= 0) {
-                out.write(buffer, 0, count);
-                totalBytes += count;
-                count = in.read(buffer);
-            }
+    @Override
+    public void uninstall(String karName) throws Exception {
+        File karDir = new File(storage, karName);
 
-            LOGGER.debug("Extracted {} bytes to {}", totalBytes, extract);
+        if (!karDir.exists()) {
+            throw new IllegalArgumentException("The KAR " + karName + " is not installed");
+        }
 
-            in.close();
-            out.flush();
-            out.close();
+        List<URI> featuresRepositories = readFromFile(new File(karDir, FEATURE_CONFIG_FILE));
+        uninstallFeatures(featuresRepositories);
+        for (URI featuresRepository : featuresRepositories) {
+            featuresService.removeRepository(featuresRepository);
         }
-        return extract;
+        
+        // Creating the URI before deleting the dir to get a "/" at the end 
+        URI karURI = karDir.toURI();
+        mavenRepoManager.removeRepo(karURI);
+        deleteRecursively(karDir);
     }
-
-    /**
-     * Check if a file is a features XML.
-     *
-     * @param artifact the file to check.
-     * @return true if the artifact is a features XML, false else.
-     */
-    private boolean isFeaturesRepository(File artifact) {
-        try {
-            if (artifact.isFile() && artifact.getName().endsWith(".xml")) {
-                Document doc = parse(artifact);
-                String name = doc.getDocumentElement().getLocalName();
-                String uri  = doc.getDocumentElement().getNamespaceURI();
-                if ("features".equals(name) && (uri == null || "".equals(uri) || uri.startsWith("http://karaf.apache.org/xmlns/features/v"))) {
-                    return true;
-                }
+    
+    @Override
+    public List<String> list() throws Exception {
+        List<String> kars = new ArrayList<String>();
+        for (File kar : storage.listFiles()) {
+            if (kar.isDirectory()) {
+                kars.add(kar.getName());
             }
-        } catch (Exception e) {
-            LOGGER.debug("File '{}' is not a features file.", artifact.getName(), e);
         }
-        return false;
+        return kars;
     }
 
-    /**
-     * Parse a features XML.
-     *
-     * @param artifact the features XML to parse.
-     * @return the parsed document.
-     * @throws Exception in case of parsing failure.
-     */
-    private Document parse(File artifact) throws Exception {
-        DocumentBuilder db = dbf.newDocumentBuilder();
-        db.setErrorHandler(new ErrorHandler() {
-            public void warning(SAXParseException exception) throws SAXException {
-            }
-            public void error(SAXParseException exception) throws SAXException {
-            }
-            public void fatalError(SAXParseException exception) throws SAXException {
-                throw exception;
-            }
-        });
-        return db.parse(artifact);
-    }
+
 
     /**
      * Add an URI to the list of features repositories.
@@ -403,7 +240,7 @@ public class KarServiceImpl implements K
             File karFile = new File(karPath);
             karFile.getParentFile().mkdirs();
             fos = new FileOutputStream(karFile);
-            Manifest manifest = createNonAutoStartManifest();
+            Manifest manifest = createNonAutoStartManifest(repo.getURI());
             jos = new JarOutputStream(new BufferedOutputStream(fos, 100000), manifest);
             
             Map<URI, Integer> locationMap = new HashMap<URI, Integer>();
@@ -459,9 +296,10 @@ public class KarServiceImpl implements K
         return featureSet;
     }
 
-    private Manifest createNonAutoStartManifest() throws UnsupportedEncodingException, IOException {
+    private Manifest createNonAutoStartManifest(URI repoUri) throws UnsupportedEncodingException, IOException {
         String manifestSt = "Manifest-Version: 1.0\n" +
-            MANIFEST_ATTR_KARAF_FEATURE_START +": false\n";
+            Kar.MANIFEST_ATTR_KARAF_FEATURE_START +": false\n" +
+            Kar.MANIFEST_ATTR_KARAF_FEATURE_REPOS + ": " + repoUri.toString() + "\n";
         InputStream manifestIs = new ByteArrayInputStream(manifestSt.getBytes("UTF-8"));
         Manifest manifest = new Manifest(manifestIs);
         return manifest;
@@ -499,25 +337,13 @@ public class KarServiceImpl implements K
             InputStream is = location.toURL().openStream();
             String path = "repository/" + parser.getArtifactPath();
             jos.putNextEntry(new JarEntry(path));
-            copyStream(is, jos);
+            Kar.copyStream(is, jos);
             is.close();
             locationMap.put(location, 1);
         } catch (Exception e) {
             LOGGER.error("Error adding " + location, e);
         }
     }
-    
-    public static long copyStream(InputStream input, OutputStream output)
-            throws IOException {
-        byte[] buffer = new byte[10000];
-        long count = 0;
-        int n = 0;
-        while (-1 != (n = input.read(buffer))) {
-            output.write(buffer, 0, n);
-            count += n;
-        }
-        return count;
-    }
 
     /**
      * Uninstall all features contained in the list of features XML.
@@ -544,20 +370,4 @@ public class KarServiceImpl implements K
         }
     }
 
-    public void setFeaturesService(FeaturesService featuresService) {
-        this.featuresService = featuresService;
-    }
-
-    public void setStorage(String storage) {
-        this.storage = storage;
-    }
-
-    public void setBase(String base) {
-        this.base = base;
-    }
-
-    public void setLocalRepo(String localRepo) {
-        this.localRepo = localRepo;
-    }
-
 }

Modified: karaf/trunk/kar/core/src/main/java/org/apache/karaf/kar/internal/Kars.java
URL: http://svn.apache.org/viewvc/karaf/trunk/kar/core/src/main/java/org/apache/karaf/kar/internal/Kars.java?rev=1352937&r1=1352936&r2=1352937&view=diff
==============================================================================
--- karaf/trunk/kar/core/src/main/java/org/apache/karaf/kar/internal/Kars.java (original)
+++ karaf/trunk/kar/core/src/main/java/org/apache/karaf/kar/internal/Kars.java Fri Jun 22 15:50:19 2012
@@ -43,10 +43,6 @@ public class Kars extends StandardMBean 
     public void uninstall(String name) throws Exception {
         karService.uninstall(name);
     }
-    
-    public void uninstall(String name, boolean clean) throws Exception {
-        karService.uninstall(name, clean);
-    }
 
     public KarService getKarService() {
         return karService;

Added: karaf/trunk/kar/core/src/main/java/org/apache/karaf/kar/internal/MavenRepoManager.java
URL: http://svn.apache.org/viewvc/karaf/trunk/kar/core/src/main/java/org/apache/karaf/kar/internal/MavenRepoManager.java?rev=1352937&view=auto
==============================================================================
--- karaf/trunk/kar/core/src/main/java/org/apache/karaf/kar/internal/MavenRepoManager.java (added)
+++ karaf/trunk/kar/core/src/main/java/org/apache/karaf/kar/internal/MavenRepoManager.java Fri Jun 22 15:50:19 2012
@@ -0,0 +1,76 @@
+package org.apache.karaf.kar.internal;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Allows to add or delete repo URIs from the pax url mvn config
+ */
+public class MavenRepoManager {
+
+    static final String ATTR_REPOS = "org.ops4j.pax.url.mvn.repositories";
+    static final String PAX_URL_MVN_CONFIG_PID = "org.ops4j.pax.url.mvn";
+    private final File configFile;
+
+    public MavenRepoManager(String karafBase) {
+        File etcFile = new File(karafBase, "etc");
+        configFile = new File(etcFile, PAX_URL_MVN_CONFIG_PID + ".cfg");
+        if (!configFile.exists() && configFile.isFile()) {
+            throw new RuntimeException("Cannot find config " + configFile);
+        }
+    }
+
+    public void addRepo(URI repo) throws IOException {
+        NoSubstProperties props = new NoSubstProperties(configFile);
+        List<String> repos = getRepos((String)props.get(ATTR_REPOS));
+        String repoSt = repo.toString();
+        if (!repos.contains(repoSt)) {
+            repos.add(repoSt);
+        }
+        props.put(ATTR_REPOS, getReposAttrValue(repos));
+        props.save();
+    }
+
+    public void removeRepo(URI repo) throws IOException {
+        NoSubstProperties props = new NoSubstProperties(configFile);
+        List<String> repos = getRepos((String)props.get(ATTR_REPOS));
+        List<String> outRepos = new ArrayList<String>();
+        String repoSt = repo.toString();
+        for (String currentRepo : repos) {
+            String temp = new String(currentRepo);
+            if (!repoSt.equals(temp)) {
+                outRepos.add(currentRepo);
+            }
+        }
+        props.put(ATTR_REPOS, getReposAttrValue(outRepos));
+        props.save();
+    }
+
+    List<String> getRepos(String repos) throws IOException {
+        repos = repos.replaceAll("\\s*\\\\\\r?\\n", "");
+        String[] origRepoAr = repos.split("\\s*,\\s*\\\\?");
+        List<String> repoList = new ArrayList<String>();
+        for (String repo : origRepoAr) {
+            repo = repo.trim();
+            if (repo != null && !"".equals(repo)) {
+                repoList.add(repo);
+            }
+        }
+        return repoList;
+    }
+
+    String getReposAttrValue(List<String> repos) {
+        StringBuilder repoAttr = new StringBuilder();
+        repoAttr.append("\\\n");
+        for (int c=0; c<repos.size(); c++) {
+            repoAttr.append(repos.get(c));
+            if (c+1<repos.size()) {
+                repoAttr.append(", \\\n");
+            }
+        }
+        return repoAttr.toString();
+    }
+}

Added: karaf/trunk/kar/core/src/main/java/org/apache/karaf/kar/internal/NoSubstProperties.java
URL: http://svn.apache.org/viewvc/karaf/trunk/kar/core/src/main/java/org/apache/karaf/kar/internal/NoSubstProperties.java?rev=1352937&view=auto
==============================================================================
--- karaf/trunk/kar/core/src/main/java/org/apache/karaf/kar/internal/NoSubstProperties.java (added)
+++ karaf/trunk/kar/core/src/main/java/org/apache/karaf/kar/internal/NoSubstProperties.java Fri Jun 22 15:50:19 2012
@@ -0,0 +1,1041 @@
+/*
+ * 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.kar.internal;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FilterWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.LineNumberReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Reader;
+import java.io.Writer;
+import java.net.URL;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.AbstractMap;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import org.osgi.framework.BundleContext;
+
+/**
+ * <p>
+ * Enhancement of the standard <code>Properties</code>
+ * managing the maintain of comments, etc.
+ * </p>
+ *
+ * @author gnodet, jbonofre
+ */
+public class NoSubstProperties extends AbstractMap<String, String> {
+
+    /** Constant for the supported comment characters.*/
+    private static final String COMMENT_CHARS = "#!";
+
+    /** The list of possible key/value separators */
+    private static final char[] SEPARATORS = new char[] {'=', ':'};
+
+    /** The white space characters used as key/value separators. */
+    private static final char[] WHITE_SPACE = new char[] {' ', '\t', '\f'};
+
+    /**
+     * The default encoding (ISO-8859-1 as specified by
+     * http://java.sun.com/j2se/1.5.0/docs/api/java/util/Properties.html)
+     */
+    private static final String DEFAULT_ENCODING = "ISO-8859-1";
+
+    /** Constant for the platform specific line separator.*/
+    private static final String LINE_SEPARATOR = AccessController.doPrivileged(new PrivilegedAction<String>() {
+            public String run() {
+                return System.getProperty("line.separator");
+            }
+        });
+
+    /** Constant for the radix of hex numbers.*/
+    private static final int HEX_RADIX = 16;
+
+    /** Constant for the length of a unicode literal.*/
+    private static final int UNICODE_LEN = 4;
+
+    private final Map<String,String> storage = new LinkedHashMap<String,String>();
+    private final Map<String,Layout> layout = new LinkedHashMap<String,Layout>();
+    private List<String> header;
+    private List<String> footer;
+    private File location;
+    private BundleContext context;
+
+    public NoSubstProperties() {
+    }
+
+    public NoSubstProperties(File location) throws IOException {
+      this(location, null);
+    }
+
+    public NoSubstProperties(File location, BundleContext context) throws IOException {
+        this.location = location;
+        this.context = context;
+        if(location.exists())
+            load(location);
+    }
+
+    public void load(File location) throws IOException {
+        InputStream is = new FileInputStream(location);
+        try {
+            load(is);
+        } finally {
+            is.close();
+        }
+    }
+
+    public void load(URL location) throws IOException {
+        InputStream is = location.openStream();
+        try {
+            load(is);
+        } finally {
+            is.close();
+        }
+    }
+
+    public void load(InputStream is) throws IOException {
+        load(new InputStreamReader(is, DEFAULT_ENCODING));
+    }
+
+    public void load(Reader reader) throws IOException {
+        loadLayout(reader);
+    }
+
+    public void save() throws IOException {
+        save(this.location);
+    }
+
+    public void save(File location) throws IOException {
+        OutputStream os = new FileOutputStream(location);
+        try {
+            save(os);
+        } finally {
+            os.close();
+        }
+    }
+
+    public void save(OutputStream os) throws IOException {
+        save(new OutputStreamWriter(os, DEFAULT_ENCODING));
+    }
+
+    public void save(Writer writer) throws IOException {
+        saveLayout(writer);
+    }
+
+    /**
+     * Store a properties into a output stream, preserving comments, special character, etc.
+     * This method is mainly to be compatible with the java.util.Properties class.
+     *
+     * @param os an output stream.
+     * @param comment this parameter is ignored as this Properties
+     * @throws IOException
+     */
+    public void store(OutputStream os, String comment) throws IOException {
+        this.save(os);
+    }
+
+    /**
+     * Searches for the property with the specified key in this property list.
+     *
+     * @param key the property key.
+     * @return the value in this property list with the specified key value.
+     */
+    public String getProperty(String key) {
+        return this.get(key);
+    }
+
+    /**
+     * Searches for the property with the specified key in this property list. If the key is not found in this property
+     * list, the default property list, and its defaults, recursively, are then checked. The method returns the default
+     * value argument if the property is not found.
+     *
+     * @param key the property key.
+     * @param defaultValue a default value.
+     * @return
+     */
+    public String getProperty(String key, String defaultValue) {
+        if (this.get(key) != null)
+            return this.get(key);
+        return defaultValue;
+    }
+
+    @Override
+    public Set<Entry<String, String>> entrySet() {
+        return storage.entrySet();
+    }
+
+    /**
+     * Returns an enumeration of all the keys in this property list, including distinct keys in the default property
+     * list if a key of the same name has not already been found from the main properties list.
+     *
+     * @return an enumeration of all the keys in this property list, including the keys in the default property list.
+     */
+    public Enumeration<?> propertyNames() {
+        return Collections.enumeration(storage.keySet());
+    }
+
+    /**
+     * Calls the map method put. Provided for parallelism with the getProperty method.
+     * Enforces use of strings for property keys and values. The value returned is the result of the map call to put.
+     *
+     * @param key the key to be placed into this property list.
+     * @param value the value corresponding to the key.
+     * @return the previous value of the specified key in this property list, or null if it did not have one.
+     */
+    public Object setProperty(String key, String value) {
+        return this.put(key, value);
+    }
+
+    @Override
+    public String put(String key, String value) {
+        String old = storage.put(key, value);
+        if (old == null || !old.equals(value)) {
+            Layout l = layout.get(key);
+            if (l != null) {
+                l.clearValue();
+            }
+        }
+        return old;
+    }
+
+    public String put(String key, List<String> commentLines, List<String> valueLines) {
+        commentLines = new ArrayList<String>(commentLines);
+        valueLines = new ArrayList<String>(valueLines);
+        String escapedKey = escapeKey(key);
+        int lastLine = valueLines.size() - 1;
+        if (valueLines.isEmpty()) {
+            valueLines.add(escapedKey + "=");
+        } else if (!valueLines.get(0).trim().startsWith(escapedKey)) {
+            valueLines.set(0, escapedKey + " = " + escapeJava(valueLines.get(0)) + (0 < lastLine? "\\": ""));
+        }
+        for (int i = 1; i < valueLines.size(); i++) {
+            valueLines.set(i, escapeJava(valueLines.get(i)) + (i < lastLine? "\\": ""));
+        }
+        StringBuilder value = new StringBuilder();
+        for (String line: valueLines) {
+            value.append(line);
+        }
+        this.layout.put(key, new Layout(commentLines, valueLines));
+        return storage.put(key, unescapeJava(value.toString()));
+    }
+
+    public String put(String key, List<String> commentLines, String value) {
+        commentLines = new ArrayList<String>(commentLines);
+        this.layout.put(key, new Layout(commentLines, null));
+        return storage.put(key, value);
+    }
+
+    public String put(String key, String comment, String value) {
+        return put(key, Collections.singletonList(comment), value);
+    }
+
+    public List<String> getRaw(String key) {
+        if (layout.containsKey(key)) {
+            if (layout.get(key).getValueLines() != null) {
+                return new ArrayList<String>(layout.get(key).getValueLines());
+            }
+        }
+        List<String> result = new ArrayList<String>();
+        if (storage.containsKey(key)) {
+            result.add(storage.get(key));
+        }
+        return result;
+    }
+
+    @Override
+    public String remove(Object key) {
+        Layout l = layout.get(key);
+        if (l != null) {
+            l.clearValue();
+        }
+        return storage.remove(key);
+    }
+
+    @Override
+    public void clear() {
+        for (Layout l : layout.values()) {
+            l.clearValue();
+        }
+        storage.clear();
+    }
+
+    /**
+     * Return the comment header.
+     *
+     * @return the comment header
+     */
+    public List<String> getHeader()
+    {
+        return header;
+    }
+
+    /**
+     * Set the comment header.
+     *
+     * @param header the header to use
+     */
+    public void setHeader(List<String> header)
+    {
+        this.header = header;
+    }
+
+    /**
+     * Return the comment footer.
+     *
+     * @return the comment footer
+     */
+    public List<String> getFooter()
+    {
+        return footer;
+    }
+
+    /**
+     * Set the comment footer.
+     *
+     * @param footer the footer to use
+     */
+    public void setFooter(List<String> footer)
+    {
+        this.footer = footer;
+    }
+
+    /**
+     * Reads a properties file and stores its internal structure. The found
+     * properties will be added to the associated configuration object.
+     *
+     * @param in the reader to the properties file
+     * @throws java.io.IOException if an error occurs
+     */
+    protected void loadLayout(Reader in) throws IOException
+    {
+        PropertiesReader reader = new PropertiesReader(in);
+        while (reader.nextProperty())
+        {
+            storage.put(reader.getPropertyName(), reader.getPropertyValue());
+            int idx = checkHeaderComment(reader.getCommentLines());
+            layout.put(reader.getPropertyName(),
+                    new Layout(idx < reader.getCommentLines().size() ?
+                                    new ArrayList<String>(reader.getCommentLines().subList(idx, reader.getCommentLines().size())) :
+                                    null,
+                               new ArrayList<String>(reader.getValueLines())));
+        }
+        footer = new ArrayList<String>(reader.getCommentLines());
+    }
+
+    /**
+     * Writes the properties file to the given writer, preserving as much of its
+     * structure as possible.
+     *
+     * @param out the writer
+     * @throws java.io.IOException if an error occurs
+     */
+    protected void saveLayout(Writer out) throws IOException
+    {
+        PropertiesWriter writer = new PropertiesWriter(out);
+        if (header != null)
+        {
+            for (String s : header)
+            {
+                writer.writeln(s);
+            }
+        }
+
+        for (String key : storage.keySet())
+        {
+            Layout l = layout.get(key);
+            if (l != null && l.getCommentLines() != null)
+            {
+                for (String s : l.getCommentLines())
+                {
+                    writer.writeln(s);
+                }
+            }
+            if (l != null && l.getValueLines() != null)
+            {
+                for (String s : l.getValueLines())
+                {
+                    writer.writeln(s);
+                }
+            }
+            else
+            {
+                writer.writeProperty(key, storage.get(key));
+            }
+        }
+        if (footer != null)
+        {
+            for (String s : footer)
+            {
+                writer.writeln(s);
+            }
+        }
+        writer.flush();
+    }
+
+    /**
+     * Checks if parts of the passed in comment can be used as header comment.
+     * This method checks whether a header comment can be defined (i.e. whether
+     * this is the first comment in the loaded file). If this is the case, it is
+     * searched for the lates blank line. This line will mark the end of the
+     * header comment. The return value is the index of the first line in the
+     * passed in list, which does not belong to the header comment.
+     *
+     * @param commentLines the comment lines
+     * @return the index of the next line after the header comment
+     */
+    private int checkHeaderComment(List<String> commentLines)
+    {
+        if (getHeader() == null && layout.isEmpty())
+        {
+            // This is the first comment. Search for blank lines.
+            int index = commentLines.size() - 1;
+            while (index >= 0 && commentLines.get(index).length() > 0)
+            {
+                index--;
+            }
+            setHeader(new ArrayList<String>(commentLines.subList(0, index + 1)));
+            return index + 1;
+        }
+        else
+        {
+            return 0;
+        }
+    }
+
+    /**
+     * Tests whether a line is a comment, i.e. whether it starts with a comment
+     * character.
+     *
+     * @param line the line
+     * @return a flag if this is a comment line
+     */
+    static boolean isCommentLine(String line) {
+        String s = line.trim();
+        // blank lines are also treated as comment lines
+        return s.length() < 1 || COMMENT_CHARS.indexOf(s.charAt(0)) >= 0;
+    }
+
+    /**
+     * <p>Unescapes any Java literals found in the <code>String</code> to a
+     * <code>Writer</code>.</p> This is a slightly modified version of the
+     * StringEscapeUtils.unescapeJava() function in commons-lang that doesn't
+     * drop escaped separators (i.e '\,').
+     *
+     * @param str  the <code>String</code> to unescape, may be null
+     * @return the processed string
+     * @throws IllegalArgumentException if the Writer is <code>null</code>
+     */
+    protected static String unescapeJava(String str) {
+        if (str == null) {
+            return null;
+        }
+        int sz = str.length();
+        StringBuffer out = new StringBuffer(sz);
+        StringBuffer unicode = new StringBuffer(UNICODE_LEN);
+        boolean hadSlash = false;
+        boolean inUnicode = false;
+        for (int i = 0; i < sz; i++) {
+            char ch = str.charAt(i);
+            if (inUnicode) {
+                // if in unicode, then we're reading unicode
+                // values in somehow
+                unicode.append(ch);
+                if (unicode.length() == UNICODE_LEN) {
+                    // unicode now contains the four hex digits
+                    // which represents our unicode character
+                    try {
+                        int value = Integer.parseInt(unicode.toString(), HEX_RADIX);
+                        out.append((char) value);
+                        unicode.setLength(0);
+                        inUnicode = false;
+                        hadSlash = false;
+                    } catch (NumberFormatException nfe) {
+                        throw new IllegalArgumentException("Unable to parse unicode value: " + unicode, nfe);
+                    }
+                }
+                continue;
+            }
+
+            if (hadSlash) {
+                // handle an escaped value
+                hadSlash = false;
+                switch (ch) {
+                    case '\\' :
+                        out.append('\\');
+                        break;
+                    case '\'' :
+                        out.append('\'');
+                        break;
+                    case '\"' :
+                        out.append('"');
+                        break;
+                    case 'r' :
+                        out.append('\r');
+                        break;
+                    case 'f' :
+                        out.append('\f');
+                        break;
+                    case 't' :
+                        out.append('\t');
+                        break;
+                    case 'n' :
+                        out.append('\n');
+                        break;
+                    case 'b' :
+                        out.append('\b');
+                        break;
+                    case 'u' :
+                        // uh-oh, we're in unicode country....
+                        inUnicode = true;
+                        break;
+                    default :
+                        out.append(ch);
+                        break;
+                }
+                continue;
+            } else if (ch == '\\') {
+                hadSlash = true;
+                continue;
+            }
+            out.append(ch);
+        }
+
+        if (hadSlash) {
+            // then we're in the weird case of a \ at the end of the
+            // string, let's output it anyway.
+            out.append('\\');
+        }
+
+        return out.toString();
+    }
+
+    /**
+     * <p>Escapes the characters in a <code>String</code> using Java String rules.</p>
+     *
+     * <p>Deals correctly with quotes and control-chars (tab, backslash, cr, ff, etc.) </p>
+     *
+     * <p>So a tab becomes the characters <code>'\\'</code> and
+     * <code>'t'</code>.</p>
+     *
+     * <p>The only difference between Java strings and JavaScript strings
+     * is that in JavaScript, a single quote must be escaped.</p>
+     *
+     * <p>Example:
+     * <pre>
+     * input string: He didn't say, "Stop!"
+     * output string: He didn't say, \"Stop!\"
+     * </pre>
+     * </p>
+     *
+     * @param str  String to escape values in, may be null
+     * @return String with escaped values, <code>null</code> if null string input
+     */
+    protected static String escapeJava(String str) {
+        if (str == null) {
+            return null;
+        }
+        int sz = str.length();
+        StringBuffer out = new StringBuffer(sz * 2);
+        for (int i = 0; i < sz; i++) {
+            char ch = str.charAt(i);
+            // handle unicode
+            if (ch > 0xfff) {
+                out.append("\\u").append(hex(ch));
+            } else if (ch > 0xff) {
+                out.append("\\u0").append(hex(ch));
+            } else if (ch > 0x7f) {
+                out.append("\\u00").append(hex(ch));
+            } else if (ch < 32) {
+                switch (ch) {
+                    case '\b' :
+                        out.append('\\');
+                        out.append('b');
+                        break;
+                    case '\n' :
+                        out.append('\n');
+                        break;
+                    case '\t' :
+                        out.append('\\');
+                        out.append('t');
+                        break;
+                    case '\f' :
+                        out.append('\\');
+                        out.append('f');
+                        break;
+                    case '\r' :
+                        out.append('\\');
+                        out.append('r');
+                        break;
+                    default :
+                        if (ch > 0xf) {
+                            out.append("\\u00").append(hex(ch));
+                        } else {
+                            out.append("\\u000").append(hex(ch));
+                        }
+                        break;
+                }
+            } else {
+                switch (ch) {
+                    case '"' :
+                        out.append('\\');
+                        out.append('"');
+                        break;
+                    default :
+                        out.append(ch);
+                        break;
+                }
+            }
+        }
+        return out.toString();
+    }
+
+    /**
+     * <p>Returns an upper case hexadecimal <code>String</code> for the given
+     * character.</p>
+     *
+     * @param ch The character to convert.
+     * @return An upper case hexadecimal <code>String</code>
+     */
+    protected static String hex(char ch) {
+        return Integer.toHexString(ch).toUpperCase(Locale.ENGLISH);
+    }
+
+    /**
+     * <p>Checks if the value is in the given array.</p>
+     *
+     * <p>The method returns <code>false</code> if a <code>null</code> array is passed in.</p>
+     *
+     * @param array  the array to search through
+     * @param valueToFind  the value to find
+     * @return <code>true</code> if the array contains the object
+     */
+    public static boolean contains(char[] array, char valueToFind) {
+        if (array == null) {
+            return false;
+        }
+        for (int i = 0; i < array.length; i++) {
+            if (valueToFind == array[i]) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Escape the separators in the key.
+     *
+     * @param key the key
+     * @return the escaped key
+     */
+    private static String escapeKey(String key)
+    {
+        StringBuffer newkey = new StringBuffer();
+
+        for (int i = 0; i < key.length(); i++)
+        {
+            char c = key.charAt(i);
+
+            if (contains(SEPARATORS, c) || contains(WHITE_SPACE, c))
+            {
+                // escape the separator
+                newkey.append('\\');
+                newkey.append(c);
+            }
+            else
+            {
+                newkey.append(c);
+            }
+        }
+
+        return newkey.toString();
+    }
+
+    /**
+     * This class is used to read properties lines. These lines do
+     * not terminate with new-line chars but rather when there is no
+     * backslash sign a the end of the line.  This is used to
+     * concatenate multiple lines for readability.
+     */
+    public static class PropertiesReader extends LineNumberReader
+    {
+        /** Stores the comment lines for the currently processed property.*/
+        private final List<String> commentLines;
+
+        /** Stores the value lines for the currently processed property.*/
+        private final List<String> valueLines;
+
+        /** Stores the name of the last read property.*/
+        private String propertyName;
+
+        /** Stores the value of the last read property.*/
+        private String propertyValue;
+
+        /**
+         * Creates a new instance of <code>PropertiesReader</code> and sets
+         * the underlaying reader and the list delimiter.
+         *
+         * @param reader the reader
+         */
+        public PropertiesReader(Reader reader)
+        {
+            super(reader);
+            commentLines = new ArrayList<String>();
+            valueLines = new ArrayList<String>();
+        }
+
+        /**
+         * Reads a property line. Returns null if Stream is
+         * at EOF. Concatenates lines ending with "\".
+         * Skips lines beginning with "#" or "!" and empty lines.
+         * The return value is a property definition (<code>&lt;name&gt;</code>
+         * = <code>&lt;value&gt;</code>)
+         *
+         * @return A string containing a property value or null
+         *
+         * @throws java.io.IOException in case of an I/O error
+         */
+        public String readProperty() throws IOException
+        {
+            commentLines.clear();
+            valueLines.clear();
+            StringBuffer buffer = new StringBuffer();
+
+            while (true)
+            {
+                String line = readLine();
+                if (line == null)
+                {
+                    // EOF
+                    return null;
+                }
+
+                if (isCommentLine(line))
+                {
+                    commentLines.add(line);
+                    continue;
+                }
+
+                valueLines.add(line);
+                line = line.trim();
+
+                if (checkCombineLines(line))
+                {
+                    line = line.substring(0, line.length() - 1);
+                    buffer.append(line);
+                }
+                else
+                {
+                    buffer.append(line);
+                    break;
+                }
+            }
+            return buffer.toString();
+        }
+
+        /**
+         * Parses the next property from the input stream and stores the found
+         * name and value in internal fields. These fields can be obtained using
+         * the provided getter methods. The return value indicates whether EOF
+         * was reached (<b>false</b>) or whether further properties are
+         * available (<b>true</b>).
+         *
+         * @return a flag if further properties are available
+         * @throws java.io.IOException if an error occurs
+         */
+        public boolean nextProperty() throws IOException
+        {
+            String line = readProperty();
+
+            if (line == null)
+            {
+                return false; // EOF
+            }
+
+            // parse the line
+            String[] property = parseProperty(line);
+            propertyName = unescapeJava(property[0]);
+            propertyValue = unescapeJava(property[1]);
+            return true;
+        }
+
+        /**
+         * Returns the comment lines that have been read for the last property.
+         *
+         * @return the comment lines for the last property returned by
+         * <code>readProperty()</code>
+         */
+        public List<String> getCommentLines()
+        {
+            return commentLines;
+        }
+
+        /**
+         * Returns the value lines that have been read for the last property.
+         *
+         * @return the raw value lines for the last property returned by
+         * <code>readProperty()</code>
+         */
+        public List<String> getValueLines()
+        {
+            return valueLines;
+        }
+
+        /**
+         * Returns the name of the last read property. This method can be called
+         * after <code>{@link #nextProperty()}</code> was invoked and its
+         * return value was <b>true</b>.
+         *
+         * @return the name of the last read property
+         */
+        public String getPropertyName()
+        {
+            return propertyName;
+        }
+
+        /**
+         * Returns the value of the last read property. This method can be
+         * called after <code>{@link #nextProperty()}</code> was invoked and
+         * its return value was <b>true</b>.
+         *
+         * @return the value of the last read property
+         */
+        public String getPropertyValue()
+        {
+            return propertyValue;
+        }
+
+        /**
+         * Checks if the passed in line should be combined with the following.
+         * This is true, if the line ends with an odd number of backslashes.
+         *
+         * @param line the line
+         * @return a flag if the lines should be combined
+         */
+        private static boolean checkCombineLines(String line)
+        {
+            int bsCount = 0;
+            for (int idx = line.length() - 1; idx >= 0 && line.charAt(idx) == '\\'; idx--)
+            {
+                bsCount++;
+            }
+
+            return bsCount % 2 != 0;
+        }
+
+        /**
+         * Parse a property line and return the key and the value in an array.
+         *
+         * @param line the line to parse
+         * @return an array with the property's key and value
+         */
+        private static String[] parseProperty(String line)
+        {
+            // sorry for this spaghetti code, please replace it as soon as
+            // possible with a regexp when the Java 1.3 requirement is dropped
+
+            String[] result = new String[2];
+            StringBuffer key = new StringBuffer();
+            StringBuffer value = new StringBuffer();
+
+            // state of the automaton:
+            // 0: key parsing
+            // 1: antislash found while parsing the key
+            // 2: separator crossing
+            // 3: value parsing
+            int state = 0;
+
+            for (int pos = 0; pos < line.length(); pos++)
+            {
+                char c = line.charAt(pos);
+
+                switch (state)
+                {
+                    case 0:
+                        if (c == '\\')
+                        {
+                            state = 1;
+                        }
+                        else if (contains(WHITE_SPACE, c))
+                        {
+                            // switch to the separator crossing state
+                            state = 2;
+                        }
+                        else if (contains(SEPARATORS, c))
+                        {
+                            // switch to the value parsing state
+                            state = 3;
+                        }
+                        else
+                        {
+                            key.append(c);
+                        }
+
+                        break;
+
+                    case 1:
+                        if (contains(SEPARATORS, c) || contains(WHITE_SPACE, c))
+                        {
+                            // this is an escaped separator or white space
+                            key.append(c);
+                        }
+                        else
+                        {
+                            // another escaped character, the '\' is preserved
+                            key.append('\\');
+                            key.append(c);
+                        }
+
+                        // return to the key parsing state
+                        state = 0;
+
+                        break;
+
+                    case 2:
+                        if (contains(WHITE_SPACE, c))
+                        {
+                            // do nothing, eat all white spaces
+                            state = 2;
+                        }
+                        else if (contains(SEPARATORS, c))
+                        {
+                            // switch to the value parsing state
+                            state = 3;
+                        }
+                        else
+                        {
+                            // any other character indicates we encoutered the beginning of the value
+                            value.append(c);
+
+                            // switch to the value parsing state
+                            state = 3;
+                        }
+
+                        break;
+
+                    case 3:
+                        value.append(c);
+                        break;
+                }
+            }
+
+            result[0] = key.toString().trim();
+            result[1] = value.toString().trim();
+
+            return result;
+        }
+    } // class PropertiesReader
+
+    /**
+     * This class is used to write properties lines.
+     */
+    public static class PropertiesWriter extends FilterWriter
+    {
+        /**
+         * Constructor.
+         *
+         * @param writer a Writer object providing the underlying stream
+         */
+        public PropertiesWriter(Writer writer)
+        {
+            super(writer);
+        }
+
+        /**
+         * Writes the given property and its value.
+         *
+         * @param key the property key
+         * @param value the property value
+         * @throws java.io.IOException if an error occurs
+         */
+        public void writeProperty(String key, String value) throws IOException
+        {
+            write(escapeKey(key));
+            write(" = ");
+            write(escapeJava(value));
+            writeln(null);
+        }
+
+        /**
+         * Helper method for writing a line with the platform specific line
+         * ending.
+         *
+         * @param s the content of the line (may be <b>null</b>)
+         * @throws java.io.IOException if an error occurs
+         */
+        public void writeln(String s) throws IOException
+        {
+            if (s != null)
+            {
+                write(s);
+            }
+            write(LINE_SEPARATOR);
+        }
+
+    } // class PropertiesWriter
+
+    /**
+     * TODO
+     */
+    protected static class Layout {
+
+        private List<String> commentLines;
+        private List<String> valueLines;
+
+        public Layout() {
+        }
+
+        public Layout(List<String> commentLines, List<String> valueLines) {
+            this.commentLines = commentLines;
+            this.valueLines = valueLines;
+        }
+
+        public List<String> getCommentLines() {
+            return commentLines;
+        }
+
+        public void setCommentLines(List<String> commentLines) {
+            this.commentLines = commentLines;
+        }
+
+        public List<String> getValueLines() {
+            return valueLines;
+        }
+
+        public void setValueLines(List<String> valueLines) {
+            this.valueLines = valueLines;
+        }
+
+        public void clearValue() {
+            this.valueLines = null;
+        }
+
+    } // class Layout
+
+}
\ No newline at end of file

Modified: karaf/trunk/kar/core/src/main/resources/OSGI-INF/blueprint/kar-core.xml
URL: http://svn.apache.org/viewvc/karaf/trunk/kar/core/src/main/resources/OSGI-INF/blueprint/kar-core.xml?rev=1352937&r1=1352936&r2=1352937&view=diff
==============================================================================
--- karaf/trunk/kar/core/src/main/resources/OSGI-INF/blueprint/kar-core.xml (original)
+++ karaf/trunk/kar/core/src/main/resources/OSGI-INF/blueprint/kar-core.xml Fri Jun 22 15:50:19 2012
@@ -20,14 +20,18 @@
            default-activation="lazy">
 
     <ext:property-placeholder placeholder-prefix="$[" placeholder-suffix="]" />
+    
+    <reference id="featuresService" interface="org.apache.karaf.features.FeaturesService"/>
+    <reference id="configAdmin" interface="org.osgi.service.cm.ConfigurationAdmin" />
 
-    <bean id="karService" class="org.apache.karaf.kar.internal.KarServiceImpl" init-method="init">
-        <property name="base" value="$[karaf.base]"/>
-        <property name="storage" value="$[karaf.data]/kar"/>
-        <property name="localRepo" value="$[karaf.base]/system"/>
-        <property name="featuresService">
-            <reference interface="org.apache.karaf.features.FeaturesService"/>
-        </property>
+    <bean id="karService" class="org.apache.karaf.kar.internal.KarServiceImpl">
+        <argument value="$[karaf.base]" />
+        <argument ref="featuresService" />
+        <argument ref="mavenRepoManager"/>
+    </bean>
+    
+    <bean id="mavenRepoManager" class="org.apache.karaf.kar.internal.MavenRepoManager">
+        <argument value="$[karaf.base]" />
     </bean>
 
     <service ref="karService" interface="org.apache.karaf.kar.KarService"/>

Added: karaf/trunk/kar/core/src/test/java/org/apache/karaf/kar/internal/MavenRepoManagerTest.java
URL: http://svn.apache.org/viewvc/karaf/trunk/kar/core/src/test/java/org/apache/karaf/kar/internal/MavenRepoManagerTest.java?rev=1352937&view=auto
==============================================================================
--- karaf/trunk/kar/core/src/test/java/org/apache/karaf/kar/internal/MavenRepoManagerTest.java (added)
+++ karaf/trunk/kar/core/src/test/java/org/apache/karaf/kar/internal/MavenRepoManagerTest.java Fri Jun 22 15:50:19 2012
@@ -0,0 +1,43 @@
+package org.apache.karaf.kar.internal;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class MavenRepoManagerTest {
+
+    @Test
+    public void testGetRepos() throws IOException {
+        String reposAttr = " \\\n" + 
+        		"    http://repo1.maven.org/maven2@id=central, \\\n" + 
+        		"    http://repository.apache.org/content/groups/snapshots-group@id=apache@snapshots@noreleases, \\\n" + 
+        		"    https://oss.sonatype.org/content/repositories/ops4j-snapshots@id=ops4j.sonatype.snapshots.deploy@snapshots@noreleases, \\\n" + 
+        		"    file:${karaf.home}/${karaf.default.repository}@id=systemrepo";
+
+        MavenRepoManager repoManager = new MavenRepoManager(null);
+        List<String> repos = repoManager.getRepos(reposAttr);
+
+        Assert.assertEquals(4, repos.size());
+        Assert.assertEquals("http://repo1.maven.org/maven2@id=central", repos.get(0));
+        Assert.assertEquals("file:${karaf.home}/${karaf.default.repository}@id=systemrepo", repos.get(3));
+    }
+    
+    @Test
+    public void testGetReposAttrValue() throws IOException {
+        List<String> repos = new ArrayList<String>();
+        repos.add("http://repo1.maven.org/maven2@id=central");
+        repos.add("http://repository.apache.org/content/groups/snapshots-group@id=apache@snapshots@noreleases");
+        repos.add("file:${karaf.home}/${karaf.default.repository}@id=systemrepo");
+
+        MavenRepoManager repoManager = new MavenRepoManager(null);
+        String reposSt = repoManager.getReposAttrValue(repos);
+
+        Assert.assertEquals("\\\n" + 
+        		"http://repo1.maven.org/maven2@id=central, \\\n" + 
+        		"http://repository.apache.org/content/groups/snapshots-group@id=apache@snapshots@noreleases, \\\n" + 
+        		"file:${karaf.home}/${karaf.default.repository}@id=systemrepo", reposSt);
+    }
+}

Modified: karaf/trunk/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/features/InstallKarsMojo.java
URL: http://svn.apache.org/viewvc/karaf/trunk/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/features/InstallKarsMojo.java?rev=1352937&r1=1352936&r2=1352937&view=diff
==============================================================================
--- karaf/trunk/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/features/InstallKarsMojo.java (original)
+++ karaf/trunk/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/features/InstallKarsMojo.java Fri Jun 22 15:50:19 2012
@@ -49,7 +49,7 @@ import org.apache.karaf.features.interna
 import org.apache.karaf.features.internal.model.Feature;
 import org.apache.karaf.features.internal.model.Features;
 import org.apache.karaf.features.internal.model.JaxbUtil;
-import org.apache.karaf.kar.internal.KarServiceImpl;
+import org.apache.karaf.kar.internal.Kar;
 import org.apache.karaf.tooling.utils.MojoSupport;
 import org.apache.maven.artifact.Artifact;
 import org.apache.maven.artifact.repository.layout.DefaultRepositoryLayout;
@@ -194,29 +194,27 @@ public class InstallKarsMojo extends Moj
                 startupPropertiesFile.getParentFile().mkdirs();
             }
         }
-        KarServiceImpl karService = new KarServiceImpl();
+
         FeaturesService featuresService = new OfflineFeaturesService();
-        karService.setFeaturesService(featuresService);
-        karService.setBase(workDirectory);
-        try {
-            karService.init();
-        } catch (Exception e) {
-            throw new MojoExecutionException("Can't init the KAR service", e);
-        }
 
         Collection<Artifact> dependencies = project.getDependencyArtifacts();
         StringBuilder buf = new StringBuilder();
         byte[] buffer = new byte[4096];
         for (Artifact artifact : dependencies) {
             dontAddToStartup = "runtime".equals(artifact.getScope());
-            karService.setLocalRepo(system.getPath());
             if ("kar".equals(artifact.getType()) && acceptScope(artifact)) {
                 File file = artifact.getFile();
                 try {
-                    karService.install(file.toURI());
+                    Kar kar = new Kar(file.toURI());
+                    kar.extract(new File(system.getPath()), new File(workDirectory));
+                    for (URI repoUri : kar.getFeatureRepos()) {
+                        featuresService.removeRepository(repoUri);
+                        featuresService.addRepository(repoUri);
+                    }
                 } catch (Exception e) {
-                    buf.append("Could not install kar: ").append(artifact.toString()).append("\n");
-                    buf.append(e.getMessage()).append("\n\n");
+                    throw new RuntimeException("Could not install kar: " + artifact.toString() + "\n", e);
+                    //buf.append("Could not install kar: ").append(artifact.toString()).append("\n");
+                    //buf.append(e.getMessage()).append("\n\n");
                 }
             }
             if ("features".equals(artifact.getClassifier()) && acceptScope(artifact)) {