/*
* Copyright (C) 2009 Google Inc.
*
* 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 com.google.sites.liberation.export;
import static com.google.gdata.util.common.base.Preconditions.checkNotNull;
import static com.google.gdata.util.common.base.Preconditions.checkArgument;
import com.google.common.collect.AbstractIterator;
import com.google.common.collect.Iterators;
import com.google.gdata.client.Query;
import com.google.gdata.client.sites.ContentQuery;
import com.google.gdata.client.sites.SitesService;
import com.google.gdata.data.sites.BaseContentEntry;
import com.google.gdata.util.ServiceException;
import com.google.gdata.util.common.base.Pair;
import com.google.sites.liberation.util.EntryProvider;
import java.io.IOException;
import java.net.URL;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Provides a continuous iterable of entries even if the results of
* a query are split across multiple feeds. This class will also return all
* valid entries in a feed even if some entries in the feed cause exceptions
* to be thrown.
*
* <p>This class can produce unexpected results if used on a feed other than the
* content feed for a Google Site.</p>
*
* @author bsimon@google.com (Benjamin Simon)
*/
final class ContinuousContentFeed implements Iterable<BaseContentEntry<?>> {
private static final Logger LOGGER = Logger.getLogger(
ContinuousContentFeed.class.getCanonicalName());
private final EntryProvider entryProvider;
private final URL feedUrl;
private final SitesService sitesService;
private final int resultsPerRequest;
/**
* Creates a new instance of {@code ContinuousContentFeed} for the given
* entry provider, feed URL, and number of entries to request per query.
*
* <p>This {@code ContinuousContentFeed} will contain all of the valid entries
* in the feed at {@code feedUrl}.</p>
*/
ContinuousContentFeed(URL feedUrl, EntryProvider entryProvider,
SitesService sitesService, int resultsPerRequest) {
this.entryProvider = checkNotNull(entryProvider);
this.feedUrl = checkNotNull(feedUrl);
this.sitesService = checkNotNull(sitesService);
checkArgument(resultsPerRequest > 0);
this.resultsPerRequest = resultsPerRequest;
}
/**
* Returns a new iterator of {@code BaseContentEntry}s for this feed.
* <p>
* The iterator returned will iterate through all of the entries corresponding
* to this {@code ContinuousContentFeed} even if the results are spread over
* multiple feeds. Subsequent calls to this method will return independent
* iterators, each starting at the beginning of the feed. However, each
* iterator instance will make its own RPC's, and so the use of multiple
* iterators should be avoided.
* </p>
*/
public AbstractIterator<BaseContentEntry<?>> iterator() {
return new FeedIterator();
}
/**
* This class defines the iterator returned by a
* {@code ContinuousContentFeed} iterable.
*/
private class FeedIterator extends AbstractIterator<BaseContentEntry<?>> {
Iterator<BaseContentEntry<?>> currentItr;
int index;
/**
* Constructs a new iterator for this {@code ContinuousContentFeed}.
*/
FeedIterator() {
currentItr = Iterators.emptyIterator();
index = 1;
}
/**
* Returns the next element if it exists, otherwise calls endOfData() and
* returns null.
*/
@Override
public BaseContentEntry<?> computeNext() {
if (!currentItr.hasNext()) {
Pair<Iterator<BaseContentEntry<?>>, Integer> pair =
getEntries(index, resultsPerRequest);
currentItr = pair.getFirst();
index += pair.getSecond();
if (!currentItr.hasNext()) {
return endOfData();
}
}
return currentItr.next();
}
/**
* Returns an iterator containing the valid entries with indices between
* {@code start} and {@code start}+{@code num}-1 and the number of entries
* the iterator contains.
*/
private Pair<Iterator<BaseContentEntry<?>>, Integer>
getEntries(int start, int num) {
Query query = new ContentQuery(feedUrl);
try {
int numReturned = 0;
Iterator<BaseContentEntry<?>> itr = Iterators.emptyIterator();
List<BaseContentEntry<?>> entries;
do {
query.setStartIndex(start + numReturned);
query.setMaxResults(num - numReturned);
entries = entryProvider.getEntries(query, sitesService);
numReturned += entries.size();
itr = Iterators.concat(itr, entries.iterator());
} while (numReturned < num && entries.size() > 0);
return Pair.of(itr, numReturned);
} catch (IOException e) {
return catchException(e, start, num);
} catch (ServiceException e) {
return catchException(e, start, num);
}
}
private Pair<Iterator<BaseContentEntry<?>>, Integer>
catchException(Exception e, int start, int num) {
String message = "Error retrieving response from query.";
LOGGER.log(Level.WARNING, message, e);
if (num == 1) {
Iterator<BaseContentEntry<?>> itr = Iterators.emptyIterator();
return Pair.of(itr, 1);
} else {
int num1 = num/2;
int num2 = num - num1;
Pair<Iterator<BaseContentEntry<?>>, Integer> pair1 =
getEntries(start, num1);
Pair<Iterator<BaseContentEntry<?>>, Integer> pair2 =
getEntries(start + num1, num2);
Iterator<BaseContentEntry<?>> itr = Iterators.concat(pair1.getFirst(),
pair2.getFirst());
int numReturned = pair1.getSecond() + pair2.getSecond();
return Pair.of(itr, numReturned);
}
}
}
}