君子博学而日参省乎己 则知明而行无过矣

博客园 首页 新随笔 联系 订阅 管理

由于一直忙于公司产品的开发工作,企业搜索引擎的连接器分析及constellio源码分析因此长时间没有更新,本次继续这项未竟事业

连接器管理都是通过servlet对外提供通信接口,由此从连接器管理提供的servlet着手,寻求分析连接器的入口

该系统提供的servlet的uml模型图如下

 

这些servlet主要提供连接器的管理,诸如连接器的配置、修改、移除、重启、设置定时调度等功能;其中StartUp跟随应用同时启动,负责初始化一些bean及相关参数等,其源码如下:

/**
 * The main purpose of this servlet is to have its "init" method called when the
 * container starts up. This is by done by means of the web.xml file. But I also
 * gave it a get and post that do the same thing.
 *
 */
public class StartUp extends HttpServlet {
  private static final Logger LOGGER =
      Logger.getLogger(StartUp.class.getName());

  @Override
  public void init() {
    NDC.push("Init");
    try {
      LOGGER.info("init");
      ServletContext servletContext = this.getServletContext();
      doConnectorManagerStartup(servletContext);
      LOGGER.info("init done");
    } finally {
      NDC.remove();
    }
  }

  @Override
  public void destroy() {
    NDC.push("Shutdown");
    try {
      LOGGER.info("destroy");
      Context.getInstance().shutdown(true);
    } finally {
      NDC.remove();
    }
  }

  @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse res)
      throws IOException {
    doPost(req, res);
  }

  @Override
  protected void doPost(HttpServletRequest req, HttpServletResponse res)
      throws IOException {
    ServletContext servletContext = this.getServletContext();
    doConnectorManagerStartup(servletContext);
    res.setContentType(ServletUtil.MIMETYPE_HTML);
    PrintWriter out = res.getWriter();
    out.println("<HTML><HEAD><TITLE>Connector Manager Started</TITLE></HEAD>"
        + "<BODY>Connector manager has been successfully started.</BODY>"
        + "</HTML>");
    out.close();
    LOGGER.info("Connector Manager started.");
  }

  private void doConnectorManagerStartup(ServletContext servletContext) {
    LOGGER.info(ServletUtil.getManagerSplash());

    // read in and set initialization parameters
    String kp = servletContext.getInitParameter("keystore_passwd_file");
    EncryptedPropertyPlaceholderConfigurer.setKeyStorePasswdPath(kp);

    String ks = servletContext.getInitParameter("keystore_file");
    String realks = servletContext.getRealPath("/WEB-INF/" + ks);
    if (null == realks) {
      // Servlet container cannot translated the virtual path to a
      // real path, so use the given path.
      EncryptedPropertyPlaceholderConfigurer.setKeyStorePath(ks);
    } else {
      EncryptedPropertyPlaceholderConfigurer.setKeyStorePath(realks);
    }

    String kt = servletContext.getInitParameter("keystore_type");
    EncryptedPropertyPlaceholderConfigurer.setKeyStoreType(kt);

    String ka = servletContext.getInitParameter("keystore_crypto_algo");
    EncryptedPropertyPlaceholderConfigurer.setKeyStoreCryptoAlgo(ka);

    // Note: default context location is /WEB-INF/applicationContext.xml
    LOGGER.info("Making an XmlWebApplicationContext");
    XmlWebApplicationContext ac = new XmlWebApplicationContext();
    ac.setServletContext(servletContext);
    ac.refresh();

    Context context = Context.getInstance();
    context.setServletContext(ac, servletContext.getRealPath("/WEB-INF"));
    context.start();
  }
}

 这里的关键方法是void doConnectorManagerStartup(ServletContext servletContext)

前面部分用于证书生成,后面部分是获取spring容器并初始化Context上下文对象

接下来分析Context的源码:

/**
 * Static services for establishing the application context. This consists of
 * configuration, instantiating singletons, start up, etc.
 * This code supports two context types: servlet (as a web application within
 * an application server) and standalone.
 * When we run junit tests, we use a standalone context.
 * Use the methods setStandaloneContext and setServletContext to select the
 * context type.
 * <p>
 * Also the interface used for event publishing.  Wraps the event publishing
 * functionality of the established context.
 */
public class Context {

  public static final String GSA_FEED_HOST_PROPERTY_KEY = "gsa.feed.host";
  public static final String GSA_FEED_PORT_PROPERTY_KEY = "gsa.feed.port";
  public static final String GSA_FEED_PORT_DEFAULT = "19900";

