Creating a REST plugin

In the previous recipe we read how to build an analyzer plugin that extends the query capabilities of Elasticsearch. In this recipe, we will see how to create one of the most common Elasticsearch plugins.

This kind of plugin allows the standard REST calls to be extended with custom ones to easily improve the capabilities of Elasticsearch.

In this recipe, we will see how to define a REST entry-point and create its action; in the next one, we'll see how to execute this action distributed in shards.

Getting ready

You need an up-and-running Elasticsearch installation as we described in the Downloading and installing Elasticsearch recipe in Chapter 2, Downloading and Setup.

A Maven tool, or an IDE that supports Java programming, such as Eclipse or IntelliJ IDEA. The code for this recipe is available in the chapter17/rest_plugin directory.

How to do it...

To create a REST entry-point, we need to create the action and then register it in the plugin. We will perform the following steps:

  1. We create a REST simple action (RestSimpleAction.java):
            ... 
            public class RestSimpleAction extends BaseRestHandler { 
                @Inject 
                public RestSimpleAction(Settings settings, Client client,   
                RestController controller) { 
                    super(settings); 
                    controller.registerHandler(POST, "/_simple", this); 
                    controller.registerHandler(POST, "/{index}/_simple", 
                    this); 
                    controller.registerHandler(POST, "/_simple/{field}",  
                    this); 
                    controller.registerHandler(GET, "/_simple", this); 
                    controller.registerHandler(GET, "/{index}/_simple",  
                    this); 
                    controller.registerHandler(GET, "/_simple/{field}", 
                    this); 
                } 
     
                @Override 
                protected RestChannelConsumer prepareRequest(RestRequest   
                request, NodeClient client) throws IOException { 
                    final SimpleRequest simpleRequest = new   
                    SimpleRequest(Strings.splitStringByCommaToArray
                    (request.param("index"))); 
                    simpleRequest.setField(request.param("field")); 
                    return channel -> client.execute(SimpleAction.INSTANCE,  
                    simpleRequest, new RestBuilderListener<SimpleResponse>      
                    (channel){ 
                @Override 
                public RestResponse buildResponse(SimpleResponse 
                simpleResponse, XContentBuilder builder) throws Exception { 
                            try { 
                                builder.startObject(); 
                                builder.field("ok", true); 
                                builder.array("terms",   
                                simpleResponse.getSimple().toArray()); 
                                builder.endObject(); 
     
                            } catch (Exception e) { 
                                onFailure(e); 
                            } 
                            return new BytesRestResponse(OK, builder); 
                        } 
                    }); 
                } 
            } 
    
  2. We need to register it in the plugin with the following lines:
           public class RestPlugin extends Plugin implements ActionPlugin { 
     
             @Override 
             public List<Class<? extends RestHandler>> getRestHandlers() { 
                 return singletonList(RestSimpleAction.class); 
             } 
     
             @Override 
             public List<ActionHandler<? extends ActionRequest<?>, ?   
             extends ActionResponse>> getActions() { 
                 return singletonList(new ActionHandler<>
                 (SimpleAction.INSTANCE, TransportSimpleAction.class)); 
                } 
     
            } 
    
  3. Now we can build the plugin via the mvn package and manually install the ZIP. If we restart the Elasticsearch server, we should see the plugin loaded:
            [...][INFO ][o.e.n.Node               ] [es] initializing ... 
            [...][INFO ][o.e.e.NodeEnvironment    ] [es] using [1] data    
            paths, mounts [[/ (/dev/disk1)]], net usable_space [24.8gb], 
            net total_space [930.7gb], spins? [unknown], types [hfs] 
            [...][INFO ][o.e.e.NodeEnvironment    ] [es] heap size [1.9gb],     
            compressed ordinary object pointers [true] 
            [...][INFO ][o.e.n.Node               ] [es] version[5.0.2],   
            pid[46225], build[f6b4951/2016-11-24T10:07:18.101Z], OS[Mac OS 
            X/10.12.1/x86_64], JVM[Oracle Corporation/Java HotSpot(TM) 64-
            Bit Server VM/1.8.0_101/25.101-b13] 
            [...][INFO ][o.e.p.PluginsService     ] [es] loaded module 
            [aggs-matrix-stats] 
            [...][INFO ][o.e.p.PluginsService     ] [es] loaded module    
            [ingest-common] 
            [...][INFO ][o.e.p.PluginsService     ] [es] loaded module   
            [lang-expression] 
            [...][INFO ][o.e.p.PluginsService     ] [es] loaded module 
            [lang-groovy] 
            [...][INFO ][o.e.p.PluginsService     ] [es] loaded module  
            [lang-mustache] 
            [...][INFO ][o.e.p.PluginsService     ] [es] loaded module    
            [lang-painless] 
            [...][INFO ][o.e.p.PluginsService     ] [es] loaded module    
            [percolator] 
            [...][INFO ][o.e.p.PluginsService     ] [es] loaded module   
            [reindex] 
            [...][INFO ][o.e.p.PluginsService     ] [es] loaded module   
            [transport-netty3] 
            [...][INFO ][o.e.p.PluginsService     ] [es] loaded module   
            [transport-netty4] 
            [...][INFO ][o.e.p.PluginsService     ] [es] loaded plugin   
            [simple-plugin] 
            [...][INFO ][o.e.n.Node               ] [es] initialized 
            [...][INFO ][o.e.n.Node               ] [es] starting ... 
    
  4. We can test out custom REST via curl:
            curl 'http://127.0.0.1:9200/_simple?field=mytest&pretty' 
    
  5. The result will be something similar to:
            { 
              "ok" : true, 
              "terms" : [ 
                "mytest_[test-index][0]", 
                "mytest_[test-index][3]", 
                "mytest_[test-index][4]", 
                "mytest_[test-index][1]", 
                "mytest_[test-index][2]" 
              ] 
            } 
    

