You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@karaf.apache.org by gg...@apache.org on 2017/06/21 13:27:23 UTC

[10/12] karaf git commit: [KARAF-5008] maven:repository-add command

[KARAF-5008] maven:repository-add command


Project: http://git-wip-us.apache.org/repos/asf/karaf/repo
Commit: http://git-wip-us.apache.org/repos/asf/karaf/commit/73953cca
Tree: http://git-wip-us.apache.org/repos/asf/karaf/tree/73953cca
Diff: http://git-wip-us.apache.org/repos/asf/karaf/diff/73953cca

Branch: refs/heads/master-maven-commands
Commit: 73953ccaa91017a575fe56235a849369c3ed8452
Parents: 420207b
Author: Grzegorz Grzybek <gr...@gmail.com>
Authored: Wed Jun 21 09:33:51 2017 +0200
Committer: Grzegorz Grzybek <gr...@gmail.com>
Committed: Wed Jun 21 09:33:51 2017 +0200

----------------------------------------------------------------------
 maven/core/pom.xml                              |   6 +
 .../command/MavenConfigurationSupport.java      |  81 +++++++-
 .../karaf/maven/command/PasswordCommand.java    |   2 +
 .../maven/command/RepositoryAddCommand.java     | 196 ++++++++++++++++++-
 .../command/RepositoryEditCommandSupport.java   |  22 ++-
 .../maven/command/RepositoryRemoveCommand.java  |   4 +-
 .../karaf/maven/command/SummaryCommand.java     |  51 -----
 .../karaf/maven/core/MavenRepositoryURL.java    |  47 +++++
 .../org/apache/karaf/maven/SettingsTest.java    |   2 +
 .../command/MavenConfigurationSupportTest.java  |   8 +-
 .../maven/core/MavenRepositoryURLTest.java      |  64 ++++++
 11 files changed, 420 insertions(+), 63 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/karaf/blob/73953cca/maven/core/pom.xml
----------------------------------------------------------------------
diff --git a/maven/core/pom.xml b/maven/core/pom.xml
index a2a724f..403835e 100644
--- a/maven/core/pom.xml
+++ b/maven/core/pom.xml
@@ -66,6 +66,11 @@
             <artifactId>maven-embedder</artifactId>
             <version>3.0.3</version>
         </dependency>
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.utils</artifactId>
+            <scope>provided</scope>
+        </dependency>
 
         <dependency>
             <groupId>org.slf4j</groupId>
@@ -111,6 +116,7 @@
                             *,
                         </Import-Package>
                         <Private-Package>
+                            org.apache.felix.utils.properties;-split-package:=merge-first,
                             org.apache.karaf.maven.command,
                             org.apache.karaf.maven.core,
                             org.apache.maven.settings.*;-split-package:=merge-first,