  public static final String GSA_ADMIN_REQUIRES_PREFIX_KEY =
      "gsa.admin.requiresPrefix";
  public static final String TEED_FEED_FILE_PROPERTY_KEY = "teedFeedFile";
  public static final String MANAGER_LOCKED_PROPERTY_KEY = "manager.locked";

  public static final Boolean GSA_ADMIN_REQUIRES_PREFIX_DEFAULT =
      Boolean.FALSE;

  public static final String DEFAULT_JUNIT_CONTEXT_LOCATION =
      "testdata/mocktestdata/applicationContext.xml";
  public static final String DEFAULT_JUNIT_COMMON_DIR_PATH =
      "testdata/mocktestdata/";

  /**
   * Id of the Spring Bean used to declare the order services are to be loaded.
   */
  public static final String ORDERED_SERVICES_BEAN_NAME = "OrderedServices";
  private static final String APPLICATION_CONTEXT_PROPERTIES_BEAN_NAME =
      "ApplicationContextProperties";

  // This is the comment written to the ApplicationContextProperties file.
  private static final String CONNECTOR_MANGER_CONFIG_HEADER =
      " Google Search Appliance Connector Manager Configuration\n"
      + "\n"
      + " The 'gsa.feed.host' property specifies the host name or IP address\n"
      + " for the feed host on the GSA.\n"
      + " For example:\n"
      + "   gsa.feed.host=172.24.2.0\n"
      + "\n"
      + " The 'gsa.feed.port' property specifies the host port for the feed\n"
      + " host on the GSA.\n"
      + " For example:\n"
      + "   gsa.feed.port=19900\n"
      + "\n"
      + " The 'manager.locked' property is used to lock out the Admin Servlet\n"
      + " and prevent it from making changes to this configuration file.\n"
      + " Specifically, the ability to set the FeedConnection properties will\n"
      + " be locked out.  If it is set to 'true' or missing the Servlet will\n"
      + " not be allowed to update this file.\n"
      + " NOTE: This property will automatically be changed to 'true' upon\n"
      + " successful update of the file by the Servlet.  Therefore, once the\n"
      + " FeedConnection properties are successfully updated by the Servlet\n"
      + " subsequent updates will be locked out until the flag is manually\n"
      + " reset to 'false'.\n"
      + " For example:\n"
      + "   manager.locked=false\n"
      + "\n"
      + " The 'feedLoggingLevel' property controls the logging of the feed\n"
      + " record to a log file.  The log record will contain the feed XML\n"
      + " without the content data.  Set this property to 'ALL' to enable feed\n"
      + " logging, 'OFF' to disable.  Customers and developers can use this\n"
      + " functionality to observe the feed record and metadata information\n"
      + " the connector manager sends to the GSA.\n"
      + " For example:\n"
      + "   feedLoggingLevel=OFF\n"
      + "\n"
      + " If you set the 'teedFeedFile' property to the name of an existing\n"
      + " file, whenever the connector manager feeds content to the GSA, it\n"
      + " will write a duplicate copy of the feed XML to the file specified by\n"
      + " the teedFeedFile property.  GSA customers and third-party developers\n"
      + " can use this functionality to observe the content the connector\n"
      + " manager sends to the GSA and reproduce any issue which may arise.\n"
      + " NOTE: The teedFeedFile will contain all feed data sent to the GSA,\n"
      + " including document content and metadata.  The teedFeedFile can\n"
      + " therefore grow quite large very quickly.\n"
      + " For example:\n"
      + "   teedFeedFile=/tmp/CMTeedFeedFile"
      + "\n"
      + " The 'feed.timezone' property defines the default time zone used\n"
      + " for Date metadata values for Documents.  A null or empty string\n"
      + " indicates that the system timezone of the machine running the\n"
      + " Connector Manager should be used.  Standard TimeZone identifiers\n"
      + " may be used.  For example:\n"
      + "   feed.timezone=America/Los_Angeles\n"
      + " If a standard TimeZone identifier is unavailable, then a custom\n"
      + " TimeZone identifier can be constructed as +/-hours[minutes] offset\n"
      + " from GMT.  For example:\n"
      + "   feed.timezone=GMT+10    # GMT + 10 hours\n"
      + "   feed.timezone=GMT+0630  # GMT + 6 hours, 30 minutes\n"
      + "   feed.timezone=GMT-0800  # GMT - 8 hours, 0 minutes\n"
      + "\n"
      + " The 'feed.file.size' property sets the target size, in bytes, of\n"
      + " an accumulated feed file. The Connector Manager tries to collect\n"
      + " many feed Documents into a single feed file to improve the\n"
      + " efficiency of sending feed data to the GSA.  Specifying too small\n"
      + " a value may result in many small feeds which might overrun the\n"
      + " GSA's feed processor.  However, specifying too large a feed size\n"
      + " reduces concurrency and may result in OutOfMemory errors in the\n"
      + " Java VM, especially if using multiple Connector Instances.\n"
      + " The default target feed size is 10MB.\n"
      + " For example:\n"
      + "   feed.file.size=10485760\n"
      + "\n"
      + " The 'feed.document.size.limit' property defines the maximum\n"
      + " allowed size, in bytes, of a Document's content.  Documents whose\n"
      + " content exceeds this size will still have metadata indexed,\n"
      + " however the content itself will not be fed.  The default value\n"
      + " is 30MB, the maximum file size accepted by the GSA.\n"
      + " For example:\n"
      + "   feed.document.size.limit=31457280\n"
      + "\n"
      + " The 'feed.backlog.*' properties are used to throttle back the\n"
      + " document feed if the GSA has fallen behind processing outstanding\n"
      + " feed items.  The Connector Manager periodically polls the GSA,\n"
      + " fetching the count of unprocessed feed items (the backlog count).\n"
      + " If the backlog count exceeds the ceiling value, feeding is paused.\n"
      + " Once the backlog count drops down below the floor value, feeding\n"
      + " resumes.\n  For example:\n"
      + " Stop feeding the GSA if its backlog exceeds this value.\n"
      + "   feed.backlog.ceiling=10000\n"
      + " Resume feeding the GSA if its backlog falls below this value.\n"
      + "   feed.backlog.floor=1000\n"
      + " How often to check for feed backlog (in seconds).\n"
      + "   feed.backlog.interval=900\n"
      + "\n"
      + " The 'traversal.batch.size' property defines the optimal number\n"
      + " of items to return in each repository traversal batch.  The batch\n"
      + " size represents the size of the roll-back that occurs during a\n"
      + " failure condition.  Batch sizes that are too small may incur\n"
      + " excessive processing overhead.  Batch sizes that are too large\n"
      + " may produce OutOfMemory conditions within a Connector or result\n"
      + " in early termination of the batch if processing time exceeds the\n"
      + " travesal.time.limit.   For example:\n"
      + "    traversal.batch.size=500\n"
      + "\n"
      + " The 'traversal.poll.interval' property defines the number of\n"
      + " seconds to wait after a traversal of the repository finds no new\n"
      + " content before looking again.  Short intervals allow new content\n"
      + " to be readily available for search, at the cost of increased\n"
      + " repository access.  Long intervals add latency before new\n"
      + " content becomes available for search.  By default, the Connector\n"
      + " Manager waits 5 minutes (300 seconds) before retraversing the\n"
      + " repository if no new content was found on the last traversal.\n"
      + " For example:\n"
      + "   traversal.poll.interval=300\n"
      + "\n"
      + " The 'traversal.time.limit' property defines the number of\n"
      + " seconds a traversal batch should run before gracefully exiting.\n"
      + " Traversals that exceed this time period risk cancelation.\n"
      + " The default time limit is 30 minutes (1800 seconds).\n"
      + " For example:\n"
      + "   traversal.time.limit=1800\n"
      + "\n"
      + " The 'traversal.enabled' property is used to enable or disable\n"
      + " Traversals and Feeds for all connector instances in this\n"
      + " Connector Manager.  Disabling Traversal would be desirable if\n"
      + " configuring a Connector Manager deployment that only authorizes\n"
      + " search results.  Traversals are enabled by default.\n"
      + " traversal.enabled=false\n"
      + "\n";

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

