企业搜索引擎开发之连接器connector(十二)

本文接着分析连接器及其构造工厂相关设计及源码,先浏览一下下面的UML模型图:

ConnectorFactory为Connector类型连接器接口,ConnectorInstanceFactor为工厂实现类(这里是反射工厂);Connector接口下面的实现类均为具体的连接器类;

ConnectorFactory接口源码如下:

/**
 * This factory provides a mechanism by which
 * {@link ConnectorType#validateConfig(Map, Locale, ConnectorFactory)
 * validateConfig} may create instances of the connector for the
 * purpose of validation. {@link Connector} instances created by the
 * factory are not added to the Connector Manager's list of running
 * connectors and do not have an on-disk representation.
 */
public interface ConnectorFactory {
  /**
   * Make a {@link Connector} instance of the {@link ConnectorType}
   * that owns this ConnectorFactory instance using the supplied
   * {@link java.util.Map} of configuration properties.
   *
   * @param config a {@link java.util.Map} of configuration properties.
   *         If null, the Map that was passed to validateConfig is used.
   * @return a {@link Connector} instance, instantiated by the Connector
   *         Manager in exactly the same way as it would if this config
   *         were valid and persisted.
   * @throws RepositoryException if the Connector construction
   *         fails for any reason.
   */
  Connector makeConnector(Map<String, String> config)
      throws RepositoryException;
}

这里的方法签名就是根据map类型的参数构造连接器实例对象(不过貌似连接器应用本身没有用到这个工厂)

实现类ConnectorInstanceFactor源码如下:

/**
 * A {@link ConnectorFactory} implementation that creates transient
 * {@link Connector} instances on behalf of a {@link ConnectorType}
 * for the purposes of validating a supplied configuration.  The
 * {@code Connector} instances are temporary, have no on-disk
 * representation, and are not considered active instances.
 * <p>
 * Any {@code Connector} instances created by this factory will be destroyed
 * when {@code ConnectorType.validateConfig()} returns.
 * <p>
 * @see com.google.enterprise.connector.spi.ConnectorType#validateConfig(Map,
 *      Locale, ConnectorFactory)
 */
class ConnectorInstanceFactory implements ConnectorFactory {
  private static final Logger LOGGER =
      Logger.getLogger(ConnectorInstanceFactory.class.getName());

  final String connectorName;
  final File connectorDir;
  final TypeInfo typeInfo;
  final Map<String, String> origConfig;
  final List<InstanceInfo> connectors;

  /**
   * Constructor takes the items needed by {@code InstanceInfo}, but not
   * provided via {@code makeConnector}.
   *
   * @param connectorName the name of this connector instance.
   * @param connectorDir the directory containing the connector prototype.
   * @param typeInfo the connector type.
   * @param config the configuration provided to {@code validateConfig}.
   */
  public ConnectorInstanceFactory(String connectorName, File connectorDir,
      TypeInfo typeInfo, Map<String, String> config) {
    this.connectorName = connectorName;
    this.connectorDir = connectorDir;
    this.typeInfo = typeInfo;
    this.origConfig = config;
    this.connectors = new LinkedList<InstanceInfo>();
  }

  /**
   * Create an instance of this {@code Connector} based upon the supplied
   * configuration data. If the supplied configuration {@code Map} is
   * {@code null}, use the original configuration.
   *
   * @see com.google.enterprise.connector.spi.ConnectorFactory#makeConnector(Map)
   */
  public Connector makeConnector(Map<String, String> config)
    throws RepositoryException {
    try {
      InstanceInfo info =
        InstanceInfo.fromNewConfig(connectorName, connectorDir, typeInfo,
                                   ((config == null) ? origConfig : config));
      if (info == null) {
        return null;
      }
      synchronized (this) {
        connectors.add(info);
      }
      return info.getConnector();
    } catch (InstantiatorException e) {
      throw new
          RepositoryException("ConnectorFactory failed to make connector.", e);
    }
  }

