/*
* Copyright (C) 2004-2008 Jive Software. All rights reserved.
*
* Licensed 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.jivesoftware.openfire;
import org.jivesoftware.openfire.session.ClientSession;
import java.util.Comparator;
import java.util.Date;
/**
* Filters and sorts lists of sessions. This allows for a very rich set of possible
* queries that can be run on session data. Some examples are: "Show all sessions
* started during the last hour by a certain user".
* <p>
* The class also supports pagination of results with the setStartIndex(int)
* and setNumResults(int) methods. If the start index is not set, it will
* begin at index 0 (the start of results). If the number of results is not set,
* it will be unbounded and return as many results as available.</p>
* <p>
* Factory methods to create common queries are provided for convenience.</p>
*
* @author Matt Tucker
*/
public class SessionResultFilter {
// ############################################################
// Search order criteria
// ############################################################
/**
* Descending sort (ie 3, 2, 1...).
*/
public static final int DESCENDING = 0;
/**
* Ascending sort (ie 3, 4, 5...).
*/
public static final int ASCENDING = 1;
// ############################################################
// Result limit search criteria
// ############################################################
/**
* Represents no result limit (infinite results).
*/
public static final int NO_RESULT_LIMIT = -1;
// ############################################################
// Sort fields
// ############################################################
public static final int SORT_USER = 0;
public static final int SORT_CREATION_DATE = 1;
public static final int SORT_LAST_ACTIVITY_DATE = 2;
public static final int SORT_NUM_CLIENT_PACKETS = 3;
public static final int SORT_NUM_SERVER_PACKETS = 4;
/**
* Creates a default SessionResultFilter: no filtering with results sorted
* by user (ascending).
*
* @return default SessionResultFilter.
*/
public static SessionResultFilter createDefaultSessionFilter() {
SessionResultFilter resultFilter = new SessionResultFilter();
resultFilter.setSortField(SORT_USER);
resultFilter.setSortOrder(ASCENDING);
return resultFilter;
}
private int sortField = SORT_LAST_ACTIVITY_DATE;
private int sortOrder = DESCENDING;
private String username = null;
/**
* The starting index for results. Default is 0.
*/
private int startIndex = 0;
/**
* Number of results to return. Default is NO_RESULT_LIMIT
* which means an unlimited number of results.
*/
private int numResults = NO_RESULT_LIMIT;
private Date creationDateRangeMin = null;
private Date creationDateRangeMax = null;
private Date lastActivityDateRangeMin = null;
private Date lastActivityDateRangeMax = null;
/**
* Returns the currently selected sort field. The default value is
* SessionResultFilter.SORT_LAST_ACTIVITY_DATE.
*
* @return current sort field.
*/
public int getSortField() {
return sortField;
}
/**
* Sets the sort field to use. The default value is
* SessionResultFilter.SORT_LAST_ACTIVITY_DATE.
*
* @param sortField the field that will be used for sorting.
*/
public void setSortField(int sortField) {
this.sortField = sortField;
}
/**
* Returns the sort order, which will be SessionResultFilter.ASCENDING for
* ascending sorting, or SessionResultFilter.DESCENDING for descending sorting.
* Descending sorting is: 3, 2, 1, etc. Ascending sorting is 1, 2, 3, etc.
*
* @return the sort order.
*/
public int getSortOrder() {
return this.sortOrder;
}
/**
* Sets the sort type. Valid arguments are SessionResultFilter.ASCENDING for
* ascending sorting or SessionResultFilter.DESCENDING for descending sorting.
* Descending sorting is: 3, 2, 1, etc. Ascending sorting is 1, 2, 3, etc.
*
* @param sortOrder the order that results will be sorted in.
*/
public void setSortOrder(int sortOrder) {
if (!(sortOrder == SessionResultFilter.ASCENDING || sortOrder == SessionResultFilter.DESCENDING)) {
throw new IllegalArgumentException();
}
this.sortOrder = sortOrder;
}
/**
* <p>Returns the max number of results that should be returned.</p>
* <p>The default value for is NO_RESULT_LIMIT, which means there will be no limit
* on the number of results. This method can be used in combination with
* setStartIndex(int) to perform pagination of results.</p>
*
* @return the max number of results to return or NO_RESULT_LIMIT for no limit
* @see #setStartIndex(int)
*/
public int getNumResults() {
return numResults;
}
/**
* <p>Sets the limit on the number of results to be returned.</p>
* <p>User NO_RESULT_LIMIT if you don't want to limit the results returned.</p>
*
* @param numResults the number of results to return or NO_RESULT_LIMIT for no limit
*/
public void setNumResults(int numResults) {
if (numResults != NO_RESULT_LIMIT && numResults < 0) {
throw new IllegalArgumentException("numResults cannot be less than 0.");
}
this.numResults = numResults;
}
/**
* Returns the index of the first result to return.
*
* @return the index of the first result which should be returned.
*/
public int getStartIndex() {
return startIndex;
}
/**
* Sets the index of the first result to return. For example, if the start
* index is set to 20, the Iterator returned will start at the 20th result
* in the query. This method can be used in combination with
* setNumResults(int) to perform pagination of results.
*
* @param startIndex the index of the first result to return.
*/
public void setStartIndex(int startIndex) {
if (startIndex < 0) {
throw new IllegalArgumentException("A start index less than 0 is not valid.");
}
this.startIndex = startIndex;
}
/**
* Returns a comparator that will sort a standard sorted set according
* to this filter's sort order.
*
* @return a comparator that sorts Sessions matching the sort order for this filter.
*/
public Comparator<ClientSession> getSortComparator() {
return new SessionComparator();
}
/**
* Compares sessions according to sort fields.
*
* @author Iain Shigeoka
*/
private class SessionComparator implements Comparator<ClientSession> {
@Override
public int compare(ClientSession lhs, ClientSession rhs) {
int comparison;
switch (sortField) {
case SessionResultFilter.SORT_CREATION_DATE:
comparison = lhs.getCreationDate().compareTo(rhs.getCreationDate());
break;
case SessionResultFilter.SORT_LAST_ACTIVITY_DATE:
comparison = lhs.getLastActiveDate().compareTo(rhs.getCreationDate());
break;
case SessionResultFilter.SORT_NUM_CLIENT_PACKETS:
comparison = (int)(lhs.getNumClientPackets() - rhs.getNumClientPackets());
break;
case SessionResultFilter.SORT_NUM_SERVER_PACKETS:
comparison = (int)(lhs.getNumServerPackets() - rhs.getNumServerPackets());
break;
case SessionResultFilter.SORT_USER:
// sort first by name, then by resource
String lUsername = lhs.isAnonymousUser() ? "" : lhs.getAddress().getNode();
String rUsername = rhs.isAnonymousUser() ? "" : rhs.getAddress().getNode();
comparison = compareString(lUsername, rUsername);
if (comparison == 0) {
comparison = compareString(lhs.getAddress().getResource(),
rhs.getAddress().getResource());
}
break;
default:
comparison = 0;
}
if (sortOrder == SessionResultFilter.DESCENDING) {
comparison *= -1; // Naturally ascending, flip sign if descending
}
return comparison;
}
private int compareString(String lhs, String rhs) {
if (lhs == null) {
lhs = "";
}
if (rhs == null) {
rhs = "";
}
return lhs.compareTo(rhs);
}
}
/**
* Rounds the given date down to the nearest specified second. The following
* table shows sample input and expected output values: (Note, only
* the time portion of the date is shown for brevity) <p>
*
* <table border="1">
* <caption></caption>
* <tr><th>Date</th><th>Seconds</th><th>Result</th></tr>
* <tr><td>1:37.48</td><td>5</td><td>1:37.45</td></tr>
* <tr><td>1:37.48</td><td>10</td><td>1:37.40</td></tr>
* <tr><td>1:37.48</td><td>30</td><td>1:37.30</td></tr>
* <tr><td>1:37.48</td><td>60</td><td>1:37.00</td></tr>
* <tr><td>1:37.48</td><td>120</td><td>1:36.00</td></tr>
* </table>
*
* <p>This method is useful when calculating the last post in
* a forum or the number of new messages from a given date. Using a rounded
* date allows Jive to internally cache the results of the date query.
* Here's an example that shows the last posted message in a forum accurate
* to the last 60 seconds:</p>
*
* <pre>
* SessionResultFilter filter = new SessionResultFilter();
* filter.setSortOrder(SessionResultFilter.DESCENDING);
* filter.setSortField(JiveGlobals.SORT_CREATION_DATE);
* <b>filter.setCreationDateRangeMin(SessionResultFilter.roundDate(forum.getModificationDate(), 60));</b>
* filter.setNumResults(1);
* Iterator messages = forum.messages(filter);
* ForumMessage lastPost = (ForumMessage)messages.next();
* </pre>
*
* @param date the <tt>Date</tt> we want to round.
* @param seconds the number of seconds we want to round the date to.
* @return the given date, rounded down to the nearest specified number of seconds.
*/
public static Date roundDate(Date date, int seconds) {
return new Date(roundDate(date.getTime(), seconds));
}
/**
* Rounds the given date down to the nearest specfied second.
*
* @param date the date (as a long) that we want to round.
* @param seconds the number of seconds we want to round the date to.
* @return the given date (as a long), rounded down to the nearest
* specified number of seconds.
*/
public static long roundDate(long date, int seconds) {
return date - (date % (1000 * seconds));
}
@Override
public boolean equals(Object object) {
if (this == object) {
return true;
}
if (object != null && object instanceof SessionResultFilter) {
SessionResultFilter o = (SessionResultFilter)object;
if (sortField != o.sortField) {
return false;
}
if (sortOrder != o.sortOrder) {
return false;
}
if (numResults != o.numResults) {
return false;
}
if (!compare(username, o.username)) {
return false;
}
if (!compare(creationDateRangeMin, o.creationDateRangeMin)) {
return false;
}
if (!compare(creationDateRangeMax, o.creationDateRangeMax)) {
return false;
}
if (!compare(lastActivityDateRangeMin, o.lastActivityDateRangeMin)) {
return false;
}
if (!compare(lastActivityDateRangeMax, o.lastActivityDateRangeMax)) {
return false;
}
// All checks passed, so equal.
return true;
}
else {
return false;
}
}
/**
* Returns true if two objects are equal. This is a helper method
* to assist with the case that one or both objects are <tt>null</tt>;
* if both objects are <tt>null</tt> then they're considered equal.
*
* @param one the first object.
* @param two the second object.
* @return true if the objects are equal.
*/
private static boolean compare (Object one, Object two) {
if (one == null && two != null) {
return false;
}
else if (one != null) {
if (!one.equals(two)) {
return false;
}
}
return true;
}
}