  private static final GenericApplicationContext genericApplicationContext =
      new GenericApplicationContext();

  private static Context INSTANCE = new Context();

  private boolean started = false;

  private boolean isServletContext = false;

  private boolean isFeeding = true;

  private String commonDirPath = null;

  // singletons
  private Manager manager = null;
  private TraversalScheduler traversalScheduler = null;
  private TraversalContext traversalContext = null;
  private SpringInstantiator instantiator = null;

  // control variables for turning off normal functionality - testing only
  private String standaloneContextLocation;


  private Boolean gsaAdminRequiresPrefix = null;

  private boolean isTeedFeedFileInitialized = false;
  private String teedFeedFile = null;

  private boolean isGsaFeedHostInitialized = false;
  private String gsaFeedHost = null;

  private int propertiesVersion = 0;

  /**
   * @param feeding to feed or not to feed
   */
  public void setFeeding(boolean feeding) {
    LOGGER.config("Traversal and Feeds are "
        + ((feeding) ? "enabled." : "disabled."));
    this.isFeeding = feeding;
  }

  public static Context getInstance() {
    return INSTANCE;
  }

  ApplicationContext applicationContext = null;

  private Context() {
    // Private to ensure singleton.
  }

  private void initializeStandaloneApplicationContext() {
    if (applicationContext != null) {
      // too late - someone else already established a context. this might
      // happen with multiple junit tests that each want to establish a context.
      // so long as they use the same context location, it's ok. if they want a
      // different context location, they should refresh() - see below
      return;
    }

    applicationContext = genericApplicationContext; // avoid recursion

    if (standaloneContextLocation == null) {
      standaloneContextLocation = DEFAULT_JUNIT_CONTEXT_LOCATION;
    }

    if (commonDirPath == null) {
      commonDirPath = DEFAULT_JUNIT_COMMON_DIR_PATH;
    }
    LOGGER.info("context file: " + standaloneContextLocation);
    LOGGER.info("common dir path: " + commonDirPath);

    applicationContext =
        new FileSystemXmlApplicationContext(standaloneContextLocation);
  }