  /**
   * Shutdown any connector instances created by the factory.
   */
  synchronized void shutdown() {
    for (InstanceInfo info : connectors) {
      Connector connector = info.getConnector();
      if (connector instanceof ConnectorShutdownAware) {
        try {
          ((ConnectorShutdownAware) connector).shutdown();
        } catch (Exception e) {
          LOGGER.log(Level.WARNING, "Failed to shutdown connector "
              + info.getName() + " created by validateConfig", e);
        }
      }
    }
    connectors.clear();
  }
}

可以看到,生成连接器实例方法makeConnector是通过先实例化InstanceInfo对象,然后从InstanceInfo对象获取连接器实例的

InstanceInfo类的源码如下:

/**
 * Container for info about a Connector Instance. Instantiable only through a
 * static factory that uses Spring.
 */
final class InstanceInfo {

  private static final Logger LOGGER =
      Logger.getLogger(InstanceInfo.class.getName());

  private static ConnectorConfigStore configStore;
  private static ConnectorScheduleStore schedStore;
  private static ConnectorStateStore stateStore;

  private static Collection<ConnectorConfigStore> legacyConfigStores;
  private static Collection<ConnectorScheduleStore> legacyScheduleStores;
  private static Collection<ConnectorStateStore> legacyStateStores;

  private final TypeInfo typeInfo;
  private final File connectorDir;
  private final String connectorName;
  private final StoreContext storeContext;

  private Properties properties;
  private Connector connector;


  /** Private Constructor for use by Static Factory Methods, below. */
  private InstanceInfo(String connectorName, File connectorDir,
      TypeInfo typeInfo) throws InstanceInfoException {
    if (connectorName == null || connectorName.length() < 1) {
      throw new NullConnectorNameException();
    }
    if (connectorDir == null) {
      throw new NullDirectoryException();
    }
    if (typeInfo == null) {
      throw new NullTypeInfoException();
    }

    this.connectorName = connectorName;
    this.connectorDir = connectorDir;
    this.typeInfo = typeInfo;
    this.storeContext = new StoreContext(connectorName, connectorDir);
  }


  /* **** Getters and Setters **** */

  public static void setConnectorStores(ConnectorConfigStore configStore,
      ConnectorScheduleStore schedStore, ConnectorStateStore stateStore) {
    InstanceInfo.configStore = configStore;
    InstanceInfo.schedStore = schedStore;
    InstanceInfo.stateStore = stateStore;
  }

  public static void setLegacyStores(
      Collection<ConnectorConfigStore> configStores,
      Collection<ConnectorScheduleStore> schedStores,
      Collection<ConnectorStateStore> stateStores) {
    legacyConfigStores = configStores;
    legacyScheduleStores = schedStores;
    legacyStateStores = stateStores;
  }

  /**
   * @return the connector
   */
  Connector getConnector() {
    return connector;
  }

  /**
   * @return the name
   */
  String getName() {
    return connectorName;
  }

  /**
   * @return the typeInfo
   */
  TypeInfo getTypeInfo() {
    return typeInfo;
  }

  /**
   * @return the connectorDir
   */
  File getConnectorDir() {
    return connectorDir;
  }


  /* **** Static Factory Methods used to Create Instances. **** */

