limit
connection records that contain
+ * the given terms, sorted by the given predicates, regardless of whether
+ * the data they are associated with is is readable by any particular user.
+ * This should only be called on behalf of a system administrator. If
+ * records are needed by a non-administrative user who must have explicit
+ * read rights, use searchReadable() instead.
+ *
+ * @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.
+ */
+ Listlimit
connection records that contain
+ * the given terms, sorted by the given predicates. Only records that are
+ * associated with data explicitly readable by the given user will be
+ * returned. If records are needed by a system administrator (who, by
+ * definition, does not need explicit read rights), use search() instead.
+ *
+ * @param user
+ * The user whose permissions should determine whether a record is
+ * returned.
+ *
+ * @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.
+ */
+ ListDATE_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, 1) - 1,
+ 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..f9d00a104
--- /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,108 @@
+/*
+ * 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.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 {
+
+ /**
+ * Service for managing connection objects.
+ */
+ @Inject
+ private ConnectionService connectionService;
+
+ /**
+ * 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