// 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.common.collect.ImmutableSortedSet;
import com.google.enterprise.connector.common.JarUtils;
import com.google.enterprise.connector.manager.Context;
import com.google.enterprise.connector.persist.ConnectorTypeNotFoundException;
import org.springframework.context.ApplicationContext;
import org.springframework.core.io.Resource;
import java.io.File;
import java.io.IOException;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* This class keeps track of the installed connector types and maintains a
* corresponding directory structure.
*/
public class TypeMap {
private static final String CONNECTOR_TYPE_PATTERN =
"classpath*:config/connectorType.xml";
private static final Logger LOGGER =
Logger.getLogger(TypeMap.class.getName());
private final String connectorTypePattern;
private final String baseDirPath;
private final Map<String, TypeInfo> innerMap =
new TreeMap<String, TypeInfo>();
private File typesDirectory = null;
/**
* Constructs an empty type map with the default connector type
* pattern and base directory path.
*/
public TypeMap() {
this(CONNECTOR_TYPE_PATTERN, null);
}
/**
* Constructs an empty type map default connector type pattern.
*
* @param baseDirPath used instead of {@code connectors} directory
*/
@VisibleForTesting
public TypeMap(String baseDirPath) {
this(CONNECTOR_TYPE_PATTERN, baseDirPath);
}
/**
* Constructs an empty type map.
*
* @param connectorTypePattern used instead of normal default
* @param baseDirPath used instead of {@code connectors} directory
*/
@VisibleForTesting
public TypeMap(String connectorTypePattern, String baseDirPath) {
this.connectorTypePattern = connectorTypePattern;
this.baseDirPath = baseDirPath;
}
/**
* Initializes this map and the supporting directories from the
* types on the classpath.
*/
@VisibleForTesting
public void init() {
initializeTypes();
initializeBaseDirectories(baseDirPath);
initializeTypeDirectories();
}
private void initializeTypes() {
ApplicationContext ac = Context.getInstance().getApplicationContext();
Resource[] resourceArray;
try {
resourceArray = ac.getResources(connectorTypePattern);
} catch (IOException e) {
LOGGER.log(Level.WARNING, "IOException from Spring while getting "
+ connectorTypePattern
+ " resources. No connector types can be found", e);
return;
}
if (resourceArray.length == 0) {
LOGGER.info("No connector types found.");
return;
}
for (Resource r : resourceArray) {
TypeInfo typeInfo = TypeInfo.fromSpringResource(r);
if (typeInfo == null) {
LOGGER.log(Level.WARNING, "Skipping " + r.getDescription());
continue;
}
innerMap.put(typeInfo.getConnectorTypeName(), typeInfo);
LOGGER.info("Found connector type: " + typeInfo.getConnectorTypeName()
+ " version: "
+ JarUtils.getJarVersion(typeInfo.getConnectorType().getClass()));
}
}
/** Adds a TypeInfo to the map. */
/* Used by tests to inject test types into the map. */
void addTypeInfo(TypeInfo typeInfo) {
String typeName = typeInfo.getConnectorTypeName();
innerMap.put(typeName, typeInfo);
if (typesDirectory != null) {
// TypeMap has already been initialized, so create typedir after the fact.
initializeTypeDirectory(typeName, typeInfo);
}
LOGGER.info("Added connector type: " + typeName);
}
private void initializeBaseDirectories(String baseDirPath) {
File baseDirectory = null;
if (baseDirPath == null) {
String commonDirPath = Context.getInstance().getCommonDirPath();
baseDirectory = new File(commonDirPath);
} else {
baseDirectory = new File(baseDirPath);
}
typesDirectory = new File(baseDirectory, "connectors");
if (!typesDirectory.exists()) {
if (!typesDirectory.mkdirs()) {
throw new IllegalStateException("Can't create connector types directory "
+ typesDirectory.getPath());
}
}
if (!typesDirectory.isDirectory()) {
throw new IllegalStateException("Unexpected file "
+ typesDirectory.getPath() + " blocks creation of types directory");
}
}
private void initializeTypeDirectories() {
for (Map.Entry<String, TypeInfo> entry : innerMap.entrySet()) {
initializeTypeDirectory(entry.getKey(), entry.getValue());
}
}
private void initializeTypeDirectory(String typeName, TypeInfo typeInfo) {
File connectorTypeDir = new File(typesDirectory, typeName);
if (!connectorTypeDir.exists()) {
if (!connectorTypeDir.mkdirs()) {
LOGGER.warning("Type " + typeName
+ " has a valid definition but no type directory - skipping it");
innerMap.remove(typeName);
return;
}
}
if (!connectorTypeDir.isDirectory()) {
LOGGER.warning("Unexpected file " + connectorTypeDir.getPath()
+ " blocks creation of instances directory for type " + typeName
+ " - skipping it");
innerMap.remove(typeName);
} else {
typeInfo.setConnectorTypeDir(connectorTypeDir);
LOGGER.info("Connector type: " + typeName + " has directory "
+ connectorTypeDir.getAbsolutePath());
}
}
public File getTypesDirectory() {
return typesDirectory;
}
public Set<String> getConnectorTypeNames() {
return ImmutableSortedSet.copyOf(innerMap.keySet());
}
public TypeInfo getTypeInfo(String connectorTypeName)
throws ConnectorTypeNotFoundException {
TypeInfo typeInfo = innerMap.get(connectorTypeName);
if (typeInfo == null) {
throw new ConnectorTypeNotFoundException("Connector Type not found: "
+ connectorTypeName);
}
return typeInfo;
}
}