  /**
   * Factory Method that Constructs a new Connector Instance based
   * upon its on-disk persistently stored configuration.
   *
   * @param connectorName the name of the Connector instance.
   * @param connectorDir the Connector's on-disk directory.
   * @param typeInfo the Connector's prototype.
   * @return new InstanceInfo representing the Connector instance.
   * @throws InstanceInfoException
   */
  public static InstanceInfo fromDirectory(String connectorName,
      File connectorDir, TypeInfo typeInfo) throws InstanceInfoException {
    InstanceInfo info = new InstanceInfo(connectorName, connectorDir, typeInfo);
    info.properties = configStore.getConnectorConfiguration(info.storeContext);

    // Upgrade from Legacy Configuration Data Stores. This method is
    // called to instantiate Connectors that were created by some
    // other (possibly older) instance of the Connector Manager.
    // If the various stored instance data is not found in the
    // expected locations, the connector may have been previously
    // created by an older version of the Connector Manager and may
    // have its instance data stored in the older legacy locations.
    // Move the data from the legacy stores to the expected locations
    // before launching the connector instance.
    if (info.properties == null) {
      upgradeConfigStore(info);
      if (info.properties == null) {
        throw new InstanceInfoException("Configuration not found for connector "
                                        + connectorName);
      }
    }
    if (schedStore.getConnectorSchedule(info.storeContext) == null) {
      upgradeScheduleStore(info);
      if (info.getConnectorSchedule() == null) {
        // If there is no schedule, create a disabled schedule rather than
        // logging "schedule not found" once a second for eternity.
        LOGGER.warning("Traversal Schedule not found for connector "
                       + connectorName + ", disabling traversal.");
        Schedule schedule = new Schedule();
        schedule.setConnectorName(connectorName);
        info.setConnectorSchedule(schedule.toString());
      }
    }
    if (stateStore.getConnectorState(info.storeContext) == null) {
      upgradeStateStore(info);
    }

    info.connector = makeConnectorWithSpring(info);
    return info;
  }

  /**
   * Factory Method that Constructs a new Connector Instance based
   * upon the supplied configuration map.  This is typically done
   * when creating new connectors from scratch.  It is also used
   * by the ConnectorFactory.
   *
   * @param connectorName the name of the Connector instance.
   * @param connectorDir the Connector's working directory.
   * @param typeInfo the Connector's prototype.
   * @param configMap configuration properties.
   * @return new InstanceInfo representing the Connector instance.
   * @throws InstanceInfoException
   */
  public static InstanceInfo fromNewConfig(String connectorName,
      File connectorDir, TypeInfo typeInfo, Map<String, String> configMap)
      throws InstanceInfoException {
    InstanceInfo info = new InstanceInfo(connectorName, connectorDir, typeInfo);
    info.properties = PropertiesUtils.fromMap(configMap);
    // Don't write properties file to disk yet.
    info.connector = makeConnectorWithSpring(info);
    return info;
  }

  /**
   * Construct a new Connector Instance based upon the connectorInstance
   * and connectorDefaults bean definitions.
   *
   * @param info the InstanceInfo object under construction.
   */
  private static Connector makeConnectorWithSpring(InstanceInfo info)
      throws InstanceInfoException {
    Context context = Context.getInstance();
    String name = info.connectorName;
    Resource prototype = null;
    if (info.connectorDir != null) {
      // If this file exists, we use this it in preference to the default
      // prototype associated with the type. This allows customers to supply
      // their own per-instance config xml.
      File customPrototype =
          new File(info.connectorDir, TypeInfo.CONNECTOR_INSTANCE_XML);
      if (customPrototype.exists()) {
        prototype = new FileSystemResource(customPrototype);
        LOGGER.info("Using connector-specific xml config for connector "
            + name + " at path " + customPrototype.getPath());
      }
    }
    if (prototype == null) {
      prototype = info.typeInfo.getConnectorInstancePrototype();
    }

    DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
    XmlBeanDefinitionReader beanReader = new XmlBeanDefinitionReader(factory);
    Resource defaults = info.typeInfo.getConnectorDefaultPrototype();
    try {
      beanReader.loadBeanDefinitions(prototype);
    } catch (BeansException e) {
      throw new FactoryCreationFailureException(e, prototype, name);
    }
    // Seems non-intuitive to load these in this order, but we want newer
    // versions of the connectors to override any default bean definitions
    // specified in old-style monolithic connectorInstance.xml files.
    if (defaults != null) {
      try {
        beanReader.loadBeanDefinitions(defaults);
      } catch (BeansException e) {
        throw new FactoryCreationFailureException(e, defaults, name);
      }
    }

    EncryptedPropertyPlaceholderConfigurer cfg = null;
    try {
        cfg = (EncryptedPropertyPlaceholderConfigurer) context.getBean(
            factory, null, EncryptedPropertyPlaceholderConfigurer.class);
    } catch (BeansException e) {
      throw new BeanInstantiationFailureException(e, prototype, name,
          EncryptedPropertyPlaceholderConfigurer.class.getName());
    }
    if (cfg == null) {
      cfg = new EncryptedPropertyPlaceholderConfigurer();
    }

    try {
      cfg.setLocation(getPropertiesResource(info));
      cfg.postProcessBeanFactory(factory);
    } catch (BeansException e) {
      throw new PropertyProcessingFailureException(e, prototype, name);
    }

    Connector connector = null;
    try {
      connector = (Connector) context.getBean(factory, null, Connector.class);
    } catch (BeansException e) {
      throw new BeanInstantiationFailureException(e, prototype, name,
          Connector.class.getName());
    }
    if (connector == null) {
      throw new NoBeansFoundException(prototype, name, Connector.class);
    }
    return connector;
  }

