Coverage Report

Created: 2025-07-11 06:46

/src/tinysparql/src/libtinysparql/tracker-serializer-json-ld.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * Copyright (C) 2020, Red Hat, Inc
3
 *
4
 * This library is free software; you can redistribute it and/or
5
 * modify it under the terms of the GNU Lesser General Public
6
 * License as published by the Free Software Foundation; either
7
 * version 2.1 of the License, or (at your option) any later version.
8
 *
9
 * This library is distributed in the hope that it will be useful,
10
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12
 * Lesser General Public License for more details.
13
 *
14
 * You should have received a copy of the GNU Lesser General Public
15
 * License along with this library; if not, write to the
16
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17
 * Boston, MA  02110-1301, USA.
18
 *
19
 * Author: Carlos Garnacho <carlosg@gnome.org>
20
 */
21
22
/* Serialization of cursors to the JSON-LD format defined at:
23
 *  https://www.w3.org/TR/json-ld/
24
 */
25
26
#include "config.h"
27
28
#include "tracker-serializer-json-ld.h"
29
30
#include <json-glib/json-glib.h>
31
32
struct _TrackerSerializerJsonLD
33
{
34
  TrackerSerializer parent_instance;
35
  JsonGenerator *generator;
36
  JsonBuilder *builder;
37
  GHashTable *resources;
38
  GList *recent_resources;
39
  GString *data;
40
  GPtrArray *vars;
41
  gchar *cur_graph;
42
  gchar *cur_subject;
43
  JsonObject *cur_resource;
44
  guint stream_closed : 1;
45
  guint cursor_started : 1;
46
  guint cursor_finished : 1;
47
  guint context_printed : 1;
48
  guint needs_separator : 1;
49
};
50
51
G_DEFINE_TYPE (TrackerSerializerJsonLD, tracker_serializer_json_ld,
52
               TRACKER_TYPE_SERIALIZER)
53
54
0
#define MAX_CACHED_RESOURCES 50
55
56
static void
57
tracker_serializer_json_ld_finalize (GObject *object)
58
0
{
59
0
  TrackerSerializerJsonLD *serializer_json_ld =
60
0
    TRACKER_SERIALIZER_JSON_LD (object);
61
62
0
  g_input_stream_close (G_INPUT_STREAM (object), NULL, NULL);
63
0
  g_clear_pointer (&serializer_json_ld->cur_graph, g_free);
64
0
  g_clear_pointer (&serializer_json_ld->cur_subject, g_free);
65
66
0
  G_OBJECT_CLASS (tracker_serializer_json_ld_parent_class)->finalize (object);
67
0
}
68
69
static void
70
generate_namespace_foreach (gpointer key,
71
                            gpointer value,
72
                            gpointer user_data)
73
0
{
74
0
  JsonBuilder *builder = user_data;;
75
76
0
  json_builder_set_member_name (builder, key);
77
0
  json_builder_add_string_value (builder, value);
78
0
}
79
80
static void
81
finish_objects (TrackerSerializerJsonLD *serializer_json_ld)
82
0
{
83
0
  GHashTableIter iter;
84
0
  JsonNode *node;
85
86
0
  g_hash_table_iter_init (&iter, serializer_json_ld->resources);
87
88
0
  while (g_hash_table_iter_next (&iter, NULL, (gpointer*) &node)) {
89
0
    if (g_list_find (serializer_json_ld->recent_resources, node)) {
90
0
      if (serializer_json_ld->needs_separator)
91
0
        g_string_append (serializer_json_ld->data, ",\n");
92
93
0
      json_generator_set_root (serializer_json_ld->generator, node);
94
0
      json_generator_to_gstring (serializer_json_ld->generator,
95
0
                                 serializer_json_ld->data);
96
0
      serializer_json_ld->needs_separator = TRUE;
97
0
    }
98
99
0
    g_hash_table_iter_remove (&iter);
100
0
  }
101
102
0
  g_clear_list (&serializer_json_ld->recent_resources, NULL);
103
0
}
104
105
static JsonNode *
106
create_node (const gchar             *id,
107
             TrackerSparqlValueType   type,
108
             TrackerNamespaceManager *namespaces)
