// 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.enterprise.connector.common.I18NUtil;
import com.google.enterprise.connector.manager.ConnectorManagerException;
import com.google.enterprise.connector.manager.Context;
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.persist.MockPersistentStore;
import com.google.enterprise.connector.persist.PersistentStore;
import com.google.enterprise.connector.spi.AuthenticationManager;
import com.google.enterprise.connector.spi.AuthorizationManager;
import com.google.enterprise.connector.spi.TraversalManager;
import com.google.enterprise.connector.test.ConnectorTestUtils;
import com.google.enterprise.connector.test.JsonObjectAsMap;
import junit.framework.TestCase;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.Locale;
import java.util.Map;
/**
* Unit test for {@link SpringInstantiator}.
*/
public class InstantiatorTest extends TestCase {
private static final String APPLICATION_CONTEXT =
"testdata/contextTests/InstantiatorTest.xml";
private static final MockPersistentStore persistentStore =
new MockPersistentStore();
private SpringInstantiator instantiator;
@Override
protected void setUp() throws Exception {
Context.refresh();
Context context = Context.getInstance();
context.setStandaloneContext(APPLICATION_CONTEXT,
Context.DEFAULT_JUNIT_COMMON_DIR_PATH);
persistentStore.clear();
instantiator = createInstantiator();
assertEquals(0, connectorCount());
}
private SpringInstantiator createInstantiator() {
Context.refresh();
Context context = Context.getInstance();
context.setStandaloneContext(APPLICATION_CONTEXT,
Context.DEFAULT_JUNIT_COMMON_DIR_PATH);
SpringInstantiator si = (SpringInstantiator) context.getRequiredBean(
"Instantiator", SpringInstantiator.class);
si.init();
return si;
}
/**
* Returns the static MockPersistentStore instance. Used by
* Spring to wire that static Persistent store into the context.
*/
public static final PersistentStore getPersistentStore() {
return persistentStore;
}
/**
* Test method for adding, updating, deleting connectors.
*/
public final void testAddUpdateDelete() throws Exception {
{
/*
* Test creation of a connector of type TestConnectorA.
* The type should already have been created.
*/
String name = "connector1";
String typeName = "TestConnectorA";
String language = "en";
String jsonConfigString =
"{Username:foo, Password:bar, Color:red, "
+ "RepositoryFile:MockRepositoryEventLog3.txt}";
updateConnectorTest(instantiator, name, typeName, language,
false, jsonConfigString);
}
{
/*
* Test creation of a connector of type TestConnectorB.
* The type should already have been created.
*/
String name = "connector2";
String typeName = "TestConnectorB";
String language = "en";
String jsonConfigString =
"{Username:foo, Password:bar, Flavor:minty-fresh, "
+ "RepositoryFile:MockRepositoryEventLog3.txt}";
updateConnectorTest(instantiator, name, typeName, language,
false, jsonConfigString);
}
assertEquals(2, connectorCount());
{
/*
* Test update of a connector instance of type TestConnectorB.
* The instance was created in an earlier test.
*/
String name = "connector2";
String typeName = "TestConnectorB";
String language = "en";
String jsonConfigString =
"{Username:foo, Password:bar, Flavor:butterscotch, "
+ "RepositoryFile:MockRepositoryEventLog2.txt}";
updateConnectorTest(instantiator, name, typeName, language,
true, jsonConfigString);
}
assertEquals(2, connectorCount());
{
/*
* Test creation of a connector second instance of type TestConnectorB.
*/
String name = "connector3";
String typeName = "TestConnectorB";
String language = "en";
String jsonConfigString =
"{Username:foo, Password:bar, Flavor:chocolate, "
+ "RepositoryFile:MockRepositoryEventLog2.txt}";
updateConnectorTest(instantiator, name, typeName, language,
false, jsonConfigString);
}
assertEquals(3, connectorCount());
{
/*
* Test update of a connector instance of type TestConnectorB, to type
* TestConnectorA. The instance was created in an earlier test.
*/
String name = "connector3";
String typeName = "TestConnectorA";
String language = "en";
String jsonConfigString =
"{Username:foo, Password:bar, Color:blue, "
+ "RepositoryFile:MockRepositoryEventLog2.txt}";
updateConnectorTest(instantiator, name, typeName, language,
true, jsonConfigString);
}
assertEquals(3, connectorCount());
{
/*
* Test create of an existing connector instance of type TestConnectorA.
* It should throw a ConnectorExistsException.
*/
String name = "connector2";
String typeName = "TestConnectorA";
String language = "en";
String jsonConfigString =
"{Username:foo, Password:bar, Flavor:butterscotch, "
+ "RepositoryFile:MockRepositoryEventLog2.txt}";
try {
updateConnectorTest(instantiator, name, typeName, language,
false, jsonConfigString);
} catch (ConnectorExistsException e) {
assertTrue(true);
} catch (ConnectorManagerException e) {
assertTrue(false);
}
}
assertEquals(3, connectorCount());
{
/*
* Test update of a non-existing connector instance of type
* TestConnectorB. It should throw a ConnectorNotFoundException.
*/
String name = "connectorNew";
String typeName = "TestConnectorB";
String language = "en";
String jsonConfigString =
"{Username:foo, Password:bar, Flavor:butterscotch, "
+ "RepositoryFile:MockRepositoryEventLog2.txt}";
try {
updateConnectorTest(instantiator, name, typeName, language,
true, jsonConfigString);
} catch (ConnectorNotFoundException e) {
assertTrue(true);
} catch (ConnectorManagerException e) {
assertTrue(false);
}
}
assertEquals(3, connectorCount());
/*
* Test dropping connectors. Once dropped, I should not be able to
* get items from its interface. Regression test for Issue 60.
*/
instantiator.removeConnector("connector1");
try {
AuthorizationManager authz =
instantiator.getAuthorizationManager("connector1");
assertNull(authz);
} catch (ConnectorNotFoundException e1) {
assertTrue(true);
}
assertFalse(connectorExists("connector1"));
instantiator.removeConnector("connector2");
try {
AuthenticationManager authn =
instantiator.getAuthenticationManager("connector2");
assertNull(authn);
} catch (ConnectorNotFoundException e2) {
assertTrue(true);
}
assertFalse(connectorExists("connector2"));
instantiator.removeConnector("connector3");
try {
instantiator.getConnectorCoordinator("connector3").getTraversalManager();
fail("Exception expected.");
} catch (ConnectorNotFoundException e3) {
assertEquals("Connector instance connector3 not available.",
e3.getMessage());
}
assertFalse(connectorExists("connector3"));
assertEquals(0, connectorCount());
}
/**
* Test method for adding, updating, deleting connectors with
* connectorInstance.xml.
*/
public final void testAddUpdateDeleteConnectorInstanceXml() throws Exception {
{
/*
* Test creation of a connector of type TestConnectorA.
* The type should already have been created.
*/
String name = "connector100";
String typeName = "TestConnectorA";
String language = "en";
String jsonConfigString =
"{Username:foo, Password:bar, Color:red, "
+ "RepositoryFile:MockRepositoryEventLog3.txt}";
updateConnectorTest(instantiator, name, language, false,
new Configuration(typeName, getConfigMap(jsonConfigString),
instantiator.getConnectorInstancePrototype(typeName)));
}
{
/*
* Test creation of a connector of type TestConnectorB.
* The type should already have been created.
*/
String name = "connector200";
String typeName = "TestConnectorB";
String language = "en";
String jsonConfigString =
"{Username:foo, Password:bar, Flavor:minty-fresh, "
+ "RepositoryFile:MockRepositoryEventLog3.txt}";
updateConnectorTest(instantiator, name, language, false,
new Configuration(typeName, getConfigMap(jsonConfigString),
instantiator.getConnectorInstancePrototype(typeName)
.replace("TestConnector", "ModifiedTestConnector")));
}
assertEquals(2, connectorCount());
{
/*
* Test update of a connector instance of type TestConnectorB.
* The instance was created in an earlier test.
*/
String name = "connector200";
String typeName = "TestConnectorB";
String language = "en";
String jsonConfigString =
"{Username:foo, Password:bar, Flavor:butterscotch, "
+ "RepositoryFile:MockRepositoryEventLog2.txt}";
updateConnectorTest(instantiator, name, language, true,
new Configuration(typeName, getConfigMap(jsonConfigString),
instantiator.getConnectorInstancePrototype(typeName)
.replace("TestConnector", "UpdatedTestConnector")));
}
assertEquals(2, connectorCount());
{
/*
* Test update of a connector instance of type TestConnectorB
* without specifying a connectorInstance.xml. Should use the
* last one saved.
*/
String name = "connector200";
String typeName = "TestConnectorB";
String language = "en";
String jsonConfigString =
"{Username:foo, Password:bar, Flavor:wild-cherry, "
+ "RepositoryFile:MockRepositoryEventLog2.txt}";
updateConnectorTest(instantiator, name, language, true,
new Configuration(typeName, getConfigMap(jsonConfigString), null));
Configuration config = instantiator.getConnectorConfiguration(name);
assertNotNull(config);
assertNotNull(config.getXml());
assertTrue(config.getXml().contains("UpdatedTestConnector"));
}
assertEquals(2, connectorCount());
/*
* Test dropping connectors. Once dropped, I should not be able to
* get items from its interface. Regression test for Issue 60.
*/
instantiator.removeConnector("connector100");
try {
AuthorizationManager authz =
instantiator.getAuthorizationManager("connector100");
assertNull(authz);
} catch (ConnectorNotFoundException e1) {
assertTrue(true);
}
assertFalse(connectorExists("connector100"));
instantiator.removeConnector("connector200");
try {
AuthenticationManager authn =
instantiator.getAuthenticationManager("connector200");
assertNull(authn);
} catch (ConnectorNotFoundException e2) {
assertTrue(true);
}
assertFalse(connectorExists("connector200"));
assertEquals(0, connectorCount());
}
private class Issue63ChildThread extends Thread {
volatile boolean didFinish = false;
volatile Exception myException;
@Override
public void run() {
try {
// Get the Traverser for our connector instance.
TraversalManager oldTraversalManager =
instantiator.getConnectorCoordinator("connector1")
.getTraversalManager();
TraversalManager newTraversalManager =
instantiator.getConnectorCoordinator("connector1")
.getTraversalManager();
if (oldTraversalManager != newTraversalManager) {
throw new Exception("oldTraverser = " + oldTraversalManager
+ " must match newTraverser = " + newTraversalManager);
}
// Sleep for a bit, allowing the test thread time
// to update the connector.
Thread.sleep(200);
// Get the Traverser for our connector instance.
// It should be a new traverser reflecting the updated connector.
newTraversalManager =
instantiator.getConnectorCoordinator("connector1")
.getTraversalManager();
if (oldTraversalManager == newTraversalManager) {
throw new Exception("oldTraverser = " + oldTraversalManager
+ " must match newTraverser = " + newTraversalManager);
}
} catch (Exception e) {
myException = e;
}
didFinish = true;
}
}
public final void testStartWithConnector() throws Exception {
final String name = "connector1";
final String typeName = "TestConnectorA";
final String language = "en";
{
/*
* Test creation of a connector of type TestConnectorA.
* The type should already have been created.
*/
String jsonConfigString =
"{Username:foo, Password:bar, Color:red, "
+ "RepositoryFile:MockRepositoryEventLog3.txt}";
updateConnectorTest(instantiator, name, typeName, language,
false, jsonConfigString);
instantiator.shutdown(true, 5000);
}
SpringInstantiator newInstantiator = createInstantiator();
assertNotSame(instantiator, newInstantiator);
{
String readTypeName = newInstantiator.getConnectorTypeName(name);
assertEquals(typeName, readTypeName);
}
}
/**
* Tests the synchronization problems that surfaced with Issue 63.
*
* @throws JSONException
* @throws InstantiatorException
* @throws ConnectorExistsException
* @throws ConnectorNotFoundException
*/
public final void testIssue63Synchronization() throws Exception {
// Create a connector.
String name = "connector1";
String typeName = "TestConnectorA";
String language = "en";
String jsonConfigString =
"{Username:foo, Password:bar, Color:red, "
+ "RepositoryFile:MockRepositoryEventLog3.txt}";
updateConnectorTest(instantiator, name, typeName, language,
false, jsonConfigString);
// Spawn a child thread that looks up the connector.
Issue63ChildThread child = new Issue63ChildThread();
child.start();
// Sleep for a bit, allowing the child to fetch the connector interface.
try {
Thread.sleep(100);
} catch (InterruptedException ie) {
fail("Unexpected thread interruption.");
}
// Update the connector with a new color config.
String newJsonConfigString =
"{Username:foo, Password:bar, Color:blue, "
+ "RepositoryFile:MockRepositoryEventLog3.txt}";
updateConnectorTest(instantiator, name, typeName, language,
true, newJsonConfigString);
// Join with the child thread, allowing it to finish.
try {
child.join();
} catch (InterruptedException e) {
fail("Unexpected thread interruption.");
}
assertTrue(child.didFinish);
if (child.myException != null) {
throw new Exception("Unexpected exception in child.", child.myException);
}
instantiator.removeConnector(name);
}
/*
* Returns the count of connectors in the InstanceMap.
*/
private int connectorCount() {
return instantiator.getConnectorNames().size();
}
/*
* Returns true if a connector exists in the InstanceMap,
* false otherwise.
*/
private boolean connectorExists(String connectorName) {
for (String name : instantiator.getConnectorNames()) {
if (connectorName.equalsIgnoreCase(name))
return true;
}
return false;
}
/*
* Create a configMap from a JSON string.
*/
private Map<String, String> getConfigMap(String jsonConfigString)
throws JSONException {
return new JsonObjectAsMap(new JSONObject(jsonConfigString));
}
private void updateConnectorTest(SpringInstantiator instantiator, String name,
String typeName, String language, boolean update, String jsonConfigString)
throws JSONException, InstantiatorException, ConnectorExistsException,
ConnectorNotFoundException, ConnectorTypeNotFoundException {
updateConnectorTest(instantiator, name, language, update,
new Configuration(typeName, getConfigMap(jsonConfigString), null));
}
private void updateConnectorTest(SpringInstantiator instantiator,
String name, String language, boolean update, Configuration config)
throws JSONException, InstantiatorException, ConnectorExistsException,
ConnectorNotFoundException, ConnectorTypeNotFoundException {
TraversalManager oldTraversersalManager = null;
if (update) {
oldTraversersalManager =
instantiator.getConnectorCoordinator(name).getTraversalManager();
}
Locale locale = I18NUtil.getLocaleFromStandardLocaleString(language);
instantiator.setConnectorConfiguration(name, config, locale, update);
// Make sure that this connector now exists.
assertTrue(connectorExists(name));
// Make sure that this connector has the correct type associated.
assertEquals(config.getTypeName(), instantiator.getConnectorTypeName(name));
AuthorizationManager authz = instantiator.getAuthorizationManager(name);
assertNotNull(authz);
AuthenticationManager authn = instantiator.getAuthenticationManager(name);
assertNotNull(authn);
TraversalManager traversalManager =
instantiator.getConnectorCoordinator(name).getTraversalManager();
assertNotNull(traversalManager);
// If this is an update, make sure that we get a different traverser.
// Regression test for Issues 35, 63.
if (update) {
assertNotSame(oldTraversersalManager, traversalManager);
}
Configuration newConfig = instantiator.getConnectorConfiguration(name);
// the password will be decrypted in the InstanceInfo
Map<String, String> instanceProps = newConfig.getMap();
String instancePasswd = instanceProps.get("Password");
String plainPasswd = config.getMap().get("Password");
assertEquals(instancePasswd, plainPasswd);
// Check if the configuration properties match those set.
ConnectorTestUtils.compareMaps(config.getMap(), instanceProps);
// Creating a new connector with no connectorInstancePrototype should
// get you the default.
if (!update && config.getXml() == null) {
assertEquals(instantiator.getConnectorInstancePrototype(
config.getTypeName()), newConfig.getXml());
}
// If a connectorInstancePrototype was supplied, make sure it was
// saved with the connector instance.
if (config.getXml() != null) {
assertEquals(config.getXml(), newConfig.getXml());
}
}
}