  /**
   * Return a Spring Resource containing the InstanceInfo
   * configuration Properties.
   */
  private static Resource getPropertiesResource(InstanceInfo info)
      throws InstanceInfoException {
    Properties properties =
        (info.properties == null) ? new Properties() : info.properties;
    try {
      return new ByteArrayResourceHack(
          PropertiesUtils.storeToString(properties, null).getBytes());
    } catch (PropertiesException e) {
      throw new PropertyProcessingInternalFailureException(e,
          info.connectorName);
    }
  }

  /* This subclass of ByteArrayResource attempts to circumvent a bug in
   * org.springframework.core.io.support.PropertiesLoaderSupport.loadProperties()
   * that tries to fetch the filename extension of the properties Resource
   * in an attempt to determine whether to parse the properties as XML or
   * traditional syntax.  ByteArrayResource throws an exception when
   * getFilename() is called because there is no associated filename.
   * This subclass returns a fake filename (without a .xml extension).
   * TODO: Remove this when Spring Framework SPR-5068 gets fixed:
   * http://jira.springframework.org/browse/SPR-5068
   */
  private static class ByteArrayResourceHack extends ByteArrayResource {
    public ByteArrayResourceHack(byte[] byteArray) {
      super(byteArray);
    }
    @Override
    public String getFilename() {
      return "ByteArrayResourceHasNoFilename";
    }
  }


  /* **** Manage the Connector Instance Persistent data store. **** */

  /**
   * Remove this Connector Instance's persistent store state.
   */
  public void removeConnector() {
    stateStore.removeConnectorState(storeContext);
    schedStore.removeConnectorSchedule(storeContext);
    configStore.removeConnectorConfiguration(storeContext);
  }

  /**
   * Get the configuration data for this connector instance.
   *
   * @return a Map&lt;String, String&gt; of its ConnectorType-specific
   * configuration data, or null if no configuration is stored.
   */
  public Map<String, String> getConnectorConfig() {
    if (properties == null) {
      properties = configStore.getConnectorConfiguration(storeContext);
    }
    return PropertiesUtils.toMap(properties);
  }

  /**
   * Set the configuration data for this connector instance.
   * Writes the supplied configuration through to the persistent store.
   *
   * @param configMap a Map&lt;String, String&gt; of its ConnectorType-specific
   *        configuration data, or null if no configuration is stored.
   */
  public void setConnectorConfig(Map<String, String> configMap) {
    properties = PropertiesUtils.fromMap(configMap);
    if (configMap == null) {
      configStore.removeConnectorConfiguration(storeContext);
    } else {
      configStore.storeConnectorConfiguration(storeContext, properties);
    }
  }

  /**
   * Sets the schedule for this connector instance.
   * Writes the modified schedule through to the persistent store.
   *
   * @param connectorSchedule String to store or null unset any existing
   * schedule.
   */
  public void setConnectorSchedule(String connectorSchedule) {
    if (connectorSchedule == null) {
      schedStore.removeConnectorSchedule(storeContext);
    } else {
      schedStore.storeConnectorSchedule(storeContext, connectorSchedule);
    }
  }

  /**
   * Gets the schedule for this connector instance.
   *
   * @return the schedule String, or null to erase any previously set schedule.
   * for this connector
   */
  public String getConnectorSchedule() {
    return schedStore.getConnectorSchedule(storeContext);
  }