How it works...

Adding a REST action is very easy: We need to create a RestXXXAction class that handles the calls.

The rest action is derived from the BaseRestHandler class and needs to implement the handleRequest method.

The constructor is very important. So let's start by writing:

@Inject 
public RestSimpleAction(Settings settings, RestController controller) 

Its signature usually injects via Guice (a lightweight dependency injection framework very popular in the Java ecosystem. See the library homepage for more details at https://code.google.com/p/google-guice/) with the following parameters:

  • Settings, which can be used to load custom settings for your rest action
  • RestController, which is used to register the REST action to the controller

In the constructor of the REST action, the list of actions that must be handled is registered in the RestController:

controller.registerHandler(POST, "/_simple", this);  
... 

To register an action, some parameters must be passed to the controller:

  • The REST method (GET/POST/PUT/DELETE/HEAD/OPTIONS)
  • The URL entry-point
  • The RestHandler, usually the same class, which must answer the call

After having defined the constructor, if an action is fired, the class method prepareRequest is called:

@Override 
protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException { 

This method is the core of the REST action. It processes the request and sends back the result. The parameters passed to the method are:

  • RestRequest: The REST request that hits the Elasticsearch server
  • RestChannel: The channel used to send back the response
  • NodeClient: The client used to communicate in the cluster

The returned value is a RestChannelConsumer that is a FunctionalInterface that accepts a RestChannel- it's a simple Lambda.

A prepareRequest method is usually composed of these phases:

  • Process the REST request and build an inner Elasticsearch request object
  • Call the client with the Elasticsearch request
  • If it is okay, process the Elasticsearch response and build the resulting JSON
  • If there are errors, send back the JSON error response

In the preceding example, we created a SimpleRequest processing the request:

final SimpleRequest simpleRequest = new SimpleRequest(Strings.splitStringByCommaToArray(request.param("index"))); 
simpleRequest.setField(request.param("field")); 

As you can see, it accepts a list of indices (we split the classic comma-separated list of indices via the Strings.splitStringByCommaToArray helper) and we had the field parameter if available.

Now that we have a SimpleRequest, we can send it to the cluster and get back SimpleResponse via the Lambda closure:

return channel -> client.execute(SimpleAction.INSTANCE, simpleRequest, new RestBuilderListener<SimpleResponse>(channel){ 

client.execute accepts an action, a request, and a RestBuilderListener class that maps a future response. We can now process the response via the definition of a onResponse method.

onResponse receives a Response object that must be converted in a JSON result.

@Override 
public RestResponse buildResponse(SimpleResponse simpleResponse, XContentBuilder builder) throws Exception { 

The builder is the standard JSON XContentBuilder that we have already seen in Chapter 14, Java Integration,

After having processed the cluster response and built the JSON, we can send the REST response.

return new BytesRestResponse(OK, builder); 

Obviously, if something goes wrong during the JSON creation, an exception must be raised.

try {/* JSON building*/ 
} catch (Exception e) { 
    onFailure(e); 
} 

We will discuss SimpleRequest in the next recipe.

See also

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.202.27