diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/connection/ConnectionRecordMapper.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/connection/ConnectionRecordMapper.java
index 8816fb69c..eaca812e5 100644
--- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/connection/ConnectionRecordMapper.java
+++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/connection/ConnectionRecordMapper.java
@@ -23,6 +23,7 @@
package org.glyptodon.guacamole.auth.jdbc.connection;
import java.util.List;
+import java.util.Set;
import org.apache.ibatis.annotations.Param;
/**
@@ -56,5 +57,26 @@ public interface ConnectionRecordMapper {
* The number of rows inserted.
*/
int insert(@Param("record") ConnectionRecordModel record);
+
+ /**
+ * Searches for up to limit connection records that contain
+ * the given terms, sorted by the given predicates.
+ *
+ * @param terms
+ * The search terms that must match the returned records.
+ *
+ * @param sortPredicates
+ * A list of predicates to sort the returned records by, in order of
+ * priority.
+ *
+ * @param limit
+ * The maximum number of records that should be returned.
+ *
+ * @return
+ * The results of the search performed with the given parameters.
+ */
+ List search(@Param("terms") Set terms,
+ @Param("sortPredicates") List sortPredicates,
+ @Param("limit") int limit);
}
diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/connection/ConnectionRecordSearchTerm.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/connection/ConnectionRecordSearchTerm.java
new file mode 100644
index 000000000..e2e62b32e
--- /dev/null
+++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/connection/ConnectionRecordSearchTerm.java
@@ -0,0 +1,296 @@
+/*
+ * 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.glyptodon.guacamole.auth.jdbc.connection;
+
+import java.util.Calendar;
+import java.util.Date;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * A search term for querying historical connection records. This will contain
+ * a the search term in string form and, if that string appears to be a date. a
+ * corresponding date range.
+ *
+ * @author James Muehlner
+ */
+public class ConnectionRecordSearchTerm {
+
+ /**
+ * A pattern that can match a year, year and month, or year and month and
+ * day.
+ */
+ private static final Pattern DATE_PATTERN =
+ Pattern.compile("(\\d+)(?:-(\\d+)?(?:-(\\d+)?)?)?");
+
+ /**
+ * The index of the group within DATE_PATTERN containing the
+ * year number.
+ */
+ private static final int YEAR_GROUP = 1;
+
+ /**
+ * The index of the group within DATE_PATTERN containing the
+ * month number, if any.
+ */
+ private static final int MONTH_GROUP = 2;
+
+ /**
+ * The index of the group within DATE_PATTERN containing the
+ * day number, if any.
+ */
+ private static final int DAY_GROUP = 3;
+
+ /**
+ * The start of the date range for records that should be retrieved, if the
+ * provided search term appears to be a date.
+ */
+ private final Date startDate;
+
+ /**
+ * The end of the date range for records that should be retrieved, if the
+ * provided search term appears to be a date.
+ */
+ private final Date endDate;
+
+ /**
+ * The string that should be searched for.
+ */
+ private final String term;
+
+ /**
+ * Parse the given string as an integer, returning the provided default
+ * value if the string is null.
+ *
+ * @param str
+ * The string to parse as an integer.
+ *
+ * @param defaultValue
+ * The value to return if str is null.
+ *
+ * @return
+ * The parsed value, or the provided default value if str
+ * is null.
+ */
+ private static int parseInt(String str, int defaultValue) {
+
+ if (str == null)
+ return defaultValue;
+
+ return Integer.parseInt(str);
+
+ }
+
+ /**
+ * Returns a new calendar representing the last millisecond of the same
+ * year as calendar.
+ *
+ * @param calendar
+ * The calendar defining the year whose end (last millisecond) is to be
+ * returned.
+ *
+ * @return
+ * A new calendar representing the last millisecond of the same year as
+ * calendar.
+ */
+ private static Calendar getEndOfYear(Calendar calendar) {
+
+ // Get first day of next year
+ Calendar endOfYear = Calendar.getInstance();
+ endOfYear.clear();
+ endOfYear.set(Calendar.YEAR, calendar.get(Calendar.YEAR) + 1);
+
+ // Transform into the last millisecond of the given year
+ endOfYear.add(Calendar.MILLISECOND, -1);
+
+ return endOfYear;
+
+ }
+
+ /**
+ * Returns a new calendar representing the last millisecond of the same
+ * month and year as calendar.
+ *
+ * @param calendar
+ * The calendar defining the month and year whose end (last millisecond)
+ * is to be returned.
+ *
+ * @return
+ * A new calendar representing the last millisecond of the same month
+ * and year as calendar.
+ */
+ private static Calendar getEndOfMonth(Calendar calendar) {
+
+ // Copy given calender only up to given month
+ Calendar endOfMonth = Calendar.getInstance();
+ endOfMonth.clear();
+ endOfMonth.set(Calendar.YEAR, calendar.get(Calendar.YEAR));
+ endOfMonth.set(Calendar.MONTH, calendar.get(Calendar.MONTH));
+
+ // Advance to the last millisecond of the given month
+ endOfMonth.add(Calendar.MONTH, 1);
+ endOfMonth.add(Calendar.MILLISECOND, -1);
+
+ return endOfMonth;
+
+ }
+
+ /**
+ * Returns a new calendar representing the last millisecond of the same
+ * year, month, and day as calendar.
+ *
+ * @param calendar
+ * The calendar defining the year, month, and day whose end
+ * (last millisecond) is to be returned.
+ *
+ * @return
+ * A new calendar representing the last millisecond of the same year,
+ * month, and day as calendar.
+ */
+ private static Calendar getEndOfDay(Calendar calendar) {
+
+ // Copy given calender only up to given month
+ Calendar endOfMonth = Calendar.getInstance();
+ endOfMonth.clear();
+ endOfMonth.set(Calendar.YEAR, calendar.get(Calendar.YEAR));
+ endOfMonth.set(Calendar.MONTH, calendar.get(Calendar.MONTH));
+ endOfMonth.set(Calendar.DAY_OF_MONTH, calendar.get(Calendar.DAY_OF_MONTH));
+
+ // Advance to the last millisecond of the given day
+ endOfMonth.add(Calendar.DAY_OF_MONTH, 1);
+ endOfMonth.add(Calendar.MILLISECOND, -1);
+
+ return endOfMonth;
+
+ }
+
+ /**
+ * Creates a new ConnectionRecordSearchTerm representing the given string.
+ * If the given string appears to be a date, the start and end dates of the
+ * implied date range will be automatically determined and made available
+ * via getStartDate() and getEndDate() respectively.
+ *
+ * @param term
+ * The string that should be searched for.
+ */
+ public ConnectionRecordSearchTerm(String term) {
+
+ // Search terms absolutely must not be null
+ if (term == null)
+ throw new NullPointerException("Search terms may not be null");
+
+ this.term = term;
+
+ // Parse start/end of date range if term appears to be a date
+ Matcher matcher = DATE_PATTERN.matcher(term);
+ if (matcher.matches()) {
+
+ // Retrieve date components from term
+ String year = matcher.group(YEAR_GROUP);
+ String month = matcher.group(MONTH_GROUP);
+ String day = matcher.group(DAY_GROUP);
+
+ // Parse start date from term
+ Calendar startCalendar = Calendar.getInstance();
+ startCalendar.clear();
+ startCalendar.set(
+ Integer.parseInt(year),
+ parseInt(month, 0),
+ parseInt(day, 1)
+ );
+
+ Calendar endCalendar;
+
+ // Derive end date from start date
+ if (month == null) {
+ endCalendar = getEndOfYear(startCalendar);
+ }
+ else if (day == null) {
+ endCalendar = getEndOfMonth(startCalendar);
+ }
+ else {
+ endCalendar = getEndOfDay(startCalendar);
+ }
+
+ // Convert results back into dates
+ this.startDate = startCalendar.getTime();
+ this.endDate = endCalendar.getTime();
+
+ }
+
+ // The search term doesn't look like a date
+ else {
+ this.startDate = null;
+ this.endDate = null;
+ }
+
+ }
+
+ /**
+ * Returns the start of the date range for records that should be retrieved,
+ * if the provided search term appears to be a date.
+ *
+ * @return
+ * The start of the date range.
+ */
+ public Date getStartDate() {
+ return startDate;
+ }
+
+ /**
+ * Returns the end of the date range for records that should be retrieved,
+ * if the provided search term appears to be a date.
+ *
+ * @return
+ * The end of the date range.
+ */
+ public Date getEndDate() {
+ return endDate;
+ }
+
+ /**
+ * Returns the string that should be searched for.
+ *
+ * @return
+ * The search term.
+ */
+ public String getTerm() {
+ return term;
+ }
+
+ @Override
+ public int hashCode() {
+ return term.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+
+ if (obj == null || !(obj instanceof ConnectionRecordSearchTerm))
+ return false;
+
+ return ((ConnectionRecordSearchTerm) obj).getTerm().equals(getTerm());
+
+ }
+
+}
diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/connection/ConnectionRecordSet.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/connection/ConnectionRecordSet.java
new file mode 100644
index 000000000..355f59fad
--- /dev/null
+++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/connection/ConnectionRecordSet.java
@@ -0,0 +1,123 @@
+/*
+ * 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.glyptodon.guacamole.auth.jdbc.connection;
+
+import com.google.inject.Inject;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import org.glyptodon.guacamole.GuacamoleException;
+import org.glyptodon.guacamole.auth.jdbc.base.RestrictedObject;
+import org.glyptodon.guacamole.net.auth.ConnectionRecord;
+
+/**
+ * A JDBC implementation of ConnectionRecordSet. Calls to asCollection() will
+ * query connection history records from the database. Which records are
+ * returned will be determined by the values passed in earlier.
+ *
+ * @author James Muehlner
+ */
+public class ConnectionRecordSet extends RestrictedObject
+ implements org.glyptodon.guacamole.net.auth.ConnectionRecordSet {
+
+ /**
+ * Mapper for accessing connection history.
+ */
+ @Inject
+ private ConnectionRecordMapper connectionRecordMapper;
+
+ /**
+ * 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.
+ */
+ private final Set requiredContents =
+ new HashSet();
+
+ /**
+ * The maximum number of connection history records that should be returned
+ * by a call to asCollection().
+ */
+ private int limit = Integer.MAX_VALUE;
+
+ /**
+ * A list of predicates to apply while sorting the resulting connection
+ * records, describing the properties involved and the sort order for those
+ * properties.
+ */
+ private final List connectionRecordSortPredicates =
+ new ArrayList();
+
+ @Override
+ public Collection asCollection()
+ throws GuacamoleException {
+
+ // Perform the search against the database
+ List searchResults =
+ connectionRecordMapper.search(requiredContents,
+ connectionRecordSortPredicates, limit);
+
+ List modeledSearchResults =
+ new ArrayList();
+
+ // Convert raw DB records into ConnectionRecords
+ for(ConnectionRecordModel model : searchResults) {
+ modeledSearchResults.add(new ModeledConnectionRecord(model));
+ }
+
+ return modeledSearchResults;
+
+ }
+
+ @Override
+ public ConnectionRecordSet contains(String value)
+ throws GuacamoleException {
+ requiredContents.add(new ConnectionRecordSearchTerm(value));
+ return this;
+ }
+
+ @Override
+ public ConnectionRecordSet limit(int limit) throws GuacamoleException {
+ this.limit = Math.min(this.limit, limit);
+ return this;
+ }
+
+ @Override
+ public ConnectionRecordSet sort(SortableProperty property, boolean desc)
+ throws GuacamoleException {
+
+ connectionRecordSortPredicates.add(new ConnectionRecordSortPredicate(
+ property,
+ desc
+ ));
+
+ return this;
+
+ }
+
+}
diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/connection/ConnectionRecordSortPredicate.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/connection/ConnectionRecordSortPredicate.java
new file mode 100644
index 000000000..279321b44
--- /dev/null
+++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/connection/ConnectionRecordSortPredicate.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.glyptodon.guacamole.auth.jdbc.connection;
+
+import org.glyptodon.guacamole.net.auth.ConnectionRecordSet;
+
+/**
+ * A sort predicate which species the property to use when sorting connection
+ * records, along with the sort order.
+ *
+ * @author James Muehlner
+ */
+public class ConnectionRecordSortPredicate {
+
+ /**
+ * The property to use when sorting ConnectionRecords.
+ */
+ private final ConnectionRecordSet.SortableProperty property;
+
+ /**
+ * Whether the sort order is descending (true) or ascending (false).
+ */
+ private final boolean descending;
+
+ /**
+ * Creates a new ConnectionRecordSortPredicate with the given sort property
+ * and sort order.
+ *
+ * @param property
+ * The property to use when sorting ConnectionRecords.
+ *
+ * @param descending
+ * Whether the sort order is descending (true) or ascending (false).
+ */
+ public ConnectionRecordSortPredicate(ConnectionRecordSet.SortableProperty property,
+ boolean descending) {
+ this.property = property;
+ this.descending = descending;
+ }
+
+ /**
+ * Returns the property that should be used when sorting ConnectionRecords.
+ *
+ * @return
+ * The property that should be used when sorting ConnectionRecords.
+ */
+ public ConnectionRecordSet.SortableProperty getProperty() {
+ return property;
+ }
+
+ /**
+ * Returns whether the sort order is descending.
+ *
+ * @return
+ * true if the sort order is descending, false if the sort order is
+ * ascending.
+ */
+ public boolean isDescending() {
+ return descending;
+ }
+
+}
diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/user/UserContext.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/user/UserContext.java
index 06bd5256e..72dea43f4 100644
--- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/user/UserContext.java
+++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/user/UserContext.java
@@ -32,6 +32,7 @@ import java.util.Collection;
import org.glyptodon.guacamole.GuacamoleException;
import org.glyptodon.guacamole.auth.jdbc.base.RestrictedObject;
import org.glyptodon.guacamole.auth.jdbc.activeconnection.ActiveConnectionDirectory;
+import org.glyptodon.guacamole.auth.jdbc.connection.ConnectionRecordSet;
import org.glyptodon.guacamole.auth.jdbc.connection.ModeledConnection;
import org.glyptodon.guacamole.auth.jdbc.connectiongroup.ModeledConnectionGroup;
import org.glyptodon.guacamole.form.Form;
@@ -39,7 +40,6 @@ import org.glyptodon.guacamole.net.auth.ActiveConnection;
import org.glyptodon.guacamole.net.auth.AuthenticationProvider;
import org.glyptodon.guacamole.net.auth.Connection;
import org.glyptodon.guacamole.net.auth.ConnectionGroup;
-import org.glyptodon.guacamole.net.auth.ConnectionRecordSet;
import org.glyptodon.guacamole.net.auth.Directory;
import org.glyptodon.guacamole.net.auth.User;
import org.glyptodon.guacamole.net.auth.simple.SimpleConnectionRecordSet;
@@ -94,6 +94,12 @@ public class UserContext extends RestrictedObject
@Inject
private Provider rootGroupProvider;
+ /**
+ * Provider for creating connection record sets.
+ */
+ @Inject
+ private Provider connectionRecordSetProvider;
+
@Override
public void init(AuthenticatedUser currentUser) {
@@ -141,7 +147,9 @@ public class UserContext extends RestrictedObject
@Override
public ConnectionRecordSet getConnectionHistory()
throws GuacamoleException {
- return new SimpleConnectionRecordSet();
+ ConnectionRecordSet connectionRecordSet = connectionRecordSetProvider.get();
+ connectionRecordSet.init(getCurrentUser());
+ return connectionRecordSet;
}
@Override
diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/resources/org/glyptodon/guacamole/auth/jdbc/connection/ConnectionRecordMapper.xml b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/resources/org/glyptodon/guacamole/auth/jdbc/connection/ConnectionRecordMapper.xml
index b5775f607..fb45aeb2d 100644
--- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/resources/org/glyptodon/guacamole/auth/jdbc/connection/ConnectionRecordMapper.xml
+++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/resources/org/glyptodon/guacamole/auth/jdbc/connection/ConnectionRecordMapper.xml
@@ -72,4 +72,62 @@
+
+
+
\ No newline at end of file