Commit 736023da authored by Marco Villegas's avatar Marco Villegas
Browse files

Issue #1796144: Added an event processor plugin type for repositories.

General changes:
- Support multiple plugins by repository plugin slot.
- Support plugins acrooss different vcs's.
- Added a VersioncontrolPluginConfigurationInterface, which defines how
  to configure a versioncontrol plugin.
- Better serialization for VersioncontrolEvent.

Specific changes:
- Call processor plugins on hook_versioncontrol_code_arrival().
- Added a per backend configuration page for event processors.
- Declare and use a new queue to run repository event processors.

Thanks to sdboyer for the comments on the architecture behind.
parent 4d008ceb
Loading
Loading
Loading
Loading
+3 −0
Changes for includes/VersioncontrolBackend.php: 3 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -105,6 +105,7 @@ abstract class VersioncontrolBackend {
      'committer_mapper' => 'VersioncontrolUserMapperInterface',
      'author_mapper' => 'VersioncontrolUserMapperInterface',
      'auth_handler' => 'VersioncontrolAuthHandlerInterface',
      'event_processor' => 'VersioncontrolSynchronizationEventProcessorInterface',
    ),
  );

@@ -357,6 +358,8 @@ abstract class VersioncontrolBackend {
  /**
   * Verify that a plugin object conforms to the required interface.
   *
   * @todo Plugin slot is a repository specific concept, move this there?
   *
   * @param mixed $entity
   *   A VersioncontrolEntity object, or string name of a VersioncontrolEntity
   *   class, for which we are verifying a plugin.
+38 −1
Changes for includes/VersioncontrolEvent.php: 38 added lines, 1 removed line.
Original line number Diff line number Diff line
@@ -7,7 +7,7 @@
/**
 * Stuff that happened in a repository at a specific time
 */
abstract class VersioncontrolEvent extends VersioncontrolEntity {
abstract class VersioncontrolEvent extends VersioncontrolEntity implements Serializable {
  protected $_id = 'elid';
  /**
   * db identifier
@@ -103,4 +103,41 @@ abstract class VersioncontrolEvent extends VersioncontrolEntity {

    module_invoke_all('versioncontrol_entity_event_delete', $this);
  }

  /**
   * @todo generalize for VersioncontrolEntity
   *
   * @see Serializable::serialize().
   */
  public function serialize() {
    $refl = new ReflectionObject($this);
    // Get all properties, except static ones.
    $props = $refl->getProperties(ReflectionProperty::IS_PRIVATE | ReflectionProperty::IS_PROTECTED | ReflectionProperty::IS_PUBLIC );

    $ser = array();
    foreach ($props as $prop) {
      // Avoid repository and backend.
      if (in_array($prop->name, array('backend', 'repository'))) {
        continue;
      }
      $ser[$prop->name] = $this->{$prop->name};
    }
    // Make sure ids are set.
    $ser['vcs'] = $this->getRepository()->vcs;
    $ser['repo_id'] = $this->getRepository()->repo_id;
    return serialize($ser);
  }

  /**
   * @see Serializable::unserialize().
   */
  public function unserialize($serialized) {
    foreach (unserialize($serialized) as $prop => $val) {
      $this->$prop = $val;
    }
    // And stripped out data members.
    $this->backend = versioncontrol_get_backends($this->vcs);
    unset($this->vcs);
    $this->repository = $this->backend->loadEntity('repo', $this->repo_id);
  }
}
+97 −12
Changes for includes/VersioncontrolRepository.php: 97 added lines, 12 removed lines.
Original line number Diff line number Diff line
@@ -89,13 +89,14 @@ abstract class VersioncontrolRepository implements VersioncontrolEntityInterface
  /**
   * An array describing the plugins that will be used for this repository.
   *
   * The current plugin types(array keys) are:
   * The current plugin slots(array keys) are:
   * - author_mapper
   * - committer_mapper
   * - webviewer_url_handler
   * - repomgr
   * - auth_handler
   * - reposync
   * - event_processors
   *
   * @var array
   */
@@ -104,6 +105,9 @@ abstract class VersioncontrolRepository implements VersioncontrolEntityInterface
  /**
   * An array of plugin instances (instanciated plugin objects).
   *
   * There is one plugin per plugin slot in most cases. For now only
   * event_processors can have multiple plugins associated.
   *
   * @var array
   */
  protected $pluginInstances = array();
@@ -878,29 +882,59 @@ abstract class VersioncontrolRepository implements VersioncontrolEntityInterface
    return $this->pluginInstances['webviewer_url_handler'];
  }

  protected function isPluginSlotMultiple($plugin_slot) {
    // @todo Maybe define metadata per plugin slot?
    return $plugin_slot == 'event_processors';
  }

  protected function isPluginSlotRequired($plugin_slot) {
    return $plugin_slot != 'event_processors';
  }

  /**
   * Get a ctools plugin based on plugin slot passed.
   * Get a list of ctools plugins based on plugin slot and type passed.
   */
  protected function getPlugin($plugin_slot, $plugin_type) {
  public function getPlugins($plugin_slot, $plugin_type) {
    ctools_include('plugins');

    $plugin_name = $this->getPluginName($plugin_slot);
    // It can be an array, some plugin slots accept multiple values.
    $plugin_names = $this->getPluginName($plugin_slot);
    if ($plugin_names == NULL) {
      return array();
    }
    if (!is_array($plugin_names)) {
      $plugin_names = array($plugin_names);
    }

    // If $plugin_name is empty ctools_get_plugins() returns an array of
    // plugins.
    if (empty($plugin_names) && !$multiple) {
      return ctools_get_plugins('versioncontrol', $plugin_type);
    }

    $plugins = array();
    foreach ($plugin_names as $plugin_name) {
      $plugin = ctools_get_plugins('versioncontrol', $plugin_type, $plugin_name);
      if (!is_array($plugin) || empty($plugin)) {
        throw new Exception("Attempted to get a plugin of type '$plugin_type' named '$plugin_name', but no such plugin could be found.", E_WARNING);
      }

    // If $plugin_name is empty ctools_get_plugins() returns an array of plugins
    // instead of a single one. Default to the first one.
    if (!$plugin_name) {
      return reset($plugin);
    }
    else {
      return $plugin;
      $plugins[] = $plugin;
    }
    return $plugins;
  }

  /**
   * Retrieve the first plugin associated with the plugin slot.
   *
   * @fixme review visibility
   */
  protected function getPlugin($plugin_slot, $plugin_type) {
    $plugins = $this->getPlugins($plugin_slot, $plugin_type);
    if ($this->isPluginSlotMultiple($plugin_slot)) {
      throw new Exception("Attempted to use getPlugin() with a multiple plugin slot named '$plugin_slot' of plugin type '$plugin_type'. Please use getPlugins() instead.", E_WARNING);
    }
    return reset($plugins);
  }

  /**
   * Retrieves the ctools plugin name to use, based on the plugin slot passed.
@@ -939,9 +973,12 @@ abstract class VersioncontrolRepository implements VersioncontrolEntityInterface
      return $plugin_name;
    }

    if ($this->isPluginSlotRequired($plugin_slot)) {
      // Could not find any default.
      throw new Exception("A default plugin name could not be retrieved for plugin type '$plugin_slot'.", E_WARNING);
    }
    return NULL;
  }

  public function getSynchronizerOptions() {
    if (!empty($this->synchronizerOptions)) {
@@ -987,6 +1024,26 @@ abstract class VersioncontrolRepository implements VersioncontrolEntityInterface
    return $plugin_object;
  }

  /**
   * @see getPluginClass().
   */
  protected function getPluginClasses($plugin_slot, $plugin_type, $class_type) {
    $plugin_objects = array();

    foreach ($this->getPlugins($plugin_slot, $plugin_type) as $plugin) {
      $class_name = ctools_plugin_get_class($plugin, $class_type);
      if (!class_exists($class_name)) {
        throw new Exception("Plugin slot '$plugin_slot' of type '$plugin_type' contains an invalid class name in handler slot '$class_type', named '$class_name' class", E_WARNING);
        return FALSE;
      }

      $plugin_object = new $class_name();
      $this->getBackend()->verifyPluginInterface($this, $plugin_slot, $plugin_object);
      $plugin_objects[$plugin['name']] = $plugin_object;
    }
    return $plugin_objects;
  }

  /**
  * Return the auth handler plugin object that this repository is
  * configured to use for implementing ACLs on this repository.
@@ -1047,6 +1104,34 @@ abstract class VersioncontrolRepository implements VersioncontrolEntityInterface
    return $this->pluginInstances['reposync'];
  }

  /**
   * Returns the event processor plugin object that this repository is
   * configured to use.
   *
   * @return array(VersioncontrolSynchronizationEventProcessorInterface)
   *   Keyed by plugin name.
   */
  public function getEventProcessors() {
    if (!isset($this->pluginInstances['event_processors'])) {
      $this->pluginInstances['event_processors'] = $this->getPluginClasses('event_processors', 'event_processor', 'handler');
      // @todo when per repository configuration is supported, add it.
      // @todo move this to getPluginClasses when data per plugin is supported on all repository plugin types.
      $variable_name = "versioncontrol_repository_plugin_defaults_{$this->vcs}_event_processors_data";
      $default_event_processor_data = variable_get($variable_name, array());
      foreach ($this->pluginInstances['event_processors'] as $name => $event_processor_object) {
        $event_processor_object->setRepository($this);
        if (empty($default_event_processor_data[$name])) {
          continue;
        }
        if ($event_processor_object instanceof VersioncontrolPluginConfigurationInterface) {
          $event_processor_object->setConfiguration($default_event_processor_data[$name]);
        }
      }
    }

    return $this->pluginInstances['event_processors'];
  }

  /**
   * Convenience method to lock the repository in preparation for a
   * synchronization run.
+7 −0
Changes for includes/VersioncontrolSynchronizationEventProcessorException.php: 7 added lines, 0 removed lines.
Original line number Diff line number Diff line
<?php
/**
 * @file
 *   An exception to be thrown when an error occurs during event procesing
 *   after repository synchronization.
 */
class VersioncontrolSynchronizationEventProcessorException extends Exception { }
+91 −0
Changes for includes/interfaces.inc: 91 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -468,3 +468,94 @@ interface VersioncontrolRepositoryHistorySynchronizerInterface {
   */
  public function verifyData();
}

/**
 * Defines a versioncontrol synchronization event processor plugin behavior.
 *
 * Note: This is similar to hook_versioncontrol_code_arrival(), but this type
 * of plugins are intended to help different configuration per repository per
 * plugin.
 */
interface VersioncontrolSynchronizationEventProcessorInterface {
  /**
   * Reacts when new code has arrived correctly.
   *
   * @param VersioncontrolEvent $event
   *   The event describing the code arrival.
   *
   * @throws VersioncontrolSynchronizationEventProcessorException $event
   *   Something wrong happened during the processing. It let caller knows if
   *   was executed correctly or not.
   */
  public function process(VersioncontrolEvent $event);

  /**
   * Set the repository object to be used by this plugin object.
   */
  public function setRepository(VersioncontrolRepository $repository);

}

/**
 * Defines a way for plugins to define configuration.
 *
 * @todo Currently it is only used on versioncontrol synchronization event
 * processor plugin. It's pending to be used on other plugins.
 * @todo Add a way to validate the form. Not added for now until we can pass
 * context to the plugin to know in which part of the form_state hierarchy to
 * find the values. Probably make a new method that receives only the known
 * context of form_state instead of the full context.
 */
interface VersioncontrolPluginConfigurationInterface {
  /**
   * Adds the values passed to the object.
   *
   * @param array $data
   *   the corresponding data to initialize values.
   *   i.e.
   *   array(
   *     'behavior' => 1,
   *     'message' => 'test',
   *   )
   */
  public function setConfiguration($data);

  /**
   * Build a configuration form to be used to configure a plugin instance.
   *
   * @param array $data
   *   the corresponding data to initialize values.
   *   i.e.
   *   array(
   *     'behavior' => 1,
   *     'message' => 'test',
   *   )
   *
   * @return array
   *   A form array to include.
   */
  public function buildForm($default_data);

  /**
   * Converts the buildForm() form data into an array to be saved.
   *
   * @param array $form
   *   A form array.
   * @param array $form_state_values_data
   *   A subset of $form_state with corresponding data.
   */
  public function getFormData($form_state_values_data);

  /**
   * Handle any special handling on form submission.
   *
   * Probably empty in most cases, getFormData should provide enough for
   * storing the configuration.
   *
   * @param array $form
   *   A form array.
   * @param array $form_state
   *   the corresponding form state.
   */
  public function submitForm(&$form, &$form_state);
}
Loading