  /**
   * Sets the remembered traversal state for this connector instance.
   * Writes the modified state through to the persistent store.
   *
   * @param connectorState String to store or null to erase any previously
   * saved traversal state.
   * @throws IllegalStateException if state store is disabled for this connector
   */
  public void setConnectorState(String connectorState) {
    if (connectorState == null) {
      stateStore.removeConnectorState(storeContext);
    } else {
      stateStore.storeConnectorState(storeContext, connectorState);
    }
  }

  /**
   * Gets the remembered traversal state for this connector instance.
   *
   * @return the state, or null if no state has been stored for this connector
   * @throws IllegalStateException if state store is disabled for this connector
   */
  public String getConnectorState() {
    return stateStore.getConnectorState(storeContext);
  }

  /**
   * Upgrade ConnectorConfigStore.  If the ConnectorConfigStore has
   * no stored configuration data for this connector, look in the
   * Legacy stores (those used in earlier versions of the product).
   * If a configuration was found in a Legacy store, move it to the
   * new store.
   *
   * @param info a partially constructed InstanceInfo describing the
   * connector.
   */
  private static void upgradeConfigStore(InstanceInfo info) {
    if (legacyConfigStores != null) {
      for (ConnectorConfigStore legacyStore : legacyConfigStores) {
        Properties properties =
            legacyStore.getConnectorConfiguration(info.storeContext);
        if (properties != null) {
          LOGGER.config("Migrating configuration information for connector "
                        + info.connectorName + " from legacy storage "
                        + legacyStore.getClass().getName() + " to "
                        + configStore.getClass().getName());
          info.properties = properties;
          configStore.storeConnectorConfiguration(info.storeContext,
                                                  properties);
          legacyStore.removeConnectorConfiguration(info.storeContext);
          return;
        }
      }
    }
    LOGGER.config("Connector " + info.connectorName
                  + " lacks saved configuration information, and none was"
                  + " found in any LegacyConnectorConfigStores.");
  }

  /**
   * Upgrade ConnectorScheduleStore.  If the ConnectorScheduleStore has
   * no stored schedule data for this connector, look in the
   * Legacy stores (those used in earlier versions of the product).
   * If a schedule was found in a Legacy store, move it to the
   * new store.
   *
   * @param info a partially constructed InstanceInfo describing the
   * connector.
   */
  private static void upgradeScheduleStore(InstanceInfo info) {
    if (legacyScheduleStores != null) {
      for (ConnectorScheduleStore legacyStore : legacyScheduleStores) {
        String schedule = legacyStore.getConnectorSchedule(info.storeContext);
        if (schedule != null) {
          LOGGER.config("Migrating traversal schedule information for connector "
                        + info.connectorName + " from legacy storage "
                        + legacyStore.getClass().getName() + " to "
                        + schedStore.getClass().getName());
          schedStore.storeConnectorSchedule(info.storeContext, schedule);
          legacyStore.removeConnectorSchedule(info.storeContext);
          return;
        }
      }
    }
    LOGGER.config("Connector " + info.connectorName
                  + " lacks saved traversal schedule information, and none"
                  + " was found in any LegacyConnectorScheduleStores.");
  }

  /**
   * Upgrade ConnectorStateStore.  If the ConnectorStateStore has
   * no stored traversal state data for this connector, look in the
   * Legacy stores (those used in earlier versions of the product).
   * If a traversal state was found in a Legacy store, move it to the
   * new store.
   *
   * @param info a partially constructed InstanceInfo describing the
   * connector.
   */
  private static void upgradeStateStore(InstanceInfo info) {
    if (legacyStateStores != null) {
      for (ConnectorStateStore legacyStore : legacyStateStores) {
        String state = legacyStore.getConnectorState(info.storeContext);
        if (state != null) {
          LOGGER.config("Migrating traversal state information for connector "
                        + info.connectorName + " from legacy storage "
                        + legacyStore.getClass().getName() + " to "
                        + stateStore.getClass().getName());
          stateStore.storeConnectorState(info.storeContext, state);
          legacyStore.removeConnectorState(info.storeContext);
          return;
        }
      }
    }
    LOGGER.config("Connector " + info.connectorName
                  + " lacks saved traversal state information, and none was"
                  + " found in any LegacyConnectorStateStores.");
  }