  /**
   * Establishes that we are operating within the standalone context. In
   * this case, we use a FileSystemApplicationContext.
   * @param contextLocation the name of the context XML file used for
   * instantiation.
   * @param commonDirPath the location of the common directory which contains
   * ConnectorType and Connector instantiation configuration data.
   */
  public void setStandaloneContext(String contextLocation,
                                   String commonDirPath) {
    this.standaloneContextLocation = contextLocation;
    this.commonDirPath = commonDirPath;
    initializeStandaloneApplicationContext();
  }

  /**
   * Establishes that we are operating from a servlet context. In this case, we
   * use an XmlWebApplicationContext, which finds its config from the servlet
   * context - WEB-INF/applicationContext.xml.
   */
  public void setServletContext(ApplicationContext servletApplicationContext,
                                String commonDirPath) {
    this.applicationContext = servletApplicationContext;
    this.commonDirPath = commonDirPath;
    isServletContext = true;
  }

  /*
   * Choose a default context, if it wasn't specified in any other way. For now,
   * we choose servlet context by default.
   */
  private void initApplicationContext() {
    if (applicationContext == null) {
      initializeStandaloneApplicationContext();
    }
    if (applicationContext == null) {
      throw new IllegalStateException("Spring failure - no application context");
    }
  }

  /**
   * Start up the Scheduler.
   */
  private void startScheduler() {
    traversalScheduler =
        (TraversalScheduler) getRequiredBean("TraversalScheduler",
            TraversalScheduler.class);
    if (traversalScheduler != null) {
      traversalScheduler.init();
    }
  }

  /**
   * Start up the Instantiator.
   */
  private void startInstantiator() {
    instantiator =
        (SpringInstantiator) getBean("Instantiator", SpringInstantiator.class);
    if (instantiator != null) {
      instantiator.init();
    }
  }

  /**
   * Do everything necessary to start up the application.
   */
  public void start() {
    if (started) {
      return;
    }
    initApplicationContext();
    startInstantiator();
    if (isFeeding) {
      startScheduler();
    }
    startServices();
    started = true;
  }

  /**
   * Starts any services declared as part of the application.
   */
  private void startServices() {
    for (ContextService service : getServices()) {
      service.start();
    }
  }

  /**
   * Gets a service by name.  Returns a matching bean if found or null
   * otherwise.
   *
   * @param serviceName the name of the service to find.
   * @return if there is a single bean with the given name it will be returned.
   *         If there are multiple beans with the same name, the first one found
   *         will be returned.  If there are no beans with the given name, null
   *         will be returned.
   */
  public ContextService findService(String serviceName) {
    return (ContextService) getBean(serviceName, null);
  }

