View Javadoc
1   /*
2    * Copyright (c) 2002-2021, City of Paris
3    * All rights reserved.
4    *
5    * Redistribution and use in source and binary forms, with or without
6    * modification, are permitted provided that the following conditions
7    * are met:
8    *
9    *  1. Redistributions of source code must retain the above copyright notice
10   *     and the following disclaimer.
11   *
12   *  2. Redistributions in binary form must reproduce the above copyright notice
13   *     and the following disclaimer in the documentation and/or other materials
14   *     provided with the distribution.
15   *
16   *  3. Neither the name of 'Mairie de Paris' nor 'Lutece' nor the names of its
17   *     contributors may be used to endorse or promote products derived from
18   *     this software without specific prior written permission.
19   *
20   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21   * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
24   * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26   * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27   * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28   * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30   * POSSIBILITY OF SUCH DAMAGE.
31   *
32   * License 1.0
33   */
34  package fr.paris.lutece.plugins.libraryelastic.util;
35  
36  import com.fasterxml.jackson.core.JsonProcessingException;
37  import com.fasterxml.jackson.databind.ObjectMapper;
38  import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
39  
40  import fr.paris.lutece.plugins.libraryelastic.business.bulk.BulkRequest;
41  import fr.paris.lutece.plugins.libraryelastic.business.search.SearchRequest;
42  import fr.paris.lutece.plugins.libraryelastic.business.suggest.AbstractSuggestRequest;
43  import fr.paris.lutece.util.httpaccess.HttpAccessException;
44  import fr.paris.lutece.util.httpaccess.InvalidResponseStatus;
45  
46  import org.apache.commons.lang3.StringUtils;
47  import org.apache.hc.core5.http.HttpStatus;
48  
49  /**
50   * Elastic
51   */
52  public class Elastic
53  {
54      private static ObjectMapper _mapper = new ObjectMapper( ).registerModule( new JavaTimeModule( ) );
55      private ElasticConnexion _connexion;
56      private String _strServerUrl;
57  
58      /**
59       * Constructor
60       * 
61       * @param strServerUrl
62       *            The Elastic server URL
63       */
64      public Elastic( String strServerUrl )
65      {
66          _strServerUrl = strServerUrl;
67          _connexion = new ElasticConnexion( );
68      }
69  
70      /**
71       * Basic Authentification constructor
72       * 
73       * @param strServerUrl
74       *            The Elastic server URL
75       * @param strServerLogin
76       *            Login
77       * @param strServerPwd
78       *            Password
79       */
80      public Elastic( String strServerUrl, String strServerLogin, String strServerPwd )
81      {
82          _strServerUrl = strServerUrl;
83          _connexion = new ElasticConnexion( strServerLogin, strServerPwd );
84      }
85  
86      /**
87       * Create a document of given type into a given index
88       * 
89       * @param strIndex
90       *            The index
91       * @param object
92       *            The document
93       * @return The JSON response from Elastic
94       * @throws ElasticClientException
95       *             If a problem occurs connecting Elastic
96       */
97      public String create( String strIndex, Object object ) throws ElasticClientException
98      {
99          return create( strIndex, StringUtils.EMPTY, object );
100     }
101 
102     /**
103      * Create a document of given type into a given index at the given id
104      * 
105      * @param strIndex
106      *            The index
107      * @param strId
108      *            The document id
109      * @param object
110      *            The document
111      * @return The JSON response from Elastic
112      * @throws ElasticClientException
113      *             If a problem occurs connecting Elastic
114      */
115     public String create( String strIndex, String strId, Object object ) throws ElasticClientException
116     {
117         String strResponse = StringUtils.EMPTY;
118         try
119         {
120             String strJSON;
121 
122             if ( object instanceof String )
123             {
124                 strJSON = (String) object;
125             }
126             else
127             {
128                 strJSON = _mapper.writeValueAsString( object );
129             }
130 
131             String strURI = getURI( strIndex ) + "_doc" + Constants.URL_PATH_SEPARATOR + strId;
132             strResponse = _connexion.POST( strURI, strJSON );
133         }
134         catch( JsonProcessingException | HttpAccessException ex )
135         {
136             throw new ElasticClientException( "ElasticLibrary : Error creating object : " + ex.getMessage( ), ex );
137         }
138         return strResponse;
139     }
140 
141     /**
142      * perform a bulk indexing of documents : this is used for indexing thousand doc with one HTTP call
143      * 
144      * @param strIndex
145      *            the elk index name
146      * @param bulkRequest
147      *            the bulkRequest
148      * @return the reponse of Elk server
149      * @throws ElasticClientException
150      */
151     public String createByBulk( String strIndex, BulkRequest bulkRequest ) throws ElasticClientException
152     {
153         String strResponse = StringUtils.EMPTY;
154         try
155         {
156             String strURI = getURI( strIndex ) + Constants.PATH_QUERY_BULK;
157             String strBulkBody = bulkRequest.getBulkBody( _mapper );
158             strResponse = _connexion.POST( strURI, strBulkBody );
159         }
160         catch( JsonProcessingException | HttpAccessException ex )
161         {
162             throw new ElasticClientException( "ElasticLibrary : Error processing bulking request : " + ex.getMessage( ), ex );
163         }
164         return strResponse;
165     }
166 
167     /**
168      * Delete a given index
169      * 
170      * @param strIndex
171      *            The index
172      * @return The JSON response from Elastic
173      * @throws ElasticClientException
174      *             If a problem occurs connecting Elastic
175      */
176     public String deleteIndex( String strIndex ) throws ElasticClientException
177     {
178         String strResponse = StringUtils.EMPTY;
179         try
180         {
181             String strURI = getURI( strIndex );
182             strResponse = _connexion.DELETE( strURI );
183         }
184         catch( HttpAccessException ex )
185         {
186             throw new ElasticClientException( "ElasticLibrary : Error deleting index : " + ex.getMessage( ), ex );
187         }
188         return strResponse;
189     }
190 
191     /**
192      * Delete a document based on its id in the index
193      * 
194      * @param strIndex
195      *            The index
196      * @param strId
197      *            The id
198      * @return The JSON response from Elastic
199      * @throws ElasticClientException
200      *             If a problem occurs connecting Elastic
201      */
202     public String deleteDocument( String strIndex, String strId ) throws ElasticClientException
203     {
204         String strResponse = StringUtils.EMPTY;
205         try
206         {
207             String strURI = getURI( strIndex ) + "_doc" + Constants.URL_PATH_SEPARATOR + strId;
208             strResponse = _connexion.DELETE( strURI );
209         }
210         catch( HttpAccessException ex )
211         {
212             throw new ElasticClientException( "ElasticLibrary : Error deleting document : " + ex.getMessage( ), ex );
213         }
214         return strResponse;
215     }
216 
217     /**
218      * Delete a documents by Query
219      * 
220      * @param strIndex
221      *            The index
222      * @param strQuery
223      *            The Query
224      * @return The JSON response from Elastic
225      * @throws ElasticClientException
226      *             If a problem occurs connecting Elastic
227      */
228     public String deleteByQuery( String strIndex, String strQuery ) throws ElasticClientException
229     {
230         String strResponse = StringUtils.EMPTY;
231         try
232         {
233             String strURI = getURI( strIndex ) + Constants.PATH_QUERY_DELETE_BY_QUERY;
234             strResponse = _connexion.POST( strURI, strQuery );
235         }
236         catch( HttpAccessException ex )
237         {
238             throw new ElasticClientException( "ElasticLibrary : Error deleting by query : " + ex.getMessage( ), ex );
239         }
240         return strResponse;
241     }
242 
243     /**
244      * Partial Updates to Documents
245      * 
246      * @param strIndex
247      *            The index
248      * @param strId
249      *            The document id
250      * @param object
251      *            The document
252      * @return The JSON response from Elastic
253      * @throws ElasticClientException
254      *             If a problem occurs connecting Elastic
255      */
256 
257     public String partialUpdate( String strIndex, String strId, Object object ) throws ElasticClientException
258     {
259         String strResponse = StringUtils.EMPTY;
260         try
261         {
262             String strJSON;
263             if ( object instanceof String )
264             {
265                 strJSON = (String) object;
266             }
267             else
268             {
269                 strJSON = _mapper.writeValueAsString( object );
270             }
271 
272             String json = buildJsonToPartialUpdate( strJSON );
273 
274             String strURI = getURI( strIndex ) + Constants.PATH_QUERY_UPDATE + Constants.URL_PATH_SEPARATOR + strId;
275             strResponse = _connexion.POST( strURI, json );
276         }
277         catch( JsonProcessingException | HttpAccessException ex )
278         {
279             throw new ElasticClientException( "ElasticLibrary : Error updating by query : " + ex.getMessage( ), ex );
280         }
281         return strResponse;
282     }
283 
284     /**
285      * Check if a given index exists
286      * 
287      * @param strIndex
288      *            The index
289      * @return if th index exists
290      * @throws ElasticClientException
291      *             If a problem occurs connecting Elastic
292      */
293     public boolean isExists( String strIndex ) throws ElasticClientException
294     {
295         try
296         {
297             String strURI = getURI( strIndex );
298             _connexion.GET( strURI );
299         }
300         catch( InvalidResponseStatus ex )
301         {
302             if ( ex.getResponseStatus( ) == HttpStatus.SC_NOT_FOUND )
303             {
304                 return false;
305             }
306             throw new ElasticClientException( "ElasticLibrary : Error getting index : " + ex.getMessage( ), ex );
307         }
308         catch( HttpAccessException ex )
309         {
310             throw new ElasticClientException( "ElasticLibrary : Error getting index : " + ex.getMessage( ), ex );
311         }
312         return true;
313     }
314 
315     /**
316      * Search a document of given type into a given index
317      * 
318      * @param strIndex
319      *            The index
320      * @param search
321      *            search request
322      * @return The JSON response from Elastic
323      * @throws ElasticClientException
324      *             If a problem occurs connecting Elastic
325      */
326     public String search( String strIndex, SearchRequest search ) throws ElasticClientException
327     {
328         String strResponse = StringUtils.EMPTY;
329         try
330         {
331             String strJSON = _mapper.writeValueAsString( search.mapToNode( ) );
332             String strURI = getURI( strIndex ) + Constants.PATH_QUERY_SEARCH;
333             strResponse = _connexion.POST( strURI, strJSON );
334         }
335         catch( JsonProcessingException | HttpAccessException ex )
336         {
337             throw new ElasticClientException( "ElasticLibrary : Error searching object : " + ex.getMessage( ), ex );
338         }
339         return strResponse;
340     }
341 
342     /**
343      * Search
344      * 
345      * @param strIndex
346      *            The index
347      * @param strSearchRequest
348      *            search request
349      * @return The JSON response from Elastic
350      * @throws ElasticClientException
351      *             If a problem occurs connecting Elastic
352      */
353     public String search( String strIndex, String searchRequest ) throws ElasticClientException
354     {
355         String strResponse = StringUtils.EMPTY;
356         try
357         {
358             String strURI = getURI( strIndex ) + Constants.PATH_QUERY_SEARCH;
359             strResponse = _connexion.POST( strURI, searchRequest );
360         }
361         catch( HttpAccessException ex )
362         {
363             throw new ElasticClientException( "ElasticLibrary : Error searching object : " + ex.getMessage( ), ex );
364         }
365         return strResponse;
366     }
367 
368     /**
369      * suggest a list of document of given type into a given index The suggest is done with a _search request with size set to 0 to avoid fetch in 'hits' so be
370      * careful with the JSON result
371      * 
372      * @param strIndex
373      *            The index
374      * @param suggest
375      *            suggest request
376      * @return The JSON response from Elastic
377      * @throws ElasticClientException
378      *             If a problem occurs connecting Elastic
379      */
380     public String suggest( String strIndex, AbstractSuggestRequest suggest ) throws ElasticClientException
381     {
382         String strResponse = StringUtils.EMPTY;
383         try
384         {
385             SearchRequestryelastic/business/search/SearchRequest.html#SearchRequest">SearchRequest search = new SearchRequest( );
386             search.setSize( 0 );
387             search.setSearchQuery( suggest );
388             String strJSON = _mapper.writeValueAsString( search.mapToNode( ) );
389             String strURI = getURI( strIndex ) + Constants.PATH_QUERY_SEARCH;
390             strResponse = _connexion.POST( strURI, strJSON );
391         }
392         catch( JsonProcessingException | HttpAccessException ex )
393         {
394             throw new ElasticClientException( "ElasticLibrary : Error suggesting object : " + ex.getMessage( ), ex );
395         }
396         return strResponse;
397     }
398 
399     /**
400      * suggest a list of document of given type into a given index The suggest is done with a _search request with size set to 0 to avoid fetch in 'hits' so be
401      * careful with the JSON result
402      * 
403      * @param strIndex
404      *            The index
405      * @param strJSON
406      *            suggest request
407      * @return The JSON response from Elastic
408      * @throws ElasticClientException
409      *             If a problem occurs connecting Elastic
410      */
411     public String suggest( String strIndex, String strJSON ) throws ElasticClientException
412     {
413         String strResponse = StringUtils.EMPTY;
414         try
415         {
416             String strURI = getURI( strIndex ) + Constants.PATH_QUERY_SEARCH;
417             strResponse = _connexion.POST( strURI, strJSON );
418         }
419         catch( HttpAccessException ex )
420         {
421             throw new ElasticClientException( "ElasticLibrary : Error suggesting object : " + ex.getMessage( ), ex );
422         }
423         return strResponse;
424     }
425 
426     /**
427      * 
428      * @param strIndex
429      * @param strJsonMappings
430      * @return
431      * @throws ElasticClientException
432      */
433     public String createMappings( String strIndex, String strJsonMappings ) throws ElasticClientException
434     {
435         String strResponse = StringUtils.EMPTY;
436         try
437         {
438             String strURI = getURI( strIndex );
439             strResponse = _connexion.PUT( strURI, strJsonMappings );
440         }
441         catch( HttpAccessException ex )
442         {
443             throw new ElasticClientException( "ElasticLibrary : Error creating mappings : " + ex.getMessage( ), ex );
444         }
445         return strResponse;
446 
447     }
448 
449     /**
450      * Build the URI of a given index
451      * 
452      * @param strIndex
453      *            The index name
454      * @return The URI
455      */
456     private String getURI( String strIndex )
457     {
458         String strURI = _strServerUrl;
459         strURI = ( strURI.endsWith( Constants.URL_PATH_SEPARATOR ) ) ? strURI : strURI + Constants.URL_PATH_SEPARATOR;
460         if ( StringUtils.isNotEmpty( strIndex ) )
461         {
462             strURI = ( ( strIndex.endsWith( Constants.URL_PATH_SEPARATOR ) ) ? strURI + strIndex : strURI + strIndex + Constants.URL_PATH_SEPARATOR );
463         }
464 
465         return strURI;
466     }
467 
468     /**
469      * Build Json to partial update
470      * 
471      * @param strJson
472      *            The json
473      * @return json
474      */
475     private String buildJsonToPartialUpdate( String strJson )
476     {
477 
478         StringBuilder sbuilder = new StringBuilder( );
479         sbuilder.append( "{ \"doc\" : " );
480         sbuilder.append( strJson );
481         sbuilder.append( "}" );
482 
483         return sbuilder.toString( );
484     }
485 }