You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@guacamole.apache.org by jm...@apache.org on 2016/03/29 06:20:14 UTC

[05/51] [abbrv] incubator-guacamole-client git commit: GUACAMOLE-1: Remove useless .net.basic subpackage, now that everything is being renamed.

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/648a6c96/guacamole/src/main/java/org/apache/guacamole/properties/AuthenticationProviderProperty.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/properties/AuthenticationProviderProperty.java b/guacamole/src/main/java/org/apache/guacamole/properties/AuthenticationProviderProperty.java
new file mode 100644
index 0000000..09f3576
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/properties/AuthenticationProviderProperty.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2015 Glyptodon LLC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package org.apache.guacamole.properties;
+
+import org.apache.guacamole.GuacamoleException;
+import org.apache.guacamole.net.auth.AuthenticationProvider;
+import org.apache.guacamole.properties.GuacamoleProperty;
+
+/**
+ * A GuacamoleProperty whose value is the name of a class to use to
+ * authenticate users. This class must implement AuthenticationProvider. Use
+ * of this property type is deprecated in favor of the
+ * GUACAMOLE_HOME/extensions directory.
+ *
+ * @author Michael Jumper
+ */
+@Deprecated
+public abstract class AuthenticationProviderProperty implements GuacamoleProperty<Class<AuthenticationProvider>> {
+
+    @Override
+    @SuppressWarnings("unchecked") // Explicitly checked within by isAssignableFrom()
+    public Class<AuthenticationProvider> parseValue(String authProviderClassName) throws GuacamoleException {
+
+        // If no property provided, return null.
+        if (authProviderClassName == null)
+            return null;
+
+        // Get auth provider instance
+        try {
+
+            // Get authentication provider class
+            Class<?> authProviderClass = org.apache.guacamole.GuacamoleClassLoader.getInstance().loadClass(authProviderClassName);
+
+            // Verify the located class is actually a subclass of AuthenticationProvider
+            if (!AuthenticationProvider.class.isAssignableFrom(authProviderClass))
+                throw new GuacamoleException("Specified authentication provider class is not a AuthenticationProvider.");
+
+            // Return located class
+            return (Class<AuthenticationProvider>) authProviderClass;
+
+        }
+        catch (ClassNotFoundException e) {
+            throw new GuacamoleException("Authentication provider class not found", e);
+        }
+
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/648a6c96/guacamole/src/main/java/org/apache/guacamole/properties/BasicGuacamoleProperties.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/properties/BasicGuacamoleProperties.java b/guacamole/src/main/java/org/apache/guacamole/properties/BasicGuacamoleProperties.java
new file mode 100644
index 0000000..9d8d99f
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/properties/BasicGuacamoleProperties.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2013 Glyptodon LLC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package org.apache.guacamole.properties;
+
+import org.apache.guacamole.properties.FileGuacamoleProperty;
+import org.apache.guacamole.properties.IntegerGuacamoleProperty;
+import org.apache.guacamole.properties.StringGuacamoleProperty;
+
+/**
+ * Properties used by the default Guacamole web application.
+ *
+ * @author Michael Jumper
+ */
+public class BasicGuacamoleProperties {
+
+    /**
+     * This class should not be instantiated.
+     */
+    private BasicGuacamoleProperties() {}
+
+    /**
+     * The authentication provider to user when retrieving the authorized
+     * configurations of a user. This property is currently supported, but
+     * deprecated in favor of the GUACAMOLE_HOME/extensions directory.
+     */
+    @Deprecated
+    public static final AuthenticationProviderProperty AUTH_PROVIDER = new AuthenticationProviderProperty() {
+
+        @Override
+        public String getName() { return "auth-provider"; }
+
+    };
+
+    /**
+     * The directory to search for authentication provider classes. This
+     * property is currently supported, but deprecated in favor of the
+     * GUACAMOLE_HOME/lib directory.
+     */
+    @Deprecated
+    public static final FileGuacamoleProperty LIB_DIRECTORY = new FileGuacamoleProperty() {
+
+        @Override
+        public String getName() { return "lib-directory"; }
+
+    };
+
+    /**
+     * The session timeout for the API, in minutes.
+     */
+    public static final IntegerGuacamoleProperty API_SESSION_TIMEOUT = new IntegerGuacamoleProperty() {
+
+        @Override
+        public String getName() { return "api-session-timeout"; }
+
+    };
+
+    /**
+     * Comma-separated list of all allowed languages, where each language is
+     * represented by a language key, such as "en" or "en_US". If specified,
+     * only languages within this list will be listed as available by the REST
+     * service.
+     */
+    public static final StringSetProperty ALLOWED_LANGUAGES = new StringSetProperty() {
+
+        @Override
+        public String getName() { return "allowed-languages"; }
+
+    };
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/648a6c96/guacamole/src/main/java/org/apache/guacamole/properties/StringSetProperty.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/properties/StringSetProperty.java b/guacamole/src/main/java/org/apache/guacamole/properties/StringSetProperty.java
new file mode 100644
index 0000000..5bcd13c
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/properties/StringSetProperty.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2015 Glyptodon LLC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package org.apache.guacamole.properties;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.regex.Pattern;
+import org.apache.guacamole.GuacamoleException;
+import org.apache.guacamole.properties.GuacamoleProperty;
+
+/**
+ * A GuacamoleProperty whose value is a Set of unique Strings. The string value
+ * parsed to produce this set is a comma-delimited list. Duplicate values are
+ * ignored, as is any whitespace following delimiters. To maintain
+ * compatibility with the behavior of Java properties in general, only
+ * whitespace at the beginning of each value is ignored; trailing whitespace
+ * becomes part of the value.
+ *
+ * @author Michael Jumper
+ */
+public abstract class StringSetProperty implements GuacamoleProperty<Set<String>> {
+
+    /**
+     * A pattern which matches against the delimiters between values. This is
+     * currently simply a comma and any following whitespace. Parts of the
+     * input string which match this pattern will not be included in the parsed
+     * result.
+     */
+    private static final Pattern DELIMITER_PATTERN = Pattern.compile(",\\s*");
+
+    @Override
+    public Set<String> parseValue(String values) throws GuacamoleException {
+
+        // If no property provided, return null.
+        if (values == null)
+            return null;
+
+        // Split string into a set of individual values
+        List<String> valueList = Arrays.asList(DELIMITER_PATTERN.split(values));
+        return new HashSet<String>(valueList);
+
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/648a6c96/guacamole/src/main/java/org/apache/guacamole/properties/package-info.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/properties/package-info.java b/guacamole/src/main/java/org/apache/guacamole/properties/package-info.java
new file mode 100644
index 0000000..0640815
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/properties/package-info.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2013 Glyptodon LLC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+/**
+ * Classes related to the properties which the Guacamole web application
+ * (and stock parts of it) read from guacamole.properties.
+ */
+package org.apache.guacamole.properties;
+

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/648a6c96/guacamole/src/main/java/org/apache/guacamole/resource/AbstractResource.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/resource/AbstractResource.java b/guacamole/src/main/java/org/apache/guacamole/resource/AbstractResource.java
new file mode 100644
index 0000000..f985361
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/resource/AbstractResource.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2015 Glyptodon LLC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package org.apache.guacamole.resource;
+
+/**
+ * Base abstract resource implementation which provides an associated mimetype,
+ * and modification time. Classes which extend AbstractResource must provide
+ * their own InputStream, however.
+ *
+ * @author Michael Jumper
+ */
+public abstract class AbstractResource implements Resource {
+
+    /**
+     * The mimetype of this resource.
+     */
+    private final String mimetype;
+
+    /**
+     * The time this resource was last modified, in milliseconds since midnight
+     * of January 1, 1970 UTC.
+     */
+    private final long lastModified;
+
+    /**
+     * Initializes this AbstractResource with the given mimetype and
+     * modification time.
+     *
+     * @param mimetype
+     *     The mimetype of this resource.
+     *
+     * @param lastModified
+     *     The time this resource was last modified, in milliseconds since
+     *     midnight of January 1, 1970 UTC.
+     */
+    public AbstractResource(String mimetype, long lastModified) {
+        this.mimetype = mimetype;
+        this.lastModified = lastModified;
+    }
+
+    /**
+     * Initializes this AbstractResource with the given mimetype. The
+     * modification time of the resource is set to the current system time.
+     *
+     * @param mimetype
+     *     The mimetype of this resource.
+     */
+    public AbstractResource(String mimetype) {
+        this(mimetype, System.currentTimeMillis());
+    }
+
+    @Override
+    public long getLastModified() {
+        return lastModified;
+    }
+
+    @Override
+    public String getMimeType() {
+        return mimetype;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/648a6c96/guacamole/src/main/java/org/apache/guacamole/resource/ByteArrayResource.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/resource/ByteArrayResource.java b/guacamole/src/main/java/org/apache/guacamole/resource/ByteArrayResource.java
new file mode 100644
index 0000000..2fa8199
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/resource/ByteArrayResource.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2015 Glyptodon LLC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package org.apache.guacamole.resource;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+
+/**
+ * A resource which contains a defined byte array.
+ *
+ * @author Michael Jumper
+ */
+public class ByteArrayResource extends AbstractResource {
+
+    /**
+     * The bytes contained by this resource.
+     */
+    private final byte[] bytes;
+
+    /**
+     * Creates a new ByteArrayResource which provides access to the given byte
+     * array. Changes to the given byte array will affect this resource even
+     * after the resource is created. Changing the byte array while an input
+     * stream from this resource is in use has undefined behavior.
+     *
+     * @param mimetype
+     *     The mimetype of the resource.
+     *
+     * @param bytes
+     *     The bytes that this resource should contain.
+     */
+    public ByteArrayResource(String mimetype, byte[] bytes) {
+        super(mimetype);
+        this.bytes = bytes;
+    }
+
+    @Override
+    public InputStream asStream() {
+        return new ByteArrayInputStream(bytes);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/648a6c96/guacamole/src/main/java/org/apache/guacamole/resource/ClassPathResource.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/resource/ClassPathResource.java b/guacamole/src/main/java/org/apache/guacamole/resource/ClassPathResource.java
new file mode 100644
index 0000000..fb81791
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/resource/ClassPathResource.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2015 Glyptodon LLC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package org.apache.guacamole.resource;
+
+import java.io.InputStream;
+
+/**
+ * A resource which is located within the classpath of an arbitrary
+ * ClassLoader.
+ *
+ * @author Michael Jumper
+ */
+public class ClassPathResource extends AbstractResource {
+
+    /**
+     * The classloader to use when reading this resource.
+     */
+    private final ClassLoader classLoader;
+
+    /**
+     * The path of this resource relative to the classloader.
+     */
+    private final String path;
+
+    /**
+     * Creates a new ClassPathResource which uses the given ClassLoader to
+     * read the resource having the given path.
+     *
+     * @param classLoader
+     *     The ClassLoader to use when reading the resource.
+     *
+     * @param mimetype
+     *     The mimetype of the resource.
+     *
+     * @param path
+     *     The path of the resource relative to the given ClassLoader.
+     */
+    public ClassPathResource(ClassLoader classLoader, String mimetype, String path) {
+        super(mimetype);
+        this.classLoader = classLoader;
+        this.path = path;
+    }
+
+    /**
+     * Creates a new ClassPathResource which uses the ClassLoader associated
+     * with the ClassPathResource class to read the resource having the given
+     * path.
+     *
+     * @param mimetype
+     *     The mimetype of the resource.
+     *
+     * @param path
+     *     The path of the resource relative to the ClassLoader associated
+     *     with the ClassPathResource class.
+     */
+    public ClassPathResource(String mimetype, String path) {
+        this(ClassPathResource.class.getClassLoader(), mimetype, path);
+    }
+
+    @Override
+    public InputStream asStream() {
+        return classLoader.getResourceAsStream(path);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/648a6c96/guacamole/src/main/java/org/apache/guacamole/resource/Resource.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/resource/Resource.java b/guacamole/src/main/java/org/apache/guacamole/resource/Resource.java
new file mode 100644
index 0000000..845bd72
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/resource/Resource.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2015 Glyptodon LLC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package org.apache.guacamole.resource;
+
+import java.io.InputStream;
+
+/**
+ * An arbitrary resource that can be served to a user via HTTP. Resources are
+ * anonymous but have a defined mimetype and corresponding input stream.
+ *
+ * @author Michael Jumper
+ */
+public interface Resource {
+
+    /**
+     * Returns the mimetype of this resource. This function MUST always return
+     * a value. If the type is unknown, return "application/octet-stream".
+     *
+     * @return
+     *     The mimetype of this resource.
+     */
+    String getMimeType();
+
+    /**
+     * Returns the time the resource was last modified in milliseconds since
+     * midnight of January 1, 1970 UTC.
+     *
+     * @return
+     *      The time the resource was last modified, in milliseconds.
+     */
+    long getLastModified();
+
+    /**
+     * Returns an InputStream which reads the contents of this resource,
+     * starting with the first byte. Reading from the returned InputStream will
+     * not affect reads from other InputStreams returned by other calls to
+     * asStream(). The returned InputStream must be manually closed when no
+     * longer needed. If the resource is unexpectedly unavailable, this will
+     * return null.
+     *
+     * @return
+     *     An InputStream which reads the contents of this resource, starting
+     *     with the first byte, or null if the resource is unavailable.
+     */
+    InputStream asStream();
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/648a6c96/guacamole/src/main/java/org/apache/guacamole/resource/ResourceServlet.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/resource/ResourceServlet.java b/guacamole/src/main/java/org/apache/guacamole/resource/ResourceServlet.java
new file mode 100644
index 0000000..f1af7c6
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/resource/ResourceServlet.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2015 Glyptodon LLC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package org.apache.guacamole.resource;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Servlet which serves a given resource for all HTTP GET requests. The HEAD
+ * method is correctly supported, and HTTP 304 ("Not Modified") responses will
+ * be properly returned for GET requests depending on the last time the
+ * resource was modified.
+ *
+ * @author Michael Jumper
+ */
+public class ResourceServlet extends HttpServlet {
+
+    /**
+     * Logger for this class.
+     */
+    private static final Logger logger = LoggerFactory.getLogger(ResourceServlet.class);
+
+    /**
+     * The size of the buffer to use when transferring data from the input
+     * stream of a resource to the output stream of a request.
+     */
+    private static final int BUFFER_SIZE = 10240;
+
+    /**
+     * The resource to serve for every GET request.
+     */
+    private final Resource resource;
+
+    /**
+     * Creates a new ResourceServlet which serves the given Resource for all
+     * HTTP GET requests.
+     *
+     * @param resource
+     *     The Resource to serve.
+     */
+    public ResourceServlet(Resource resource) {
+        this.resource = resource;
+    }
+
+    @Override
+    protected void doHead(HttpServletRequest request, HttpServletResponse response)
+            throws ServletException, IOException {
+
+        // Set last modified and content type headers
+        response.addDateHeader("Last-Modified", resource.getLastModified());
+        response.setContentType(resource.getMimeType());
+
+    }
+
+    @Override
+    protected void doGet(HttpServletRequest request, HttpServletResponse response)
+            throws ServletException, IOException {
+
+        // Get input stream from resource
+        InputStream input = resource.asStream();
+
+        // If resource does not exist, return not found
+        if (input == null) {
+            logger.debug("Resource does not exist: \"{}\"", request.getServletPath());
+            response.setStatus(HttpServletResponse.SC_NOT_FOUND);
+            return;
+        }
+
+        try {
+
+            // Write headers
+            doHead(request, response);
+
+            // If not modified since "If-Modified-Since" header, return not modified
+            long ifModifiedSince = request.getDateHeader("If-Modified-Since");
+            if (resource.getLastModified() - ifModifiedSince < 1000) {
+                logger.debug("Resource not modified: \"{}\"", request.getServletPath());
+                response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
+                return;
+            }
+
+            int length;
+            byte[] buffer = new byte[BUFFER_SIZE];
+
+            // Write resource to response body
+            OutputStream output = response.getOutputStream();
+            while ((length = input.read(buffer)) != -1)
+                output.write(buffer, 0, length);
+
+        }
+
+        // Ensure input stream is always closed
+        finally {
+            input.close();
+        }
+
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/648a6c96/guacamole/src/main/java/org/apache/guacamole/resource/SequenceResource.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/resource/SequenceResource.java b/guacamole/src/main/java/org/apache/guacamole/resource/SequenceResource.java
new file mode 100644
index 0000000..fe407b5
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/resource/SequenceResource.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2015 Glyptodon LLC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package org.apache.guacamole.resource;
+
+import java.io.InputStream;
+import java.io.SequenceInputStream;
+import java.util.Arrays;
+import java.util.Enumeration;
+import java.util.Iterator;
+
+/**
+ * A resource which is the logical concatenation of other resources.
+ *
+ * @author Michael Jumper
+ */
+public class SequenceResource extends AbstractResource {
+
+    /**
+     * The resources to be concatenated.
+     */
+    private final Iterable<Resource> resources;
+
+    /**
+     * Returns the mimetype of the first resource in the given Iterable, or
+     * "application/octet-stream" if no resources are provided.
+     *
+     * @param resources
+     *     The resources from which the mimetype should be retrieved.
+     *
+     * @return
+     *     The mimetype of the first resource, or "application/octet-stream"
+     *     if no resources were provided.
+     */
+    private static String getMimeType(Iterable<Resource> resources) {
+
+        // If no resources, just assume application/octet-stream
+        Iterator<Resource> resourceIterator = resources.iterator();
+        if (!resourceIterator.hasNext())
+            return "application/octet-stream";
+
+        // Return mimetype of first resource
+        return resourceIterator.next().getMimeType();
+
+    }
+
+    /**
+     * Creates a new SequenceResource as the logical concatenation of the
+     * given resources. Each resource is concatenated in iteration order as
+     * needed when reading from the input stream of the SequenceResource.
+     *
+     * @param mimetype
+     *     The mimetype of the resource.
+     *
+     * @param resources
+     *     The resources to concatenate within the InputStream of this
+     *     SequenceResource.
+     */
+    public SequenceResource(String mimetype, Iterable<Resource> resources) {
+        super(mimetype);
+        this.resources = resources;
+    }
+
+    /**
+     * Creates a new SequenceResource as the logical concatenation of the
+     * given resources. Each resource is concatenated in iteration order as
+     * needed when reading from the input stream of the SequenceResource. The
+     * mimetype of the resulting concatenation is derived from the first
+     * resource.
+     *
+     * @param resources
+     *     The resources to concatenate within the InputStream of this
+     *     SequenceResource.
+     */
+    public SequenceResource(Iterable<Resource> resources) {
+        super(getMimeType(resources));
+        this.resources = resources;
+    }
+
+    /**
+     * Creates a new SequenceResource as the logical concatenation of the
+     * given resources. Each resource is concatenated in iteration order as
+     * needed when reading from the input stream of the SequenceResource.
+     *
+     * @param mimetype
+     *     The mimetype of the resource.
+     *
+     * @param resources
+     *     The resources to concatenate within the InputStream of this
+     *     SequenceResource.
+     */
+    public SequenceResource(String mimetype, Resource... resources) {
+        this(mimetype, Arrays.asList(resources));
+    }
+
+    /**
+     * Creates a new SequenceResource as the logical concatenation of the
+     * given resources. Each resource is concatenated in iteration order as
+     * needed when reading from the input stream of the SequenceResource. The
+     * mimetype of the resulting concatenation is derived from the first
+     * resource.
+     *
+     * @param resources
+     *     The resources to concatenate within the InputStream of this
+     *     SequenceResource.
+     */
+    public SequenceResource(Resource... resources) {
+        this(Arrays.asList(resources));
+    }
+
+    @Override
+    public InputStream asStream() {
+        return new SequenceInputStream(new Enumeration<InputStream>() {
+
+            /**
+             * Iterator over all resources associated with this
+             * SequenceResource.
+             */
+            private final Iterator<Resource> resourceIterator = resources.iterator();
+
+            @Override
+            public boolean hasMoreElements() {
+                return resourceIterator.hasNext();
+            }
+
+            @Override
+            public InputStream nextElement() {
+                return resourceIterator.next().asStream();
+            }
+
+        });
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/648a6c96/guacamole/src/main/java/org/apache/guacamole/resource/WebApplicationResource.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/resource/WebApplicationResource.java b/guacamole/src/main/java/org/apache/guacamole/resource/WebApplicationResource.java
new file mode 100644
index 0000000..b8aae14
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/resource/WebApplicationResource.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2015 Glyptodon LLC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package org.apache.guacamole.resource;
+
+import java.io.InputStream;
+import javax.servlet.ServletContext;
+
+/**
+ * A resource which is located within the classpath associated with another
+ * class.
+ *
+ * @author Michael Jumper
+ */
+public class WebApplicationResource extends AbstractResource {
+
+    /**
+     * The servlet context to use when reading the resource and, if necessary,
+     * when determining the mimetype of the resource.
+     */
+    private final ServletContext context;
+
+    /**
+     * The path of this resource relative to the ServletContext.
+     */
+    private final String path;
+
+    /**
+     * Derives a mimetype from the filename within the given path using the
+     * given ServletContext, if possible.
+     *
+     * @param context
+     *     The ServletContext to use to derive the mimetype.
+     *
+     * @param path
+     *     The path to derive the mimetype from.
+     *
+     * @return
+     *     An appropriate mimetype based on the name of the file in the path,
+     *     or "application/octet-stream" if no mimetype could be determined.
+     */
+    private static String getMimeType(ServletContext context, String path) {
+
+        // If mimetype is known, use defined mimetype
+        String mimetype = context.getMimeType(path);
+        if (mimetype != null)
+            return mimetype;
+
+        // Otherwise, default to application/octet-stream
+        return "application/octet-stream";
+
+    }
+
+    /**
+     * Creates a new WebApplicationResource which serves the resource at the
+     * given path relative to the given ServletContext. Rather than deriving
+     * the mimetype of the resource from the filename within the path, the
+     * mimetype given is used.
+     *
+     * @param context
+     *     The ServletContext to use when reading the resource.
+     *
+     * @param mimetype
+     *     The mimetype of the resource.
+     *
+     * @param path
+     *     The path of the resource relative to the given ServletContext.
+     */
+    public WebApplicationResource(ServletContext context, String mimetype, String path) {
+        super(mimetype);
+        this.context = context;
+        this.path = path;
+    }
+
+    /**
+     * Creates a new WebApplicationResource which serves the resource at the
+     * given path relative to the given ServletContext. The mimetype of the
+     * resource is automatically determined based on the filename within the
+     * path.
+     *
+     * @param context
+     *     The ServletContext to use when reading the resource and deriving the
+     *     mimetype.
+     *
+     * @param path
+     *     The path of the resource relative to the given ServletContext.
+     */
+    public WebApplicationResource(ServletContext context, String path) {
+        this(context, getMimeType(context, path), path);
+    }
+
+    @Override
+    public InputStream asStream() {
+        return context.getResourceAsStream(path);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/648a6c96/guacamole/src/main/java/org/apache/guacamole/resource/package-info.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/resource/package-info.java b/guacamole/src/main/java/org/apache/guacamole/resource/package-info.java
new file mode 100644
index 0000000..edbe504
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/resource/package-info.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2015 Glyptodon LLC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+/**
+ * Classes which describe and provide access to arbitrary resources, such as
+ * the contents of the classpath of a classloader, or files within the web
+ * application itself.
+ */
+package org.apache.guacamole.resource;

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/648a6c96/guacamole/src/main/java/org/apache/guacamole/rest/APIError.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/APIError.java b/guacamole/src/main/java/org/apache/guacamole/rest/APIError.java
new file mode 100644
index 0000000..74fa7d2
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/rest/APIError.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2015 Glyptodon LLC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package org.apache.guacamole.rest;
+
+import java.util.Collection;
+import javax.ws.rs.core.Response;
+import org.apache.guacamole.form.Field;
+
+/**
+ * Describes an error that occurred within a REST endpoint.
+ *
+ * @author James Muehlner
+ * @author Michael Jumper
+ */
+public class APIError {
+
+    /**
+     * The error message.
+     */
+    private final String message;
+
+    /**
+     * All expected request parameters, if any, as a collection of fields.
+     */
+    private final Collection<Field> expected;
+
+    /**
+     * The type of error that occurred.
+     */
+    private final Type type;
+
+    /**
+     * All possible types of REST API errors.
+     */
+    public enum Type {
+
+        /**
+         * The requested operation could not be performed because the request
+         * itself was malformed.
+         */
+        BAD_REQUEST(Response.Status.BAD_REQUEST),
+
+        /**
+         * The credentials provided were invalid.
+         */
+        INVALID_CREDENTIALS(Response.Status.FORBIDDEN),
+
+        /**
+         * The credentials provided were not necessarily invalid, but were not
+         * sufficient to determine validity.
+         */
+        INSUFFICIENT_CREDENTIALS(Response.Status.FORBIDDEN),
+
+        /**
+         * An internal server error has occurred.
+         */
+        INTERNAL_ERROR(Response.Status.INTERNAL_SERVER_ERROR),
+
+        /**
+         * An object related to the request does not exist.
+         */
+        NOT_FOUND(Response.Status.NOT_FOUND),
+
+        /**
+         * Permission was denied to perform the requested operation.
+         */
+        PERMISSION_DENIED(Response.Status.FORBIDDEN);
+
+        /**
+         * The HTTP status associated with this error type.
+         */
+        private final Response.Status status;
+
+        /**
+         * Defines a new error type associated with the given HTTP status.
+         *
+         * @param status
+         *     The HTTP status to associate with the error type.
+         */
+        Type(Response.Status status) {
+            this.status = status;
+        }
+
+        /**
+         * Returns the HTTP status associated with this error type.
+         *
+         * @return
+         *     The HTTP status associated with this error type.
+         */
+        public Response.Status getStatus() {
+            return status;
+        }
+
+    }
+
+    /**
+     * Create a new APIError with the specified error message.
+     *
+     * @param type
+     *     The type of error that occurred.
+     *
+     * @param message
+     *     The error message.
+     */
+    public APIError(Type type, String message) {
+        this.type     = type;
+        this.message  = message;
+        this.expected = null;
+    }
+
+    /**
+     * Create a new APIError with the specified error message and parameter
+     * information.
+     *
+     * @param type
+     *     The type of error that occurred.
+     *
+     * @param message
+     *     The error message.
+     *
+     * @param expected
+     *     All parameters expected in the original request, or now required as
+     *     a result of the original request, as a collection of fields.
+     */
+    public APIError(Type type, String message, Collection<Field> expected) {
+        this.type     = type;
+        this.message  = message;
+        this.expected = expected;
+    }
+
+    /**
+     * Returns the type of error that occurred.
+     *
+     * @return
+     *     The type of error that occurred.
+     */
+    public Type getType() {
+        return type;
+    }
+
+    /**
+     * Returns a collection of all required parameters, where each parameter is
+     * represented by a field.
+     *
+     * @return
+     *     A collection of all required parameters.
+     */
+    public Collection<Field> getExpected() {
+        return expected;
+    }
+
+    /**
+     * Returns a human-readable error message describing the error that
+     * occurred.
+     *
+     * @return
+     *     A human-readable error message.
+     */
+    public String getMessage() {
+        return message;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/648a6c96/guacamole/src/main/java/org/apache/guacamole/rest/APIException.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/APIException.java b/guacamole/src/main/java/org/apache/guacamole/rest/APIException.java
new file mode 100644
index 0000000..7a70fb8
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/rest/APIException.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2014 Glyptodon LLC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package org.apache.guacamole.rest;
+
+import java.util.Collection;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.Response;
+import org.apache.guacamole.form.Field;
+
+/**
+ * An exception that will result in the given error error information being
+ * returned from the API layer. All error messages have the same format which
+ * is defined by APIError.
+ *
+ * @author James Muehlner
+ * @author Michael Jumper
+ */
+public class APIException extends WebApplicationException {
+
+    /**
+     * Construct a new APIException with the given error. All information
+     * associated with this new exception will be extracted from the given
+     * APIError.
+     *
+     * @param error
+     *     The error that occurred.
+     */
+    public APIException(APIError error) {
+        super(Response.status(error.getType().getStatus()).entity(error).build());
+    }
+
+    /**
+     * Creates a new APIException with the given type and message. The
+     * corresponding APIError will be created from the provided information.
+     *
+     * @param type
+     *     The type of error that occurred.
+     *
+     * @param message
+     *     A human-readable message describing the error.
+     */
+    public APIException(APIError.Type type, String message) {
+        this(new APIError(type, message));
+    }
+
+    /**
+     * Creates a new APIException with the given type, message, and parameter
+     * information. The corresponding APIError will be created from the
+     * provided information.
+     *
+     * @param type
+     *     The type of error that occurred.
+     *
+     * @param message
+     *     A human-readable message describing the error.
+     *
+     * @param expected
+     *     All parameters expected in the original request, or now required as
+     *     a result of the original request, as a collection of fields.
+     */
+    public APIException(APIError.Type type, String message, Collection<Field> expected) {
+        this(new APIError(type, message, expected));
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/648a6c96/guacamole/src/main/java/org/apache/guacamole/rest/APIPatch.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/APIPatch.java b/guacamole/src/main/java/org/apache/guacamole/rest/APIPatch.java
new file mode 100644
index 0000000..08ee062
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/rest/APIPatch.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2014 Glyptodon LLC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package org.apache.guacamole.rest;
+
+/**
+ * An object for representing the body of a HTTP PATCH method.
+ * See https://tools.ietf.org/html/rfc6902
+ * 
+ * @author James Muehlner
+ * @param <T> The type of object being patched.
+ */
+public class APIPatch<T> {
+    
+    /**
+     * The possible operations for a PATCH request.
+     */
+    public enum Operation {
+        add, remove, test, copy, replace, move
+    }
+    
+    /**
+     * The operation to perform for this patch.
+     */
+    private Operation op;
+    
+    /**
+     * The value for this patch.
+     */
+    private T value;
+    
+    /**
+     * The path for this patch.
+     */
+    private String path;
+
+    /**
+     * Returns the operation for this patch.
+     * @return the operation for this patch. 
+     */
+    public Operation getOp() {
+        return op;
+    }
+
+    /**
+     * Set the operation for this patch.
+     * @param op The operation for this patch.
+     */
+    public void setOp(Operation op) {
+        this.op = op;
+    }
+
+    /**
+     * Returns the value of this patch.
+     * @return The value of this patch.
+     */
+    public T getValue() {
+        return value;
+    }
+
+    /**
+     * Sets the value of this patch.
+     * @param value The value of this patch.
+     */
+    public void setValue(T value) {
+        this.value = value;
+    }
+
+    /**
+     * Returns the path for this patch.
+     * @return The path for this patch.
+     */
+    public String getPath() {
+        return path;
+    }
+
+    /**
+     * Set the path for this patch.
+     * @param path The path for this patch.
+     */
+    public void setPath(String path) {
+        this.path = path;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/648a6c96/guacamole/src/main/java/org/apache/guacamole/rest/APIRequest.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/APIRequest.java b/guacamole/src/main/java/org/apache/guacamole/rest/APIRequest.java
new file mode 100644
index 0000000..cd4fcd5
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/rest/APIRequest.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2015 Glyptodon LLC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package org.apache.guacamole.rest;
+
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import javax.ws.rs.core.MultivaluedMap;
+
+/**
+ * Wrapper for HttpServletRequest which uses a given MultivaluedMap to provide
+ * the values of all request parameters.
+ * 
+ * @author Michael Jumper
+ */
+public class APIRequest extends HttpServletRequestWrapper {
+
+    /**
+     * Map of all request parameter names to their corresponding values.
+     */
+    private final Map<String, String[]> parameters;
+
+    /**
+     * Wraps the given HttpServletRequest, using the given MultivaluedMap to
+     * provide all request parameters. All HttpServletRequest functions which
+     * do not deal with parameter names and values are delegated to the wrapped
+     * request.
+     *
+     * @param request
+     *     The HttpServletRequest to wrap.
+     *
+     * @param parameters
+     *     All request parameters.
+     */
+    public APIRequest(HttpServletRequest request,
+            MultivaluedMap<String, String> parameters) {
+
+        super(request);
+
+        // Copy parameters from given MultivaluedMap 
+        this.parameters = new HashMap<String, String[]>(parameters.size());
+        for (Map.Entry<String, List<String>> entry : parameters.entrySet()) {
+
+            // Get parameter name and all corresponding values
+            String name = entry.getKey();
+            List<String> values = entry.getValue();
+
+            // Add parameters to map
+            this.parameters.put(name, values.toArray(new String[values.size()]));
+            
+        }
+        
+    }
+
+    @Override
+    public String[] getParameterValues(String name) {
+        return parameters.get(name);
+    }
+
+    @Override
+    public Enumeration<String> getParameterNames() {
+        return Collections.enumeration(parameters.keySet());
+    }
+
+    @Override
+    public Map<String, String[]> getParameterMap() {
+        return Collections.unmodifiableMap(parameters);
+    }
+
+    @Override
+    public String getParameter(String name) {
+
+        // If no such parameter exists, just return null
+        String[] values = getParameterValues(name);
+        if (values == null)
+            return null;
+
+        // Otherwise, return first value
+        return values[0];
+
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/648a6c96/guacamole/src/main/java/org/apache/guacamole/rest/ObjectRetrievalService.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/ObjectRetrievalService.java b/guacamole/src/main/java/org/apache/guacamole/rest/ObjectRetrievalService.java
new file mode 100644
index 0000000..1cf8919
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/rest/ObjectRetrievalService.java
@@ -0,0 +1,280 @@
+/*
+ * Copyright (C) 2015 Glyptodon LLC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package org.apache.guacamole.rest;
+
+import java.util.List;
+import org.apache.guacamole.GuacamoleException;
+import org.apache.guacamole.GuacamoleResourceNotFoundException;
+import org.apache.guacamole.net.auth.AuthenticationProvider;
+import org.apache.guacamole.net.auth.Connection;
+import org.apache.guacamole.net.auth.ConnectionGroup;
+import org.apache.guacamole.net.auth.Directory;
+import org.apache.guacamole.net.auth.User;
+import org.apache.guacamole.net.auth.UserContext;
+import org.apache.guacamole.GuacamoleSession;
+import org.apache.guacamole.rest.connectiongroup.APIConnectionGroup;
+
+/**
+ * Provides easy access and automatic error handling for retrieval of objects,
+ * such as users, connections, or connection groups. REST API semantics, such
+ * as the special root connection group identifier, are also handled
+ * automatically.
+ */
+public class ObjectRetrievalService {
+
+    /**
+     * Retrieves a single UserContext from the given GuacamoleSession, which
+     * may contain multiple UserContexts.
+     *
+     * @param session
+     *     The GuacamoleSession to retrieve the UserContext from.
+     *
+     * @param authProviderIdentifier
+     *     The unique identifier of the AuthenticationProvider that created the
+     *     UserContext being retrieved. Only one UserContext per User per
+     *     AuthenticationProvider can exist.
+     *
+     * @return
+     *     The UserContext that was created by the AuthenticationProvider
+     *     having the given identifier.
+     *
+     * @throws GuacamoleException
+     *     If an error occurs while retrieving the UserContext, or if the
+     *     UserContext does not exist.
+     */
+    public UserContext retrieveUserContext(GuacamoleSession session,
+            String authProviderIdentifier) throws GuacamoleException {
+
+        // Get list of UserContexts
+        List<UserContext> userContexts = session.getUserContexts();
+
+        // Locate and return the UserContext associated with the
+        // AuthenticationProvider having the given identifier, if any
+        for (UserContext userContext : userContexts) {
+
+            // Get AuthenticationProvider associated with current UserContext
+            AuthenticationProvider authProvider = userContext.getAuthenticationProvider();
+
+            // If AuthenticationProvider identifier matches, done
+            if (authProvider.getIdentifier().equals(authProviderIdentifier))
+                return userContext;
+
+        }
+
+        throw new GuacamoleResourceNotFoundException("Session not associated with authentication provider \"" + authProviderIdentifier + "\".");
+
+    }
+
+    /**
+     * Retrieves a single user from the given user context.
+     *
+     * @param userContext
+     *     The user context to retrieve the user from.
+     *
+     * @param identifier
+     *     The identifier of the user to retrieve.
+     *
+     * @return
+     *     The user having the given identifier.
+     *
+     * @throws GuacamoleException 
+     *     If an error occurs while retrieving the user, or if the
+     *     user does not exist.
+     */
+    public User retrieveUser(UserContext userContext,
+            String identifier) throws GuacamoleException {
+
+        // Get user directory
+        Directory<User> directory = userContext.getUserDirectory();
+
+        // Pull specified user
+        User user = directory.get(identifier);
+        if (user == null)
+            throw new GuacamoleResourceNotFoundException("No such user: \"" + identifier + "\"");
+
+        return user;
+
+    }
+
+    /**
+     * Retrieves a single user from the given GuacamoleSession.
+     *
+     * @param session
+     *     The GuacamoleSession to retrieve the user from.
+     *
+     * @param authProviderIdentifier
+     *     The unique identifier of the AuthenticationProvider that created the
+     *     UserContext from which the user should be retrieved. Only one
+     *     UserContext per User per AuthenticationProvider can exist.
+     *
+     * @param identifier
+     *     The identifier of the user to retrieve.
+     *
+     * @return
+     *     The user having the given identifier.
+     *
+     * @throws GuacamoleException 
+     *     If an error occurs while retrieving the user, or if the
+     *     user does not exist.
+     */
+    public User retrieveUser(GuacamoleSession session, String authProviderIdentifier,
+            String identifier) throws GuacamoleException {
+
+        UserContext userContext = retrieveUserContext(session, authProviderIdentifier);
+        return retrieveUser(userContext, identifier);
+
+    }
+
+    /**
+     * Retrieves a single connection from the given user context.
+     *
+     * @param userContext
+     *     The user context to retrieve the connection from.
+     *
+     * @param identifier
+     *     The identifier of the connection to retrieve.
+     *
+     * @return
+     *     The connection having the given identifier.
+     *
+     * @throws GuacamoleException 
+     *     If an error occurs while retrieving the connection, or if the
+     *     connection does not exist.
+     */
+    public Connection retrieveConnection(UserContext userContext,
+            String identifier) throws GuacamoleException {
+
+        // Get connection directory
+        Directory<Connection> directory = userContext.getConnectionDirectory();
+
+        // Pull specified connection
+        Connection connection = directory.get(identifier);
+        if (connection == null)
+            throw new GuacamoleResourceNotFoundException("No such connection: \"" + identifier + "\"");
+
+        return connection;
+
+    }
+
+    /**
+     * Retrieves a single connection from the given GuacamoleSession.
+     *
+     * @param session
+     *     The GuacamoleSession to retrieve the connection from.
+     *
+     * @param authProviderIdentifier
+     *     The unique identifier of the AuthenticationProvider that created the
+     *     UserContext from which the connection should be retrieved. Only one
+     *     UserContext per User per AuthenticationProvider can exist.
+     *
+     * @param identifier
+     *     The identifier of the connection to retrieve.
+     *
+     * @return
+     *     The connection having the given identifier.
+     *
+     * @throws GuacamoleException 
+     *     If an error occurs while retrieving the connection, or if the
+     *     connection does not exist.
+     */
+    public Connection retrieveConnection(GuacamoleSession session,
+            String authProviderIdentifier, String identifier)
+            throws GuacamoleException {
+
+        UserContext userContext = retrieveUserContext(session, authProviderIdentifier);
+        return retrieveConnection(userContext, identifier);
+
+    }
+
+    /**
+     * Retrieves a single connection group from the given user context. If
+     * the given identifier the REST API root identifier, the root connection
+     * group will be returned. The underlying authentication provider may
+     * additionally use a different identifier for root.
+     *
+     * @param userContext
+     *     The user context to retrieve the connection group from.
+     *
+     * @param identifier
+     *     The identifier of the connection group to retrieve.
+     *
+     * @return
+     *     The connection group having the given identifier, or the root
+     *     connection group if the identifier the root identifier.
+     *
+     * @throws GuacamoleException 
+     *     If an error occurs while retrieving the connection group, or if the
+     *     connection group does not exist.
+     */
+    public ConnectionGroup retrieveConnectionGroup(UserContext userContext,
+            String identifier) throws GuacamoleException {
+
+        // Use root group if identifier is the standard root identifier
+        if (identifier != null && identifier.equals(APIConnectionGroup.ROOT_IDENTIFIER))
+            return userContext.getRootConnectionGroup();
+
+        // Pull specified connection group otherwise
+        Directory<ConnectionGroup> directory = userContext.getConnectionGroupDirectory();
+        ConnectionGroup connectionGroup = directory.get(identifier);
+
+        if (connectionGroup == null)
+            throw new GuacamoleResourceNotFoundException("No such connection group: \"" + identifier + "\"");
+
+        return connectionGroup;
+
+    }
+
+    /**
+     * Retrieves a single connection group from the given GuacamoleSession. If
+     * the given identifier is the REST API root identifier, the root
+     * connection group will be returned. The underlying authentication
+     * provider may additionally use a different identifier for root.
+     *
+     * @param session
+     *     The GuacamoleSession to retrieve the connection group from.
+     *
+     * @param authProviderIdentifier
+     *     The unique identifier of the AuthenticationProvider that created the
+     *     UserContext from which the connection group should be retrieved.
+     *     Only one UserContext per User per AuthenticationProvider can exist.
+     *
+     * @param identifier
+     *     The identifier of the connection group to retrieve.
+     *
+     * @return
+     *     The connection group having the given identifier, or the root
+     *     connection group if the identifier is the root identifier.
+     *
+     * @throws GuacamoleException 
+     *     If an error occurs while retrieving the connection group, or if the
+     *     connection group does not exist.
+     */
+    public ConnectionGroup retrieveConnectionGroup(GuacamoleSession session,
+            String authProviderIdentifier, String identifier) throws GuacamoleException {
+
+        UserContext userContext = retrieveUserContext(session, authProviderIdentifier);
+        return retrieveConnectionGroup(userContext, identifier);
+
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/648a6c96/guacamole/src/main/java/org/apache/guacamole/rest/PATCH.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/PATCH.java b/guacamole/src/main/java/org/apache/guacamole/rest/PATCH.java
new file mode 100644
index 0000000..f87ca98
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/rest/PATCH.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2014 Glyptodon LLC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+
+package org.apache.guacamole.rest;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import javax.ws.rs.HttpMethod;
+
+/**
+ * An annotation for using the HTTP PATCH method in the REST endpoints.
+ * 
+ * @author James Muehlner
+ */
+@Target({ElementType.METHOD}) 
+@Retention(RetentionPolicy.RUNTIME) 
+@HttpMethod("PATCH") 
+public @interface PATCH {} 
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/648a6c96/guacamole/src/main/java/org/apache/guacamole/rest/RESTExceptionWrapper.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/RESTExceptionWrapper.java b/guacamole/src/main/java/org/apache/guacamole/rest/RESTExceptionWrapper.java
new file mode 100644
index 0000000..0193e22
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/rest/RESTExceptionWrapper.java
@@ -0,0 +1,274 @@
+/*
+ * Copyright (C) 2015 Glyptodon LLC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package org.apache.guacamole.rest;
+
+import com.google.inject.Inject;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import javax.ws.rs.FormParam;
+import javax.ws.rs.QueryParam;
+import org.aopalliance.intercept.MethodInterceptor;
+import org.aopalliance.intercept.MethodInvocation;
+import org.apache.guacamole.GuacamoleClientException;
+import org.apache.guacamole.GuacamoleException;
+import org.apache.guacamole.GuacamoleResourceNotFoundException;
+import org.apache.guacamole.GuacamoleSecurityException;
+import org.apache.guacamole.GuacamoleUnauthorizedException;
+import org.apache.guacamole.net.auth.credentials.GuacamoleInsufficientCredentialsException;
+import org.apache.guacamole.net.auth.credentials.GuacamoleInvalidCredentialsException;
+import org.apache.guacamole.rest.auth.AuthenticationService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A method interceptor which wraps custom exception handling around methods
+ * which can throw GuacamoleExceptions and which are exposed through the REST
+ * interface. The various types of GuacamoleExceptions are automatically
+ * translated into appropriate HTTP responses, including JSON describing the
+ * error that occurred.
+ *
+ * @author James Muehlner
+ * @author Michael Jumper
+ */
+public class RESTExceptionWrapper implements MethodInterceptor {
+
+    /**
+     * Logger for this class.
+     */
+    private final Logger logger = LoggerFactory.getLogger(RESTExceptionWrapper.class);
+
+    /**
+     * Service for authenticating users and managing their Guacamole sessions.
+     */
+    @Inject
+    private AuthenticationService authenticationService;
+
+    /**
+     * Determines whether the given set of annotations describes an HTTP
+     * request parameter of the given name. For a parameter to be associated
+     * with an HTTP request parameter, it must be annotated with either the
+     * <code>@QueryParam</code> or <code>@FormParam</code> annotations.
+     *
+     * @param annotations
+     *     The annotations associated with the Java parameter being checked.
+     *
+     * @param name
+     *     The name of the HTTP request parameter.
+     *
+     * @return
+     *     true if the given set of annotations describes an HTTP request
+     *     parameter having the given name, false otherwise.
+     */
+    private boolean isRequestParameter(Annotation[] annotations, String name) {
+
+        // Search annotations for associated HTTP parameters
+        for (Annotation annotation : annotations) {
+
+            // Check if parameter is associated with the HTTP query string
+            if (annotation instanceof QueryParam && name.equals(((QueryParam) annotation).value()))
+                return true;
+
+            // Failing that, check whether the parameter is associated with the
+            // HTTP request body
+            if (annotation instanceof FormParam && name.equals(((FormParam) annotation).value()))
+                return true;
+
+        }
+
+        // No parameter annotations are present
+        return false;
+
+    }
+
+    /**
+     * Returns the authentication token that was passed in the given method
+     * invocation. If the given method invocation is not associated with an
+     * HTTP request (it lacks the appropriate JAX-RS annotations) or there is
+     * no authentication token, null is returned.
+     *
+     * @param invocation
+     *     The method invocation whose corresponding authentication token
+     *     should be determined.
+     *
+     * @return
+     *     The authentication token passed in the given method invocation, or
+     *     null if there is no such token.
+     */
+    private String getAuthenticationToken(MethodInvocation invocation) {
+
+        Method method = invocation.getMethod();
+
+        // Get the types and annotations associated with each parameter
+        Annotation[][] parameterAnnotations = method.getParameterAnnotations();
+        Class<?>[] parameterTypes = method.getParameterTypes();
+
+        // The Java standards require these to be parallel arrays
+        assert(parameterAnnotations.length == parameterTypes.length);
+
+        // Iterate through all parameters, looking for the authentication token
+        for (int i = 0; i < parameterTypes.length; i++) {
+
+            // Only inspect String parameters
+            Class<?> parameterType = parameterTypes[i];
+            if (parameterType != String.class)
+                continue;
+
+            // Parameter must be declared as a REST service parameter
+            Annotation[] annotations = parameterAnnotations[i];
+            if (!isRequestParameter(annotations, "token"))
+                continue;
+
+            // The token parameter has been found - return its value
+            Object[] args = invocation.getArguments();
+            return (String) args[i];
+
+        }
+
+        // No token parameter is defined
+        return null;
+
+    }
+
+    @Override
+    public Object invoke(MethodInvocation invocation) throws Throwable {
+
+        try {
+
+            // Invoke wrapped method
+            try {
+                return invocation.proceed();
+            }
+
+            // Ensure any associated session is invalidated if unauthorized
+            catch (GuacamoleUnauthorizedException e) {
+
+                // Pull authentication token from request
+                String token = getAuthenticationToken(invocation);
+
+                // If there is an associated auth token, invalidate it
+                if (authenticationService.destroyGuacamoleSession(token))
+                    logger.debug("Implicitly invalidated session for token \"{}\".", token);
+
+                // Continue with exception processing
+                throw e;
+
+            }
+
+        }
+
+        // Additional credentials are needed
+        catch (GuacamoleInsufficientCredentialsException e) {
+
+            // Generate default message
+            String message = e.getMessage();
+            if (message == null)
+                message = "Permission denied.";
+
+            throw new APIException(
+                APIError.Type.INSUFFICIENT_CREDENTIALS,
+                message,
+                e.getCredentialsInfo().getFields()
+            );
+        }
+
+        // The provided credentials are wrong
+        catch (GuacamoleInvalidCredentialsException e) {
+
+            // Generate default message
+            String message = e.getMessage();
+            if (message == null)
+                message = "Permission denied.";
+
+            throw new APIException(
+                APIError.Type.INVALID_CREDENTIALS,
+                message,
+                e.getCredentialsInfo().getFields()
+            );
+        }
+
+        // Generic permission denied
+        catch (GuacamoleSecurityException e) {
+
+            // Generate default message
+            String message = e.getMessage();
+            if (message == null)
+                message = "Permission denied.";
+
+            throw new APIException(
+                APIError.Type.PERMISSION_DENIED,
+                message
+            );
+
+        }
+
+        // Arbitrary resource not found
+        catch (GuacamoleResourceNotFoundException e) {
+
+            // Generate default message
+            String message = e.getMessage();
+            if (message == null)
+                message = "Not found.";
+
+            throw new APIException(
+                APIError.Type.NOT_FOUND,
+                message
+            );
+
+        }
+        
+        // Arbitrary bad requests
+        catch (GuacamoleClientException e) {
+
+            // Generate default message
+            String message = e.getMessage();
+            if (message == null)
+                message = "Invalid request.";
+
+            throw new APIException(
+                APIError.Type.BAD_REQUEST,
+                message
+            );
+
+        }
+
+        // All other errors
+        catch (GuacamoleException e) {
+
+            // Generate default message
+            String message = e.getMessage();
+            if (message == null)
+                message = "Unexpected server error.";
+
+            // Ensure internal errors are logged at the debug level
+            logger.debug("Unexpected exception in REST endpoint.", e);
+
+            throw new APIException(
+                APIError.Type.INTERNAL_ERROR,
+                message
+            );
+
+        }
+
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/648a6c96/guacamole/src/main/java/org/apache/guacamole/rest/RESTMethodMatcher.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/RESTMethodMatcher.java b/guacamole/src/main/java/org/apache/guacamole/rest/RESTMethodMatcher.java
new file mode 100644
index 0000000..0283e63
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/rest/RESTMethodMatcher.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2015 Glyptodon LLC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package org.apache.guacamole.rest;
+
+import com.google.inject.matcher.AbstractMatcher;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import javax.ws.rs.HttpMethod;
+import org.apache.guacamole.GuacamoleException;
+
+/**
+ * A Guice Matcher which matches only methods which throw GuacamoleException
+ * (or a subclass thereof) and are explicitly annotated as with an HTTP method
+ * annotation like <code>@GET</code> or <code>@POST</code>. Any method which
+ * throws GuacamoleException and is annotated with an annotation that is
+ * annotated with <code>@HttpMethod</code> will match.
+ *
+ * @author Michael Jumper
+ */
+public class RESTMethodMatcher extends AbstractMatcher<Method> {
+
+    /**
+     * Returns whether the given method throws the specified exception type,
+     * including any subclasses of that type.
+     *
+     * @param method
+     *     The method to test.
+     *
+     * @param exceptionType
+     *     The exception type to test for.
+     *
+     * @return
+     *     true if the given method throws an exception of the specified type,
+     *     false otherwise.
+     */
+    private boolean methodThrowsException(Method method,
+            Class<? extends Exception> exceptionType) {
+
+        // Check whether the method throws an exception of the specified type
+        for (Class<?> thrownType : method.getExceptionTypes()) {
+            if (exceptionType.isAssignableFrom(thrownType))
+                return true;
+        }
+
+        // No such exception is declared to be thrown
+        return false;
+        
+    }
+
+    /**
+     * Returns whether the given method is annotated as a REST method. A REST
+     * method is annotated with an annotation which is annotated with
+     * <code>@HttpMethod</code>.
+     *
+     * @param method
+     *     The method to test.
+     *
+     * @return
+     *     true if the given method is annotated as a REST method, false
+     *     otherwise.
+     */
+    private boolean isRESTMethod(Method method) {
+
+        // Check whether the required REST annotations are present
+        for (Annotation annotation : method.getAnnotations()) {
+
+            // A method is a REST method if it is annotated with @HttpMethod
+            Class<? extends Annotation> annotationType = annotation.annotationType();
+            if (annotationType.isAnnotationPresent(HttpMethod.class))
+                return true;
+
+        }
+
+        // The method is not an HTTP method
+        return false;
+
+    }
+
+    @Override
+    public boolean matches(Method method) {
+
+        // Guacamole REST methods are REST methods which throw
+        // GuacamoleExceptions
+        return isRESTMethod(method)
+            && methodThrowsException(method, GuacamoleException.class);
+
+    }
+
+}