  /**
   * Returns an ordered list of services attached to the context.  Collection is
   * ordered according to the startup order of the services.
   * <p>
   * To get the list in reverse order use {@link Collections#reverse(List)}.
   *
   * @return an ordered list of ContextService objects.  If no services are
   *         registered an empty list will be returned.
   */
  public List<ContextService> getServices() {
    // TODO: Investigate the use of the GenericBeanFactoryAccessor here.
    Map<?, ?> orderedServices = (Map<?, ?>)
        getBean(ORDERED_SERVICES_BEAN_NAME, null);
    Map<?, ?> services = applicationContext.getBeansOfType(ContextService.class);
    List<ContextService> result = new ArrayList<ContextService>();

    if (orderedServices != null) {
      for (Iterator<?> iter = orderedServices.keySet().iterator();
          iter.hasNext(); ) {
        ContextService service =
            (ContextService) orderedServices.get(iter.next());
        result.add(service);
      }
    }
    for (Iterator<?> iter = services.values().iterator(); iter.hasNext(); ) {
        ContextService service = (ContextService) iter.next();
      if (!result.contains(service)) {
        result.add(service);
      }
    }

    return result;
  }

  /**
   * Get a bean from the application context that we MUST have to operate.
   *
   * @param beanName the name of the bean we're looking for. Typically, the same
   *        as its most general interface.
   * @param clazz the class of the bean we're looking for.
   * @return if there is a single bean of the required type, we return it,
   *         regardless of name. If there are multiple beans of the required
   *         type, we return the one with the required name, if present, or the
   *         first one we find, if there is none of the right name.
   * @throws IllegalStateException if there are no beans of the right type, or
   *         if there is an instantiation problem.
   */
  public Object getRequiredBean(String beanName, Class<?> clazz) {
    try {
      Object object = getBean(beanName, clazz);
      if (object != null) {
        return object;
      }
      throw new IllegalStateException("The context has no " + beanName);
    } catch (BeansException e) {
      throw new IllegalStateException("Spring failure - can't instantiate "
          + beanName + ": (" + e.toString() + ")");
    }
  }

  /**
   * Get an optional bean from the application context.
   *
   * @param beanName the name of the bean we're looking for. Typically,
   *        the same as its most general interface.
   * @param clazz the class of the bean we're looking for.
   * @return if there is a single bean of the required type, we return it,
   *         regardless of name. If there are multiple beans of the required
   *         type, we return the one with the required name, if present, or the
   *         first one we find, if there is none of the right name.  Returns
   *         null if no bean of the appropriate name or type is found.
   * @throws BeansException if there is an instantiation problem.
   */
  public Object getBean(String beanName, Class<?> clazz)
      throws BeansException {
    initApplicationContext();
    return getBean(applicationContext, beanName, clazz);
  }

  /**
   * Get a bean from the supplied BeanFactory.  First look for a bean with
   * the given name and type.  If none is found, look for the first bean
   * of the specified type.
   *
   * @param factory a ListableBeanFactory
   * @param beanName the name of the bean we're looking for. Typically, the same
   *        as its most general interface.  If null, return the first bean
   *        of the requested type.
   * @param clazz the class of the bean we're looking for.  If null, return
   *        any bean of the specified name.
   * @return if there is a single bean of the required type, we return it,
   *         regardless of name. If there are multiple beans of the required
   *         type, we return the one with the required name, if present, or the
   *         first one we find, if there is none of the right name.  Returns
   *         null if no bean of the appropriate name or type is found.
   * @throws BeansException if there is an instantiation problem.
   */
  public Object getBean(ListableBeanFactory factory, String beanName,
      Class<?> clazz) throws BeansException {
    Object result = null;

    // First, look for a bean with the specified name and type.
    try {
      if (beanName != null && beanName.length() > 0) {
        result = factory.getBean(beanName, clazz);
        if (result != null) {
          return result;
        }
      }
    } catch (NoSuchBeanDefinitionException e) {
      // Not a problem yet.  Look for any bean of the appropriate type.
    }

    // If no bean type was specified, we are done.
    if (clazz == null) {
      return null;
    }

    // Get the list of beans defined in the bean factory of the required type.
    String[] beanList = factory.getBeanNamesForType(clazz);

    // Make sure there is at least one
    if (beanList.length < 1) {
      return null;
    }

    // If more beans were found issue a warning.
    if (beanList.length > 1) {
      StringBuilder buf = new StringBuilder();
      for (int i = 1; i < beanList.length; i++) {
        buf.append(" ");
        buf.append(beanList[i]);
      }
      LOGGER.warning("Resource contains multiple " + clazz.getName() +
          " definitions. Using the first: " + beanList[0] +
          ". Skipping: " + buf);
    }

    return factory.getBean(beanList[0]);
  }