  /* **** InstanceInfoExcepetions **** */

  static class InstanceInfoException extends InstantiatorException {
    InstanceInfoException(String message, Throwable cause) {
      super(message, cause);
    }
    InstanceInfoException(String message) {
      super(message);
    }
  }

  static class NullConnectorNameException extends InstanceInfoException {
    NullConnectorNameException() {
      super("Attempt to instantiate a connector with a null or empty name");
    }
  }

  static class NullDirectoryException extends InstanceInfoException {
    NullDirectoryException() {
      super("Attempt to instantiate a connector with a null directory");
    }
  }

  static class NullTypeInfoException extends InstanceInfoException {
    NullTypeInfoException() {
      super("Attempt to instantiate a connector with a null TypeInfo");
    }
  }

  static class FactoryCreationFailureException extends InstanceInfoException {
    FactoryCreationFailureException(Throwable cause,
        Resource prototype, String connectorName) {
      super("Spring factory creation failure for connector " + connectorName
          + " using resource " + prototype.getDescription(),
          cause);
    }
  }

  static class NoBeansFoundException extends InstanceInfoException {
    NoBeansFoundException(Resource prototype,
        String connectorName, Class<?> clazz) {
      super("No beans found of type " + clazz.getName() + " for connector "
          + connectorName + " using resource "
          + prototype.getDescription());
    }
  }

  static class BeanInstantiationFailureException extends InstanceInfoException {
    BeanInstantiationFailureException(Throwable cause,
        Resource prototype, String connectorName, String beanName) {
      super("Spring failure while instantiating bean " + beanName
          + " for connector " + connectorName + " using resource "
          + prototype.getDescription(), cause);
    }
  }

  static class PropertyProcessingInternalFailureException extends
      InstanceInfoException {
    PropertyProcessingInternalFailureException(Throwable cause,
        String connectorName) {
      super("Spring internal failure while processing configuration properties"
            + " for connector " + connectorName, cause);
    }
  }

  static class PropertyProcessingFailureException extends InstanceInfoException {
    PropertyProcessingFailureException(Throwable cause, Resource prototype,
        String connectorName) {
      super("Problem while processing configuration properties for connector "
            + connectorName + " using resource "
            + prototype.getDescription(), cause);
    }
  }
}

InstanceInfo fromDirectory(String connectorName,File connectorDir, TypeInfo typeInfo)方法与InstanceInfo fromNewConfig(String connectorName,File connectorDir, TypeInfo typeInfo, Map<String, String> configMap)方法的区别在于前者基于目录构造InstanceInfo实例,后者基于configuration map对象构造InstanceInfo实例

PropertiesUtils是工具类,用于从属性文件加载Properties及map类型与Properties类型的转换

其源码如下:

public class PropertiesUtils {

  private static final Logger LOGGER =
      Logger.getLogger(PropertiesUtils.class.getName());

  public static final String GOOGLE_CONNECTOR_NAME =
      "googleConnectorName";
  public static final String GOOGLE_CONNECTOR_WORK_DIR =
      "googleConnectorWorkDir";
  public static final String GOOGLE_WORK_DIR = "googleWorkDir";
  public static final String GOOGLE_PROPERTIES_VERSION =
      "googlePropertiesVersion";
  public static final int GOOGLE_PROPERTIES_VERSION_NUMBER = 3;

  // Non-XML format Properties files are by definition 8859-1 encoding.
  public static final String PROPERTIES_ENCODING = "ISO-8859-1";

  private PropertiesUtils() {
    // prevents instantiation
  }