109
0
{
110
0
  JsonNode *node;
111
0
  JsonObject *object;
112
113
0
  node = json_node_new (JSON_NODE_OBJECT);
114
0
  object = json_object_new ();
115
116
0
  if (type == TRACKER_SPARQL_VALUE_TYPE_BLANK_NODE) {
117
0
    gchar *bnode_label;
118
119
0
    bnode_label = g_strconcat ("_:", id, NULL);
120
0
    g_strdelimit (&bnode_label[2], ":", '_');
121
0
    json_object_set_string_member (object, "@id", bnode_label);
122
0
    g_free (bnode_label);
123
0
  } else {
124
0
    gchar *compressed;
125
126
0
    compressed = tracker_namespace_manager_compress_uri (namespaces, id);
127
128
0
    if (compressed) {
129
0
      json_object_set_string_member (object, "@id", compressed);
130
0
      g_free (compressed);
131
0
    } else {
132
0
      json_object_set_string_member (object, "@id", id);
133
0
    }
134
0
  }
135
136
0
  json_node_set_object (node, object);
137
0
  json_object_unref (object);
138
139
0
  return node;
140
0
}
141
142
static JsonNode *
143
create_value_object (const gchar *value,
144
                     const gchar *langtag,
145
                     const gchar *datatype)
146
0
{
147
0
  JsonNode *node;
148
0
  JsonObject *object;
149
150
0
  node = json_node_new (JSON_NODE_OBJECT);
151
0
  object = json_object_new ();
152
0
  json_object_set_string_member (object, "@value", value);
153
154
0
  if (langtag)
155
0
    json_object_set_string_member (object, "@language", langtag);
156
0
  if (datatype)
157
0
    json_object_set_string_member (object, "@type", datatype);
158
159
0
  json_node_set_object (node, object);
160
0
  json_object_unref (object);
161
162
0
  return node;
163
0
}
164
165
static gboolean
166
serialize_up_to_position (TrackerSerializerJsonLD  *serializer_json_ld,
167
                          gsize                     pos,
168
                          GCancellable             *cancellable,
169
                          GError                  **error)
