1 /******************************************************************************
2  *
3  * Project:  MapServer
4  * Purpose:  MapCache tile caching support file: ElasticSearch dimension support
5  * Author:   Jerome Boue and the MapServer team.
6  *
7  ******************************************************************************
8  * Copyright (c) 1996-2018 Regents of the University of Minnesota.
9  *
10  * Permission is hereby granted, free of charge, to any person obtaining a
11  * copy of this software and associated documentation files (the "Software"),
12  * to deal in the Software without restriction, including without limitation
13  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
14  * and/or sell copies of the Software, and to permit persons to whom the
15  * Software is furnished to do so, subject to the following conditions:
16  *
17  * The above copyright notice and this permission notice shall be included in
18  * all copies of this Software or works derived from this Software.
19  *
20  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
21  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
23  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
26  * DEALINGS IN THE SOFTWARE.
27  *****************************************************************************/
28 
29 #include "mapcache.h"
30 #include <apr_strings.h>
31 #include <float.h>
32 #include "cJSON.h"
33 
34 typedef struct mapcache_dimension_elasticsearch mapcache_dimension_elasticsearch;
35 
36 struct mapcache_dimension_elasticsearch {
37   mapcache_dimension dimension;
38   mapcache_http *http;
39   char *get_values_for_entry_query;
40   char *get_all_values_query;
41   char *response_format_to_validate_query;
42   char *response_format_to_list_query;
43 };
44 
45 
_mapcache_dimension_elasticsearch_parse_xml(mapcache_context * ctx,mapcache_dimension * dim,ezxml_t node)46 static void _mapcache_dimension_elasticsearch_parse_xml(mapcache_context *ctx, mapcache_dimension *dim, ezxml_t node)
47 {
48   mapcache_dimension_elasticsearch *dimension;
49   ezxml_t child;
50 
51   dimension = (mapcache_dimension_elasticsearch *)dim;
52 
53   child = ezxml_child(node,"http");
54   if (child) {
55     dimension->http = mapcache_http_configuration_parse_xml(ctx,child);
56   } else {
57     ctx->set_error(ctx,400,"elasticsearch dimension \"%s\" has no <http> node",dim->name);
58     return;
59   }
60   child = ezxml_child(node,"validate_query");
61   if(child) {
62     dimension->get_values_for_entry_query = apr_pstrdup(ctx->pool, child->txt);
63   } else {
64     ctx->set_error(ctx,400,"elasticsearch dimension \"%s\" has no <validate_query> node", dim->name);
65     return;
66   }
67   child = ezxml_child(node,"list_query");
68   if(child) {
69     dimension->get_all_values_query = apr_pstrdup(ctx->pool, child->txt);
70   } else {
71     ctx->set_error(ctx,400,"elasticsearch dimension \"%s\" has no <list_query> node", dim->name);
72     return;
73   }
74   child = ezxml_child(node,"validate_response");
75   if(child) {
76     dimension->response_format_to_validate_query = apr_pstrdup(ctx->pool, child->txt);
77   } else {
78     ctx->set_error(ctx,400,"elasticsearch dimension \"%s\" has no <validate_response> node", dim->name);
79     return;
80   }
81   child = ezxml_child(node,"list_response");
82   if(child) {
83     dimension->response_format_to_list_query = apr_pstrdup(ctx->pool, child->txt);
84   } else {
85     ctx->set_error(ctx,400,"elasticsearch dimension \"%s\" has no <list_response> node", dim->name);
86     return;
87   }
88 }
89 
90 
_mapcache_dimension_elasticsearch_bind_parameters(mapcache_context * ctx,const char * req,const char * value,time_t start,time_t end,mapcache_tileset * tileset,mapcache_extent * extent,mapcache_grid * grid)91 static char * _mapcache_dimension_elasticsearch_bind_parameters(mapcache_context *ctx, const char * req, const char * value,
92   time_t start, time_t end, mapcache_tileset *tileset, mapcache_extent *extent, mapcache_grid *grid)
93 {
94   char * res;
95   char * val = NULL;
96 
97   if (value) {
98     // Sanitize dimension value for safe insertion in JSON request
99     cJSON * json_str;
100     json_str = cJSON_CreateString(value);
101     val = cJSON_Print(json_str);
102     if (val) {
103       // Discard double quotes while copying
104       val = apr_pstrndup(ctx->pool,val+1,strlen(val)-2);
105     }
106   }
107   res = mapcache_util_str_replace_all(ctx->pool,req,":dim",val);
108 
109   if (tileset) res = mapcache_util_str_replace_all(ctx->pool,res,":tileset",tileset->name);
110 
111   if (grid) res = mapcache_util_str_replace_all(ctx->pool,res,":gridsrs",grid->srs);
112 
113   res = mapcache_util_dbl_replace_all(ctx->pool,res,":minx",extent?extent->minx:-DBL_MAX);
114   res = mapcache_util_dbl_replace_all(ctx->pool,res,":miny",extent?extent->miny:-DBL_MAX);
115   res = mapcache_util_dbl_replace_all(ctx->pool,res,":maxx",extent?extent->maxx:DBL_MAX);
116   res = mapcache_util_dbl_replace_all(ctx->pool,res,":maxy",extent?extent->maxy:DBL_MAX);
117 
118   res = mapcache_util_str_replace_all(ctx->pool,res,":start_timestamp",apr_psprintf(ctx->pool,"%ld",start*1000));
119   res = mapcache_util_str_replace_all(ctx->pool,res,":end_timestamp",apr_psprintf(ctx->pool,"%ld",end*1000));
120 
121   return res;
122 }
123 
124 
_mapcache_dimension_elasticsearch_do_query(mapcache_context * ctx,mapcache_http * http,const char * query,const char * response_format)125 static apr_array_header_t * _mapcache_dimension_elasticsearch_do_query(mapcache_context * ctx, mapcache_http * http, const char * query, const char * response_format)
126 {
127   char * resp;
128   cJSON * json_resp;
129   cJSON * json_fmt;
130   cJSON * index;
131   cJSON * extract;
132   cJSON * item;
133   cJSON * sub;
134 
135   apr_array_header_t *table = apr_array_make(ctx->pool,0,sizeof(char*));
136 
137   // Build and execute HTTP request
138   mapcache_buffer * buffer = mapcache_buffer_create(1,ctx->pool);
139   mapcache_http * req = mapcache_http_clone(ctx,http);
140   req->post_body = apr_pstrdup(ctx->pool,query);
141   req->post_len = strlen(req->post_body);
142   mapcache_http_do_request(ctx,req,buffer,NULL,NULL);
143   if (GC_HAS_ERROR(ctx)) {
144     return table;
145   }
146   mapcache_buffer_append(buffer,1,"");
147   resp = (char*)buffer->buf;
148 
149   // Parse response format: this should be a list of keys or integers
150   json_fmt = cJSON_Parse(response_format);
151   if (!json_fmt) {
152     ctx->set_error(ctx,500,"elasticsearch dimension backend failed on response format: %s",response_format);
153     goto cleanup;
154   }
155 
156   // Parse response
157   json_resp = cJSON_Parse(resp);
158   if (!json_resp) {
159     ctx->set_error(ctx,500,"elasticsearch dimension backend failed on query response: %s",resp);
160     goto cleanup;
161   }
162 
163   // Analyze response according to response format
164   extract = json_resp;
165   cJSON_ArrayForEach(index,json_fmt) {
166     char * key = index->valuestring;
167     int    pos = index->valueint;
168 
169     // Key on Dict => return Dict[Key]
170     if (cJSON_IsString(index) && cJSON_IsObject(extract)) {
171       extract = cJSON_GetObjectItem(extract,key);
172 
173     // Index on List of lists => return [ list[Index] for list in List ]
174     } else if (cJSON_IsNumber(index) && cJSON_IsArray(extract)
175                && cJSON_IsArray(cJSON_GetArrayItem(extract,0))) {
176       sub = cJSON_CreateArray();
177       cJSON_ArrayForEach(item,extract) {
178         cJSON * value = cJSON_GetArrayItem(item,pos);
179         if (value) cJSON_AddItemToArray(sub,cJSON_Duplicate(value,1));
180       }
181       extract = sub;
182 
183     // Index on List => return List[Index]
184     } else if (cJSON_IsNumber(index) && cJSON_IsArray(extract)) {
185       extract = cJSON_GetArrayItem(extract,pos);
186 
187     // Key on List => return [ Dict[Key] for Dict in List ]
188     } else if (cJSON_IsString(index) && cJSON_IsArray(extract)) {
189       sub = cJSON_CreateArray();
190       cJSON_ArrayForEach(item,extract) {
191         cJSON * value = cJSON_GetObjectItem(item,key);
192         if (value) cJSON_AddItemToArray(sub,cJSON_Duplicate(value,1));
193       }
194       extract = sub;
195 
196     } else {
197         ctx->set_error(ctx,500,"elasticsearch dimension backend failed on query response: %s",resp);
198         goto cleanup;
199     }
200 
201   }
202 
203   if (!cJSON_IsArray(extract)) {
204     item = extract;
205     extract = cJSON_CreateArray();
206     cJSON_AddItemToArray(extract,item);
207   }
208   cJSON_ArrayForEach(item,extract) {
209     APR_ARRAY_PUSH(table,char*) = apr_pstrdup(ctx->pool, cJSON_GetStringValue(item));
210   }
211 
212 cleanup:
213   return table;
214 }
215 
216 
_mapcache_dimension_elasticsearch_get_all_entries(mapcache_context * ctx,mapcache_dimension * dim,mapcache_tileset * tileset,mapcache_extent * extent,mapcache_grid * grid)217 static apr_array_header_t * _mapcache_dimension_elasticsearch_get_all_entries(mapcache_context *ctx, mapcache_dimension *dim,
218   mapcache_tileset *tileset, mapcache_extent *extent, mapcache_grid *grid)
219 {
220   mapcache_dimension_elasticsearch *dimension = (mapcache_dimension_elasticsearch*)dim;
221   char * req = dimension->get_all_values_query;
222   char * resp_fmt = dimension->response_format_to_list_query;
223   char * res = _mapcache_dimension_elasticsearch_bind_parameters(ctx,req,NULL,0,0,tileset,extent,grid);
224   apr_array_header_t *table = _mapcache_dimension_elasticsearch_do_query(ctx,dimension->http,res,resp_fmt);
225 
226   return table;
227 }
228 
229 
_mapcache_dimension_elasticsearch_get_entries_for_time_range(mapcache_context * ctx,mapcache_dimension * dim,const char * dim_value,time_t start,time_t end,mapcache_tileset * tileset,mapcache_extent * extent,mapcache_grid * grid)230 static apr_array_header_t* _mapcache_dimension_elasticsearch_get_entries_for_time_range(mapcache_context *ctx,
231   mapcache_dimension *dim, const char *dim_value, time_t start, time_t end,
232   mapcache_tileset *tileset, mapcache_extent *extent, mapcache_grid *grid)
233 {
234   mapcache_dimension_elasticsearch *dimension = (mapcache_dimension_elasticsearch*)dim;
235   char * req = dimension->get_values_for_entry_query;
236   char * resp_fmt = dimension->response_format_to_validate_query;
237   char * res = _mapcache_dimension_elasticsearch_bind_parameters(ctx,req,dim_value,start,end,tileset,extent,grid);
238   apr_array_header_t *table = _mapcache_dimension_elasticsearch_do_query(ctx,dimension->http,res,resp_fmt);
239 
240   return table;
241 }
242 
243 
_mapcache_dimension_elasticsearch_get_entries_for_value(mapcache_context * ctx,mapcache_dimension * dim,const char * value,mapcache_tileset * tileset,mapcache_extent * extent,mapcache_grid * grid)244 static apr_array_header_t* _mapcache_dimension_elasticsearch_get_entries_for_value(mapcache_context *ctx,
245   mapcache_dimension *dim, const char *value, mapcache_tileset *tileset, mapcache_extent *extent, mapcache_grid *grid)
246 {
247   return _mapcache_dimension_elasticsearch_get_entries_for_time_range(ctx,dim,value,0,0,tileset,extent,grid);
248 }
249 
250 
mapcache_dimension_elasticsearch_create(mapcache_context * ctx,apr_pool_t * pool)251 mapcache_dimension* mapcache_dimension_elasticsearch_create(mapcache_context *ctx, apr_pool_t *pool)
252 {
253   mapcache_dimension_elasticsearch *dimension = apr_pcalloc(pool, sizeof(mapcache_dimension_elasticsearch));
254   dimension->dimension.type = MAPCACHE_DIMENSION_ELASTICSEARCH;
255   dimension->http = NULL;
256   dimension->dimension._get_entries_for_value = _mapcache_dimension_elasticsearch_get_entries_for_value;
257   dimension->dimension._get_entries_for_time_range = _mapcache_dimension_elasticsearch_get_entries_for_time_range;
258   dimension->dimension.configuration_parse_xml = _mapcache_dimension_elasticsearch_parse_xml;
259   dimension->dimension.get_all_entries = _mapcache_dimension_elasticsearch_get_all_entries;
260   dimension->dimension.get_all_ogc_formatted_entries = _mapcache_dimension_elasticsearch_get_all_entries;
261   return (mapcache_dimension*)dimension;
262 }
263 
264 
265 /* vim: ts=2 sts=2 et sw=2
266 */
267