You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by he...@apache.org on 2017/06/30 14:02:59 UTC

[11/27] brooklyn-server git commit: add routines to convert brooklyn/osgi versions

add routines to convert brooklyn/osgi versions

and bundle finder supports either, and does the conversion for you


Project: http://git-wip-us.apache.org/repos/asf/brooklyn-server/repo
Commit: http://git-wip-us.apache.org/repos/asf/brooklyn-server/commit/ed98e27e
Tree: http://git-wip-us.apache.org/repos/asf/brooklyn-server/tree/ed98e27e
Diff: http://git-wip-us.apache.org/repos/asf/brooklyn-server/diff/ed98e27e

Branch: refs/heads/master
Commit: ed98e27e934ddf54d6c1c80b4a61afaab1225e47
Parents: 45cb10a
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Wed Jun 21 18:26:43 2017 +0100
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Wed Jun 21 18:26:43 2017 +0100

----------------------------------------------------------------------
 .../apache/brooklyn/core/BrooklynVersion.java   |   5 +
 .../catalog/internal/BasicBrooklynCatalog.java  |   4 +-
 .../brooklyn/util/core/ClassLoaderUtils.java    |   5 +-
 .../apache/brooklyn/util/core/osgi/Osgis.java   |  18 +++-
 .../core/typereg/RegisteredTypeNamingTest.java  |   6 +-
 .../util/core/ClassLoaderUtilsTest.java         |   2 +-
 .../core/xstream/OsgiClassPrefixerTest.java     |   2 +-
 .../apache/brooklyn/util/osgi/OsgiUtils.java    |   4 +
 .../util/text/BrooklynVersionSyntax.java        | 103 ++++++++++++++++++-
 .../brooklyn/util/osgi/OsgiUtilsTest.java       |   3 +
 .../brooklyn/util/osgi/VersionedNameTest.java   |   3 +-
 .../util/text/BrooklynVersionSyntaxTest.java    |  98 ++++++++++++++++++
 12 files changed, 239 insertions(+), 14 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/ed98e27e/core/src/main/java/org/apache/brooklyn/core/BrooklynVersion.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/BrooklynVersion.java b/core/src/main/java/org/apache/brooklyn/core/BrooklynVersion.java
index 161eff0..f28d3fb 100644
--- a/core/src/main/java/org/apache/brooklyn/core/BrooklynVersion.java
+++ b/core/src/main/java/org/apache/brooklyn/core/BrooklynVersion.java
@@ -45,6 +45,7 @@ import org.apache.brooklyn.util.exceptions.Exceptions;
 import org.apache.brooklyn.util.guava.Maybe;
 import org.apache.brooklyn.util.osgi.OsgiUtil;
 import org.apache.brooklyn.util.stream.Streams;
+import org.apache.brooklyn.util.text.BrooklynVersionSyntax;
 import org.apache.brooklyn.util.text.Strings;
 import org.apache.brooklyn.util.text.VersionComparator;
 import org.osgi.framework.Bundle;
@@ -458,4 +459,8 @@ public class BrooklynVersion implements BrooklynVersionService {
             return true;
         }
     }
+
+    public static String getOsgiVersion() {
+        return BrooklynVersionSyntax.toValidOsgiVersion(get());
+    }
 }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/ed98e27e/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java
index 006d3e8..b0d028f 100644
--- a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java
+++ b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java
@@ -59,8 +59,8 @@ import org.apache.brooklyn.util.guava.Maybe;
 import org.apache.brooklyn.util.javalang.AggregateClassLoader;
 import org.apache.brooklyn.util.javalang.JavaClassNames;
 import org.apache.brooklyn.util.javalang.LoadedClassLoader;
-import org.apache.brooklyn.util.osgi.OsgiUtils;
 import org.apache.brooklyn.util.osgi.VersionedName;
+import org.apache.brooklyn.util.text.BrooklynVersionSyntax;
 import org.apache.brooklyn.util.text.Strings;
 import org.apache.brooklyn.util.time.Duration;
 import org.apache.brooklyn.util.time.Time;