170
0
{
171
0
  TrackerSparqlCursor *cursor;
172
0
  TrackerNamespaceManager *namespaces;
173
0
  GError *inner_error = NULL;
174
0
  JsonNode *node;
175
0
  gboolean check_finish = FALSE;
176
177
0
  if (!serializer_json_ld->data)
178
0
    serializer_json_ld->data = g_string_new (NULL);
179
0
  if (!serializer_json_ld->generator)
180
0
    serializer_json_ld->generator = json_generator_new ();
181
0
  if (!serializer_json_ld->builder)
182
0
    serializer_json_ld->builder = json_builder_new ();
183
0
  if (!serializer_json_ld->vars)
184
0
    serializer_json_ld->vars = g_ptr_array_new_with_free_func (g_free);
185
186
0
  if (!serializer_json_ld->resources) {
187
0
    serializer_json_ld->resources = g_hash_table_new_full (g_str_hash, g_str_equal,
188
0
                                                           g_free, (GDestroyNotify) json_node_unref);
189
0
  }
190
191
0
  if (pos < serializer_json_ld->data->len)
192
0
    return TRUE;
193
194
0
  cursor = tracker_serializer_get_cursor (TRACKER_SERIALIZER (serializer_json_ld));
195
0
  namespaces = tracker_serializer_get_namespaces (TRACKER_SERIALIZER (serializer_json_ld));
196
197
0
  if (!serializer_json_ld->context_printed) {
198
0
    JsonBuilder *builder;
199
200
0
    g_string_append (serializer_json_ld->data, "{");
201
202
0
    builder = json_builder_new ();
203
0
    json_builder_begin_object (builder);
204
0
    tracker_namespace_manager_foreach (namespaces,
205
0
                                       generate_namespace_foreach,
206
0
                                       builder);
207
0
    json_builder_end_object (builder);
208
209
0
    node = json_builder_get_root (builder);
210
0
    json_generator_set_root (serializer_json_ld->generator, node);
211
0
    g_object_unref (builder);
212
0
    json_node_unref (node);
213
214
0
    g_string_append (serializer_json_ld->data, "\"@context\":");
215
0
    json_generator_to_gstring (serializer_json_ld->generator,
216
0
                               serializer_json_ld->data);
217
218
0
    g_string_append (serializer_json_ld->data,
219
0
                     ",\"@graph\":[");
220
221
0
    serializer_json_ld->context_printed = TRUE;
222
0
  }
223
224
0
  while (!serializer_json_ld->cursor_finished) {
225
0
    const gchar *graph, *subject, *predicate, *langtag;
226
0
    gboolean graph_changed, subject_changed;
227
0
    TrackerSparqlValueType subject_type, object_type;
228
0
    JsonNode *value = NULL;
229
0
    gchar *prop = NULL;
230
231
0
    if (!tracker_sparql_cursor_next (cursor, cancellable, &inner_error)) {
232
0
      if (inner_error) {
233
0
        g_propagate_error (error, inner_error);
234
0
        return FALSE;
235
0
      } else {
236
0
        finish_objects (serializer_json_ld);
237
0
        serializer_json_ld->cursor_finished = TRUE;
238
0
        if (serializer_json_ld->cur_graph)
239
0
          g_string_append (serializer_json_ld->data, "]}");
240
241
0
        g_string_append (serializer_json_ld->data, "]}");
242
243
0
        return TRUE;
244
0
      }
245
0
    }
246
247
0
    subject = tracker_sparql_cursor_get_string (cursor, 0, NULL);
248
0
    subject_type = tracker_sparql_cursor_get_value_type (cursor, 0);
249
0
    predicate = tracker_sparql_cursor_get_string (cursor, 1, NULL);
250
0
    object_type = tracker_sparql_cursor_get_value_type (cursor, 2);
251
0
    graph = tracker_sparql_cursor_get_string (cursor, 3, NULL);
252
253
0
    graph_changed = g_strcmp0 (graph, serializer_json_ld->cur_graph) != 0;
254
0
    subject_changed = g_strcmp0 (subject, serializer_json_ld->cur_subject) != 0;
255
256
0
    if (graph_changed) {
257
      /* New/different graph */
258
0
      if (serializer_json_ld->cursor_started && graph_changed) {
259
0
        finish_objects (serializer_json_ld);
260
0
        if (serializer_json_ld->cur_graph)
261
0
          g_string_append (serializer_json_ld->data, "]}");
262
263
0
        g_string_append (serializer_json_ld->data, ",\n");
264
0
      }
265
266
0
      if (graph) {
267
0
        g_string_append_printf (serializer_json_ld->data,
268
0
                                "{\"@id\":\"%s\",\"@graph\":[",
269
0
                                graph);
270
0
      }
271
272
0
      g_clear_pointer (&serializer_json_ld->cur_graph, g_free);
273
0
      serializer_json_ld->cur_graph = g_strdup (graph);
274
0
      serializer_json_ld->needs_separator = FALSE;
275
0
    }
276
277
0
    if (subject_changed || graph_changed) {
278
0
      JsonObject *object;
279
280
      /* New/different subject */
281
0
      if (serializer_json_ld->cursor_started && subject_changed)
282
0
        check_finish = TRUE;
283
284
0
      if (g_hash_table_size (serializer_json_ld->resources) > MAX_CACHED_RESOURCES)
285
0
        finish_objects (serializer_json_ld);
286
287
0
      node = g_hash_table_lookup (serializer_json_ld->resources, subject);
288
289
0
      if (!node) {
290
0
        node = create_node (subject, subject_type, namespaces);
291
292
0
        g_hash_table_insert (serializer_json_ld->resources, g_strdup (subject), node);
293
0
        serializer_json_ld->recent_resources =
294
0
          g_list_prepend (serializer_json_ld->recent_resources, node);
295
0
      }
296
297
0
      object = json_node_get_object (node);
298
0
      serializer_json_ld->cur_resource = object;
299
300
0
      g_clear_pointer (&serializer_json_ld->cur_subject, g_free);
301
0
      serializer_json_ld->cur_subject = g_strdup (subject);
302
0
    }
303
304
0
    if (g_strcmp0 (predicate, TRACKER_PREFIX_RDF "type") == 0) {
305
0
      const gchar *type;
306
0
      gchar *compressed;
307
308
0
      type = tracker_sparql_cursor_get_string (cursor, 2, NULL);
309
0
      compressed = tracker_namespace_manager_compress_uri (namespaces, type);
310
311
0
      prop = g_strdup ("@type");
312
313
0
      value = json_node_alloc ();
314
0
      json_node_init_string (value, compressed);
315
0
      g_free (compressed);
316
0
    } else {
317
0
      TrackerNamespaceManager *namespaces;
318
0
      const gchar *res;
319
320
0
      namespaces = tracker_serializer_get_namespaces (TRACKER_SERIALIZER (serializer_json_ld));
321
0
      prop = tracker_namespace_manager_compress_uri (namespaces, predicate);
322
323
0
      switch (object_type) {
324
0
      case TRACKER_SPARQL_VALUE_TYPE_URI:
325
0
      case TRACKER_SPARQL_VALUE_TYPE_BLANK_NODE:
326
0
        res = tracker_sparql_cursor_get_string (cursor, 2, NULL);
327
328
0
        node = g_hash_table_lookup (serializer_json_ld->resources,
329
0
                                    res);
330
331
0
        if (node &&
332
0
            serializer_json_ld->recent_resources->next != NULL &&
333
0
            serializer_json_ld->cur_resource != json_node_get_object (node) &&
334
0
            g_list_find (serializer_json_ld->recent_resources, node)) {
335
          /* This is still a "root" node, make it part of this tree */
336
0
          serializer_json_ld->recent_resources =
337
0
            g_list_remove (serializer_json_ld->recent_resources, node);
338
0
          value = json_node_ref (node);
339
0
        } else {
340
          /* Unknown object, or one already referenced elsewhere */
341
0
          value = create_node (res, object_type, namespaces);
342
0
        }
343
0
        break;
344
0
      case TRACKER_SPARQL_VALUE_TYPE_DATETIME:
345
0
        res = tracker_sparql_cursor_get_string (cursor, 2, NULL);
346
0
        value = create_value_object (res, NULL, TRACKER_PREFIX_XSD "dateTime");
347
0
        break;
348
0
      case TRACKER_SPARQL_VALUE_TYPE_STRING:
349
0
        res = tracker_sparql_cursor_get_langstring (cursor, 2, &langtag, NULL);
350
0
        if (langtag) {
351
0
          value = create_value_object (res, langtag, TRACKER_PREFIX_RDF "langString");
352
0
        } else {
353
0
          value = json_node_alloc ();
354
0
          json_node_init_string (value, res);
355
0
        }
356
0
        break;
357
0
      case TRACKER_SPARQL_VALUE_TYPE_INTEGER:
358
0
        value = json_node_alloc ();
359
0
        json_node_init_int (value, tracker_sparql_cursor_get_integer (cursor, 2));
360
0
        break;
361
0
      case TRACKER_SPARQL_VALUE_TYPE_DOUBLE:
362
0
        value = json_node_alloc ();
363
0
        json_node_init_double (value, tracker_sparql_cursor_get_double (cursor, 2));
364
0
        break;
365
0
      case TRACKER_SPARQL_VALUE_TYPE_BOOLEAN:
366
0
        value = json_node_alloc ();
367
0
        json_node_init_boolean (value, tracker_sparql_cursor_get_boolean (cursor, 2));
368
0
        break;
369
0
      case TRACKER_SPARQL_VALUE_TYPE_UNBOUND:
370
0
        break;
371
0
      }
372
0
    }
373
374
0
    if (prop && value) {
375
0
      JsonNode *prev;
376
0
      JsonArray *array;
377
378
0
      prev = json_object_get_member (serializer_json_ld->cur_resource,
379
0
                                     prop);
380
381
0
      if (!prev) {
382
0
        json_object_set_member (serializer_json_ld->cur_resource,
383
0
                                prop, value);
384
0
      } else if (JSON_NODE_HOLDS_ARRAY (prev)) {
385
0
        array = json_node_get_array (prev);
386
0
        json_array_add_element (array, value);
387
0
      } else if (!json_node_equal (prev, value)) {
388
0
        array = json_array_new ();
389
0
        json_array_add_element (array, json_node_ref (prev));
390
0
        json_array_add_element (array, value);
391
392
0
        json_object_set_array_member (serializer_json_ld->cur_resource,
393
0
                                      prop, array);
394
0
      }
395
0
    }
396
397
0
    g_free (prop);
398
0
    serializer_json_ld->cursor_started = TRUE;
399
400
0
    if (check_finish && serializer_json_ld->data->len > pos)
401
0
      break;
402
0
  }
403
404
0
  return TRUE;
405
0
}
406
407
static gssize
408
tracker_serializer_json_ld_read (GInputStream  *istream,
409
                                 gpointer       buffer,
410
                                 gsize          count,
411
                                 GCancellable  *cancellable,
412
                                 GError       **error)
