GUACAMOLE-1123: Extract common base REST resources for representing ActivityRecordSets.
diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/history/ActivityRecordSetResource.java b/guacamole/src/main/java/org/apache/guacamole/rest/history/ActivityRecordSetResource.java
new file mode 100644
index 0000000..8599a95
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/rest/history/ActivityRecordSetResource.java
@@ -0,0 +1,144 @@
+/*
+ * 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.rest.history;
+
+import java.util.ArrayList;
+import java.util.List;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.MediaType;
+import org.apache.guacamole.GuacamoleException;
+import org.apache.guacamole.net.auth.ActivityRecord;
+import org.apache.guacamole.net.auth.ActivityRecordSet;
+
+/**
+ * A REST resource which abstracts the operations available on an
+ * ActivityRecordSet, such as the connection or user history available via the
+ * UserContext.
+ *
+ * @param <InternalRecordType>
+ *     The type of ActivityRecord that is contained
+ *     within the ActivityRecordSet represented by this resource. To avoid
+ *     coupling the REST API too tightly to the extension API, these objects
+ *     are not directly serialized or deserialized when handling REST requests.
+ *
+ * @param <ExternalRecordType>
+ *     The type of object used in interchange (ie: serialized/deserialized as
+ *     JSON) between REST clients and this resource to represent the
+ *     InternalRecordType.
+ */
+@Produces(MediaType.APPLICATION_JSON)
+@Consumes(MediaType.APPLICATION_JSON)
+public abstract class ActivityRecordSetResource<InternalRecordType extends ActivityRecord,
+        ExternalRecordType extends APIActivityRecord> {
+
+    /**
+     * The maximum number of history records to return in any one response.
+     */
+    private static final int MAXIMUM_HISTORY_SIZE = 1000;
+
+    /**
+     * The ActivityRecordSet whose records are being exposed.
+     */
+    private ActivityRecordSet<InternalRecordType> history;
+
+    /**
+     * Creates a new ActivityRecordSetResource which exposes the records within
+     * the given ActivityRecordSet.
+     *
+     * @param history
+     *     The ActivityRecordSet whose records should be exposed.
+     */
+    public ActivityRecordSetResource(ActivityRecordSet<InternalRecordType> history) {
+        this.history = history;
+    }
+
+    /**
+     * Converts the given internal record object to a record object which is
+     * decoupled from the extension API and is intended to be used in
+     * interchange via the REST API.
+     *
+     * @param record
+     *     The record to convert for the sake of interchange.
+     *
+     * @return
+     *     A new record object containing the same data as the given internal
+     *     record, but intended for use in interchange.
+     */
+    protected abstract ExternalRecordType toExternalRecord(InternalRecordType record);
+
+    /**
+     * Retrieves the list of activity records stored within the underlying
+     * ActivityRecordSet which match the given, arbitrary criteria. If
+     * specified, the returned records will also be sorted according to the
+     * given sort predicates.
+     *
+     * @param requiredContents
+     *     The set of strings that each must occur somewhere within the
+     *     returned records, whether within the associated username,
+     *     the name of some associated object (such as a connection), or any
+     *     associated date. If non-empty, any record not matching each of the
+     *     strings within the collection will be excluded from the results.
+     *
+     * @param sortPredicates
+     *     A list of predicates to apply while sorting the resulting records,
+     *     describing the properties involved and the sort order for those
+     *     properties.
+     *
+     * @return
+     *     The list of records which match the provided criteria, optionally
+     *     sorted as specified.
+     *
+     * @throws GuacamoleException
+     *     If an error occurs while applying the given filter criteria or
+     *     sort predicates.
+     */
+    @GET
+    public List<ExternalRecordType> getRecords(
+            @QueryParam("contains") List<String> requiredContents,
+            @QueryParam("order") List<APISortPredicate> sortPredicates)
+            throws GuacamoleException {
+
+        // Restrict to records which contain the specified strings
+        for (String required : requiredContents) {
+            if (!required.isEmpty())
+                history = history.contains(required);
+        }
+
+        // Sort according to specified ordering
+        for (APISortPredicate predicate : sortPredicates)
+            history = history.sort(predicate.getProperty(), predicate.isDescending());
+
+        // Limit to maximum result size
+        history = history.limit(MAXIMUM_HISTORY_SIZE);
+
+        // Convert record set to collection of API records
+        List<ExternalRecordType> apiRecords = new ArrayList<>();
+        for (InternalRecordType record : history.asCollection())
+            apiRecords.add(toExternalRecord(record));
+
+        // Return the converted history
+        return apiRecords;
+
+    }
+
+}
diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/history/ConnectionHistoryResource.java b/guacamole/src/main/java/org/apache/guacamole/rest/history/ConnectionHistoryResource.java
new file mode 100644
index 0000000..42e8a85
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/rest/history/ConnectionHistoryResource.java
@@ -0,0 +1,49 @@
+/*
+ * 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.rest.history;
+
+import org.apache.guacamole.net.auth.ActivityRecordSet;
+import org.apache.guacamole.net.auth.ConnectionRecord;
+
+/**
+ * A REST resource for retrieving and managing the history records of Guacamole
+ * connections. Connection history records describe the start/end times of each
+ * usage of a connection (when a user connects and disconnects), as well as the
+ * specific user that connected/disconnected.
+ */
+public class ConnectionHistoryResource extends ActivityRecordSetResource<ConnectionRecord, APIConnectionRecord> {
+
+    /**
+     * Creates a new ConnectionHistoryResource which exposes the connection
+     * history records of the given ActivityRecordSet.
+     *
+     * @param history
+     *     The ActivityRecordSet whose records should be exposed.
+     */
+    public ConnectionHistoryResource(ActivityRecordSet<ConnectionRecord> history) {
+        super(history);
+    }
+
+    @Override
+    protected APIConnectionRecord toExternalRecord(ConnectionRecord record) {
+        return new APIConnectionRecord(record);
+    }
+
+}
diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/history/HistoryResource.java b/guacamole/src/main/java/org/apache/guacamole/rest/history/HistoryResource.java
index 559ebd5..8da8355 100644
--- a/guacamole/src/main/java/org/apache/guacamole/rest/history/HistoryResource.java
+++ b/guacamole/src/main/java/org/apache/guacamole/rest/history/HistoryResource.java
@@ -19,18 +19,11 @@
 
 package org.apache.guacamole.rest.history;
 
