API set for plugins on the adaption phase

Abstract

Each Droonga Engine plugin can have its adapter. On the adaption phase, adapters can modify both incoming messages (from the Protocol Adapter to the Droonga Engine, in other words, they are “request”s) and outgoing messages (from the Droonga Engine to the Protocol Adapter, in other words, they are “response”s).

How to define an adapter?

For example, here is a sample plugin named “foo” with an adapter:

require "droonga/plugin"

module Droonga::Plugins::FooPlugin
  extend Plugin
  register("foo")

  class Adapter < Droonga::Adapter
    # operations to configure this adapter
    XXXXXX = XXXXXX

    def adapt_input(input_message)
      # operations to modify incoming messages
      input_message.XXXXXX = XXXXXX
    end

    def adapt_output(output_message)
      # operations to modify outgoing messages
      output_message.XXXXXX = XXXXXX
    end
  end
end

Steps to define an adapter:

  1. Define a module for your plugin (ex. Droonga::Plugins::FooPlugin) and register it as a plugin. (required)
  2. Define an adapter class (ex. Droonga::Plugins::FooPlugin::Adapter) inheriting Droonga::Adapter. (required)
  3. Configure conditions to apply the adapter. (required)
  4. Define adaption logic for incoming messages as #adapt_input. (optional)
  5. Define adaption logic for outgoing messages as #adapt_output. (optional)

See also the plugin development tutorial.

How an adapter works?

An adapter works like following:

  1. The Droonga Engine starts.
    • A global instance of the adapter class (ex. Droonga::Plugins::FooPlugin::Adapter) is created and it is registered.
      • The input pattern and the output pattern are registered.
    • The Droonga Engine starts to wait for incoming messages.
  2. An incoming message is transferred from the Protocol Adapter to the Droonga Engine. Then, the adaption phase (for an incoming message) starts.
  3. After all adapters are applied, the adaption phase for an incoming message ends, and the message is transferred to the next “planning” phase.
  4. An outgoing message returns from the previous “collection” phase. Then, the adaption phase (for an outgoing message) starts.
    • The adapter’s #adapt_output is called, if the message meets following both requirements:
      • It is originated from an incoming message which was processed by the adapter itself.
      • It matches to the output matching pattern of the adapter.
    • The method can modify the given outgoing message, via its methods.
  5. After all adapters are applied, the adaption phase for an outgoing message ends, and the outgoing message is transferred to the Protocol Adapter.

As described above, the Droonga Engine creates only one global instance of the adapter class for each plugin. You should not keep stateful information for a pair of incoming and outgoing messages as instance variables of the adapter itself. Instead, you should give stateful information as a part of the incoming message body, and receive it from the body of the corresponding outgoing message.

Any error raised from the adapter is handled by the Droonga Engine itself. See also error handling.

Configurations

input_message.pattern (matching pattern, optional, default=nil)
A matching pattern for incoming messages. If no pattern (nil) is given, any message is regarded as “matched”.
output_message.pattern (matching pattern, optional, default=nil)
A matching pattern for outgoing messages. If no pattern (nil) is given, any message is regarded as “matched”.

Classes and methods

Droonga::Adapter

This is the common base class of any adapter. Your plugin’s adapter class must inherit this.

#adapt_input(input_message)

This method receives a Droonga::InputMessage wrapped incoming message. You can modify the incoming message via its methods.

In this base class, this method is defined as just a placeholder and it does nothing. To modify incoming messages, you have to override it by yours, like following:

module Droonga::Plugins::QueryFixer
  class Adapter < Droonga::Adapter
    def adapt_input(input_message)
      input_message.body["query"] = "fixed query"
    end
  end
end

#adapt_output(output_message)

This method receives a Droonga::OutputMessage wrapped outgoing message. You can modify the outgoing message via its methods.

In this base class, this method is defined as just a placeholder and it does nothing. To modify outgoing messages, you have to override it by yours, like following:

