// Copyright 2011 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.pusher; import com.google.enterprise.connector.pusher.Pusher.PusherStatus; import com.google.enterprise.connector.spi.Document; import com.google.enterprise.connector.spi.DocumentAcceptor; import com.google.enterprise.connector.spi.DocumentAcceptorException; import com.google.enterprise.connector.spi.RepositoryDocumentException; import com.google.enterprise.connector.spi.RepositoryException; import java.util.logging.Level; import java.util.logging.Logger; /** * Implementation for {@link DocumentAcceptor} - something that takes spi * Documents and sends them along on their way. */ // TODO: Impose Error delay where appropriate. public class DocumentAcceptorImpl implements DocumentAcceptor { private static final Logger LOGGER = Logger.getLogger(DocumentAcceptorImpl.class.getName()); private final String connectorName; private final PusherFactory pusherFactory; private Pusher pusher; // Sleep milliseconds when waiting for Pusher to resume OK status. private long shortSleep = 30 * 1000L; private long longSleep = 5 * 60 * 1000L; private int retryCount = 10; public DocumentAcceptorImpl(String connectorName, PusherFactory pusherFactory) throws DocumentAcceptorException, RepositoryException { this.connectorName = connectorName; this.pusherFactory = pusherFactory; } /* Used by tests to shorten sleep times. */ void setSleepIntervals(long shortSleep, long longSleep, int retryCount) { this.shortSleep = shortSleep; this.longSleep = longSleep; this.retryCount = retryCount; } /** * Takes an spi Document and pushes it along, presumably to the GSA Feed. * * @param document A Document * @throws RepositoryException if transient error accessing the Repository * @throws RepositoryDocumentException if fatal error accessing the Document * @throws DocumentAcceptorException if a transient error occurs in the * DocumentAcceptor */ public synchronized void take(Document document) throws DocumentAcceptorException, RepositoryException { try { if (pusher.take(document) != PusherStatus.OK) { waitForOkStatus(); } } catch (NullPointerException e) { // Ugly, but avoids checking for null Pusher on every call to take. if (pusher == null) { // Opps. We need to get a new Pusher. try { pusher = pusherFactory.newPusher(connectorName); this.take(document); } catch (PushException pe) { LOGGER.log(Level.SEVERE, "DocumentAcceptor failed to get Pusher", e); throw new DocumentAcceptorException("Failed to get Pusher", e); } } else { throw e; } } catch (PushException e) { LOGGER.log(Level.SEVERE, "DocumentAcceptor failed to take document", e); throw new DocumentAcceptorException("Failed to take document", e); } catch (FeedException e) { LOGGER.log(Level.SEVERE, "DocumentAcceptor failed to take document", e); throw new DocumentAcceptorException("Failed to take document", e); } catch (RepositoryException e) { LOGGER.log(Level.WARNING, "DocumentAcceptor failed to take document", e); throw e; } catch (InterruptedException e) { // Woke from sleep. Just return. } } /** * Wait for the PusherStatus to clear. But don't wait forever. */ private void waitForOkStatus() throws PushException, FeedException, RepositoryException, InterruptedException { for (int retries = 0; retries < retryCount; retries++) { switch (pusher.getPusherStatus()) { case OK: return; case DISABLED: // This is not likely, but trigger getting a new Pusher. throw new NullPointerException(); case LOW_MEMORY: case LOCAL_FEED_BACKLOG: Thread.sleep(shortSleep); break; case GSA_FEED_BACKLOG: Thread.sleep(longSleep); break; } } } /** * Finishes processing a document feed. If the caller anticipates no * further calls to {@link #take(Document)} will be made, this method * should be called, so that the DocumentAcceptor may send a cached, * accumulated feed to the GSA. * * @throws RepositoryException if transient error accessing the Repository * @throws RepositoryDocumentException if fatal error accessing the Document * @throws DocumentAcceptorException if a transient error occurs in the * DocumentAcceptor */ public synchronized void flush() throws DocumentAcceptorException, RepositoryException { try { if (pusher != null) { pusher.flush(); pusher = null; } } catch (PushException e) { LOGGER.log(Level.SEVERE, "DocumentAcceptor failed to flush feed.", e); throw new DocumentAcceptorException("Failed to flush feed", e); } catch (FeedException e) { LOGGER.log(Level.SEVERE, "DocumentAcceptor failed to flush feed.", e); throw new DocumentAcceptorException("Failed to flush feed", e); } catch (RepositoryException e) { LOGGER.log(Level.WARNING, "DocumentAcceptor failed to flush feed.", e); throw e; } } /** * Cancels a feed. Discard any accumulated feed data. Note that some * documents submitted to this DocumentAcceptor may have already been * sent on to the GSA. */ public synchronized void cancel() { if (pusher != null) { pusher.cancel(); pusher = null; } } }