-import java.util.ArrayList;
-import java.util.List;
 import javax.ws.rs.Consumes;
-import javax.ws.rs.GET;
 import javax.ws.rs.Path;
 import javax.ws.rs.Produces;
-import javax.ws.rs.QueryParam;
 import javax.ws.rs.core.MediaType;
 import org.apache.guacamole.GuacamoleException;
-import org.apache.guacamole.net.auth.ActivityRecord;
-import org.apache.guacamole.net.auth.ActivityRecordSet;
-import org.apache.guacamole.net.auth.ConnectionRecord;
 import org.apache.guacamole.net.auth.UserContext;
 
 /**
@@ -42,11 +35,6 @@
 public class HistoryResource {
 
     /**
-     * The maximum number of history records to return in any one response.
-     */
-    private static final int MAXIMUM_HISTORY_SIZE = 1000;
-
-    /**
      * The UserContext whose associated connection history is being exposed.
      */
     private final UserContext userContext;
@@ -63,114 +51,37 @@
     }
 
     /**
-     * Retrieves the usage history for all connections, restricted by optional
-     * filter parameters.
-     *
-     * @param requiredContents
-     *     The set of strings that each must occur somewhere within the
-     *     returned connection records, whether within the associated username,
-     *     the name of the associated connection, or any associated date. If
-     *     non-empty, any connection record not matching each of the strings
-     *     within the collection will be excluded from the results.
-     *
-     * @param sortPredicates
-     *     A list of predicates to apply while sorting the resulting connection
-     *     records, describing the properties involved and the sort order for
-     *     those properties.
+     * Retrieves the usage history for all connections. Filtering may be
+     * applied via the returned ConnectionHistoryResource.
      *
      * @return
-     *     A list of connection records, describing the start and end times of
-     *     various usages of this connection.
+     *     A resource which exposes connection records that may optionally be
+     *     filtered, each record describing the start and end times that a
+     *     particular connection was used.
      *
      * @throws GuacamoleException
      *     If an error occurs while retrieving the connection history.
      */
-    @GET
     @Path("connections")
