Fix: Destroy utf8 buffer with utf-8 buffer destroy
[yaz-moved-to-github.git] / src / solr.c
1 /* This file is part of the YAZ toolkit.
2  * Copyright (C) 1995-2010 Index Data
3  * See the file LICENSE for details.
4  */
5 /**
6  * \file solr.c
7  * \brief Implements SOAP Webservice decoding/encoding
8  */
9 #if HAVE_CONFIG_H
10 #include <config.h>
11 #endif
12
13 #include <stdlib.h>
14 #include <assert.h>
15 #include <yaz/srw.h>
16 #include <yaz/matchstr.h>
17 #include <yaz/yaz-iconv.h>
18 #include <yaz/log.h>
19 #include <yaz/facet.h>
20 #include <yaz/wrbuf.h>
21
22 #include "sru-p.h"
23
24 #define SOLR_MAX_PARAMETERS  100
25
26 #if YAZ_HAVE_XML2
27 #include <libxml/parser.h>
28 #include <libxml/tree.h>
29
30 const char *xml_node_attribute_value_get(xmlNodePtr ptr, const char *node_name, const char *attribute_name) {
31
32     struct _xmlAttr *attr;
33     // check if the node name matches
34     if (strcmp((const char*) ptr->name, node_name))
35         return 0;
36     // check if the attribute name and return the value
37     for (attr = ptr->properties; attr; attr = attr->next)
38         if (attr->children && attr->children->type == XML_TEXT_NODE) {
39             if (!strcmp((const char *) attr->name, attribute_name))
40                 return (const char *) attr->children->content;
41         }
42     return 0;
43 }
44
45
46 static int match_xml_node_attribute(xmlNodePtr ptr, const char *node_name, const char *attribute_name, const char *value)
47 {
48     const char *attribute_value;
49     // check if the node name matches
50     if (strcmp((const char*) ptr->name, node_name))
51         return 0;
52     attribute_value = xml_node_attribute_value_get(ptr, node_name, attribute_name);
53     if (attribute_value && !strcmp(attribute_value, value))
54         return 1;
55     return 0;
56 }
57
58 static void yaz_solr_decode_result_docs(ODR o, xmlNodePtr ptr, Odr_int start, Z_SRW_searchRetrieveResponse *sr) {
59     xmlNodePtr node;
60     int offset = 0;
61     int i = 0;
62
63     sr->num_records = 0;
64     for (node = ptr->children; node; node = node->next)
65         if (node->type == XML_ELEMENT_NODE)
66             sr->num_records++;
67
68     sr->records = odr_malloc(o, sizeof(*sr->records) * sr->num_records);
69
70     for (node = ptr->children; node; node = node->next)
71     {
72         if (node->type == XML_ELEMENT_NODE)
73         {
74             Z_SRW_record *record = sr->records + i;
75             xmlBufferPtr buf = xmlBufferCreate();
76             xmlNode *tmp = xmlCopyNode(node, 1);
77
78             xmlNodeDump(buf, tmp->doc, tmp, 0, 0);
79
80             xmlFreeNode(tmp);
81
82             record->recordSchema = 0;
83             record->recordPacking = Z_SRW_recordPacking_XML;
84             record->recordData_len = buf->use;
85             record->recordData_buf = odr_malloc(o, buf->use + 1);
86             memcpy(record->recordData_buf, buf->content, buf->use);
87             record->recordData_buf[buf->use] = '\0';
88             // TODO Solve the real problem in zoom-sru, that doesnt work with 0-based indexes.
89             // Work-around: Making the recordPosition 1-based.
90             record->recordPosition = odr_intdup(o, start + offset + 1);
91
92             xmlBufferFree(buf);
93
94             offset++;
95             i++;
96         }
97     }
98 }
99
100 static int  yaz_solr_decode_result(ODR o, xmlNodePtr ptr, Z_SRW_searchRetrieveResponse *sr) {
101     Odr_int start = 0;
102     struct _xmlAttr *attr;
103     for (attr = ptr->properties; attr; attr = attr->next)
104         if (attr->children && attr->children->type == XML_TEXT_NODE) {
105             if (!strcmp((const char *) attr->name, "numFound")) {
106                 sr->numberOfRecords = odr_intdup(o, odr_atoi(
107                         (const char *) attr->children->content));
108             } 
109             else if (!strcmp((const char *) attr->name, "start")) {
110                 start = odr_atoi((const char *) attr->children->content);
111             }
112         }
113     if (sr->numberOfRecords && *sr->numberOfRecords > 0)
114         yaz_solr_decode_result_docs(o, ptr, start, sr);
115     if (sr->numberOfRecords)
116         return 0;
117     return -1;
118 }
119
120 static Z_AttributeList *yaz_solr_use_atttribute_create(ODR o, const char *name) {
121     Z_AttributeList *attributes= (Z_AttributeList *) odr_malloc(o, sizeof(*attributes));
122     Z_AttributeElement ** elements;
123     attributes->num_attributes = 1;
124     /* TODO check on name instead
125     if (!attributes->num_attributes) {
126         attributes->attributes = (Z_AttributeElement**)odr_nullval();
127         return attributes;
128     }
129     */
130     elements = (Z_AttributeElement**) odr_malloc (o, attributes->num_attributes * sizeof(*elements));
131     elements[0] = (Z_AttributeElement*)odr_malloc(o,sizeof(**elements));
132     elements[0]->attributeType = odr_malloc(o, sizeof(*elements[0]->attributeType));
133    *elements[0]->attributeType = 1;
134     elements[0]->attributeSet = odr_nullval();
135     elements[0]->which = Z_AttributeValue_complex;
136     elements[0]->value.complex = (Z_ComplexAttribute *) odr_malloc(o, sizeof(Z_ComplexAttribute));
137     elements[0]->value.complex->num_list = 1;
138     elements[0]->value.complex->list = (Z_StringOrNumeric **) odr_malloc(o, 1 * sizeof(Z_StringOrNumeric *));
139     elements[0]->value.complex->list[0] = (Z_StringOrNumeric *) odr_malloc(o, sizeof(Z_StringOrNumeric));
140     elements[0]->value.complex->list[0]->which = Z_StringOrNumeric_string;
141     elements[0]->value.complex->list[0]->u.string = (Z_InternationalString *) odr_strdup(o, name);
142     elements[0]->value.complex->semanticAction = 0;
143     elements[0]->value.complex->num_semanticAction = 0;
144     attributes->attributes = elements;
145     return attributes;
146 }
147
148
149 static const char *get_facet_term_count(xmlNodePtr node, int *freq) {
150
151     const char *term = xml_node_attribute_value_get(node, "int", "name");
152     xmlNodePtr child;
153     WRBUF wrbuf = wrbuf_alloc();
154     if (!term)
155         return term;
156
157     for (child = node->children; child ; child = child->next) {
158         if (child->type == XML_TEXT_NODE)
159         wrbuf_puts(wrbuf, (const char *) child->content);
160     }
161     *freq = atoi(wrbuf_cstr(wrbuf));
162     wrbuf_destroy(wrbuf);
163     return term;
164 }
165
166 Z_FacetField *yaz_solr_decode_facet_field(ODR o, xmlNodePtr ptr, Z_SRW_searchRetrieveResponse *sr)
167
168 {
169     Z_AttributeList *list;
170     Z_FacetField *facet_field;
171     int num_terms = 0;
172     int index = 0;
173     xmlNodePtr node;
174     // USE attribute
175     const char* name = xml_node_attribute_value_get(ptr, "lst", "name");
176     char *pos = strstr(name, "_exact");
177     /* HACK */
178     if (pos) {
179         pos[0] = 0;
180     }
181     list = yaz_solr_use_atttribute_create(o, name);
182     for (node = ptr->children; node; node = node->next) {
183         num_terms++;
184     }
185     facet_field = facet_field_create(o, list, num_terms);
186     index = 0;
187     for (node = ptr->children; node; node = node->next) {
188         int count = 0;
189         const char *term = get_facet_term_count(node, &count);
190         facet_field_term_set(o, facet_field, facet_term_create(o, term_create(o, term), count), index);
191         index++;
192     }
193     return facet_field;
194 }
195
196 static int yaz_solr_decode_facet_counts(ODR o, xmlNodePtr root, Z_SRW_searchRetrieveResponse *sr) {
197     xmlNodePtr ptr;
198     for (ptr = root->children; ptr; ptr = ptr->next)
199     {
200         if (match_xml_node_attribute(ptr, "lst", "name", "facet_fields"))
201         {
202             xmlNodePtr node;
203             Z_FacetList *facet_list;
204             int num_facets = 0;
205             for (node = ptr->children; node; node= node->next)
206             {
207                 num_facets++;
208             }
209             facet_list = facet_list_create(o, num_facets);
210             num_facets = 0;
211             for (node = ptr->children; node; node= node->next)
212             {
213                 facet_list_field_set(o, facet_list, yaz_solr_decode_facet_field(o, node, sr), num_facets);
214                 num_facets++;
215             }
216             sr->facetList = facet_list;
217             break;
218         }
219     }
220     return 0;
221 }
222
223 #endif
224
225 int yaz_solr_decode_response(ODR o, Z_HTTP_Response *hres, Z_SRW_PDU **pdup)
226 {
227 #if YAZ_HAVE_XML2
228     const char *content_buf = hres->content_buf;
229     int content_len = hres->content_len;
230     xmlDocPtr doc = xmlParseMemory(content_buf, content_len);
231     int ret = 0;
232     xmlNodePtr ptr = 0;
233     Z_SRW_PDU *pdu = yaz_srw_get(o, Z_SRW_searchRetrieve_response);
234     Z_SRW_searchRetrieveResponse *sr = pdu->u.response;
235
236     if (!doc)
237     {
238         ret = -1;
239     }
240     if (doc)
241     {
242         xmlNodePtr root = xmlDocGetRootElement(doc);
243         if (!root)
244         {
245             ret = -1;
246         }
247         else if (strcmp((const char *) root->name, "response"))
248         {
249             ret = -1;
250         }
251         else
252         {
253             /** look for result (required) and facets node (optional) */
254             int rc_result = -1;
255             int rc_facets = 0;
256             for (ptr = root->children; ptr; ptr = ptr->next)
257             {
258                 if (ptr->type == XML_ELEMENT_NODE &&
259                     !strcmp((const char *) ptr->name, "result"))
260                         rc_result = yaz_solr_decode_result(o, ptr, sr);
261                 /* TODO The check on hits is a work-around to avoid garbled facets on zero results from the SOLR server.
262                  * The work-around works because the results is before the facets in the xml. */
263                 if (rc_result == 0 && match_xml_node_attribute(ptr, "lst", "name", "facet_counts"))
264                     rc_facets =  yaz_solr_decode_facet_counts(o, ptr, sr);
265             }
266             ret = rc_result + rc_facets;
267         }
268     }
269     if (doc)
270         xmlFreeDoc(doc);
271     if (ret == 0)
272         *pdup = pdu;
273     return ret;
274 #else
275     return -1;
276 #endif
277 }
278
279 static void yaz_solr_encode_facet_field(ODR encode, char **name, char **value, int *i, Z_FacetField *facet_field, int *limit) {
280       Z_AttributeList *attribute_list = facet_field->attributes;
281       struct yaz_facet_attr attr_values;
282       yaz_facet_attr_init(&attr_values);
283       yaz_facet_attr_get_z_attributes(attribute_list, &attr_values);
284       // TODO do we want to support server decided
285       if (!attr_values.errcode && attr_values.useattr) {
286           WRBUF wrbuf = wrbuf_alloc();
287           wrbuf_puts(wrbuf, (char *) attr_values.useattr);
288           /* Skip date field */
289           if (strcmp("date", attr_values.useattr) != 0)
290               wrbuf_puts(wrbuf, "_exact");
291           yaz_add_name_value_str(encode, name, value, i, "facet.field", odr_strdup(encode, wrbuf_cstr(wrbuf)));
292           if (attr_values.limit > 0) {
293               WRBUF wrbuf2 = wrbuf_alloc();
294               Odr_int olimit;
295               wrbuf_puts(wrbuf2, "f.");
296               wrbuf_puts(wrbuf2, wrbuf_cstr(wrbuf));
297               wrbuf_puts(wrbuf2, ".facet.limit");
298               olimit = attr_values.limit;
299               yaz_add_name_value_int(encode, name, value, i, odr_strdup(encode, wrbuf_cstr(wrbuf2)), &olimit);
300               wrbuf_destroy(wrbuf2);
301           }
302           wrbuf_destroy(wrbuf);
303       }
304 }
305
306 static void yaz_solr_encode_facet_list(ODR encode, char **name, char **value, int *i, Z_FacetList *facet_list, int *limit) {
307
308     int index;
309     for (index = 0; index < facet_list->num; index++)  {
310         yaz_solr_encode_facet_field(encode, name, value, i, facet_list->elements[index], limit);
311
312     }
313 }
314
315
316 int yaz_solr_encode_request(Z_HTTP_Request *hreq, Z_SRW_PDU *srw_pdu,
317                             ODR encode, const char *charset)
318 {
319     const char *solr_op = 0;
320     //TODO Change. not a nice hard coded, unchecked limit.
321     char *name[SOLR_MAX_PARAMETERS], *value[SOLR_MAX_PARAMETERS];
322     char *uri_args;
323     char *path;
324     int i = 0;
325
326     z_HTTP_header_add_basic_auth(encode, &hreq->headers, 
327                                  srw_pdu->username, srw_pdu->password);
328
329     switch (srw_pdu->which)
330     {
331     case Z_SRW_searchRetrieve_request: {
332         Z_SRW_searchRetrieveRequest *request = srw_pdu->u.request;
333         solr_op = "select";
334         switch(srw_pdu->u.request->query_type)
335         {
336         case Z_SRW_query_type_pqf:
337             yaz_add_name_value_str(encode, name, value, &i,
338                                    "q", request->query.pqf);
339             break;
340         case Z_SRW_query_type_cql:
341             yaz_add_name_value_str(encode, name, value, &i,
342                                    "q", request->query.cql);
343             break;
344         default:
345             return -1;
346         }
347         if (srw_pdu->u.request->startRecord)
348         {
349             Odr_int start = *request->startRecord - 1;
350             yaz_add_name_value_int(encode, name, value, &i,
351                                    "start", &start);
352         }
353         yaz_add_name_value_int(encode, name, value, &i,
354                                "rows", request->maximumRecords);
355         yaz_add_name_value_str(encode, name, value, &i,
356                                "fl", request->recordSchema);
357
358         if (request->facetList) {
359             Z_FacetList *facet_list = request->facetList;
360             int limit = 0;
361             yaz_add_name_value_str(encode, name, value, &i, "facet", "true");
362             yaz_add_name_value_str(encode, name, value, &i, "facet.mincount", "1");
363             yaz_solr_encode_facet_list(encode, name, value, &i, facet_list, &limit);
364             /*
365             olimit = limit;
366             yaz_add_name_value_int(encode, name, value, &i, "facet.limit", &olimit);
367              */
368
369         }
370         break;
371     }
372     default:
373         return -1;
374     }
375     name[i] = 0;
376     yaz_array_to_uri(&uri_args, encode, name, value);
377     
378     hreq->method = "GET";
379     
380     path = (char *)
381         odr_malloc(encode, strlen(hreq->path) +
382                    strlen(uri_args) + strlen(solr_op) + 4);
383
384     sprintf(path, "%s/%s?%s", hreq->path, solr_op, uri_args);
385     hreq->path = path;
386
387     z_HTTP_header_add_content_type(encode, &hreq->headers,
388                                    "text/xml", charset);
389     return 0;
390 }
391
392
393 /*
394  * Local variables:
395  * c-basic-offset: 4
396  * c-file-style: "Stroustrup"
397  * indent-tabs-mode: nil
398  * End:
399  * vim: shiftwidth=4 tabstop=8 expandtab
400  */
401