  /**
   * Gets the singleton Manager.
   *
   * @return the Manager
   */
  public Manager getManager() {
    if (manager != null) {
      return manager;
    }
    manager = (Manager) getRequiredBean("Manager", Manager.class);
    return manager;
  }

  /**
   * Gets the singleton TraversalContext.
   *
   * @return the TraversalContext
   */
  public TraversalContext getTraversalContext() {
    if (traversalContext != null) {
      return traversalContext;
    }
    try {
      traversalContext = (TraversalContext) getRequiredBean("TraversalContext",
          TraversalContext.class);
    } catch (IllegalStateException e) {
      LOGGER.warning("Can't find suitable " + TraversalContext.class.getName()
          + " bean in context, using default.");
      traversalContext = new ProductionTraversalContext();
    }
    return traversalContext;
  }

  /**
   * Throws out the current context instance and gets another one. For testing
   * only. This could really boolux things up if it were used in production!
   *
   */
  public static void refresh() {
    INSTANCE = new Context();
  }

  /**
   * Gets the applicationContext. For testing only.
   *
   * @return the applicationContext
   */
  public ApplicationContext getApplicationContext() {
    initApplicationContext();
    return applicationContext;
  }

  public synchronized void shutdown(boolean force) {
    LOGGER.info("Shutdown initiated...");
    stopServices(force);
    if (null != traversalScheduler) {
      traversalScheduler.shutdown();
      traversalScheduler = null;
    }
    if (null != instantiator) {
      instantiator.shutdown(force,
          ThreadPool.DEFAULT_SHUTDOWN_TIMEOUT_MILLIS);
      instantiator = null;
    }
    started = false;
  }

  /**
   * Stops any services declared as part of the application.
   */
  private void stopServices(boolean force) {
    initApplicationContext();
    List<ContextService> services = getServices();
    Collections.reverse(services);
    for (ContextService service : services) {
      service.stop(force);
    }
  }

  /**
   * Retrieves the prefix for the Common directory file depending on whether
   * it is a standalone context or servlet context.
   *
   * @return prefix for the Repository file.
   */
  public String getCommonDirPath() {
    initApplicationContext();
    return commonDirPath;
  }

  private String getPropFileName() throws InstantiatorException {
    String propFileName = null;
    try {
      propFileName =
          (String) applicationContext.getBean(
              APPLICATION_CONTEXT_PROPERTIES_BEAN_NAME, java.lang.String.class);
    } catch (BeansException e) {
      throw new InstantiatorException("Spring exception while getting "
          + APPLICATION_CONTEXT_PROPERTIES_BEAN_NAME + " bean", e);
    }
    if (propFileName == null || propFileName.length() < 1) {
      throw new InstantiatorException("Null or empty file name returned from "
          + "Spring while getting " + APPLICATION_CONTEXT_PROPERTIES_BEAN_NAME
          + " bean");
    }
    return propFileName;
  }

  private File getPropFile(String propFileName) throws InstantiatorException {
    Resource propResource = applicationContext.getResource(propFileName);
    File propFile;
    try {
      propFile = propResource.getFile();
    } catch (IOException e) {
      throw new InstantiatorException(e);
    }
    return propFile;
  }

  public Properties getConnectorManagerConfig() throws InstantiatorException {
    initApplicationContext();
    Properties result = new Properties();
    // Get the properties out of the CM properties file if present.
    String propFileName = getPropFileName();
    File propFile = getPropFile(propFileName);
    try {
      Properties props = PropertiesUtils.loadFromFile(propFile);
      result.setProperty(GSA_FEED_HOST_PROPERTY_KEY,
          props.getProperty(GSA_FEED_HOST_PROPERTY_KEY));
      result.setProperty(GSA_FEED_PORT_PROPERTY_KEY,
          props.getProperty(GSA_FEED_PORT_PROPERTY_KEY, GSA_FEED_PORT_DEFAULT));
    } catch (PropertiesException e) {
      LOGGER.log(Level.WARNING, "Unable to read application context properties"
          + " file " + propFileName,
          e);
    }
    return result;
  }

