// 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.enterprise.connector.instantiator;
import com.google.enterprise.connector.spi.AuthenticationManager;
import com.google.enterprise.connector.spi.AuthorizationManager;
import com.google.enterprise.connector.spi.Connector;
import com.google.enterprise.connector.spi.ConnectorShutdownAware;
import com.google.enterprise.connector.spi.DocumentList;
import com.google.enterprise.connector.spi.Session;
import com.google.enterprise.connector.spi.SimpleDocument;
import com.google.enterprise.connector.spi.SimpleDocumentList;
import com.google.enterprise.connector.spi.TraversalManager;
import com.google.enterprise.connector.test.ConnectorTestUtils;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;
/**
* A Test Connector that provides
* <OL>
* <LI> Synchronization between a test and the {@link TraversalManager} so the
* test can coordinate test actions such as cancel during the running of a
* batch.</LI>
* <LI> Tracking of traversal related events for test validation purposes.</LI>
* </OL>
* This implementation supports repeated instantiation by Spring. Currently
* tests interact with the {@link TraversalManager} through static singleton
* Objects. Hence concurrent management of multiple active
* {@link TraversalManager} instances is not well supported.
*/
public class SyncingConnector implements Connector, ConnectorShutdownAware {
private static final Logger LOGGER =
Logger.getLogger(SyncingConnector.class.getName());
private volatile static Tracker tracker = new Tracker();
private static BlockingQueue<DocumentList> traversalResults =
new ArrayBlockingQueue<DocumentList>(100);
/**
* Milliseconds a traversal will block polling traversalResults for a
* {@link DocumentList} before giving up.
*/
private static long pollTimeOutMillis = 5000;
/**
* Sets the Poll timeout in milliseconds.
*/
static synchronized void setPollTimeout(long millis) {
pollTimeOutMillis = millis;
}
/**
* Gets the Poll timeout in milliseconds.
*/
static synchronized long getPollTimeout() {
return pollTimeOutMillis;
}
/**
* Creates a single document {@link DocumentList} and queues it for the
* {@link TraversalManager} to return on an upcoming
* {@link TraversalManager#startTraversal()} or
* {@link TraversalManager#resumeTraversal(String)} call. Note these calls
* block until a {@link DocumentList} has been queued or the
* {@code timeOutMillis} milliseconds have elapsed.
*
* @return A {@link List} with the queued document.
*/
static List<SimpleDocument> createaAndQueueDocumentList() {
List<SimpleDocument> result = createDocumentList(tracker.nextDocId());
traversalResults.add(new SimpleDocumentList(result));
return result;
}
/**
* Returns a {@link SyncingConnector.Tracker} for tracking and coordinating
* with a {@link TraversalManager}
*/
static synchronized Tracker getTracker() {
return tracker;
}
public SyncingConnector() {
}
@Override
public Session login() {
tracker.incrementLoginCount();
return new SyncingConnectorSession();
}
public void delete() {
tracker.incrementDeleteCount();
}
public void shutdown() {
tracker.incrementShutdownCount();
}
/**
* Resets static state so the calling test will not be affected by ealier
* usage.
*/
static void reset() {
traversalResults.clear();
tracker = new Tracker();
}
private class SyncingConnectorSession implements Session {
public AuthenticationManager getAuthenticationManager() {
return null;
}
public AuthorizationManager getAuthorizationManager() {
return null;
}
public TraversalManager getTraversalManager() {
tracker.incrementTraversalManagerCount();
return new SyncingConnectorTraversalManager();
}
}
private class SyncingConnectorTraversalManager implements
TraversalManager {
public DocumentList resumeTraversal(String checkPoint) {
tracker.incrementResumeTraversalCount();
tracker.traversingStarted();
return poll();
}
public void setBatchHint(int batchHint) {
// Ignored.
}
public DocumentList startTraversal() {
tracker.incrementStartTraversalCount();
tracker.traversingStarted();
return poll();
}
private DocumentList poll() {
try {
DocumentList result = traversalResults.poll(getPollTimeout(),
TimeUnit.MILLISECONDS);
if(result == null) {
LOGGER.warning("poll returned null document.");
}
return result;
} catch (InterruptedException ie) {
tracker.incrementInterruptedCount();
tracker.traversingInterrupted();
return null;
}
}
}
/**
* Provides tracking and synchronization with a {@link TraversalManager}.
*/
static class Tracker {
// BlockingQueueConnectorTraversalManager writes to this queue in
// start/resumeTraversal. Tests read from the queue to block until
// the traversal is underway.
private final BlockingQueue<Object> traversingQueue =
new ArrayBlockingQueue<Object>(200);
// BlockingQueueConnectorTraversalManager writes to this queue
// when a poll is interrupted. Tests read from the queue to block until
// the interrupt occurs.
private final BlockingQueue<Object> traversingInterrupted =
new ArrayBlockingQueue<Object>(200);
private int loginCount;
private int deleteCount;
private int shutdownCount;
private int interruptedCount;
private int traversalManagerCount;
private int startTraversalCount;
private int resumeTraversalCount;
private int nextDocId;
public final void traversingStarted() {
traversingQueue.add(new Object());
}
public final void blockUntilTraversing()
throws InterruptedException {
traversingQueue.poll(getPollTimeout(), TimeUnit.MILLISECONDS);
}
public final void traversingInterrupted() {
traversingInterrupted.add(new Object());
}
public void blockUntilTraversingInterrupted()
throws InterruptedException {
if (null == traversingInterrupted.poll(getPollTimeout(),
TimeUnit.MILLISECONDS)) {
throw new InterruptedException("poll timed out");
}
}
public int getLoginCount() {
return loginCount;
}
public synchronized void incrementLoginCount() {
loginCount++;
}
public synchronized int getDeleteCount() {
return deleteCount;
}
public synchronized void incrementDeleteCount() {
deleteCount++;
}
public synchronized int getShutdownCount() {
return shutdownCount;
}
public synchronized void incrementShutdownCount() {
shutdownCount++;
}
public synchronized int getInterruptedCount() {
return interruptedCount;
}
public void incrementInterruptedCount() {
interruptedCount++;
}
public synchronized int getTraversalManagerCount() {
return traversalManagerCount;
}
public synchronized void incrementTraversalManagerCount() {
traversalManagerCount++;
}
public synchronized int getStartTraversalCount() {
return startTraversalCount;
}
public synchronized void incrementStartTraversalCount() {
startTraversalCount++;
}
public synchronized int getResumeTraversalCount() {
return resumeTraversalCount;
}
public synchronized void incrementResumeTraversalCount() {
resumeTraversalCount++;
}
public synchronized String nextDocId() {
return Integer.toString(nextDocId++);
}
@Override
public synchronized String toString() {
return "Tracker"
+ " loginCount=" + loginCount
+ " deleteCount=" + deleteCount
+ " shutdownCount=" + shutdownCount
+ " interruptedCount=" + interruptedCount
+ " startTraversalCount=" + startTraversalCount
+ " resumeTraversalCount=" + resumeTraversalCount
+ " nextDocId=" + nextDocId;
}
}
public static List<SimpleDocument> createDocumentList(String docId) {
SimpleDocument document = ConnectorTestUtils.createSimpleDocument(docId);
List<SimpleDocument> docList = new LinkedList<SimpleDocument>();
docList.add(document);
return docList;
}
}