module Droonga::Plugins::ErrorConcealer
  class Adapter < Droonga::Adapter
    def adapt_output(output_message)
      output_message.status_code = Droonga::StatusCode::OK
    end
  end
end

Droonga::InputMessage

#type, #type=(type)

This returns the "type" of the incoming message.

You can override it by assigning a new string value, like:

module Droonga::Plugins::MySearch
  class Adapter < Droonga::Adapter
    input_message.pattern = ["type", :equal, "my-search"]

    def adapt_input(input_message)
      p input_message.type
      # => "my-search"
      #    This message will be handled by a plugin
      #    for the custom "my-search" type.

      input_message.type = "search"

      p input_message.type
      # => "search"
      #    The messge type (type) is changed.
      #    This message will be handled by the "search" plugin,
      #    as a regular search request.
    end
  end
end

#body, #body=(body)

This returns the "body" of the incoming message.

You can override it by assigning a new value, partially or fully. For example:

module Droonga::Plugins::MinimumLimit
  class Adapter < Droonga::Adapter
    input_message.pattern = ["type", :equal, "search"]

    MAXIMUM_LIMIT = 10

    def adapt_input(input_message)
      input_message.body["queries"].each do |name, query|
        query["output"] ||= {}
        query["output"]["limit"] ||= MAXIMUM_LIMIT
        query["output"]["limit"] = [query["output"]["limit"], MAXIMUM_LIMIT].min
      end
      # Now, all queries have "output.limit=10".
    end
  end
end

Another case:

module Droonga::Plugins::MySearch
  class Adapter < Droonga::Adapter
    input_message.pattern = ["type", :equal, "my-search"]

    def adapt_input(input_message)
      # Extract the query string from the custom type message.
      query_string = input_message["body"]["query"]

      # Construct internal search request for the "search" type.
      input_message.type = "search"
      input_message.body = {
        "queries" => {
          "source"    => "Store",
          "condition" => {
            "query"   => query_string,
            "matchTo" => ["name"],
          },
          "output" => {
            "elements" => ["records"],
            "limit"    => 10,
          },
        },
      }
      # Now, both "type" and "body" are completely replaced.
    end
  end
end

Droonga::OutputMessage

#status_code, #status_code=(status_code)

This returns the "statusCode" of the outgoing message.

You can override it by assigning a new status code. For example:

module Droonga::Plugins::ErrorConcealer
  class Adapter < Droonga::Adapter
    input_message.pattern = ["type", :equal, "search"]

    def adapt_output(output_message)
      unless output_message.status_code == StatusCode::InternalServerError
        output_message.status_code = Droonga::StatusCode::OK
        output_message.body = {}
        output_message.errors = nil
        # Now any internal server error is ignored and clients
        # receive regular responses.
      end
    end
  end
end

#errors, #errors=(errors)

This returns the "errors" of the outgoing message.

You can override it by assigning new error information, partially or fully. For example:

module Droonga::Plugins::ErrorExporter
  class Adapter < Droonga::Adapter
    input_message.pattern = ["type", :equal, "search"]

    def adapt_output(output_message)
      output_message.errors.delete(secret_database)
      # Delete error information from secret database

      output_message.body["errors"] = {
        "records" => output_message.errors.collect do |database, error|
          {
            "database" => database,
            "error" => error
          }
        end,
      }
      # Convert error informations to a fake search result named "errors".
    end
  end
end

#body, #body=(body)

This returns the "body" of the outgoing message.

You can override it by assigning a new value, partially or fully. For example:

module Droonga::Plugins::SponsoredSearch
  class Adapter < Droonga::Adapter
    input_message.pattern = ["type", :equal, "search"]

    def adapt_output(output_message)
      output_message.body.each do |name, result|
        next unless result["records"]
        result["records"].unshift(sponsored_entry)
      end
      # Now all search results include sponsored entry.
    end

    def sponsored_entry
      {
        "title"=> "SALE!",
        "url"=>   "http://..."
      }
    end
  end
end