-    public List<APIConnectionRecord> getConnectionHistory(
-            @QueryParam("contains") List<String> requiredContents,
-            @QueryParam("order") List<APISortPredicate> sortPredicates)
-            throws GuacamoleException {
-
-        // Retrieve overall connection history
-        ActivityRecordSet<ConnectionRecord> history = userContext.getConnectionHistory();
-
-        // Restrict to records which contain the specified strings
-        for (String required : requiredContents) {
-            if (!required.isEmpty())
-                history = history.contains(required);
-        }
-
-        // Sort according to specified ordering
-        for (APISortPredicate predicate : sortPredicates)
-            history = history.sort(predicate.getProperty(), predicate.isDescending());
-
-        // Limit to maximum result size
-        history = history.limit(MAXIMUM_HISTORY_SIZE);
-
-        // Convert record set to collection of API connection records
-        List<APIConnectionRecord> apiRecords = new ArrayList<APIConnectionRecord>();
-        for (ConnectionRecord record : history.asCollection())
-            apiRecords.add(new APIConnectionRecord(record));
-
-        // Return the converted history
-        return apiRecords;
-
+    public ConnectionHistoryResource getConnectionHistory() throws GuacamoleException {
+        return new ConnectionHistoryResource(userContext.getConnectionHistory());
     }
 
     /**
-     * Retrieves the login history for all users, restricted by optional filter
-     * parameters.
-     *
-     * @param requiredContents
-     *     The set of strings that each must occur somewhere within the
-     *     returned user records, whether within the associated username or any
-     *     associated date. If non-empty, any user record not matching each of
-     *     the strings within the collection will be excluded from the results.
-     *
-     * @param sortPredicates
-     *     A list of predicates to apply while sorting the resulting user
-     *     records, describing the properties involved and the sort order for
-     *     those properties.
+     * Retrieves the login history for all users. Filtering may be applied via
+     * the returned UserHistoryResource.
      *
      * @return
-     *     A list of user records, describing the start and end times of user
-     *     sessions.
+     *     A resource which exposes user records that may optionally be
+     *     filtered, each record describing the start and end times of a user
+     *     session.
      *
      * @throws GuacamoleException
      *     If an error occurs while retrieving the user history.
      */
-    @GET
     @Path("users")
-    public List<APIActivityRecord> getUserHistory(
-            @QueryParam("contains") List<String> requiredContents,
-            @QueryParam("order") List<APISortPredicate> sortPredicates)
-            throws GuacamoleException {
-
-        // Retrieve overall user history
-        ActivityRecordSet<ActivityRecord> history = userContext.getUserHistory();
-
-        // Restrict to records which contain the specified strings
-        for (String required : requiredContents) {
-            if (!required.isEmpty())
-                history = history.contains(required);
-        }
-
-        // Sort according to specified ordering
-        for (APISortPredicate predicate : sortPredicates)
-            history = history.sort(predicate.getProperty(), predicate.isDescending());
-
-        // Limit to maximum result size
-        history = history.limit(MAXIMUM_HISTORY_SIZE);
-
-        // Convert record set to collection of API user records
-        List<APIActivityRecord> apiRecords = new ArrayList<APIActivityRecord>();
-        for (ActivityRecord record : history.asCollection())
-            apiRecords.add(new APIActivityRecord(record));
-
-        // Return the converted history
-        return apiRecords;
-
+    public UserHistoryResource getUserHistory() throws GuacamoleException {
+        return new UserHistoryResource(userContext.getUserHistory());
     }
 
 }
diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/history/UserHistoryResource.java b/guacamole/src/main/java/org/apache/guacamole/rest/history/UserHistoryResource.java
new file mode 100644
index 0000000..6324873
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/rest/history/UserHistoryResource.java
@@ -0,0 +1,48 @@
+/*
+ * 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.rest.history;
+
+import org.apache.guacamole.net.auth.ActivityRecord;
+import org.apache.guacamole.net.auth.ActivityRecordSet;
+
+/**
+ * A REST resource for retrieving and managing the history records of Guacamole
+ * user sessions. User session history records describe the start/end times of
+ * individual user sessions (when specific users logged in and out).
+ */
+public class UserHistoryResource extends ActivityRecordSetResource<ActivityRecord, APIActivityRecord> {
+
+    /**
+     * Creates a new UserHistoryResource which exposes the user session history
+     * records of the given ActivityRecordSet.
+     *
+     * @param history
+     *     The ActivityRecordSet whose records should be exposed.
+     */
+    public UserHistoryResource(ActivityRecordSet<ActivityRecord> history) {
+        super(history);
+    }
+
+    @Override
+    protected APIActivityRecord toExternalRecord(ActivityRecord record) {
+        return new APIActivityRecord(record);
+    }
+
+}