/* * Copyright (C) 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.archive; import org.xmpp.packet.JID; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Date; /** * Defines a search query for use with an {@link ArchiveSearcher}. In general, there * are two types of searches that users might perform:<ul> * * <li>Query string search: search conversations for specific keywords, optionally * filtering the results by conversation participants or date range. * <li>Meta-data search: find all conversations by certain users or within a certain * date range. This search is typical for compliance purposes. * * @author Matt Tucker */ public class ArchiveSearch { /** * An integer value that represents NULL. The actual value is * Integer.MAX_VALUE - 123 (an arbitrary number that has a very low * probability of actually being selected by a user as a valid value). */ public static final int NULL_INT = Integer.MAX_VALUE - 123; private String queryString; private Collection<JID> participants = Collections.emptyList(); /** * Start of conversation has to be bigger or equal to this value (if set) */ private Date dateRangeMin; /** * Start of conversation has to be smaller or equal to this value (if set) */ private Date dateRangeMax; /** * Specified timestamp has to be between start and last activity dates */ private Date includeTimestamp; private JID room; private int startIndex = 0; private int numResults = NULL_INT; private SortField sortField; private SortOrder sortOrder; private boolean externalWildcardMode; /** * Creates a new search on a query string. * * @param queryString the query string to use for the search. * @return an ArchiveSearch instance to search using the specified query string. */ public static ArchiveSearch createKeywordSearch(String queryString) { ArchiveSearch search = new ArchiveSearch(); search.setQueryString(queryString); search.setSortField(SortField.relevance); return search; } /** * Constructs a new archive search, sorted on date descending. */ public ArchiveSearch() { this.sortOrder = SortOrder.descending; this.sortField = SortField.date; } /** * Returns the query string used for the search or <tt>null</tt> if no query string * has been set. The query String can contain the full * <a href="http://lucene.apache.org/java/docs/queryparsersyntax.html">search syntax</a> * supported by Lucene. * * @return the query string or <tt>null</tt> if no query string has been set. */ public String getQueryString() { return queryString; } /** * Sets the query string used for the search, which can be <tt>null</tt> to indicate that * no query string should be used. The query String can contain the full * <a href="http://lucene.apache.org/java/docs/queryparsersyntax.html">search syntax</a> * supported by Lucene. * * @param queryString the query string or <tt>null</tt> if no query string should be used. */ public void setQueryString(String queryString) { this.queryString = queryString; } /** * Returns the participants that this search covers. If no participants are specified * (via an empty collection), then this search will wildcard match against both users. * If a single participant is specified, this search will wildcard match against the * other participant. The wildcard matching mode is either external users only, or all * users, depending on the value returned by {@link #isExternalWildcardMode()}. * * @return the participants that this search covers. */ public Collection<JID> getParticipants() { return participants; } /** * Sets the participants that this search covers. If no participants are specified * then this search will wildcard match against both users. If a single participant * is specified, this search will wildcard match against the other participant. * The wildcard matching mode is either external users only, or all * users, depending on the value returned by {@link #isExternalWildcardMode()}. * * @param participants the participants that this search covers. */ public void setParticipants(JID... participants) { if (participants == null) { this.participants = Collections.emptyList(); } else { if (participants.length > 2) { throw new IllegalArgumentException("Not possible to search on more than " + "two participants."); } // Enforce using the bare JID. for (int i=0; i<participants.length; i++) { participants[i] = new JID(participants[i].toBareJID()); } this.participants = Arrays.asList(participants); } } /** * Returns the date that represents the lower boundary for conversations * that will be returned by the search. If this value has not been set, the method * will return <tt>null</tt>. * * @return a Date representing the lower bound for dates to search on or <tt>null</tt> * if there is no lower bound. */ public Date getDateRangeMin() { return dateRangeMin; } /** * Sets the date that represents the lower boundary for conversations * that will be returned by the search. A value of <tt>null</tt> indicates that * there should be no lower boundary. * * @param dateRangeMin a Date representing the lower bound for dates to search on * or <tt>null</tt> if there is no lower bound. */ public void setDateRangeMin(Date dateRangeMin) { this.dateRangeMin = dateRangeMin; } /** * Returns the date that represents the upper boundary for conversations * that will be returned by the search. If this value has not been set, the method * will return <tt>null</tt>. * * @return a Date representing the upper bound for dates to search on or <tt>null</tt> * if there is no upper bound. */ public Date getDateRangeMax() { return dateRangeMax; } /** * Sets the date that represents the upper boundary for conversations * that will be returned by the search. A value of <tt>null</tt> indicates that * there should be no upper boundary. * * @param dateRangeMax a Date representing the upper bound for dates to search on * or <tt>null</tt> if there is no upper bound. */ public void setDateRangeMax(Date dateRangeMax) { this.dateRangeMax = dateRangeMax; } /** * Returns the JID of the room for conversations that will be returned by the search. If * this value has not been set, the method will return <tt>null</tt>. * * @return JID of the room or <tt>null</tt> if there is no room to filter on. */ public JID getRoom() { return room; } /** * Sets the JID of the room for conversations that will be returned by the search. If * this value has not been set, the method will return <tt>null</tt>. * * @param room JID of the room or <tt>null</tt> if there is no room to filter on. */ public void setRoom(JID room) { this.room = room; } /** * Returns the timestamp to use for filtering conversations. This timestamp * has to be between the time when the conversation started and ended. * * @return timestamp between the time when the conversation started and ended. */ public Date getIncludeTimestamp() { return includeTimestamp; } /** * Set the timestamp to use for filtering conversations. This timestamp * has to be between the time when the conversation started and ended. * * @param includeTimestamp timestamp between the time when the conversation started and ended. */ public void setIncludeTimestamp(Date includeTimestamp) { this.includeTimestamp = includeTimestamp; } /** * Returns the sort order, which will be {@link SortOrder#ascending ascending} or * {@link SortOrder#descending descending}. * * @return the sort order. */ public SortOrder getSortOrder() { return this.sortOrder; } /** * Sets the sort type, which will be {@link SortOrder#ascending ascending} or * {@link SortOrder#descending descending}. * * @param sortOrder the order that results will be sorted in. */ public void setSortOrder(SortOrder sortOrder) { this.sortOrder = sortOrder; } /** * Returns the sort field, which will be {@link SortField#relevance relevance} or * {@link SortField#relevance relevance}. * * @return the sort field. */ public SortField getSortField() { return this.sortField; } /** * Sets the sort field, which will be {@link SortField#relevance relevance} or * {@link SortField#relevance relevance}. * * @param sortField the field that results will be sorted on. */ public void setSortField(SortField sortField) { this.sortField = sortField; } /** * Returns the max number of results that should be returned. * The default value for is NULL_INT, 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. * * @return the max number of results to return. * @see #setStartIndex(int) */ public int getNumResults() { return numResults; } /** * Sets the limit on the number of results to be returned. * * @param numResults the number of results to return. */ public void setNumResults(int numResults) { if (numResults != NULL_INT && 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 true if the wildcard matching for participants is for users * on external servers. Otherwise, wildcard matching will apply to any user * (external or internal). For example, if a single participant "jsmith" is set * and external wildcard mode is true, then the search will match any conversation * between "jsmith" and any external users. If the external wildcard mode is false, * then the search will match all conversations between "jsmith" and any other users. * * @return true if external wildcard mode is enabled. */ public boolean isExternalWildcardMode() { return externalWildcardMode; } /** * Sets whether wildcard matching for participants is for users on external * servers. Otherwise, wildcard matching will apply to any user (external or * internal). For example, if a single participant "jsmith" is set and external * wildcard mode is true, then the search will match any conversation between * "jsmith" and any external users. If the external wildcard mode is false, then * the search will match all conversations between "jsmith" and any other users. * * @param mode true if external wildcard mode is enabled. */ public void setExternalWildcardMode(boolean mode) { this.externalWildcardMode = mode; } /** * The sort order of search results. The default sort order is descending. Note that * if if the sort field is {@link SortField#relevance} (for a query string search), * then the sort order is irrelevant. Relevance searches will always display the * most relevant results first. */ public enum SortOrder { /** * Ascending sort (ie 3, 4, 5...). */ ascending, /** * Descending sort (ie 3, 2, 1...). */ descending } /** * The field to sort results on. */ public enum SortField { /** * Sort results based on relevance. This sort type can only be used when * searching with a query string. It <b>should</b> be the default sort field when * doing a query string search. */ relevance, /** * Sort results based on date. */ date } }