http://git-wip-us.apache.org/repos/asf/karaf/blob/73953cca/maven/core/src/main/java/org/apache/karaf/maven/command/MavenConfigurationSupport.java
----------------------------------------------------------------------
diff --git a/maven/core/src/main/java/org/apache/karaf/maven/command/MavenConfigurationSupport.java b/maven/core/src/main/java/org/apache/karaf/maven/command/MavenConfigurationSupport.java
index 0588afd..b64e583 100644
--- a/maven/core/src/main/java/org/apache/karaf/maven/command/MavenConfigurationSupport.java
+++ b/maven/core/src/main/java/org/apache/karaf/maven/command/MavenConfigurationSupport.java
@@ -460,7 +460,7 @@ public abstract class MavenConfigurationSupport implements Action {
         String[] repositories = listOfValues((String) config.get(property));
 
         if (remote) {
-            if (repositories.length == 0 || repositories.length > 0 && repositories[0].charAt(0) == '+') {
+            if (repositories.length == 0 || repositories[0].charAt(0) == '+') {
                 if (repositories.length > 0) {
                     repositories[0] = repositories[0].substring(1);
                 }
@@ -604,13 +604,92 @@ public abstract class MavenConfigurationSupport implements Action {
         return result;
     }
 
+    /**
+     * This method controls whether passwords are tried to be decrypted.
+     * @return
+     */
     protected boolean showPasswords() {
         return false;
     }
 
+    /**
+     * Parses update policy value and returns {@link SourceAnd}<code>&lt;String&gt;</code> about the value
+     * @param policy
+     * @return
+     */
+    protected SourceAnd<String> updatePolicy(String policy) {
+        SourceAnd<String> result = new SourceAnd<>();
+        result.value = policy;
+
+        if (policy == null || "".equals(policy.trim())) {
+            result.value = "";
+            result.valid = false;
+            result.source = "Implicit \"never\", but doesn't override repository-specific value";
+            return result;
+        }
+
+        result.source = String.format(PATTERN_PID_PROPERTY, PID, PID + "." + PROPERTY_GLOBAL_UPDATE_POLICY);
+        if ("always".equals(policy) || "never".equals(policy) || "daily".equals(policy)) {
+            // ok
+            result.valid = true;
+        } else if (policy.startsWith("interval")) {
+            int minutes = 1440;
+            try {
+                String n = policy.substring("interval".length() + 1);
+                minutes = Integer.parseInt(n);
+                result.valid = true;
+            } catch (Exception e) {
+                result.valid = false;
+                result.value = "interval:1440";
+                result.source = "Implicit \"interval:1440\" (error parsing \"" + policy + "\")";
+            }
+        } else {
+            result.valid = false;
+            result.value = "never";
+            result.source = "Implicit \"never\" (unknown value \"" + policy + "\")";
+        }
+
+        return result;
+    }
+
+    /**
+     * Parses checksum policy value and returns {@link SourceAnd}<code>&lt;String&gt;</code> about the value
+     * @param policy
+     * @return
+     */
+    protected SourceAnd<String> checksumPolicy(String policy) {
+        SourceAnd<String> result = new SourceAnd<>();
+        result.value = policy;
+
+        if (policy == null || "".equals(policy.trim())) {
+            result.valid = false;
+            result.value = "warn";
+            result.source = "Default \"warn\"";
+            return result;
+        }
+
+        result.source = String.format(PATTERN_PID_PROPERTY, PID, PID + "." + PROPERTY_GLOBAL_CHECKSUM_POLICY);
+        if ("ignore".equals(policy) || "warn".equals(policy) || "fail".equals(policy)) {
+            // ok
+            result.valid = true;
+        } else {
+            result.valid = false;
+            result.value = "warn";
+            result.source = "Implicit \"warn\" (unknown value \"" + policy + "\")";
+        }
+
+        return result;
+    }
+
+    /**
+     * Handy class containing value and information about its origin. <code>valid</code> may be used to indicate
+     * if the value is correct. It may be implicit, but the interpretation of <code>valid </code> is not defined.
+     * @param <T>
+     */
     protected static class SourceAnd<T> {
         String source;
         T value;
+        boolean valid;
 
         public SourceAnd() {
         }

http://git-wip-us.apache.org/repos/asf/karaf/blob/73953cca/maven/core/src/main/java/org/apache/karaf/maven/command/PasswordCommand.java
----------------------------------------------------------------------
diff --git a/maven/core/src/main/java/org/apache/karaf/maven/command/PasswordCommand.java b/maven/core/src/main/java/org/apache/karaf/maven/command/PasswordCommand.java
index ed44b35..4243212 100644
--- a/maven/core/src/main/java/org/apache/karaf/maven/command/PasswordCommand.java
+++ b/maven/core/src/main/java/org/apache/karaf/maven/command/PasswordCommand.java
@@ -61,6 +61,7 @@ public class PasswordCommand extends MavenConfigurationSupport {
             }
             String password = session.readLine("Password to encrypt: ", '*');
             System.out.println("Encrypted password: " + cipher.encryptAndDecorate(password, masterPassword));
+            System.out.println("You can use this encrypted password when defining repositories and proxies");
             return;
         }
 
@@ -79,6 +80,7 @@ public class PasswordCommand extends MavenConfigurationSupport {
                 File dataDir = context.getDataFile(".");
                 if (!dataDir.isDirectory()) {
                     System.err.println("Can't access data directory for " + context.getBundle().getSymbolicName() + " bundle");
+                    return;
                 }
                 File newSecuritySettingsFile = nextSequenceFile(dataDir, RE_SECURITY_SETTINGS, PATTERN_SECURITY_SETTINGS);
                 try (FileWriter fw = new FileWriter(newSecuritySettingsFile)) {

http://git-wip-us.apache.org/repos/asf/karaf/blob/73953cca/maven/core/src/main/java/org/apache/karaf/maven/command/RepositoryAddCommand.java
----------------------------------------------------------------------
diff --git a/maven/core/src/main/java/org/apache/karaf/maven/command/RepositoryAddCommand.java b/maven/core/src/main/java/org/apache/karaf/maven/command/RepositoryAddCommand.java
index 13bcbdb..11034d3 100644
--- a/maven/core/src/main/java/org/apache/karaf/maven/command/RepositoryAddCommand.java
+++ b/maven/core/src/main/java/org/apache/karaf/maven/command/RepositoryAddCommand.java
@@ -16,20 +16,210 @@
  */
 package org.apache.karaf.maven.command;
 
+import java.io.File;
+import java.io.FileWriter;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Arrays;
+import java.util.Dictionary;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+import org.apache.felix.utils.properties.InterpolationHelper;
+import org.apache.karaf.maven.core.MavenRepositoryURL;
+import org.apache.karaf.shell.api.action.Argument;
 import org.apache.karaf.shell.api.action.Command;
 import org.apache.karaf.shell.api.action.Option;
 import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.maven.settings.Server;
+import org.apache.maven.settings.io.xpp3.SettingsXpp3Writer;
+import org.ops4j.pax.url.mvn.ServiceConstants;
+import org.osgi.service.cm.Configuration;
 
 @Command(scope = "maven", name = "repository-add", description = "Adds Maven repository")
 @Service
 public class RepositoryAddCommand extends RepositoryEditCommandSupport {
 
-    @Option(name = "-x", aliases = { "--show-passwords" }, description = "Do not hide passwords related to Maven encryption", required = false, multiValued = false)
-    boolean showPasswords;
+    @Option(name = "-idx", description = "Index at which new repository is to be inserted (0-based) (defaults to last - repository will be appended)", required = false, multiValued = false)
+    int idx = -1;
+
+    @Option(name = "-s", aliases = { "--snapshots" }, description = "Enable SNAPSHOT handling in the repository", required = false, multiValued = false)
+    boolean snapshots = false;
+
+    @Option(name = "-nr", aliases = { "--no-releases" }, description = "Disable release handling in this repository", required = false, multiValued = false)
+    boolean noReleases = false;
+
+    @Option(name = "-up", aliases = { "--update-policy" }, description = "Update policy for repository (never, daily (default), interval:N, always)", required = false, multiValued = false)
+    String updatePolicy = "daily";
+
+    @Option(name = "-cp", aliases = { "--checksum-policy" }, description = "Checksum policy for repository (ignore, warn (default), fail)", required = false, multiValued = false)
+    String checksumPolicy = "warn";
+
+    @Argument(description = "Repository URI. It may be file:// based, http(s):// based, may use other known protocol or even property placeholders (like ${karaf.base})")
+    String uri;
+
+    @Option(name = "-u", aliases = { "--username" }, description = "Username for remote repository", required = false, multiValued = false)
+    String username;
+
+    @Option(name = "-p", aliases = { "--password" }, description = "Password for remote repository (may be encrypted, see \"maven:password -ep\")", required = false, multiValued = false)
+    String password;
 
     @Override
-    protected void edit() {
+    protected void edit(String prefix, Dictionary<String, Object> config) throws Exception {
+        MavenRepositoryURL[] repositories = repositories(config, !defaultRepository);
+
+        MavenRepositoryURL[] repositoriesFromPidProperty = Arrays.stream(repositories)
+                .filter((repo) -> repo.getFrom() == MavenRepositoryURL.FROM.PID)
+                .toArray(MavenRepositoryURL[]::new);
+
+        if (idx > repositoriesFromPidProperty.length) {
+            // TOCONSIDER: should we allow to add repository to settings.xml too?
+            System.err.printf("List of %s repositories has %d elements. Can't insert at position %s.\n",
+                    (defaultRepository ? "default" : "remote"), repositories.length, id);
+            return;
+        }
+
+        if (id == null || "".equals(id.trim())) {
+            System.err.println("Please specify ID of repository");
+            return;
+        }
+
+        Optional<MavenRepositoryURL> first = Arrays.stream(repositories)
+                .filter((repo) -> id.equals(repo.getId())).findAny();
+        if (first.isPresent()) {
+            System.err.printf("Repository with ID \"%s\" is already configured for URL %s\n", id, first.get().getURL());
+            return;
+        }
+
+        SourceAnd<String> up = updatePolicy(updatePolicy);
+        if (!up.valid) {
+            System.err.println("Unknown value of update policy: \"" + updatePolicy + "\"");
+            return;
+        }
+
+        SourceAnd<String> cp = checksumPolicy(checksumPolicy);
+        if (!cp.valid) {
+            System.err.println("Unknown value of checksum policy: \"" + checksumPolicy + "\"");
+            return;
+        }
+
+        if (uri == null || "".equals(uri.trim())) {
+            System.err.println("Please specify repository location");
+            return;
+        }
+        String urlResolved = InterpolationHelper.substVars(uri, "uri", null, null, context);
+        URL url = null;
+        try {
+            url = new URL(urlResolved);
+            urlResolved = url.toString();
+
+            if ("file".equals(url.getProtocol()) && new File(url.toURI()).isDirectory()) {
+                System.err.println("Location \"" + urlResolved + "\" is not accessible");
+                return;
+            }
+        } catch (MalformedURLException e) {
+            // a directory?
+            File location = new File(urlResolved);
+            if (!location.exists() || !location.isDirectory()) {
+                System.err.println("Location \"" + urlResolved + "\" is not accessible");
+                return;
+            } else {
+                url = location.toURI().toURL();
+                urlResolved = url.toString();
+            }
+        }
+
+        if (defaultRepository && !"file".equals(url.getProtocol())) {
+            System.err.println("Default repositories should be locally accessible (use file:// protocol or normal directory path)");
+            return;
+        }
+
+        boolean hasUsername = username != null && !"".equals(username.trim());
+        boolean hasPassword = password != null && !"".equals(password.trim());
+        boolean hasCredentials = hasUsername && hasPassword;
+
+        if ((hasUsername && !hasPassword) || (!hasUsername && hasPassword)) {
+            System.err.println("Please specify both username and password");
+            return;
+        }
+
+        if (defaultRepository && hasCredentials) {
+            System.out.println("User credentials won't be used for default repository");
+            // no return
+        }
+
+        // credentials for remote repository can be stored only in settings.xml
+
+        if (!defaultRepository && hasCredentials) {
+            if (!confirm("Maven settings will be updated and org.ops4j.pax.url.mvn.settings property will change. Continue? (y/N) ")) {
+                return;
+            }
+
+            File dataDir = context.getDataFile(".");
+            if (!dataDir.isDirectory()) {
+                System.err.println("Can't access data directory for " + context.getBundle().getSymbolicName() + " bundle");
+                return;
+            }
+
+            Optional<Server> existingServer = mavenSettings.getServers().stream()
+                    .filter((s) -> id.equals(s.getId())).findAny();
+            Server server = null;
+            if (existingServer.isPresent()) {
+                server = existingServer.get();
+            } else {
+                server = new Server();
+                server.setId(id);
+                mavenSettings.getServers().add(server);
+            }
+            server.setUsername(username);
+            server.setPassword(password);
+
+            // prepare configadmin configuration update
+            File newSettingsFile = nextSequenceFile(dataDir, RE_SETTINGS, PATTERN_SETTINGS);
+            config.put(prefix + PROPERTY_SETTINGS_FILE, newSettingsFile.getCanonicalPath());
+
+            try (FileWriter fw = new FileWriter(newSettingsFile)) {
+                new SettingsXpp3Writer().write(fw, mavenSettings);
+            }
+            System.out.println("New settings stored in \"" + newSettingsFile.getCanonicalPath() + "\"");
+        }
+
+        StringBuilder sb = new StringBuilder();
+        sb.append(urlResolved);
+        sb.append(ServiceConstants.SEPARATOR_OPTIONS + ServiceConstants.OPTION_ID + "=" + id);
+        if (snapshots) {
+            sb.append(ServiceConstants.SEPARATOR_OPTIONS + ServiceConstants.OPTION_ALLOW_SNAPSHOTS);
+        }
+        if (noReleases) {
+            sb.append(ServiceConstants.SEPARATOR_OPTIONS + ServiceConstants.OPTION_DISALLOW_RELEASES);
+        }
+        sb.append(ServiceConstants.SEPARATOR_OPTIONS + ServiceConstants.OPTION_UPDATE + "=" + updatePolicy);
+        sb.append(ServiceConstants.SEPARATOR_OPTIONS + ServiceConstants.OPTION_CHECKSUM + "=" + checksumPolicy);
+
+        MavenRepositoryURL newRepository = new MavenRepositoryURL(sb.toString());
+        List<MavenRepositoryURL> newRepos = new LinkedList<>(Arrays.asList(repositoriesFromPidProperty));
+        if (idx >= 0) {
+            newRepos.add(idx, newRepository);
+        } else {
+            newRepos.add(newRepository);
+        }
+
+        String newList = newRepos.stream().map(MavenRepositoryURL::asRepositorySpec)
+                .collect(Collectors.joining(","));
+
+        if (defaultRepository) {
+            config.put(prefix + PROPERTY_DEFAULT_REPOSITORIES, newList);
+        } else {
+            config.put(prefix + PROPERTY_REPOSITORIES, newList);
+        }
+
+        Configuration cmConfig = cm.getConfiguration(PID);
+        cmConfig.update(config);
 
+        success = true;
     }
 
 }

http://git-wip-us.apache.org/repos/asf/karaf/blob/73953cca/maven/core/src/main/java/org/apache/karaf/maven/command/RepositoryEditCommandSupport.java
----------------------------------------------------------------------
diff --git a/maven/core/src/main/java/org/apache/karaf/maven/command/RepositoryEditCommandSupport.java b/maven/core/src/main/java/org/apache/karaf/maven/command/RepositoryEditCommandSupport.java
index 12700d0..9f403c1 100644
--- a/maven/core/src/main/java/org/apache/karaf/maven/command/RepositoryEditCommandSupport.java
+++ b/maven/core/src/main/java/org/apache/karaf/maven/command/RepositoryEditCommandSupport.java
@@ -23,13 +23,27 @@ import org.apache.karaf.shell.api.action.Option;
 
 public abstract class RepositoryEditCommandSupport extends MavenSecuritySupport {
 
+    @Option(name = "-id", description = "Identifier of repository", required = true, multiValued = false)
+    String id;
+
+    @Option(name = "-d", aliases = { "--default" }, description = "Edit default repository instead of remote one", required = false, multiValued = false)
+    boolean defaultRepository = false;
+
+    boolean success = false;
+
     @Override
-    public void doAction(String prefix, Dictionary<String, Object> config) throws Exception {
-        edit();
+    public final void doAction(String prefix, Dictionary<String, Object> config) throws Exception {
+        edit(prefix, config);
 
-        session.execute("maven:repository-list");
+        if (success) {
+            if (showPasswords) {
+                session.execute("maven:repository-list -x");
+            } else {
+                session.execute("maven:repository-list");
+            }
+        }
     }
 
-    protected abstract void edit();
+    protected abstract void edit(String prefix, Dictionary<String, Object> config) throws Exception;
 
 }

http://git-wip-us.apache.org/repos/asf/karaf/blob/73953cca/maven/core/src/main/java/org/apache/karaf/maven/command/RepositoryRemoveCommand.java
----------------------------------------------------------------------
diff --git a/maven/core/src/main/java/org/apache/karaf/maven/command/RepositoryRemoveCommand.java b/maven/core/src/main/java/org/apache/karaf/maven/command/RepositoryRemoveCommand.java
index 46f82c8..eaf62e5 100644
--- a/maven/core/src/main/java/org/apache/karaf/maven/command/RepositoryRemoveCommand.java
+++ b/maven/core/src/main/java/org/apache/karaf/maven/command/RepositoryRemoveCommand.java
@@ -16,6 +16,8 @@
  */
 package org.apache.karaf.maven.command;
 
+import java.util.Dictionary;
+
 import org.apache.karaf.shell.api.action.Command;
 import org.apache.karaf.shell.api.action.lifecycle.Service;
 
@@ -24,7 +26,7 @@ import org.apache.karaf.shell.api.action.lifecycle.Service;
 public class RepositoryRemoveCommand extends RepositoryEditCommandSupport {
 
     @Override
-    protected void edit() {
+    protected void edit(String prefix, Dictionary<String, Object> config) throws Exception {
 
     }
 

http://git-wip-us.apache.org/repos/asf/karaf/blob/73953cca/maven/core/src/main/java/org/apache/karaf/maven/command/SummaryCommand.java
----------------------------------------------------------------------
diff --git a/maven/core/src/main/java/org/apache/karaf/maven/command/SummaryCommand.java b/maven/core/src/main/java/org/apache/karaf/maven/command/SummaryCommand.java
index 3f7e91a..c4e4c03 100644
--- a/maven/core/src/main/java/org/apache/karaf/maven/command/SummaryCommand.java
+++ b/maven/core/src/main/java/org/apache/karaf/maven/command/SummaryCommand.java
@@ -185,55 +185,4 @@ public class SummaryCommand extends MavenSecuritySupport {
         }
     }
 
-    private SourceAnd<String> updatePolicy(String cmProperty) {
-        SourceAnd<String> result = new SourceAnd<>();
-        result.value = cmProperty;
-
-        if (cmProperty == null || "".equals(cmProperty.trim())) {
-            result.value = "";
-            result.source = "Implicit \"never\", but doesn't override repository-specific value";
-            return result;
-        }
-
-        result.source = String.format(PATTERN_PID_PROPERTY, PID, PID + "." + PROPERTY_GLOBAL_UPDATE_POLICY);
-        if ("always".equals(cmProperty) || "never".equals(cmProperty) || "daily".equals(cmProperty)) {
-            // ok
-        } else if (cmProperty.startsWith("interval")) {
-            int minutes = 1440;
-            try {
-                String n = cmProperty.substring("interval".length() + 1);
-                minutes = Integer.parseInt(n);
-            } catch (Exception e) {
-                result.value = "interval:1440";
-                result.source = "Implicit \"interval:1440\" (error parsing \"" + cmProperty + "\")";
-            }
-        } else {
-            result.value = "never";
-            result.source = "Implicit \"never\" (unknown value \"" + cmProperty + "\")";
-        }
-
-        return result;
-    }
-
-    private SourceAnd<String> checksumPolicy(String cmProperty) {
-        SourceAnd<String> result = new SourceAnd<>();
-        result.value = cmProperty;
-
-        if (cmProperty == null || "".equals(cmProperty.trim())) {
-            result.value = "warn";
-            result.source = "Default \"warn\"";
-            return result;
-        }
-
-        result.source = String.format(PATTERN_PID_PROPERTY, PID, PID + "." + PROPERTY_GLOBAL_CHECKSUM_POLICY);
-        if ("ignore".equals(cmProperty) || "warn".equals(cmProperty) || "fail".equals(cmProperty)) {
-            // ok
-        } else {
-            result.value = "warn";
-            result.source = "Implicit \"warn\" (unknown value \"" + cmProperty + "\")";
-        }
-
-        return result;
-    }
-
 }

http://git-wip-us.apache.org/repos/asf/karaf/blob/73953cca/maven/core/src/main/java/org/apache/karaf/maven/core/MavenRepositoryURL.java
----------------------------------------------------------------------
diff --git a/maven/core/src/main/java/org/apache/karaf/maven/core/MavenRepositoryURL.java b/maven/core/src/main/java/org/apache/karaf/maven/core/MavenRepositoryURL.java
index 630976b..1c422c8 100644
--- a/maven/core/src/main/java/org/apache/karaf/maven/core/MavenRepositoryURL.java
+++ b/maven/core/src/main/java/org/apache/karaf/maven/core/MavenRepositoryURL.java
@@ -22,6 +22,7 @@ import java.net.MalformedURLException;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.net.URL;
+import java.util.Objects;
 
 import org.ops4j.pax.url.mvn.ServiceConstants;
 import org.slf4j.Logger;
@@ -367,6 +368,52 @@ public class MavenRepositoryURL
             .toString();
     }
 
+    public String asRepositorySpec() {
+        StringBuilder sb = new StringBuilder();
+
+        sb.append(m_repositoryURL.toString());
+        if (m_id != null) {
+            sb.append(ServiceConstants.SEPARATOR_OPTIONS + ServiceConstants.OPTION_ID + "=" + m_id);
+        }
+        if (!m_releasesEnabled) {
+            sb.append(ServiceConstants.SEPARATOR_OPTIONS + ServiceConstants.OPTION_DISALLOW_RELEASES);
+        }
+        if (m_snapshotsEnabled) {
+            sb.append(ServiceConstants.SEPARATOR_OPTIONS + ServiceConstants.OPTION_ALLOW_SNAPSHOTS);
+        }
+        if (m_releasesEnabled) {
+            if (!m_snapshotsEnabled) {
+                if (m_releasesUpdatePolicy != null) {
+                    sb.append(ServiceConstants.SEPARATOR_OPTIONS + ServiceConstants.OPTION_RELEASES_UPDATE + "=" + m_releasesUpdatePolicy);
+                }
+                if (m_releasesChecksumPolicy != null) {
+                    sb.append(ServiceConstants.SEPARATOR_OPTIONS + ServiceConstants.OPTION_RELEASES_CHECKSUM + "=" + m_releasesChecksumPolicy);
+                }
+            }
+        }
+        if (m_snapshotsEnabled) {
+            if (!m_releasesEnabled) {
+                if (m_snapshotsUpdatePolicy != null) {
+                    sb.append(ServiceConstants.SEPARATOR_OPTIONS + ServiceConstants.OPTION_SNAPSHOTS_UPDATE + "=" + m_snapshotsUpdatePolicy);
+                }
+                if (m_snapshotsChecksumPolicy != null) {
+                    sb.append(ServiceConstants.SEPARATOR_OPTIONS + ServiceConstants.OPTION_SNAPSHOTS_CHECKSUM + "=" + m_snapshotsChecksumPolicy);
+                }
+            }
+        }
+        if (m_snapshotsEnabled && m_releasesEnabled) {
+            // compact snapshots & release update & checksum policies?
+            if (m_releasesUpdatePolicy != null && Objects.equals(m_releasesUpdatePolicy, m_snapshotsUpdatePolicy)) {
+                sb.append(ServiceConstants.SEPARATOR_OPTIONS + ServiceConstants.OPTION_UPDATE + "=" + m_releasesUpdatePolicy);
+            }
+            if (m_releasesChecksumPolicy != null && Objects.equals(m_releasesChecksumPolicy, m_snapshotsChecksumPolicy)) {
+                sb.append(ServiceConstants.SEPARATOR_OPTIONS + ServiceConstants.OPTION_CHECKSUM + "=" + m_releasesChecksumPolicy);
+            }
+        }
+
+        return sb.toString();
+    }
+
     public static enum FROM {
         PID("PID configuration"),
         SETTINGS("Maven XML settings"),

http://git-wip-us.apache.org/repos/asf/karaf/blob/73953cca/maven/core/src/test/java/org/apache/karaf/maven/SettingsTest.java
----------------------------------------------------------------------
diff --git a/maven/core/src/test/java/org/apache/karaf/maven/SettingsTest.java b/maven/core/src/test/java/org/apache/karaf/maven/SettingsTest.java
index 3ecaf30..901efc3 100644
--- a/maven/core/src/test/java/org/apache/karaf/maven/SettingsTest.java
+++ b/maven/core/src/test/java/org/apache/karaf/maven/SettingsTest.java
@@ -32,6 +32,8 @@ public class SettingsTest {
         Settings settings = new Settings();
         Server s = new Server();
         s.setId("id");
+        s.setUsername("admin");
+        s.setPassword("admin");
         settings.getServers().add(s);
 
         new DefaultSettingsWriter().write(System.out, null, settings);

http://git-wip-us.apache.org/repos/asf/karaf/blob/73953cca/maven/core/src/test/java/org/apache/karaf/maven/command/MavenConfigurationSupportTest.java
----------------------------------------------------------------------
diff --git a/maven/core/src/test/java/org/apache/karaf/maven/command/MavenConfigurationSupportTest.java b/maven/core/src/test/java/org/apache/karaf/maven/command/MavenConfigurationSupportTest.java
index 16ffc3f..991ace1 100644
--- a/maven/core/src/test/java/org/apache/karaf/maven/command/MavenConfigurationSupportTest.java
+++ b/maven/core/src/test/java/org/apache/karaf/maven/command/MavenConfigurationSupportTest.java
@@ -22,11 +22,13 @@ import java.io.IOException;
 import java.util.Dictionary;
 import java.util.regex.Pattern;
 
+import org.hamcrest.CoreMatchers;
 import org.junit.Test;
 import shaded.org.apache.commons.io.FileUtils;
 
 import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
 
 public class MavenConfigurationSupportTest {
 
@@ -42,19 +44,19 @@ public class MavenConfigurationSupportTest {
         };
 
         File newFile = support.nextSequenceFile(dataDir, Pattern.compile("file-(\\d+).txt"), "file-%04d.txt");
-        assertThat(newFile.getName(), equalTo("file-0001.txt"));
+        assertTrue(Pattern.compile("^file-\\d+\\.txt$").matcher(newFile.getName()).matches());
 
         try (FileWriter fw = new FileWriter(new File(dataDir, "file-abcd.txt"))) {
             fw.write("~");
         }
         newFile = support.nextSequenceFile(dataDir, Pattern.compile("file-(\\d+).txt"), "file-%04d.txt");
-        assertThat(newFile.getName(), equalTo("file-0001.txt"));
+        assertTrue(Pattern.compile("^file-\\d+\\.txt$").matcher(newFile.getName()).matches());
 
         try (FileWriter fw = new FileWriter(new File(dataDir, "file-0004.txt"))) {
             fw.write("~");
         }
         newFile = support.nextSequenceFile(dataDir, Pattern.compile("file-(\\d+).txt"), "file-%04d.txt");
-        assertThat(newFile.getName(), equalTo("file-0005.txt"));
+        assertTrue(Pattern.compile("^file-\\d+\\.txt$").matcher(newFile.getName()).matches());
     }
 
 }

http://git-wip-us.apache.org/repos/asf/karaf/blob/73953cca/maven/core/src/test/java/org/apache/karaf/maven/core/MavenRepositoryURLTest.java
----------------------------------------------------------------------
diff --git a/maven/core/src/test/java/org/apache/karaf/maven/core/MavenRepositoryURLTest.java b/maven/core/src/test/java/org/apache/karaf/maven/core/MavenRepositoryURLTest.java
new file mode 100644
index 0000000..d460e0e
--- /dev/null
+++ b/maven/core/src/test/java/org/apache/karaf/maven/core/MavenRepositoryURLTest.java
@@ -0,0 +1,64 @@
+/*
+ * 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.maven.core;
+
+import java.net.MalformedURLException;
+
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class MavenRepositoryURLTest {
+
+    @Test
+    public void uris() throws MalformedURLException {
+        String uri1, uri2;
+        MavenRepositoryURL mavenURI;
+
+        uri1 = "http://localhost/@id=id1@snapshots@update=interval:42@_from=" + MavenRepositoryURL.FROM.SETTINGS;
+        uri2 = "http://localhost/@id=id1@snapshots@update=interval:42";
+        mavenURI = new MavenRepositoryURL(uri1);
+        assertThat(mavenURI.asRepositorySpec(), equalTo(uri2));
+
+        uri1 = "http://localhost/@id=id1@snapshots@checksum=fail@_from=" + MavenRepositoryURL.FROM.SETTINGS;
+        uri2 = "http://localhost/@id=id1@snapshots@checksum=fail";
+        mavenURI = new MavenRepositoryURL(uri1);
+        assertThat(mavenURI.asRepositorySpec(), equalTo(uri2));
+
+        uri1 = "http://localhost/@id=id1@snapshots@noreleases@update=interval:42@_from=" + MavenRepositoryURL.FROM.SETTINGS;
+        uri2 = "http://localhost/@id=id1@noreleases@snapshots@snapshotsUpdate=interval:42";
+        mavenURI = new MavenRepositoryURL(uri1);
+        assertThat(mavenURI.asRepositorySpec(), equalTo(uri2));
+
+        uri1 = "http://localhost/@id=id1@update=interval:42@_from=" + MavenRepositoryURL.FROM.SETTINGS;
+        uri2 = "http://localhost/@id=id1@releasesUpdate=interval:42";
+        mavenURI = new MavenRepositoryURL(uri1);
+        assertThat(mavenURI.asRepositorySpec(), equalTo(uri2));
+
+        uri1 = "http://localhost/@id=id1@snapshots@noreleases@checksum=fail@_from=" + MavenRepositoryURL.FROM.SETTINGS;
+        uri2 = "http://localhost/@id=id1@noreleases@snapshots@snapshotsChecksum=fail";
+        mavenURI = new MavenRepositoryURL(uri1);
+        assertThat(mavenURI.asRepositorySpec(), equalTo(uri2));
+
+        uri1 = "http://localhost/@id=id1@checksum=fail@_from=" + MavenRepositoryURL.FROM.SETTINGS;
+        uri2 = "http://localhost/@id=id1@releasesChecksum=fail";
+        mavenURI = new MavenRepositoryURL(uri1);
+        assertThat(mavenURI.asRepositorySpec(), equalTo(uri2));
+    }
+
+}