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