  public void setConnectorManagerConfig(String feederGateHost,
      int feederGatePort) throws InstantiatorException {
    initApplicationContext();

    // Update the feed host and port in the CM properties file.
    String propFileName = getPropFileName();
    File propFile = getPropFile(propFileName);
    Properties props;
    try {
      props = PropertiesUtils.loadFromFile(propFile);
    } catch (PropertiesException e) {
      LOGGER.log(Level.WARNING, "Unable to read application context properties"
          + " file "+ propFileName + "; attempting instantiation stand-alone.",
          e);
      props = new Properties();
    }
    props.put(GSA_FEED_HOST_PROPERTY_KEY, feederGateHost);
    props.put(GSA_FEED_PORT_PROPERTY_KEY, Integer.toString(feederGatePort));
    // Lock down the manager at this point.
    props.put(MANAGER_LOCKED_PROPERTY_KEY, Boolean.TRUE.toString());
    try {
      PropertiesUtils.storeToFile(props, propFile,
          CONNECTOR_MANGER_CONFIG_HEADER);
    } catch (PropertiesException e) {
      LOGGER.log(Level.WARNING, "Unable to save application context properties"
          + " file " + propFileName + ". ", e);
      throw new InstantiatorException(e);
    }
    LOGGER.info("Updated Connector Manager Config: "
        + GSA_FEED_HOST_PROPERTY_KEY + "=" + feederGateHost + "; "
        + GSA_FEED_PORT_PROPERTY_KEY + "=" + feederGatePort + ";"
        + MANAGER_LOCKED_PROPERTY_KEY + "="
        + props.getProperty(MANAGER_LOCKED_PROPERTY_KEY));

    // Update our local cached feed host.
    gsaFeedHost = feederGateHost;
    isGsaFeedHostInitialized = true;

    // Notify the GsaFeedConnection of new host and port.
    try {
      GsaFeedConnection feeder = (GsaFeedConnection)
        applicationContext.getBean("FeedConnection", GsaFeedConnection.class);
      feeder.setFeedHostAndPort(feederGateHost, feederGatePort);
    } catch (BeansException be) {
      // The configured FeedConnection isn't a GSA, so it doesn't care
      // about the GSA host and port.
    } catch (MalformedURLException e) {
      throw new InstantiatorException("Invalid GSA Feed specification", e);
    }
  }

  /**
   * Whether or not the GSA requires the connector manager to prepend a
   * connector-manager-specific prefix to connector configuration
   * property names.  Older GSA require the prefix, and newer GSAs do not.
   * This value is read from the <code>gsa.admin.requiresPrefix</code>
   * property in the application context properties file.
   * If the <code>gsa.admin.requiresPrefix</code> property is not defined, the
   * default value is <code>false</code>.
   */
  public boolean gsaAdminRequiresPrefix() {
    initApplicationContext();
    if (gsaAdminRequiresPrefix == null) {
        String prop = getProperty(
            GSA_ADMIN_REQUIRES_PREFIX_KEY,
            GSA_ADMIN_REQUIRES_PREFIX_DEFAULT.toString());
        gsaAdminRequiresPrefix = Boolean.valueOf(prop);
    }
    return gsaAdminRequiresPrefix;
  }

  /**
   * Reads <code>teedFeedFile</code> from the application context properties file.
   * See google-enterprise-connector-manager/projects/connector-manager/etc/applicationContext.properties
   * for additional documentation.
   */
  public String getTeedFeedFile() {
    initApplicationContext();
    if (!isTeedFeedFileInitialized) {
      teedFeedFile = getProperty(TEED_FEED_FILE_PROPERTY_KEY, null);
      isTeedFeedFileInitialized = true;
    }
    return teedFeedFile;
  }

  /**
   * Reads <code>gsa.feed.host</code> from the application context properties file.
   * See google-enterprise-connector-manager/projects/connector-manager/etc/applicationContext.properties
   * for additional documentation.
   */
  public String getGsaFeedHost() {
    initApplicationContext();
    if (!isGsaFeedHostInitialized) {
      gsaFeedHost = getProperty(GSA_FEED_HOST_PROPERTY_KEY, null);
      isGsaFeedHostInitialized = true;
    }
    return gsaFeedHost;
  }

  /**
   * Reads <code>manager.locked</code> property from the application context
   * properties file.
   *
   * @return true if the property does not exist.  Returns true if the property
   *         is set to 'true', ignoring case.  Returns false otherwise.
   */
  public boolean getIsManagerLocked() {
    initApplicationContext();
    String isManagerLocked = getProperty(MANAGER_LOCKED_PROPERTY_KEY, null);
    if (isManagerLocked != null) {
      return Boolean.valueOf(isManagerLocked).booleanValue();
    }
    // Consider older, but uninitialized properties files to be unlocked.
    if (propertiesVersion < 2 && "localhost".equals(getGsaFeedHost())) {
      return false;
    }
    return true;
  }