413
0
{
414
0
  TrackerSerializerJsonLD *serializer_json_ld = TRACKER_SERIALIZER_JSON_LD (istream);
415
0
  gsize bytes_copied;
416
417
0
  if (serializer_json_ld->stream_closed ||
418
0
      (serializer_json_ld->cursor_finished &&
419
0
       serializer_json_ld->data->len == 0))
420
0
    return 0;
421
422
0
  if (!serialize_up_to_position (serializer_json_ld,
423
0
                                 count,
424
0
                                 cancellable,
425
0
                                 error))
426
0
    return -1;
427
428
0
  bytes_copied = MIN (count, serializer_json_ld->data->len);
429
430
0
  memcpy (buffer,
431
0
          serializer_json_ld->data->str,
432
0
          bytes_copied);
433
0
  g_string_erase (serializer_json_ld->data, 0, bytes_copied);
434
435
0
  return bytes_copied;
436
0
}
437
438
static gboolean
439
tracker_serializer_json_ld_close (GInputStream  *istream,
440
                                  GCancellable  *cancellable,
441
                                  GError       **error)
442
0
{
443
0
  TrackerSerializerJsonLD *serializer_json_ld = TRACKER_SERIALIZER_JSON_LD (istream);
444
445
0
  if (serializer_json_ld->data) {
446
0
    g_string_free (serializer_json_ld->data, TRUE);
447
0
    serializer_json_ld->data = NULL;
448
0
  }
449
450
0
  g_clear_object (&serializer_json_ld->generator);
451
0
  g_clear_object (&serializer_json_ld->builder);
452
0
  serializer_json_ld->stream_closed = TRUE;
453
0
  g_clear_pointer (&serializer_json_ld->vars, g_ptr_array_unref);
454
455
0
  return TRUE;
456
0
}
457
458
static void
459
tracker_serializer_json_ld_class_init (TrackerSerializerJsonLDClass *klass)
460
0
{
461
0
  GObjectClass *object_class = G_OBJECT_CLASS (klass);
462
0
  GInputStreamClass *istream_class = G_INPUT_STREAM_CLASS (klass);
463
464
0
  object_class->finalize = tracker_serializer_json_ld_finalize;
465
466
0
  istream_class->read_fn = tracker_serializer_json_ld_read;
467
0
  istream_class->close_fn = tracker_serializer_json_ld_close;
468
0
}
469
470
static void
471
tracker_serializer_json_ld_init (TrackerSerializerJsonLD *serializer)
472
0
{
473
0
}