@@ -448,7 +448,7 @@ public class BasicBrooklynCatalog implements BrooklynCatalog {
         if (Strings.isBlank(version)) {
             throw new IllegalStateException("Catalog BOM must define version if bundle is defined");
         }
-        return new VersionedName(bundle, Version.valueOf(OsgiUtils.toOsgiVersion(version)));
+        return new VersionedName(bundle, Version.valueOf(BrooklynVersionSyntax.toValidOsgiVersion(version)));
     }
 
     private void collectCatalogItems(String yaml, List<CatalogItemDtoAbstract<?, ?>> result, Map<?, ?> parentMeta) {

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/ed98e27e/core/src/main/java/org/apache/brooklyn/util/core/ClassLoaderUtils.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/util/core/ClassLoaderUtils.java b/core/src/main/java/org/apache/brooklyn/util/core/ClassLoaderUtils.java
index a6be53d..8d5cf0f 100644
--- a/core/src/main/java/org/apache/brooklyn/util/core/ClassLoaderUtils.java
+++ b/core/src/main/java/org/apache/brooklyn/util/core/ClassLoaderUtils.java
@@ -40,6 +40,7 @@ import org.apache.brooklyn.util.core.osgi.Osgis;
 import org.apache.brooklyn.util.exceptions.Exceptions;
 import org.apache.brooklyn.util.guava.Maybe;
 import org.apache.brooklyn.util.osgi.OsgiUtils;
+import org.apache.brooklyn.util.text.BrooklynVersionSyntax;
 import org.apache.brooklyn.util.text.Strings;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
@@ -65,7 +66,7 @@ public class ClassLoaderUtils {
     static final String WHITE_LIST_KEY = "org.apache.brooklyn.classloader.fallback.bundles";
     static final String CLASS_NAME_DELIMITER = ":";
     private static final String WHITE_LIST_DEFAULT =
-        "org\\.apache\\.brooklyn\\..*:" + OsgiUtils.toOsgiVersion(BrooklynVersion.get());
+        "org\\.apache\\.brooklyn\\..*:" + BrooklynVersion.getOsgiVersion();
 
     // Class.forName gets the class loader from the calling class.
     // We don't have access to the same reflection API so need to pass it explicitly.
@@ -307,7 +308,7 @@ public class ClassLoaderUtils {
         if (framework != null) {
             Maybe<Bundle> bundle = Osgis.bundleFinder(framework)
                 .symbolicName(symbolicName)
-                .version(OsgiUtils.toOsgiVersion(version))
+                .version(version)
                 .find();
             if (bundle.isAbsent()) {
                 throw new IllegalStateException("Bundle " + toBundleString(symbolicName, version)

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/ed98e27e/core/src/main/java/org/apache/brooklyn/util/core/osgi/Osgis.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/util/core/osgi/Osgis.java b/core/src/main/java/org/apache/brooklyn/util/core/osgi/Osgis.java
index a33b446..d46f098 100644
--- a/core/src/main/java/org/apache/brooklyn/util/core/osgi/Osgis.java
+++ b/core/src/main/java/org/apache/brooklyn/util/core/osgi/Osgis.java
@@ -39,6 +39,7 @@ import org.apache.brooklyn.util.net.Urls;
 import org.apache.brooklyn.util.os.Os;
 import org.apache.brooklyn.util.osgi.OsgiUtils;
 import org.apache.brooklyn.util.stream.Streams;
+import org.apache.brooklyn.util.text.BrooklynVersionSyntax;
 import org.apache.brooklyn.util.text.Strings;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleException;
@@ -98,6 +99,7 @@ public class Osgis {
             return this;
         }
 
+        /** Accepts non-osgi version syntax, converting to OSGi version syntax */
         public BundleFinder version(String version) {
             this.version = version;
             return this;
@@ -174,12 +176,26 @@ public class Osgis {
         }
         
         /** Finds all matching bundles, in decreasing version order. */
+        @SuppressWarnings("deprecation")
         public List<Bundle> findAll() {
             boolean urlMatched = false;
             List<Bundle> result = MutableList.of();
+            String v=null, vDep = null;
+            if (version!=null) {
+                v = BrooklynVersionSyntax.toValidOsgiVersion(version);
+                vDep = OsgiUtils.toOsgiVersion(version);
+            }
             for (Bundle b: framework.getBundleContext().getBundles()) {
                 if (symbolicName!=null && !symbolicName.equals(b.getSymbolicName())) continue;
-                if (version!=null && !Version.parseVersion(version).equals(b.getVersion())) continue;
+                if (version!=null) {
+                    String bv = b.getVersion().toString();
+                    if (!v.equals(bv)) {
+                        if (!vDep.equals(bv)) {
+                            continue;
+                        }
+                        LOG.warn("Legacy inferred OSGi version string '"+vDep+"' found to match "+symbolicName+":"+version+"; switch to '"+v+"' format to avoid issues with deprecated version syntax");
+                    }
+                }
                 if (!Predicates.and(predicates).apply(b)) continue;
 
                 // check url last, because if it isn't mandatory we should only clear if we find a url

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/ed98e27e/core/src/test/java/org/apache/brooklyn/core/typereg/RegisteredTypeNamingTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/core/typereg/RegisteredTypeNamingTest.java b/core/src/test/java/org/apache/brooklyn/core/typereg/RegisteredTypeNamingTest.java
index ec07c8a..3fc8dc3 100644
--- a/core/src/test/java/org/apache/brooklyn/core/typereg/RegisteredTypeNamingTest.java
+++ b/core/src/test/java/org/apache/brooklyn/core/typereg/RegisteredTypeNamingTest.java
@@ -88,9 +88,5 @@ public class RegisteredTypeNamingTest {
         Assert.assertEquals(RegisteredTypeNaming.isValidOsgiTypeColonVersion(candidate), isOsgi, "osgi name:version '"+candidate+"'");
         Assert.assertEquals(RegisteredTypeNaming.isGoodBrooklynTypeColonVersion(candidate), isGood, "good name:version '"+candidate+"'");
     }
-
-    public void testConvertToOsgiVersion() {
-        Assert.assertEquals(OsgiUtils.toOsgiVersion("1-foo"), "1.0.0.foo");
-        Assert.assertEquals(OsgiUtils.toOsgiVersion("1"), "1.0.0");
-    }
+    
 }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/ed98e27e/core/src/test/java/org/apache/brooklyn/util/core/ClassLoaderUtilsTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/util/core/ClassLoaderUtilsTest.java b/core/src/test/java/org/apache/brooklyn/util/core/ClassLoaderUtilsTest.java
index a9ea938..1477798 100644
--- a/core/src/test/java/org/apache/brooklyn/util/core/ClassLoaderUtilsTest.java
+++ b/core/src/test/java/org/apache/brooklyn/util/core/ClassLoaderUtilsTest.java
@@ -283,7 +283,7 @@ public class ClassLoaderUtilsTest {
                 Entity.class.getName());
         new ClassLoaderUtils(this, mgmt).loadClass(
                 "org.apache.brooklyn.api",
-                OsgiUtils.toOsgiVersion(BrooklynVersion.get()),
+                BrooklynVersion.getOsgiVersion(),
                 Entity.class.getName());
         try {
             new ClassLoaderUtils(this, mgmt).loadClass(

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/ed98e27e/core/src/test/java/org/apache/brooklyn/util/core/xstream/OsgiClassPrefixerTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/util/core/xstream/OsgiClassPrefixerTest.java b/core/src/test/java/org/apache/brooklyn/util/core/xstream/OsgiClassPrefixerTest.java
index aa2e659..b1d4278 100644
--- a/core/src/test/java/org/apache/brooklyn/util/core/xstream/OsgiClassPrefixerTest.java
+++ b/core/src/test/java/org/apache/brooklyn/util/core/xstream/OsgiClassPrefixerTest.java
@@ -62,7 +62,7 @@ public class OsgiClassPrefixerTest {
     public void testGetPrefixWithWhitelistedBundle() throws Exception {
         final Bundle bundle = Mockito.mock(Bundle.class);
         Mockito.when(bundle.getSymbolicName()).thenReturn("org.apache.brooklyn.my-bundle");
-        Mockito.when(bundle.getVersion()).thenReturn(Version.valueOf(OsgiUtils.toOsgiVersion(BrooklynVersion.get())));
+        Mockito.when(bundle.getVersion()).thenReturn(Version.valueOf(BrooklynVersion.getOsgiVersion()));
         
         Function<Class<?>, Optional<Bundle>> bundleRetriever = new Function<Class<?>, Optional<Bundle>>() {
             @Override public Optional<Bundle> apply(Class<?> input) {

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/ed98e27e/utils/common/src/main/java/org/apache/brooklyn/util/osgi/OsgiUtils.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/osgi/OsgiUtils.java b/utils/common/src/main/java/org/apache/brooklyn/util/osgi/OsgiUtils.java
index df41686..1fe07f5 100644
--- a/utils/common/src/main/java/org/apache/brooklyn/util/osgi/OsgiUtils.java
+++ b/utils/common/src/main/java/org/apache/brooklyn/util/osgi/OsgiUtils.java
@@ -25,6 +25,7 @@ import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
 import org.apache.brooklyn.util.guava.Maybe;
+import org.apache.brooklyn.util.text.BrooklynVersionSyntax;
 import org.apache.brooklyn.util.text.Strings;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.Constants;
@@ -101,6 +102,9 @@ public class OsgiUtils {
         return Maybe.of(new VersionedName(parts[0], v));
     }
 
+    /** @deprecated since 0.12.0 use {@link BrooklynVersionSyntax#toValidOsgiVersion(String)};
+     * but note it has slightly different semantics in some odd cases, e.g. this maps "1x" to "0.0.0.1x" whereas
+     * the new method maps to "1.0.0.x" */
     public static String toOsgiVersion(String version) {
         if (version != null) {
             return DefaultMaven2OsgiConverter.cleanupVersion(version);

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/ed98e27e/utils/common/src/main/java/org/apache/brooklyn/util/text/BrooklynVersionSyntax.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/text/BrooklynVersionSyntax.java b/utils/common/src/main/java/org/apache/brooklyn/util/text/BrooklynVersionSyntax.java
index 9a59d11..63c69e1 100644
--- a/utils/common/src/main/java/org/apache/brooklyn/util/text/BrooklynVersionSyntax.java
+++ b/utils/common/src/main/java/org/apache/brooklyn/util/text/BrooklynVersionSyntax.java
@@ -18,11 +18,16 @@
  */
 package org.apache.brooklyn.util.text;
 
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import com.google.common.base.Preconditions;
+
 /** Utilities for parsing and working with versions following the recommended Brooklyn scheme,
  * following <code>major.minor.patch-qualifier</code> syntax,
  * with support for mapping to OSGi. 
  * <p>
- * See {@link VersionComparator} and its tests for examples.
+ * See tests for examples, {@link VersionComparator} for more notes, and its tests for more examples.
  */
 public class BrooklynVersionSyntax {
 
@@ -84,4 +89,100 @@ public class BrooklynVersionSyntax {
         return candidate!=null && candidate.matches(VALID_OSGI_VERSION_REGEX);
     }
 
+    /** Creates a string satisfying {@link #isValidOsgiVersion(String)} based on the input.
+     * For input satisfying {@link #isGoodBrooklynVersion(String)} the only change will be in the qualifer separator
+     * (from "-" to ".") and making any "0" minor/patch token explicit (so "1-x" becomes "1.0.0.x"),
+     * and the change can be reversed using {@link #toGoodBrooklynVersion(String)} (modulo insertion of "0"'s for minor/patch numbers if missing).
+     * For input satisfying {@link #isValidOsgiVersion(String)}, the only change will be insertions of 0 for minor/patch.
+     * Precise behaviour for other input is not guaranteed but callers can expect output which resembles the input,
+     * with any major/minor/patch string at the front preserved and internal contiguous alphanumeric sequences preserved. */
+    public static String toValidOsgiVersion(String input) {
+        Preconditions.checkNotNull(input);
+        return toGoodVersion(input, ".", true);
+        /* Note Maven has and used:  DefaultMaven2OsgiConverter
+         * from https://github.com/apache/felix/blob/trunk/tools/maven-bundle-plugin/src/main/java/org/apache/maven/shared/osgi/DefaultMaven2OsgiConverter.java
+         * but it (a) is more complicated, and (b) doesn't aggressively find numbers e.g. "1beta" goes to "0.0.0.1beta" instead of "1.0.0.beta" 
+         */
+    }
+
+    /** Creates a string satisfying {@link #isGoodBrooklynVersion(String)} based on the input.
+     * For input satisfying {@link #isGoodBrooklynVersion(String)} the input will be returned unchanged.
+     * For input satisfying {@link #isValidOsgiVersion(String)} the qualifier separator will be changed to "-",
+     * and {@link #toValidOsgiVersion(String)} can be used to reverse the input (modulo insertion of "0"'s for minor/patch numbers if missing).
+     * Precise behaviour for other input is not guaranteed but callers can expect output which resembles the input,
+     * with any major/minor/patch string at the front preserved and internal contiguous alphanumeric sequences preserved. */
+    public static String toGoodBrooklynVersion(String input) {
+        return toGoodVersion(input, "-", false);
+    }
+    
+    private static String toGoodVersion(String input, String qualifierSeparator, boolean requireMinorAndPatch) {
+        Preconditions.checkNotNull(input);
+        final String FUZZY_REGEX = 
+            "(" + NUMBER + "(" + "\\." + NUMBER + "(" + "\\." + NUMBER + ")?)?)?" +  
+            "(" + ".*)";
+        Matcher m = Pattern.compile(FUZZY_REGEX).matcher(input);
+        if (!m.matches()) {
+            throw new IllegalStateException("fuzzy matcher should match anything: '"+input+"'");  // sanity check - shouldn't happen
+        }
+        StringBuilder result = new StringBuilder();
+        if (Strings.isEmpty(m.group(1))) {
+            result.append("0.0.0");
+        } else {
+            result.append(m.group(1));
+            if (requireMinorAndPatch) {
+                if (Strings.isEmpty(m.group(2))) {
+                    result.append(".0");
+                }            
+                if (Strings.isEmpty(m.group(3))) {
+                    result.append(".0");
+                }            
+            }
+        }
+        String q = m.group(4);
+        if (Strings.isNonEmpty(q)) {
+            boolean collapsedUnsupported = false;
+            boolean starting = true;
+            result.append(qualifierSeparator);
+            for (int i=0; i<q.length(); i++) {
+                char c = q.charAt(i);
+                boolean include;
+                boolean unsupported = false;
+                if (starting) {
+                    // treat first char as separator char unless it is a letter/number 
+                    include = ('A' <= c && 'Z' >= c) || ('a' <= c && 'z' >= c) || ('0' <= c && '9' >= c);
+                    starting = false;
+                    if (!include) {
+                        if (c=='-' || c=='_' || c=='.') {
+                            // treat these as separator chars, and drop them
+                            if (q.length()==1) {
+                                // unless there are no other chars (e.g. version "1." becomes "1-_"
+                                unsupported = true;
+                            }
+                        } else {
+                            // treat other chars as unsupported
+                            unsupported = true;
+                        }
+                    }
+                } else {
+                    include = ('A' <= c && 'Z' >= c) || ('a' <= c && 'z' >= c) || ('0' <= c && '9' >= c) || c=='-' || c=='_';
+                    if (!include) {
+                        // treat as unsupported, unless we've collapsed
+                        if (!collapsedUnsupported) {
+                            unsupported = true;
+                        }
+                    }
+                }
+                if (include) {
+                    result.append(c);
+                    collapsedUnsupported = false;
+                } else if (unsupported) {
+                    // stick a "_" in for unsupported chars
+                    result.append('_');
+                    collapsedUnsupported = true;
+                }
+            }
+        }
+        return result.toString();
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/ed98e27e/utils/common/src/test/java/org/apache/brooklyn/util/osgi/OsgiUtilsTest.java
----------------------------------------------------------------------
diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/osgi/OsgiUtilsTest.java b/utils/common/src/test/java/org/apache/brooklyn/util/osgi/OsgiUtilsTest.java
index e41b00a..7a8550e 100644
--- a/utils/common/src/test/java/org/apache/brooklyn/util/osgi/OsgiUtilsTest.java
+++ b/utils/common/src/test/java/org/apache/brooklyn/util/osgi/OsgiUtilsTest.java
@@ -21,6 +21,8 @@ import org.testng.annotations.Test;
 
 public class OsgiUtilsTest {
 
+    // TODO other utils tests ... or maybe other tests in this package are sufficient?
+    
     @Test
     public void testToOsgiVersion() {
         assertVersion("0.10.0-20160713.1653", "0.10.0.20160713_1653");
@@ -43,6 +45,7 @@ public class OsgiUtilsTest {
         assertVersion("4aug2000r7-dev", "0.0.0.4aug2000r7-dev");
     }
 
+    @Deprecated
     private void assertVersion(String ver, String expected) {
         assertEquals(OsgiUtils.toOsgiVersion(ver), expected);
     }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/ed98e27e/utils/common/src/test/java/org/apache/brooklyn/util/osgi/VersionedNameTest.java
----------------------------------------------------------------------
diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/osgi/VersionedNameTest.java b/utils/common/src/test/java/org/apache/brooklyn/util/osgi/VersionedNameTest.java
index c838258..f162673 100644
--- a/utils/common/src/test/java/org/apache/brooklyn/util/osgi/VersionedNameTest.java
+++ b/utils/common/src/test/java/org/apache/brooklyn/util/osgi/VersionedNameTest.java
@@ -16,6 +16,7 @@
 package org.apache.brooklyn.util.osgi;
 
 import org.apache.brooklyn.util.javalang.coerce.TypeCoercerExtensible;
+import org.apache.brooklyn.util.text.BrooklynVersionSyntax;
 import org.osgi.framework.Version;
 import org.testng.Assert;
 import org.testng.annotations.Test;
@@ -37,7 +38,7 @@ public class VersionedNameTest {
     @Test
     public void testManuallyCorrectingVersion() {
         Assert.assertEquals(new VersionedName("foo", new Version("1.0.0.alpha")), VersionedName.fromString("foo:"+
-            OsgiUtils.toOsgiVersion("1.0-alpha")));
+            BrooklynVersionSyntax.toValidOsgiVersion("1.0-alpha")));
     }
 
 }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/ed98e27e/utils/common/src/test/java/org/apache/brooklyn/util/text/BrooklynVersionSyntaxTest.java
----------------------------------------------------------------------
diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/text/BrooklynVersionSyntaxTest.java b/utils/common/src/test/java/org/apache/brooklyn/util/text/BrooklynVersionSyntaxTest.java
new file mode 100644
index 0000000..b938935
--- /dev/null
+++ b/utils/common/src/test/java/org/apache/brooklyn/util/text/BrooklynVersionSyntaxTest.java
@@ -0,0 +1,98 @@
+/*
+ * 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.brooklyn.util.text;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+@Test
+public class BrooklynVersionSyntaxTest {
+
+    public void testVersions() {
+        assertVersion("1", true, true, true);
+        assertVersion("1.0.0", true, true, true);
+        assertVersion("1.0.0.SNAPSHOT", true, true, false);
+        assertVersion("1.0.0-SNAPSHOT", true, false, true);
+        
+        assertVersion("", false, false, false);
+        assertVersion(null, false, false, false);
+        assertVersion("1:1", false, false, false);
+        
+        assertVersion("1.SNAPSHOT", true, false, false);
+        assertVersion("1.0.0_SNAPSHOT", true, false, false);
+        assertVersion(".1", true, false, false);
+        assertVersion("v1", true, false, false);
+        assertVersion("!$&", true, false, false);
+    }
+    
+    public void testConvert() {
+        assertConverts("0", "0", "0.0.0");
+        assertConverts("1.1", "1.1", "1.1.0");
+        assertConverts("x", "0.0.0-x", "0.0.0.x");
+        assertConverts("0.x", "0-x", "0.0.0.x");
+        assertConverts("1.1.x", "1.1-x", "1.1.0.x");
+        assertConverts("1.1.1.1.x", "1.1.1-1_x", "1.1.1.1_x");
+        assertConverts("1x", "1-x", "1.0.0.x");
+        assertConverts("1$", "1-_", "1.0.0._");
+        assertConverts("1-", "1-_", "1.0.0._");
+        assertConverts("1.1.", "1.1-_", "1.1.0._");
+        assertConverts("1$$$", "1-_", "1.0.0._");
+        assertConverts("1._$", "1-__", "1.0.0.__");
+        assertConverts("1a$$$", "1-a_", "1.0.0.a_");
+        assertConverts("1a$$$SNAPSHOT", "1-a_SNAPSHOT", "1.0.0.a_SNAPSHOT");
+    }
+    
+    private void assertConverts(String input, String bklyn, String osgi) {
+        Assert.assertEquals(BrooklynVersionSyntax.toGoodBrooklynVersion(input), bklyn, "conversion to good brooklyn");
+        Assert.assertEquals(BrooklynVersionSyntax.toValidOsgiVersion(input), osgi, "conversion to valid osgi");
+    }
+
+    private void assertVersion(String candidate, boolean isUsable, boolean isOsgi, boolean isGood) {
+        Assert.assertEquals(BrooklynVersionSyntax.isUsableVersion(candidate), isUsable, "usable version '"+candidate+"'");
+        Assert.assertEquals(BrooklynVersionSyntax.isValidOsgiVersion(candidate), isOsgi, "osgi version '"+candidate+"'");
+        Assert.assertEquals(BrooklynVersionSyntax.isGoodBrooklynVersion(candidate), isGood, "good version '"+candidate+"'");
+    }
+    
+    private void assertOsgiVersion(String input, String osgi) {
+        Assert.assertEquals(BrooklynVersionSyntax.toValidOsgiVersion(input), osgi, "conversion to valid osgi");
+    }
+    
+    public void testOsgiVersions() {
+        assertOsgiVersion("0.10.0-20160713.1653", "0.10.0.20160713_1653");
+
+        assertOsgiVersion("2.1.0-SNAPSHOT", "2.1.0.SNAPSHOT");
+        assertOsgiVersion("2.1-SNAPSHOT", "2.1.0.SNAPSHOT");
+        assertOsgiVersion("0.1-SNAPSHOT", "0.1.0.SNAPSHOT");
+        assertOsgiVersion("2-SNAPSHOT", "2.0.0.SNAPSHOT");
+        assertOsgiVersion("2", "2.0.0");
+        assertOsgiVersion("2.1", "2.1.0");
+        assertOsgiVersion("2.1.3", "2.1.3");
+        assertOsgiVersion("2.1.3.4", "2.1.3.4");
+        assertOsgiVersion("1.1-alpha-2", "1.1.0.alpha-2");
+        assertOsgiVersion("1.0-alpha-16-20070122.203121-13", "1.0.0.alpha-16-20070122_203121-13");
+        assertOsgiVersion("1.0-20070119.021432-1", "1.0.0.20070119_021432-1");
+        assertOsgiVersion("1-20070119.021432-1", "1.0.0.20070119_021432-1");
+        assertOsgiVersion("1.4.1-20070217.082013-7", "1.4.1.20070217_082013-7");
+        assertOsgiVersion("0.0.0.4aug2000r7-dev", "0.0.0.4aug2000r7-dev");
+        assertOsgiVersion("0-4aug2000r7-dev", "0.0.0.4aug2000r7-dev");
+        assertOsgiVersion("-4aug2000r7-dev", "0.0.0.4aug2000r7-dev");
+        // potentially surprising, and different to maven (0.0.0.4aug..)
+        assertOsgiVersion("4aug2000r7-dev", "4.0.0.aug2000r7-dev");
+    }
+}