  /**
   * Reads a property from the application context properties file.
   *
   * @param key the property name
   * @param defaultValue if property does not exist
   */
  private String getProperty(String key, String defaultValue) {
    try {
      String propFileName = getPropFileName();
      File propFile = getPropFile(propFileName);
      try {
        Properties props = PropertiesUtils.loadFromFile(propFile);
        propertiesVersion = PropertiesUtils.getPropertiesVersion(props);
        return props.getProperty(key, defaultValue);
      } catch (PropertiesException e) {
        LOGGER.log(Level.WARNING, "Unable to read application context "
                   + "properties file " + propFileName, e);
      }
    } catch (InstantiatorException ie) {
      LOGGER.log(Level.WARNING, "Unable to read application context "
                 + "properties file.", ie);
    }
    return defaultValue;
  }

  /**
   * Notify all listeners registered with this context of an application event.
   * Events may be framework events or application-specific events.
   *
   * @param event the event to publish.
   */
  public void publishEvent(ApplicationEvent event) {
    initApplicationContext();
    applicationContext.publishEvent(event);
  }
}

 这里可以看到,Context上下文对象支持两种方式初始化,作为web应用方式(部署在应用服务器内)和以独立方式

如果以独立方式初始化,则我们可以如下调用:

public static void main(String[] args) throws ConnectorNotFoundException, InstantiatorException {
        // TODO Auto-generated method stub
        Context.refresh();         
        Context.getInstance().setStandaloneContext("/WebRoot/WEB-INF/applicationContext.xml", "D:/googlework/app");
        Context.getInstance().setFeeding(false);
        Context.getInstance().start();        
        
        SpringInstantiator instantiator = (SpringInstantiator) Context.getInstance().getBean("Instantiator", SpringInstantiator.class);
        //instantiator.init();
        //Manager manager=Context.getInstance().getManager();
        for( String str:instantiator.getConnectorTypeNames())
        {
           System.out.println("ConnectorTypeName:"+str);
        }
        for( String str:instantiator.getConnectorNames())
        {
           System.out.println("ConnectorName:"+str);
        }
       
        ConfigureResponse cr = instantiator.getConfigFormForConnector("default_huilan_http-connector_1363539625968", "http-connector", Locale.ENGLISH);
        System.out.println(cr.getFormSnippet());        
                
        Map<String, String> map =instantiator.getConnectorConfig("default_huilan_http-connector_1363539625968");
        Iterator<String> it=map.keySet().iterator();
       while(it.hasNext()){
           String key= (String) it.next();
           System.out.println("key:"+key+"||value:"+map.get(key));
       }        
    
        System.out.println("ConnectorSchedule:"+instantiator.getConnectorSchedule("default_huilan_http-connector_1363539625968"));        
    
        ConnectorCoordinator coordinator = instantiator.getConnectorCoordinator("default_huilan_http-connector_1363539625968");
               
        Context.getInstance().shutdown(true);
        Context.refresh();
    }

需要注意的是以独立方式初始化需要修改applicationContext.xml文件里面applicationContext.properties的路径,并且在applicationContext.properties文件里面指定catalina.base变量(如果是tomcat容器,默认为tomcat的安装路径,如我的是C:/apache-tomcat-7.0.33)

Context上下文对象对外提供spring容器里面的bean,并负责初始化相关bean,它的启动方法如下

/**
   * Do everything necessary to start up the application.
   */
  public void start() {
    if (started) {
      return;
    }
    initApplicationContext();
    startInstantiator();
    if (isFeeding) {
      startScheduler();
    }
    startServices();
    started = true;
  }
/**
   * Start up the Instantiator.
   */
  private void startInstantiator() {
    instantiator =
        (SpringInstantiator) getBean("Instantiator", SpringInstantiator.class);
    if (instantiator != null) {
      instantiator.init();
    }
  }
 /**
   * Start up the Scheduler.
   */
  private void startScheduler() {
    traversalScheduler =
        (TraversalScheduler) getRequiredBean("TraversalScheduler",
            TraversalScheduler.class);
    if (traversalScheduler != null) {
      traversalScheduler.init();
    }
  }

这里的start方法启动实例化对象和定时调度对象,这两个对象的类源码及功能待下文分析吧

posted on 2013-03-17 03:23  刺猬的温驯  阅读(719)  评论(0编辑  收藏  举报