  /**
   * Read Properties from a file.  Decrypt passwords.
   *
   * @param propertiesFile Properties File to read
   * @return Properties as read from file
   * @throws PropertiesException if error reading file
   */
  public static Properties loadFromFile(File propertiesFile)
    throws PropertiesException {
    try {
      InputStream is =
          new BufferedInputStream(new FileInputStream(propertiesFile));
      try {
        return loadProperties(is);
      } finally {
        is.close();
      }
    } catch (Exception e) {
      throw new PropertiesException("Unable to load Properties from file "
                                    + propertiesFile.getPath(), e);
    }
  }

  /**
   * Write the properties to a file.  Encrypt passwords,
   * version the properties.
   *
   * @param properties Properties to write
   * @param propertiesFile File to write properties to
   * @param comment optional comment String to pass to Properties.store()
   * @throws PropertiesException if error writing to file
   */
  public static void storeToFile(Properties properties, File propertiesFile,
      String comment) throws PropertiesException {
    try {
      FileOutputStream fos = new FileOutputStream(propertiesFile);
      try {
        storeProperties(properties, fos, comment);
      } finally {
        fos.close();
      }
    } catch (Exception e) {
      throw new PropertiesException("Unable to store Properties to file "
                                    + propertiesFile.getPath(), e);
    }
  }

  /**
   * Store a set of Properties to a String.  This is effectively
   * java.util.Properties.store(StringOutputStream), if there were
   * such a thing as StringOutputStream.  The returned string is
   * suitable for loading back into as set of Properties using
   * fromString(String).
   *
   * @param properties to encode into a String
   * @param comment optional comment string to pass to Properties.store()
   * @return a String object with containing the properties.
   * @throws PropertiesException
   */
  public static String storeToString(Properties properties, String comment)
      throws PropertiesException {
    try {
      ByteArrayOutputStream os = null;
      try {
        os = new ByteArrayOutputStream();
        storeProperties(properties, os, comment);
        return os.toString(PROPERTIES_ENCODING);
      } finally {
        os.close();
      }
    } catch (IOException e) {
      throw new PropertiesException("Unable to encode Properties to String", e);
    }
  }

  /**
   * Load a set of Properties from a String.  This is effectively
   * java.util.Properties.load(StringInputStream), if there were
   * such a thing as StringInputStream.  This should be able to
   * load properties from strings created by toString();
   *
   * @param propertiesString
   * @return a Properties object, or null if null string
   * @throws PropertiesException
   */
  public static Properties loadFromString(String propertiesString)
      throws PropertiesException {
    if (propertiesString != null) {
      try {
        ByteArrayInputStream is = null;
        try {
          is = new ByteArrayInputStream(
             propertiesString.getBytes(PROPERTIES_ENCODING));
          return loadProperties(is);
        } finally {
          is.close();
        }
      } catch (IOException e) {
        throw new PropertiesException("Unable to decode Properties from String",
                                      e);
      }
    }
    return null;
  }

  /**
   * Read Properties from an InputStream.  Decrypt passwords.
   *
   * @param inputStream InputStream to read Properties from
   * @return Properties as read from inputStream
   * @throws PropertiesException
   */
  public static Properties loadProperties(InputStream inputStream)
      throws PropertiesException {
    if (inputStream == null) {
      return null;
    }
    Properties properties = new Properties();
    try {
      properties.load(inputStream);
    } catch (Exception e) {
      throw new PropertiesException("Error loading properties from stream", e);
    }

    // Decrypt stored passwords.
    decryptSensitiveProperties(properties);

    return properties;
  }

  /**
   * Write the properties to an OutputStream.  Encrypt passwords,
   * version the properties.
   *
   * @param properties Properties to write
   * @param outputStream OutputStream to write properties to
   * @param comment optional comment String
   * @throws PropertiesException if error writing to stream
   */
  public static void storeProperties(Properties properties,
      OutputStream outputStream, String comment) throws PropertiesException {
    if (properties == null) {
      return;
    }
    try {
      // Make a copy of the Properties before munging them.
      Properties props = copy(properties);
      stampPropertiesVersion(props);
      encryptSensitiveProperties(props);
      // If the comment contains embedded newlines, we must comment out each
      // subsequent line after the first, as Java Properties won't do it for us.
      if (comment != null && comment.indexOf('\n') > 0) {
        comment = comment.replaceAll("\n", "\n#");
      }
      props.store(outputStream, comment);
    } catch (Exception e) {
      throw new PropertiesException("Error storing properties to stream", e);
    }
  }

