// Copyright (C) 2006-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.spi;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.logging.Logger;
/**
* Simple implementation of the {@link ConnectorType} interface. Implementors
* may use this directly or for reference. This implementation has no
* internationalization. It is initialized by being given a {@code List}
* of configuration keys uses a list of configuration keys.
* These keys are used for both validation and display;
* i.e., the strings in the list are used to name the configuration elements
* that this instance requires, and are used as display values in the html
* forms this instance generates.
* <p>
* This simple implementation considers any parameter to be valid, so long as
* it is non-{@code null} and non-empty.
* <p>
* Implementors may want to override the
* {@link #validateConfigPair(String, String)} method.
* This is used to validate a particular key-value pair.
*
* @since 1.0
*/
public class SimpleConnectorType implements ConnectorType {
private static final Logger LOGGER =
Logger.getLogger(SimpleConnectorType.class.getName());
private static final String VALUE = "value";
private static final String NAME = "name";
private static final String TEXT = "text";
private static final String TYPE = "type";
private static final String INPUT = "input";
private static final String CLOSE_ELEMENT = "/>";
private static final String OPEN_ELEMENT = "<";
private static final String PASSWORD = "password";
private static final String TR_END = "</tr>\r\n";
private static final String TD_END = "</td>\r\n";
private static final String TD_START = "<td>";
private static final String TR_START = "<tr>\r\n";
private List<String> keys = null;
private Set<String> keySet = null;
private String initialConfigForm = null;
public SimpleConnectorType() {
//
}
/**
* Set the keys that are required for configuration. One of the overloadings
* of this method must be called exactly once before the SPI methods are used.
*
* @param keys A list of String keys
*/
public void setConfigKeys(List<String> keys) {
if (this.keys != null) {
throw new IllegalStateException();
}
this.keys = keys;
this.keySet = new HashSet<String>(keys);
}
/**
* Set the keys that are required for configuration. One of the overloadings
* of this method must be called exactly once before the SPI methods are used.
*
* @param keys An array of String keys
*/
public void setConfigKeys(String[] keys) {
setConfigKeys(Arrays.asList(keys));
}
/**
* Sets the form to be used by this configurer. This is optional. If this
* method is used, it must be called before the SPI methods are used.
*
* @param formSnippet A String snippet of html
*/
public void setInitialConfigForm(String formSnippet) {
if (this.initialConfigForm != null) {
throw new IllegalStateException();
}
this.initialConfigForm = formSnippet;
}
private String getInitialConfigForm() {
if (initialConfigForm != null) {
return initialConfigForm;
}
if (keys == null) {
throw new IllegalStateException();
}
this.initialConfigForm = makeConfigForm(null);
return initialConfigForm;
}
/**
* Validates whether a string is an acceptable value for a specific key.
*
* @param key
* @param val
* @return true if the val is acceptable for this key
*/
public boolean validateConfigPair(String key, String val) {
if (val == null || val.length() == 0) {
return false;
}
return true;
}
private boolean validateConfigMap(Map<String, String> configData) {
for (String key : keys) {
String val = configData.get(key);
if (!validateConfigPair(key, val)) {
return false;
}
}
return true;
}
private void appendAttribute(StringBuilder buf, String attrName,
String attrValue) {
try {
XmlUtils.xmlAppendAttr(attrName, attrValue, buf);
} catch (IOException e) {
// Can't happen with StringBuilder.
throw new AssertionError(e);
}
}
/**
* Make a config form snippet using the keys (in the supplied order) and, if
* passed a non-{@code null} config map, pre-filling values in from that map.
*
* @param configMap
* @return config form snippet
*/
private String makeConfigForm(Map<String, String> configMap) {
StringBuilder buf = new StringBuilder(2048);
for (String key : keys) {
appendStartRow(buf, key, false);
buf.append(OPEN_ELEMENT);
buf.append(INPUT);
if (key.equalsIgnoreCase(PASSWORD)) {
appendAttribute(buf, TYPE, PASSWORD);
} else {
appendAttribute(buf, TYPE, TEXT);
}
appendAttribute(buf, NAME, key);
if (configMap != null) {
String value = configMap.get(key);
if (value != null) {
appendAttribute(buf, VALUE, value);
}
}
appendEndRow(buf);
}
return buf.toString();
}
private String makeValidatedForm(Map<String, String> configMap) {
StringBuilder buf = new StringBuilder(2048);
for (String key : keys) {
String value = configMap.get(key);
if (!validateConfigPair(key, value)) {
appendStartRow(buf, key, true);
buf.append(OPEN_ELEMENT);
buf.append(INPUT);
if (key.equalsIgnoreCase(PASSWORD)) {
appendAttribute(buf, TYPE, PASSWORD);
} else {
appendAttribute(buf, TYPE, TEXT);
}
} else {
appendStartRow(buf, key, false);
buf.append(OPEN_ELEMENT);
buf.append(INPUT);
if (key.equalsIgnoreCase(PASSWORD)) {
appendAttribute(buf, TYPE, PASSWORD);
} else {
appendAttribute(buf, TYPE, TEXT);
}
appendAttribute(buf, VALUE, value);
}
appendAttribute(buf, NAME, key);
appendEndRow(buf);
}
// Toss in all the stuff that's in the map but isn't in the keyset
// taking care to list them in alphabetic order (this is mainly for
// testability).
for (String key : new TreeSet<String>(configMap.keySet())) {
if (!keySet.contains(key)) {
// add another hidden field to preserve this data
String val = configMap.get(key);
buf.append("<input type=\"hidden\" value=\"");
buf.append(val);
buf.append("\" name=\"");
buf.append(key);
buf.append("\"/>\r\n");
}
}
return buf.toString();
}
private void appendStartRow(StringBuilder buf, String key, boolean red) {
buf.append(TR_START);
buf.append(TD_START);
if (red) {
buf.append("<font color=\"red\">");
}
buf.append(key);
if (red) {
buf.append("</font>");
}
buf.append(TD_END);
buf.append(TD_START);
}
private void appendEndRow(StringBuilder buf) {
buf.append(CLOSE_ELEMENT);
buf.append(TD_END);
buf.append(TR_END);
}
@Override
public ConfigureResponse getConfigForm(Locale locale) {
ConfigureResponse result =
new ConfigureResponse("", getInitialConfigForm());
LOGGER.info("getConfigForm form:\n" + result.getFormSnippet());
return result;
}
/** @since 1.0.1 */
@Override
public ConfigureResponse validateConfig(Map<String, String> configData,
Locale locale, ConnectorFactory connectorFactory) {
if (validateConfigMap(configData)) {
// all is ok
return null;
}
String form = makeValidatedForm(configData);
LOGGER.info("validateConfig new form:\n" + form);
return new ConfigureResponse(
"Some required configuration is missing", form);
}
@Override
public ConfigureResponse getPopulatedConfigForm(
Map<String, String> configMap, Locale locale) {
return new ConfigureResponse("", makeConfigForm(configMap));
}
}