// Copyright 2006 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.common.annotations.VisibleForTesting;
import com.google.enterprise.connector.common.ScheduledTimer;
import com.google.enterprise.connector.common.StringUtils;
import com.google.enterprise.connector.persist.ConnectorExistsException;
import com.google.enterprise.connector.persist.ConnectorNotFoundException;
import com.google.enterprise.connector.persist.ConnectorTypeNotFoundException;
import com.google.enterprise.connector.scheduler.Schedule;
import com.google.enterprise.connector.spi.AuthenticationManager;
import com.google.enterprise.connector.spi.AuthorizationManager;
import com.google.enterprise.connector.spi.ConfigureResponse;
import com.google.enterprise.connector.spi.ConnectorType;
import com.google.enterprise.connector.spi.Retriever;
import com.google.enterprise.connector.util.filter.DocumentFilterFactory;
import org.springframework.core.io.Resource;
import java.io.IOException;
import java.util.Locale;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* {@link Instantiator} that supports Spring-based connector instantiation and
* persistent storage of connector configuration, schedule and traversal state.
*/
public class SpringInstantiator implements Instantiator {
private static final Logger LOGGER =
Logger.getLogger(SpringInstantiator.class.getName());
// State that is filled in by setters from Spring.
private ConnectorCoordinatorMap coordinatorMap;
private ThreadPool threadPool;
private TypeMap typeMap;
private ChangeDetectorTask changeDetectorTask;
private final ScheduledTimer timer = new ScheduledTimer();
/**
* Normal constructor.
*/
public SpringInstantiator() {
// NOTE: we can't call init() here because then there would be a
// circular dependency on the Context, which hasn't been constructed yet
}
/**
* Sets the {@link ConnectorCoordinatorMap} instance used to manage the
* instances of {@link ConnectorCoordinator}.
*
* @param coordinatorMap a {@link ConnectorCoordinatorMap} instance
*/
public void setConnectorCoordinatorMap(
ConnectorCoordinatorMap coordinatorMap) {
this.coordinatorMap = coordinatorMap;
}
/**
* Sets the {@link ThreadPool} used for running traversals.
*
* @param threadPool a {@link ThreadPool} implementation.
*/
public void setThreadPool(ThreadPool threadPool) {
this.threadPool = threadPool;
}
/**
* Sets the {@link TypeMap} of installed {@link ConnectorType}s.
*
* @param typeMap a {@link TypeMap}.
*/
public void setTypeMap(TypeMap typeMap) {
this.typeMap = typeMap;
}
/**
* Sets the {@link ChangeDetectorTask}.
*
* @param changeDetectorTask a {@code ChangeDetector} task
*/
public void setChangeDetectorTask(ChangeDetectorTask changeDetectorTask) {
this.changeDetectorTask = changeDetectorTask;
}
/**
* Initializes the Context, post bean construction.
*/
public synchronized void init() {
LOGGER.info("Initializing instantiator");
// typeMap must be initialized before the ChangeDetector task is run.
typeMap.init();
// Run the ChangeDetector periodically to update the internal
// state. The initial execution will create connector instances
// from the persistent store.
timer.schedule(changeDetectorTask);
}
/**
* Shutdown all connector instances.
*/
@Override
public void shutdown(boolean interrupt, long timeoutMillis) {
timer.cancel();
coordinatorMap.shutdown();
try {
if (threadPool != null) {
threadPool.shutdown(interrupt, timeoutMillis);
}
} catch (InterruptedException ie) {
LOGGER.log(Level.SEVERE, "TraversalScheduler shutdown interrupted: ", ie);
}
}
@Override
public void removeConnector(String connectorName) {
LOGGER.info("Dropping connector: " + connectorName);
ConnectorCoordinator existing = coordinatorMap.get(connectorName);
if (existing != null) {
existing.removeConnector();
}
}
@Override
public AuthenticationManager getAuthenticationManager(String connectorName)
throws ConnectorNotFoundException, InstantiatorException {
return getConnectorCoordinator(connectorName).getAuthenticationManager();
}
@Override
public void startBatch(String connectorName)
throws ConnectorNotFoundException {
getConnectorCoordinator(connectorName).startBatch();
}
@VisibleForTesting
ConnectorCoordinator getConnectorCoordinator(String connectorName)
throws ConnectorNotFoundException {
ConnectorCoordinator connectorCoordinator =
coordinatorMap.get(connectorName);
if (connectorCoordinator == null) {
// If we are clustered, perhaps another CM created a new connector
// instance for this connector and we haven't detected it yet.
changeDetectorTask.run();
connectorCoordinator = coordinatorMap.get(connectorName);
if (connectorCoordinator == null) {
throw new ConnectorNotFoundException();
}
}
return connectorCoordinator;
}
private ConnectorCoordinator getOrAddConnectorCoordinator(
String connectorName) {
if (typeMap == null) {
throw new IllegalStateException(
"Init must be called before accessing connectors.");
}
ConnectorCoordinator connectorCoordinator =
coordinatorMap.get(connectorName);
if (connectorCoordinator == null) {
// If we are clustered, perhaps another CM created a new connector
// instance for this connector and we haven't detected it yet.
changeDetectorTask.run();
connectorCoordinator = coordinatorMap.getOrAdd(connectorName);
}
return connectorCoordinator;
}
@Override
public AuthorizationManager getAuthorizationManager(String connectorName)
throws ConnectorNotFoundException, InstantiatorException {
return getConnectorCoordinator(connectorName).getAuthorizationManager();
}
@Override
public Retriever getRetriever(String connectorName)
throws ConnectorNotFoundException, InstantiatorException {
return getConnectorCoordinator(connectorName).getRetriever();
}
@Override
public ConfigureResponse getConfigFormForConnector(String connectorName,
String connectorTypeName, Locale locale)
throws ConnectorNotFoundException, InstantiatorException {
return getConnectorCoordinator(connectorName).getConfigForm(locale);
}
@Override
public String getConnectorInstancePrototype(String connectorTypeName)
throws ConnectorTypeNotFoundException {
Resource resource = typeMap.getTypeInfo(connectorTypeName)
.getConnectorInstancePrototype();
try {
return StringUtils.streamToStringAndThrow(resource.getInputStream());
} catch (IOException ioe) {
LOGGER.log(Level.WARNING, "Failed to extract connectorInstance.xml "
+ " for connector " + connectorTypeName, ioe);
}
return null;
}
@Override
public synchronized ConnectorType getConnectorType(String typeName)
throws ConnectorTypeNotFoundException {
return typeMap.getTypeInfo(typeName).getConnectorType();
}
@Override
public synchronized Set<String> getConnectorTypeNames() {
return typeMap.getConnectorTypeNames();
}
@Override
public void restartConnectorTraversal(String connectorName)
throws ConnectorNotFoundException {
LOGGER.info("Restarting traversal for Connector: " + connectorName);
getConnectorCoordinator(connectorName).restartConnectorTraversal();
}
@Override
public Set<String> getConnectorNames() {
return coordinatorMap.getConnectorNames();
}
@Override
public String getConnectorTypeName(String connectorName)
throws ConnectorNotFoundException {
return getConnectorCoordinator(connectorName).getConnectorTypeName();
}
@Override
public ConfigureResponse setConnectorConfiguration(String connectorName,
Configuration configuration, Locale locale, boolean update)
throws ConnectorNotFoundException, ConnectorExistsException,
InstantiatorException {
LOGGER.info("Configuring connector: " + connectorName);
TypeInfo typeInfo;
try {
typeInfo = typeMap.getTypeInfo(configuration.getTypeName());
} catch (ConnectorTypeNotFoundException ctnf) {
throw new ConnectorNotFoundException("Incorrect type", ctnf);
}
ConnectorCoordinator ci = getOrAddConnectorCoordinator(connectorName);
return ci.setConnectorConfiguration(typeInfo, configuration, locale, update);
}
@Override
public Configuration getConnectorConfiguration(String connectorName)
throws ConnectorNotFoundException {
return getConnectorCoordinator(connectorName).getConnectorConfiguration();
}
@Override
public void setConnectorSchedule(String connectorName, Schedule schedule)
throws ConnectorNotFoundException {
getConnectorCoordinator(connectorName).setConnectorSchedule(schedule);
}
@Override
public Schedule getConnectorSchedule(String connectorName)
throws ConnectorNotFoundException {
return getConnectorCoordinator(connectorName).getConnectorSchedule();
}
@Override
public DocumentFilterFactory getDocumentFilterFactory(String connectorName)
throws ConnectorNotFoundException {
return getConnectorCoordinator(connectorName).getDocumentFilterFactory();
}
@Override
public void setGDataConfig() {
for (String name : getConnectorNames()) {
try {
getConnectorCoordinator(name).setGDataConfig();
} catch (ConnectorNotFoundException cnfe) {
// Shouldn't happen, but if it does, skip it.
} catch (InstantiatorException ie) {
LOGGER.log(Level.WARNING, "", ie);
}
}
}
}