diff --git a/guacamole/pom.xml b/guacamole/pom.xml
index e362b3391..6cf75842c 100644
--- a/guacamole/pom.xml
+++ b/guacamole/pom.xml
@@ -374,6 +374,13 @@
1.2.2
+
+
+ junit
+ junit
+ test
+
+
diff --git a/guacamole/src/main/java-templates/org/apache/guacamole/extension/GuacamoleVersion.java b/guacamole/src/main/java-templates/org/apache/guacamole/extension/GuacamoleVersion.java
new file mode 100644
index 000000000..ef5e85325
--- /dev/null
+++ b/guacamole/src/main/java-templates/org/apache/guacamole/extension/GuacamoleVersion.java
@@ -0,0 +1,159 @@
+/*
+ * 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.guacamole.extension;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Representation of the Guacamole webapp version. This is used both to
+ * represent the expected minimum webapp version required by an extension and
+ * to represent the running version of Guacamole.
+ */
+public class GuacamoleVersion {
+
+ /**
+ * Regular expression that splits a version number into its major, minor,
+ * and patch components, as well as an optional version suffix.
+ */
+ private static final Pattern VERSION_PATTERN = Pattern.compile("(\\d+)\\.(\\d+)\\.(\\d+)(.+)?");
+
+ /**
+ * The version of this copy of the Guacamole web application. This value is
+ * substituted by Maven during the build.
+ */
+ public static final GuacamoleVersion RUNNING_VERSION = new GuacamoleVersion("${project.version}");
+
+ /**
+ * The first (leftmost) version number of a Guacamole version. This is the
+ * number of greatest significance and denotes compatibility. If two
+ * Guacamole versions have the same major number, extensions for the older
+ * version should still work with the newer version.
+ */
+ private final int major;
+
+ /**
+ * The second (middle) version number of a Guacamole version. If two
+ * Guacamole versions have the same major number but different minor
+ * numbers, they would be expected to differ in the features available.
+ */
+ private final int minor;
+
+ /**
+ * The third (rightmost) version number of a Guacamole version. This is the
+ * number of least significance and denotes minor changes or fixes. If
+ * two Guacamole versions have the same major and minor numbers but
+ * different patch numbers, the newer version would be expected to have
+ * fixes or improvements that correct issues affecting the older version,
+ * but would not be expected to have major new features.
+ */
+ private final int patch;
+
+ /**
+ * An arbitrary suffix that might be appended by downstream package
+ * maintainers or vendors. A Guacamole version with a particular suffix
+ * would be expected to have patches applied relative to the version without
+ * the suffix but is otherwise compatible (as if the patch or minor numbers
+ * differed instead of the suffix).
+ */
+ private final String suffix;
+
+ /**
+ * Creates a new GuacamoleVersion representing the version of Guacamole
+ * that has the given version string. The version string must be in the
+ * format "MAJOR.MINOR.PATCH" with an optional and arbitrary version
+ * suffix.
+ *
+ * @param versionStr
+ * The Guacamole version string to parse.
+ *
+ * @throws IllegalArgumentException
+ * If the version string is not in the correct format.
+ */
+ public GuacamoleVersion(String versionStr) throws IllegalArgumentException {
+
+ // Use all-zeroes without suffix to represent the wildcard version
+ if ("*".equals(versionStr)) {
+ major = minor = patch = 0;
+ suffix = null;
+ }
+
+ // Extract major/minor/patch and optional suffix from version string
+ else {
+
+ Matcher versionMatcher = VERSION_PATTERN.matcher(versionStr);
+ if (!versionMatcher.matches())
+ throw new IllegalArgumentException("Format of version "
+ + "string \"" + versionStr + "\" is not valid");
+
+ major = Integer.parseInt(versionMatcher.group(1));
+ minor = Integer.parseInt(versionMatcher.group(2));
+ patch = Integer.parseInt(versionMatcher.group(3));
+ suffix = versionMatcher.group(4);
+
+ }
+
+ }
+
+ /**
+ * Returns whether this Guacamole version is expected to be compatible with
+ * an extension that requires at least the given Guacamole version.
+ *
+ * @param required
+ * The minimum Guacamole version required.
+ *
+ * @return
+ * true if this Guacamole version is expected to be compatible with an
+ * extension requiring at least the given version, false otherwise.
+ */
+ public boolean provides(GuacamoleVersion required) {
+
+ // Wildcard version always matches
+ if (required.major == 0 && required.minor == 0 && required.patch == 0
+ && required.suffix == null)
+ return true;
+
+ // Major version component MUST be identical
+ if (this.major != required.major)
+ return false;
+
+ // Minor version component must be the same or greater than the
+ // requirement
+ if (this.minor < required.minor)
+ return false;
+
+ // If both major and minor are identical, the patch version component
+ // must be the same or greater than the requirement
+ if (this.minor == required.minor && this.patch < required.patch)
+ return false;
+
+ // Any suffix denotes vendor-specific patching that must be assumed to
+ // be an arbitrary requirement only satisfied by that suffix. If no
+ // suffix is required, any suffix of the current Guacamole version can
+ // just be ignored.
+
+ if (required.suffix == null)
+ return true;
+
+ return required.suffix.equals(this.suffix);
+
+ }
+
+}
diff --git a/guacamole/src/main/java/org/apache/guacamole/extension/ExtensionModule.java b/guacamole/src/main/java/org/apache/guacamole/extension/ExtensionModule.java
index 21adbe81e..ae91ce722 100644
--- a/guacamole/src/main/java/org/apache/guacamole/extension/ExtensionModule.java
+++ b/guacamole/src/main/java/org/apache/guacamole/extension/ExtensionModule.java
@@ -24,7 +24,6 @@ import com.google.inject.servlet.ServletModule;
import java.io.File;
import java.io.FileFilter;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
@@ -57,27 +56,6 @@ public class ExtensionModule extends ServletModule {
*/
private final Logger logger = LoggerFactory.getLogger(ExtensionModule.class);
- /**
- * The version strings of all Guacamole versions whose extensions are
- * compatible with this release.
- */
- private static final List ALLOWED_GUACAMOLE_VERSIONS =
- Collections.unmodifiableList(Arrays.asList(
- "*",
- "1.0.0",
- "1.1.0",
- "1.2.0",
- "1.3.0",
- "1.4.0",
- "1.5.0",
- "1.5.1",
- "1.5.2",
- "1.5.3",
- "1.5.4",
- "1.5.5",
- "1.6.0"
- ));
-
/**
* The name of the directory within GUACAMOLE_HOME containing any .jars
* which should be included in the classpath of all extensions.
@@ -394,21 +372,6 @@ public class ExtensionModule extends ServletModule {
}
- /**
- * Returns whether the given version of Guacamole is compatible with this
- * version of Guacamole as far as extensions are concerned.
- *
- * @param guacamoleVersion
- * The version of Guacamole the extension was built for.
- *
- * @return
- * true if the given version of Guacamole is compatible with this
- * version of Guacamole, false otherwise.
- */
- private boolean isCompatible(String guacamoleVersion) {
- return ALLOWED_GUACAMOLE_VERSIONS.contains(guacamoleVersion);
- }
-
/**
* Returns the set of identifiers of all authentication providers whose
* internal failures should be tolerated during the authentication process.
@@ -513,7 +476,8 @@ public class ExtensionModule extends ServletModule {
Extension extension = new Extension(getParentClassLoader(), extensionFile, temporaryFiles);
// Validate Guacamole version of extension
- if (!isCompatible(extension.getGuacamoleVersion())) {
+ GuacamoleVersion requiredVersion = new GuacamoleVersion(extension.getGuacamoleVersion());
+ if (!GuacamoleVersion.RUNNING_VERSION.provides(requiredVersion)) {
logger.debug("Declared Guacamole version \"{}\" of extension \"{}\" is not compatible with this version of Guacamole.",
extension.getGuacamoleVersion(), extensionFile.getName());
throw new GuacamoleServerException("Extension \"" + extension.getName() + "\" is not "
@@ -523,7 +487,7 @@ public class ExtensionModule extends ServletModule {
extensions.add(extension);
}
- catch (GuacamoleException e) {
+ catch (GuacamoleException | IllegalArgumentException e) {
logger.error("Extension \"{}\" could not be loaded: {}",
extensionFile.getName(), e.getMessage(), e);
}
diff --git a/guacamole/src/test/java/org/apache/guacamole/extension/GuacamoleVersionTest.java b/guacamole/src/test/java/org/apache/guacamole/extension/GuacamoleVersionTest.java
new file mode 100644
index 000000000..53ced59b7
--- /dev/null
+++ b/guacamole/src/test/java/org/apache/guacamole/extension/GuacamoleVersionTest.java
@@ -0,0 +1,150 @@
+/*
+ * 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.guacamole.extension;
+
+import java.util.Arrays;
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * Unit tests which verify GuacamoleVersion correctly parses and compares
+ * Guacamole versions.
+ */
+public class GuacamoleVersionTest {
+
+ /**
+ * Verifies that trivially valid version numbers are correctly parsed.
+ */
+ @Test
+ public void testValidVersion() {
+ GuacamoleVersion version = new GuacamoleVersion("1.2.3");
+ Assert.assertTrue(version.provides(version));
+ }
+
+ /**
+ * Verifies that invalid version numbers correctly fail to parse.
+ */
+ @Test
+ public void testInvalidVersion() {
+ for (String invalid : Arrays.asList("1.2")) {
+ try {
+ GuacamoleVersion version = new GuacamoleVersion(invalid);
+ Assert.fail("Invalid Guacamole version string \""
+ + invalid + "\" should have failed to parse.");
+ }
+ catch (IllegalArgumentException e) {
+ // Expected
+ }
+ }
+ }
+
+ /**
+ * Verifies that newer versions are recognized as providing older versions
+ * when they differ by minor number (but not the other way around).
+ */
+ @Test
+ public void testMinorNewerProvidesOlder() {
+
+ GuacamoleVersion olderVersion = new GuacamoleVersion("1.2.3");
+ GuacamoleVersion newerVersion = new GuacamoleVersion("1.22.3");
+
+ Assert.assertTrue(newerVersion.provides(olderVersion));
+ Assert.assertFalse(olderVersion.provides(newerVersion));
+
+ }
+
+ /**
+ * Verifies that newer versions are recognized as providing older versions
+ * when they differ by patch number (but not the other way around).
+ */
+ @Test
+ public void testPatchNewerProvidesOlder() {
+
+ GuacamoleVersion olderVersion = new GuacamoleVersion("1.2.3");
+ GuacamoleVersion newerVersion = new GuacamoleVersion("1.2.7");
+
+ Assert.assertTrue(newerVersion.provides(olderVersion));
+ Assert.assertFalse(olderVersion.provides(newerVersion));
+
+ }
+
+ /**
+ * Verifies that two versions are NOT recognized as providing each other
+ * when they differ by major number (regardless of which is newer).
+ */
+ @Test
+ public void testPatchMajorIncompatible() {
+
+ GuacamoleVersion olderVersion = new GuacamoleVersion("1.2.3");
+ GuacamoleVersion newerVersion = new GuacamoleVersion("2.2.3");
+
+ Assert.assertFalse(newerVersion.provides(olderVersion));
+ Assert.assertFalse(olderVersion.provides(newerVersion));
+
+ }
+
+ /**
+ * Verifies that a version number with an arbitrary suffix is recognized
+ * as providing that same version without the suffix (but not the other way
+ * around).
+ */
+ @Test
+ public void testCompatibleSuffix() {
+
+ GuacamoleVersion olderVersion = new GuacamoleVersion("1.2.3");
+ GuacamoleVersion newerVersion = new GuacamoleVersion("1.2.3-foo");
+
+ Assert.assertTrue(newerVersion.provides(olderVersion));
+ Assert.assertFalse(olderVersion.provides(newerVersion));
+
+ }
+
+ /**
+ * Verifies that two version numbers that differ by arbitrary suffix are
+ * NOT recognized as providing each other, even when the version numbers
+ * are otherwise the same.
+ */
+ @Test
+ public void testIncompatibleSuffix() {
+
+ GuacamoleVersion olderVersion = new GuacamoleVersion("1.2.3-bar");
+ GuacamoleVersion newerVersion = new GuacamoleVersion("1.2.3-foo");
+
+ Assert.assertFalse(newerVersion.provides(olderVersion));
+ Assert.assertFalse(olderVersion.provides(newerVersion));
+
+ }
+
+ /**
+ * Verifies that the special wildcard version is provided by any version,
+ * but not the other way around.
+ */
+ @Test
+ public void testWildcardVersion() {
+
+ GuacamoleVersion specificVersion = new GuacamoleVersion("1.2.3-foo");
+ GuacamoleVersion wildcardVersion = new GuacamoleVersion("*");
+
+ Assert.assertTrue(specificVersion.provides(wildcardVersion));
+ Assert.assertFalse(wildcardVersion.provides(specificVersion));
+
+ }
+
+}
diff --git a/pom.xml b/pom.xml
index 92070e942..ca7eb5923 100644
--- a/pom.xml
+++ b/pom.xml
@@ -490,6 +490,31 @@
+
+
+ filter-java-source
+
+ src/main/java-templates
+
+
+
+
+ org.codehaus.mojo
+ templating-maven-plugin
+ 3.0.0
+
+
+ filter-java-source
+
+ filter-sources
+
+
+
+
+
+
+
+
filter-resources