/* * Copyright (C) 2005-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.resultsetmanager; import org.dom4j.DocumentHelper; import org.dom4j.Element; import org.dom4j.QName; import java.util.*; /** * A result set representation as described in XEP-0059. A result set is a * collection of objects that each have a unique identifier (UID). * * It's expected that some implementations will have the complete result set * loaded into memory, whereas more complex implementations might keep * references to partial sets only. This latter would have considerable * advantages if the result set is extremely large, or if the operation to get * all results in the set is expensive. * * @author Guus der Kinderen, guus@nimbuzz.com * * @param <E> * Each result set should be a collection of instances of the exact * same class. This class must implement the {@link Result} * interface. * @deprecated Replaced by {@link org.xmpp.resultsetmanagement.ResultSet} */ @Deprecated public abstract class ResultSet<E extends Result> extends AbstractCollection<E> { /** * A list of field names that are valid in jabber:iq:search */ private final static Collection<String> validRequestFields = new ArrayList<>(); static { validRequestFields.add("max"); // required validRequestFields.add("before"); validRequestFields.add("after"); validRequestFields.add("index"); } /** * The namespace that identifies Result Set Management functionality. */ public static final String NAMESPACE_RESULT_SET_MANAGEMENT = "http://jabber.org/protocol/rsm"; /** * Returns a List of results starting with the first result after the * provided result (the returned List is exclusive). * * The lenght of the list is equal to 'maxAmount', unless there are no more * elements available (in which case the length of the result will be * truncated). * * @param result * The element that is right before the first element in the * result. * @param maxAmount * The maximum number of elements to return. * @return A List of elements the are exactly after the element that is * provided as a parameter. * @throws NullPointerException * if the result does not exist in the result set. */ public List<E> getAfter(E result, int maxAmount) { return getAfter(result.getUID(), maxAmount); } /** * Returns a List of results starting with the first result after the result * that's identified by the provided UID (the returned List is exclusive). * * The lenght of the list is equal to 'maxAmount', unless there are no more * elements available (in which case the length of the result will be * truncated). * * @param uid * The UID of the element that is right before the first element * in the result. * @param maxAmount * The maximum number of elements to return. * @return A List of elements the are exactly after the element that is * provided as a parameter. * @throws NullPointerException * if there is no result in the result set that matches the UID. */ public abstract List<E> getAfter(String uid, int maxAmount); /** * Returns a list of results ending with the element right before the * provided result (the returned List is exclusive). * * At most 'maxAmount' elements are in the returned List, but the lenght of * the result might be smaller if no more applicable elements are available. * * Note that the order of the result is equal to the order of the results of * other methods of this class: the last element in the returned List * immediately preceeds the element denoted by the 'result' parameter. * * @param result * The element preceding the last element returned by this * function. * @param maxAmount * The length of the List that is being returned. * @return A List of elements that are exactly before the element that's * provided as a parameter. * @throws NullPointerException * if the result does not exist in the result set. * */ public List<E> getBefore(E result, int maxAmount) { return getBefore(result.getUID(), maxAmount); } /** * Returns a list of results ending with the element right before the * element identified by the provided UID (the returned List is exclusive). * * At most 'maxAmount' elements are in the returned List, but the lenght of * the result might be smaller if no more applicable elements are available. * * Note that the order of the result is equal to the order of the results of * other methods of this class: the last element in the returned List * immediately preceeds the element denoted by the 'result' parameter. * * @param uid * The UID of the element preceding the last element returned by * this function. * @param maxAmount * The length of the List that is being returned. * @return A List of elements that are exactly before the element that's * provided as a parameter. * @throws NullPointerException * if there is no result in the result set that matches the UID. */ public abstract List<E> getBefore(String uid, int maxAmount); /** * Returns the first elements from this result set. * * @param maxAmount * the number of elements to return. * @return the last 'maxAmount' elements of this result set. */ public abstract List<E> getFirst(int maxAmount); /** * Returns the last elements from this result set. * * @param maxAmount * the number of elements to return. * @return the last 'maxAmount' elements of this result set. */ public abstract List<E> getLast(int maxAmount); /** * Returns the element denoted by the index. * * @param index * Index of the element to be returned * @return the Element at 'index'. */ public abstract E get(int index); /** * Returns a list of results, starting with the result that's at the * specified index. If the difference between the startIndex and the index * of the last element in the entire resultset is smaller than the size * supplied in the 'amount' parameter, the length of the returned list will * be smaller than the 'amount' paramater. If the supplied index is equal * to, or larger than the size of the result set, an empty List is returned. * * @param fromIndex * The index of the first element to be returned. * @param maxAmount * The maximum number of elements to return. * @return A list of elements starting with (inclusive) the element * referenced by 'fromIndex'. An empty List if startIndex is equal * to or bigger than the size of this entire result set. */ public abstract List<E> get(int fromIndex, int maxAmount); /** * Returns the UID of the object at the specified index in this result set. * * @param index * The index of the UID to be returned. * @return UID of the object on the specified index. */ public String getUID(int index) { return get(index).getUID(); } /** * Returns the index in the full resultset of the element identified by the * UID in te supplied argument. * * @param uid * The UID of the element to search for * @return The index of the element. * @throws NullPointerException * if there is no result in the result set that matches the UID. * */ public abstract int indexOf(String uid); /** * Returns the index in the full resultset of the supplied argument. * * @param element * The element to search for * @return The index of the element. */ public int indexOf(E element) { return indexOf(element.getUID()); } /* * (non-Javadoc) * * @see java.util.AbstractCollection#iterator() */ @Override public Iterator<E> iterator() { return new Itr(); } /** * Applies the 'result set management' directives to this result set, and * returns a list of Results that matches the directives. Note that the * orignal set is untouched. Instead, a new List is returned. * * @param rsmElement * The XML element that contains the 'result set management' * directives. * @return a list of Results that matches the directives. */ public List<E> applyRSMDirectives(Element rsmElement) { if (rsmElement == null || !isValidRSMRequest(rsmElement)) { throw new IllegalArgumentException( "The 'rsmElement' argument must be a valid, non-null RSM element."); } final int max = Integer.parseInt(rsmElement.element("max").getText()); if (max == 0) { // this is a request for a resultset count. return Collections.emptyList(); } // optional elements final Element afterElement = rsmElement.element("after"); final Element beforeElement = rsmElement.element("before"); final Element indexElement = rsmElement.element("index"); // Identify the pointer object in this set. This is the object before // (or after) the first (respectivly last) element of the subset that // should be returned. If no pointer is specified, the pointer is said // to be before or after the first respectivly last element of the set. String pointerUID = null; // by default, the pointer is before the // first element of the set. // by default, the search list is forward oriented. boolean isForwardOriented = true; if (afterElement != null) { pointerUID = afterElement.getText(); } else if (beforeElement != null) { pointerUID = beforeElement.getText(); isForwardOriented = false; } else if (indexElement != null) { final int index = Integer.parseInt(indexElement.getText()); if (index > 0) { pointerUID = getUID(index - 1); } } if (pointerUID != null && pointerUID.equals("")) { pointerUID = null; } if (isForwardOriented) { if (pointerUID == null) { return getFirst(max); } return getAfter(pointerUID, max); } if (pointerUID == null) { return getLast(max); } return getBefore(pointerUID, max); } /** * Generates a Result Set Management 'set' element that describes the parto * of the result set that was generated. You typically would use the List * that was returned by {@link #applyRSMDirectives(Element)} as an argument * to this method. * * @param returnedResults * The subset of Results that is returned by the current query. * @return An Element named 'set' that can be included in the result IQ * stanza, which returns the subset of results. */ public Element generateSetElementFromResults(List<E> returnedResults) { if (returnedResults == null) { throw new IllegalArgumentException( "Argument 'returnedResults' cannot be null."); } final Element setElement = DocumentHelper.createElement(QName.get( "set", ResultSet.NAMESPACE_RESULT_SET_MANAGEMENT)); // the size element contains the size of this entire result set. setElement.addElement("count").setText(String.valueOf(size())); // if the query wasn't a 'count only' query, add two more elements if (returnedResults.size() > 0) { final Element firstElement = setElement.addElement("first"); firstElement.addText(returnedResults.get(0).getUID()); firstElement.addAttribute("index", String .valueOf(indexOf(returnedResults.get(0)))); setElement.addElement("last").addText( returnedResults.get(returnedResults.size() - 1).getUID()); } return setElement; } /** * Checks if the Element that has been passed as an argument is a valid * Result Set Management element, in a request context. * * @param rsmElement * The Element to check. * @return ''true'' if this is a valid RSM query representation, ''false'' * otherwise. */ // Dom4J doesn't do generics, sadly. @SuppressWarnings("unchecked") public static boolean isValidRSMRequest(Element rsmElement) { if (rsmElement == null) { throw new IllegalArgumentException( "The argument 'rsmElement' cannot be null."); } if (!rsmElement.getName().equals("set")) { // the name of the element must be "set". return false; } if (!rsmElement.getNamespaceURI().equals( NAMESPACE_RESULT_SET_MANAGEMENT)) { // incorrect namespace return false; } final Element maxElement = rsmElement.element("max"); if (maxElement == null) { // The 'max' element in an RSM request must be available return false; } final String sMax = maxElement.getText(); if (sMax == null || sMax.length() == 0) { // max element must contain a value. return false; } try { if (Integer.parseInt(sMax) < 0) { // must be a postive integer. return false; } } catch (NumberFormatException e) { // the value of 'max' must be an integer value. return false; } List<Element> allElements = rsmElement.elements(); int optionalElements = 0; for (Element element : allElements) { final String name = element.getName(); if (!validRequestFields.contains(name)) { // invalid element. return false; } if (!name.equals("max")) { optionalElements++; } if (optionalElements > 1) { // only one optional element is allowed. return false; } if (name.equals("index")) { final String value = element.getText(); if (value == null || value.equals("")) { // index elements must have a numberic value. return false; } try { if (Integer.parseInt(value) < 0) { // index values must be positive. return false; } } catch (NumberFormatException e) { // index values must be numeric. return false; } } } return true; } /** * Basic Iterator implementation. Forward scrolling only. Does not support * modification. * * @author Guus der Kinderen, guus@nimbuzz.com * */ class Itr implements Iterator<E> { /** * Index of element to be returned by subsequent call to next. */ int cursor = 0; /* * (non-Javadoc) * * @see java.util.Iterator#hasNext() */ @Override public boolean hasNext() { return cursor != size(); } /* * (non-Javadoc) * * @see java.util.Iterator#next() */ @Override public E next() { return get(cursor++); } /* * (non-Javadoc) * * @see java.util.Iterator#remove() */ @Override public void remove() { throw new UnsupportedOperationException(); } } }