  /**
   * Make a Properties object from a Map, copying all the keys and values.
   *
   * @param sourceMap a Map representing properties key-value map
   * @return new Properties object that may be modified without altering
   *          the source properties.
   */
  public static Properties fromMap(Map<String, String> sourceMap) {
    if (sourceMap == null) {
      return null;
    }
    Properties properties = new Properties();
    properties.putAll(sourceMap);
    return properties;
  }

  /**
   * Make a Map&lt;String, String&gt; from the supplied Properties,
   * copying all the keys and values.
   *
   * @param sourceProperties Properties representing properties key-value map.
   * @return a Map&lt;String, String&gt; representation of the source
   *          Properties.
   */
  public static Map<String, String> toMap(Properties sourceProperties) {
    if (sourceProperties == null) {
      return null;
    }
    Map<String, String> configMap = new HashMap<String, String>();
    Iterator<?> iter = sourceProperties.keySet().iterator();
    while (iter.hasNext()) {
      String key = (String) iter.next();
      configMap.put(key, sourceProperties.getProperty(key));
    }
    return configMap;
  }

  /**
   * Make a deep copy of a Properties object - copying all
   * the keys and values.  This is in contrast to java.util.Propeties.copy(),
   * which makes a shallow copy.
   *
   * @param sourceProperties a source set of Properties.
   * @return new Properties object that may be modified without altering
   * the source properties.
   */
  public static Properties copy(Properties sourceProperties) {
    Properties props = new Properties();
    props.putAll(sourceProperties);
    return props;
  }

  /**
   * Encrypt Properties values that may be sensitive.  At this point,
   * any property that has the case-insensitive substring 'password'
   * in the key is considered sensitive.  Encrypting sensitive properties
   * is advisable when storing or transmitting properties in plain text.
   *
   * @param properties a set of Properties.
   */
  public static void encryptSensitiveProperties(Properties properties) {
    EncryptedPropertyPlaceholderConfigurer.encryptSensitiveProperties(properties);
  }

  /**
   * Decrypt Properties values that may be sensitive.  At this point,
   * any property that has the case-insensitive substring 'password'
   * in the key is considered sensitive.  This decrypts a set of
   * properties that was encrypted via encryptSensitiveProperties();
   *
   * @param properties a set of Properties.
   */
  public static void decryptSensitiveProperties(Properties properties) {
    EncryptedPropertyPlaceholderConfigurer.decryptSensitiveProperties(properties);
  }

  /**
   * Stamp the Properties set with the current Properties Version.
   *
   * @param properties a set of Properties.
   */
  public static void stampPropertiesVersion(Properties properties) {
    properties.put(GOOGLE_PROPERTIES_VERSION,
        Integer.toString(GOOGLE_PROPERTIES_VERSION_NUMBER));
  }

  /**
   * Retrieve the Properties Version stamp from this Properties set.
   *
   * @param properties a set of Properties.
   */
  public static int getPropertiesVersion(Properties properties) {
    String versionStr = properties.getProperty(
        GOOGLE_PROPERTIES_VERSION, "0");
    int version = 0;
    try {
      version = Integer.parseInt(versionStr);
      if (version > GOOGLE_PROPERTIES_VERSION_NUMBER) {
        LOGGER.warning("Properties appear to have been written by a newer "
            + "version of Connector Manager (" + version + ")");
      }
    } catch (NumberFormatException e) {
      LOGGER.warning("Invalid Properties Version: " + versionStr);
    }
    return version;
  }
}

本系列企业搜索引擎开发之连接器connector系本人原创

转载请注明出处 博客园 刺猬的温驯

本文链接http://www.cnblogs.com/chenying99/archive/2013/03/19/2970304.html

原文地址:https://www.cnblogs.com/chenying99/p/2970304.html