001: /*
002:
003: This software is OSI Certified Open Source Software.
004: OSI Certified is a certification mark of the Open Source Initiative.
005:
006: The license (Mozilla version 1.0) can be read at the MMBase site.
007: See http://www.MMBase.org/license
008:
009: */
010:
011: package org.mmbase.bridge.util;
012:
013: import org.mmbase.bridge.*;
014: import org.mmbase.storage.search.*;
015: import org.mmbase.cache.CachePolicy;
016: import org.mmbase.util.logging.*;
017:
018: import java.util.*;
019:
020: /**
021: * Iterates the big result of a query. It avoids using a lot of memory (which you would need if you
022: * get the complete NodeList first), and pollution of the (node) cache. In this current
023: * implementation the Query is 'batched' to avoid reading in all nodes in memory, and the queries
024: * are marked with {@link CachePolicy#NEVER}.
025: *
026: * @author Michiel Meeuwissen
027: * @version $Id: HugeNodeListIterator.java,v 1.6 2007/02/10 15:47:42 nklasens Exp $
028: * @since MMBase-1.8
029: */
030:
031: public class HugeNodeListIterator implements NodeIterator {
032:
033: public static final int DEFAULT_BATCH_SIZE = 10000;
034:
035: // log
036: private static final Logger log = Logging
037: .getLoggerInstance(HugeNodeListIterator.class);
038:
039: protected NodeIterator nodeIterator;
040: protected Node nextNode;
041: protected Node previousNode;
042:
043: protected Query originalQuery;
044: protected int batchSize = DEFAULT_BATCH_SIZE;
045:
046: protected int nextIndex = 0;
047:
048: /**
049: * Constructor for this Iterator.
050: *
051: * @param query The query which is used as a base for the querie(s) to be executed.
052: * @param batchSize The (approximate) size of the sub-queries, should be a reasonably large
053: * number, like 10000 or so.
054: */
055: public HugeNodeListIterator(Query query, int batchSize) {
056: this .batchSize = batchSize;
057: init(query);
058: }
059:
060: /**
061: * Constructor for this Iterator. The 'batchSize' is taken from the query's 'maxnumber'
062: * properties, or, it that is not set, it is defaulted to 10000.
063: *
064: * @param query The query which is used as a base for the querie(s) to be executed.
065: */
066: public HugeNodeListIterator(Query query) {
067: if (query.getMaxNumber() != SearchQuery.DEFAULT_MAX_NUMBER) {
068: batchSize = query.getMaxNumber();
069: } // else leave on default;
070: init(query);
071: }
072:
073: /**
074: * Called by constructors only
075: */
076: private void init(Query query) {
077: if (query.getOffset() > 0) {
078: throw new UnsupportedOperationException(
079: "Not implemented for queries with offset");
080: }
081: Queries.sortUniquely(query);
082: originalQuery = query;
083: executeNextQuery((Query) originalQuery.clone());
084: }
085:
086: /**
087: * Executes the given query, taking into account the fact wether it is NodeQuery or not, and
088: * applying the 'batchSize'. The result is available in the 'nodeIterator' member.
089: */
090: protected void executeQuery(Query currentQuery) {
091: currentQuery.setMaxNumber(batchSize);
092: if (log.isDebugEnabled()) {
093: log.trace("Running query: " + currentQuery);
094: }
095: NodeList list;
096: currentQuery.setCachePolicy(CachePolicy.NEVER);
097: if (originalQuery instanceof NodeQuery) {
098: NodeQuery nq = (NodeQuery) currentQuery;
099: list = nq.getNodeManager().getList(nq);
100: } else {
101: list = currentQuery.getCloud().getList(currentQuery);
102: }
103: if (log.isDebugEnabled()) {
104: log.trace("Query result: " + list.size() + " nodes");
105: }
106: nodeIterator = list.nodeIterator();
107: }
108:
109: /**
110: * Executes the given query, and prepares to do 'next', so setting 'nextNode' and 'previousNode'.
111: */
112: protected void executeNextQuery(Query q) {
113: executeQuery(q);
114: previousNode = nextNode;
115: if (nodeIterator.hasNext()) {
116: nextNode = nodeIterator.nextNode();
117: } else {
118: nextNode = null;
119: }
120: }
121:
122: /**
123: * Executes the given query, and prepares to do 'previous', so setting 'nextNode' and
124: * 'previousNode', and winds the new nodeIterator to the end.
125: */
126: protected void executePreviousQuery(Query q) {
127: executeQuery(q);
128: nextNode = previousNode;
129: while (nodeIterator.hasNext()) {
130: nodeIterator.next();
131: }
132: if (nodeIterator.hasPrevious()) {
133: previousNode = nodeIterator.nextNode();
134: } else {
135: previousNode = null;
136: }
137: }
138:
139: /**
140: * {@inheritDoc}
141: */
142: public boolean hasNext() {
143: return nextNode != null;
144: }
145:
146: /**
147: * {@inheritDoc}
148: */
149: public boolean hasPrevious() {
150: return previousNode != null;
151: }
152:
153: /**
154: * {@inheritDoc}
155: */
156: public Node next() {
157: return nextNode();
158: }
159:
160: /**
161: * {@inheritDoc}
162: */
163: public Node previous() {
164: return previousNode();
165: }
166:
167: /**
168: * {@inheritDoc}
169: */
170: public int previousIndex() {
171: return nextIndex - 1;
172: }
173:
174: /**
175: * {@inheritDoc}
176: */
177: public int nextIndex() {
178: return nextIndex;
179: }
180:
181: /**
182: * Used by nextNode and previousNode. Does a field-by-field compare of two Node objects, on
183: * the fields used to order the nodes.
184: * This is used to determine whether a node comes after or before another - allowing
185: * the node iterator to skip nodes it already 'had'.
186: * @return -1 if node1 is smaller than node 2, 0 if both nodes are equals, and +1 is node 1 is greater than node 2.
187: */
188: protected int compares(Node node1, Node node2) {
189: return Queries.compare(node1, node2, originalQuery
190: .getSortOrders());
191: }
192:
193: /**
194: * {@inheritDoc}
195: *
196: * Implementation calculates also the next next Node, and gives back the 'old' next Node, from
197: * now on known as 'previousNode'.
198: */
199: public Node nextNode() {
200: if (nextNode != null) {
201: nextIndex++;
202: previousNode = nextNode;
203: if (nodeIterator.hasNext()) {
204: nextNode = nodeIterator.nextNode();
205: } else {
206: Query currentQuery = (Query) originalQuery.clone();
207:
208: // We don't use offset to determin the 'next' batch of query results
209: // because there could have been deletions/insertions.
210: // We use the sort-order to apply a constraint.
211: SortOrder order = originalQuery.getSortOrders().get(0);
212: Object value = Queries.getSortOrderFieldValue(
213: previousNode, order);
214: Constraint cons;
215: if (order.getDirection() == SortOrder.ORDER_ASCENDING) {
216: cons = currentQuery
217: .createConstraint(
218: order.getField(),
219: FieldCompareConstraint.GREATER_EQUAL,
220: value);
221: } else {
222: cons = currentQuery.createConstraint(order
223: .getField(),
224: FieldCompareConstraint.LESS_EQUAL, value);
225: }
226: Queries.addConstraint(currentQuery, cons);
227:
228: executeNextQuery(currentQuery);
229:
230: // perhaps the sort-order did not find a unique result, skip some nodes in that case.
231: // XXX This goes wrong if (which is unlikely) there follow more nodes than 'batchSize'.
232: while (nextNode != null
233: && compares(nextNode, previousNode) <= 0) {
234: if (nodeIterator.hasNext()) {
235: nextNode = nodeIterator.nextNode();
236: } else {
237: nextNode = null;
238: }
239: }
240: }
241:
242: return previousNode; // looks odd, but really is wat is meant.
243: } else {
244: throw new NoSuchElementException("No next element");
245: }
246: }
247:
248: /**
249: * {@inheritDoc}
250: *
251: * Implementation is analogous to nextNode.
252: */
253: public Node previousNode() {
254: if (previousNode != null) {
255: nextNode = previousNode;
256: nextIndex--;
257: if (nodeIterator.hasPrevious()) {
258: previousNode = nodeIterator.previousNode();
259: } else {
260: Query currentQuery = (Query) originalQuery.clone();
261: SortOrder order = originalQuery.getSortOrders().get(0);
262: Object value = Queries.getSortOrderFieldValue(nextNode,
263: order);
264: Constraint cons;
265: if (order.getDirection() == SortOrder.ORDER_ASCENDING) {
266: cons = currentQuery.createConstraint(order
267: .getField(),
268: FieldCompareConstraint.LESS_EQUAL, value);
269: } else {
270: cons = currentQuery
271: .createConstraint(
272: order.getField(),
273: FieldCompareConstraint.GREATER_EQUAL,
274: value);
275: }
276: Queries.addConstraint(currentQuery, cons);
277: executePreviousQuery(currentQuery);
278: while (previousNode != null
279: && compares(nextNode, previousNode) >= 0) {
280: if (nodeIterator.hasPrevious()) {
281: previousNode = nodeIterator.previousNode();
282: } else {
283: previousNode = null;
284: }
285: }
286:
287: }
288: return nextNode;
289: } else {
290: throw new NoSuchElementException("No previous element");
291: }
292: }
293:
294: /**
295: * @throws UnsupportedOperationException
296: */
297: public void remove() {
298: throw new UnsupportedOperationException(
299: "Optional operation 'remove' not implemented");
300: }
301:
302: /**
303: * @throws UnsupportedOperationException
304: */
305: public void add(Node o) {
306: throw new UnsupportedOperationException(
307: "Optional operation 'add' not implemented");
308: }
309:
310: /**
311: * @throws UnsupportedOperationException
312: */
313: public void set(Node o) {
314: throw new UnsupportedOperationException(
315: "Optional operation 'set' not implemented");
316: }
317:
318: /**
319: * Main only for testing.
320: */
321: public static void main(String[] args) {
322: Cloud cloud = ContextProvider.getDefaultCloudContext()
323: .getCloud("mmbase");
324: NodeQuery q = cloud.getNodeManager("object").createQuery();
325: HugeNodeListIterator nodeIterator = new HugeNodeListIterator(q,
326: 20);
327: int i = 0;
328: while (nodeIterator.hasNext()) {
329: Node node = nodeIterator.nextNode();
330: System.out.println("" + (i++) + ": " + node.getNumber()
331: + " " + node.getNodeManager().getName() + " "
332: + node.getFunctionValue("gui", null).toString());
333: }
334:
335: }
336:
337: }
|