/* * Copyright 2014-2017 the original author or authors. * * 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.springframework.data.solr.core.query.result; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.NoSuchElementException; import org.apache.solr.client.solrj.SolrQuery; import org.apache.solr.common.params.CursorMarkParams; import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; /** * {@link DelegatingCursor} is a base {@link Cursor} implementation that temporarily holds data fetched in one run and * delegates iteration. * * @author Christoph Strobl * @param <T> */ public abstract class DelegatingCursor<T> implements Cursor<T> { private State state; private String cursorMark; private long position; private Iterator<T> delegate; private final SolrQuery referenceQuery; protected DelegatingCursor(SolrQuery query) { this(query, CursorMarkParams.CURSOR_MARK_START); } protected DelegatingCursor(SolrQuery query, String initalCursorMark) { this.referenceQuery = query; this.cursorMark = StringUtils.hasText(initalCursorMark) ? initalCursorMark : CursorMarkParams.CURSOR_MARK_START; this.state = State.REDAY; this.delegate = Collections.<T> emptyList().iterator(); } /* * (non-Javadoc) * @see java.util.Iterator#hasNext() */ @Override public boolean hasNext() { validateState(); if (!delegate.hasNext() && !isFinished()) { load(getCursorMark()); } if (delegate.hasNext()) { return true; } return false; } /* * (non-Javadoc) * @see java.util.Iterator#next() */ @Override public T next() { validateState(); if (!hasNext()) { throw new NoSuchElementException("No more elements available for cursor " + getCursorMark() + "."); } T next = moveNext(delegate); position++; return next; } /** * Move one position next in given source. * * @param source * @return */ protected T moveNext(Iterator<T> source) { return source.next(); } private void load(String cursorMark) { SolrQuery query = referenceQuery.getCopy(); query.set(CursorMarkParams.CURSOR_MARK_PARAM, this.getCursorMark()); PartialResult<T> result = doLoad(query); process(result); } /** * Read data from Solr. * * @param nativeQuery The query to execute already positioned at the next cursor mark. * @return */ protected abstract PartialResult<T> doLoad(SolrQuery nativeQuery); private void process(PartialResult<T> result) { if (result == null) { this.delegate = Collections.<T> emptyList().iterator(); this.state = State.FINISHED; return; } if (getCursorMark().equals(result.getNextCursorMark())) { this.state = State.FINISHED; } this.cursorMark = result.getNextCursorMark(); if (!CollectionUtils.isEmpty(result.getItems())) { delegate = result.iterator(); } else { Collections.<T> emptyList().iterator(); } } /* * (non-Javadoc) * @see org.springframework.data.solr.core.query.result.Cursor#open() */ @Override public DelegatingCursor<T> open() { if (!isReady()) { throw new InvalidDataAccessApiUsageException("Cursor already " + state + ". Cannot (re)open it."); } this.state = State.OPEN; doOpen(this.getCursorMark()); return this; } /** * Customization hook for {@link #open()}. * * @param cursorMark */ protected void doOpen(String cursorMark) { load(cursorMark); } /* * (non-Javadoc) * @see java.io.Closeable#close() */ @Override public void close() throws IOException { try { doClose(); } finally { this.state = State.CLOSED; } } /** * Customization hook for clean up operations */ protected void doClose() { this.delegate = Collections.<T> emptyList().iterator(); this.referenceQuery.clear(); this.position = -1; this.cursorMark = null; } /* * (non-Javadoc) * @see java.util.Iterator#remove() */ @Override public void remove() { throw new UnsupportedOperationException("Removing elements from cursor is not supported"); } /* * (non-Javadoc) * @see org.springframework.data.solr.core.query.result.Cursor#getPosition() */ @Override public long getPosition() { return this.position; } /* * (non-Javadoc) * @see org.springframework.data.solr.core.query.result.Cursor#getCursorMark() */ @Override public String getCursorMark() { return this.cursorMark; } /** * @return true if {@link State#REDAY} */ public boolean isReady() { return State.REDAY.equals(state); } /* * (non-Javadoc) * @see org.springframework.data.solr.core.query.result.Cursor#isOpen() */ public boolean isOpen() { return State.OPEN.equals(state); } /** * @return true if {@link State#FINISHED} */ public boolean isFinished() { return State.FINISHED.equals(state); } /* * (non-Javadoc) * @see org.springframework.data.solr.core.query.result.Cursor#isClosed() */ @Override public boolean isClosed() { return State.CLOSED.equals(state); } private void validateState() { if (isReady() || isClosed()) { throw new InvalidDataAccessApiUsageException("Cannot access closed cursor. Did you forget to call open()?"); } } /** * {@link PartialResult} provided by a round trip to SolrClient loading data for an iteration. Also holds the cursor * mark to use next. * * @author Christoph Strobl * @param <T> */ public static class PartialResult<T> implements Iterable<T> { private String nextCursorMark; private Collection<T> items; public PartialResult(String nextCursorMark, Collection<T> items) { this.nextCursorMark = nextCursorMark; this.items = (items != null ? new ArrayList<>(items) : Collections.<T> emptyList()); } /** * Get the next cursor mark to use. * * @return */ public String getNextCursorMark() { return nextCursorMark; } /** * Get items returned from server. <br/> * * @return never {@literal null} */ public Collection<T> getItems() { return items; } /* * (non-Javadoc) * @see java.lang.Iterable#iterator() */ @Override public Iterator<T> iterator() { return items.iterator(); } } }