#include "dres.h" LSUP_rc LSR_dres_new_multi (LSUP_Graph *const *data, LSR_Dres **rsrc_p) { LSUP_rc rc = LSUP_OK; LSR_Dres *rsrc; MALLOC_GUARD (rsrc, LSUP_MEM_ERR); uuid_generate_random (rsrc->id); // Default context graph. LSUP_Term *main_data_urn = LSR_id_to_urn (rsrc->id, "__main"); rsrc->main_data = LSUP_graph_new (NULL, main_data_urn, LSUP_default_nsm); LSUP_term_free (main_data_urn); LOG_DEBUG ( "Main data graph: %s", LSUP_graph_uri(rsrc->main_data)->data); PCHECK (LSR_dres_update (rsrc, NULL, data), finally); LSUP_Term *rsrc_uri = LSR_id_to_urn (rsrc->id, NULL); /* BEGIN adding managed (admin) data. */ LSUP_Term *admin_uri = LSR_id_to_urn (rsrc->id, "__admin"); rsrc->admin_data = LSUP_graph_new (NULL, admin_uri, NULL); LOG_DEBUG ( "Admin data graph (@%p): %s", LSUP_graph_uri (rsrc->admin_data), LSUP_graph_uri (rsrc->admin_data)->data); // Calculate timestamp. time_t now; time (&now); char tstamp [sizeof ("0000-00-00T00:00:00Z")]; strftime (tstamp, sizeof (tstamp), "%FT%TZ", gmtime (&now)); LSUP_Term *s = LSUP_iriref_new("", NULL), // Relative to resource URI. *created_t = LSUP_iriref_new ("lsup:created", LSUP_default_nsm), *rsrc_t = LSUP_iriref_new ("lsup:Resource", LSUP_default_nsm), *dres_t = LSUP_iriref_new ( "lsup:DescriptiveResource", LSUP_default_nsm), *ts_t = LSUP_literal_new ( tstamp, LSUP_iriref_new ("xsd:dateTime", LSUP_default_nsm)); LSUP_Triple *admin_trp[] = { LSUP_triple_new (s, LSR_rdf_t, rsrc_t), LSUP_triple_new (s, LSR_rdf_t, dres_t), LSUP_triple_new (s, created_t, ts_t), NULL, }; rc = LSUP_graph_add (rsrc->admin_data, admin_trp, NULL); PCHECK (rc, finally); for (size_t i = 0; admin_trp[i]; i++) free (admin_trp[i]); LSUP_term_free (s); LSUP_term_free (created_t); LSUP_term_free (rsrc_t); LSUP_term_free (dres_t); LSUP_term_free (ts_t); /* END adding admin data. */ /* BEGIN adding graph metadata (main). */ LSUP_Term *meta_t = LSUP_iriref_new ("lsup:Metadata", LSUP_default_nsm), *admin_meta_t = LSUP_iriref_new ( "lsup:AdminMetadata", LSUP_default_nsm), *topic_t = LSUP_iriref_new ("foaf:primaryTopic", LSUP_default_nsm); LSUP_Triple *main_trp[] = { LSUP_triple_new (admin_uri, LSR_rdf_t, meta_t), LSUP_triple_new (admin_uri, LSR_rdf_t, admin_meta_t), LSUP_triple_new (admin_uri, topic_t, rsrc_uri), NULL }; rc = LSUP_graph_add (rsrc->main_data, main_trp, NULL); for (size_t i = 0; main_trp[i]; i++) free (main_trp[i]); LSUP_term_free (meta_t); LSUP_term_free (admin_meta_t); LSUP_term_free (topic_t); /* END adding graph metadata. */ finally: LSUP_term_free (rsrc_uri); LSUP_term_free (admin_uri); if (rc < 0) goto fail; rsrc->flags |= LSR_RS_DIRTY; *rsrc_p = rsrc; return rc; fail: LSR_dres_free (rsrc); *rsrc_p = NULL; return rc; } void LSR_dres_free (LSR_Dres *rsrc) { size_t i = 0; while (rsrc->user_data[i]) LSUP_graph_free (rsrc->user_data[i++]); free (rsrc->user_data); LSUP_graph_free (rsrc->admin_data); LSUP_graph_free (rsrc->main_data); free (rsrc); } LSUP_rc LSR_dres_store (const LSR_Dres *rsrc) { LSR_Dres *old_rsrc; LSUP_rc get_rc = LSR_dres_get (rsrc->id, &old_rsrc); PRCCK (get_rc); /* * BEGIN txn */ void *txn; LSUP_store_begin (LSR_store, 0, &txn); // Remove all existing user graphs. if (get_rc == LSUP_OK) { for (size_t i = 0; old_rsrc->user_data[i] != NULL; i++) { const LSUP_Term *gr_uri = LSUP_graph_uri (old_rsrc->user_data[i]); size_t ct; // Remove triples from user graph. PCHECK (LSUP_graph_remove_txn ( txn, old_rsrc->user_data[i], NULL, NULL, NULL, &ct), fail); LOG_DEBUG ("Removed %lu triples from graph %s", ct, gr_uri->data); // Remove user graph metadata. PCHECK (LSUP_graph_remove_txn ( txn, old_rsrc->main_data, gr_uri, NULL, NULL, NULL), fail); PCHECK (LSUP_graph_remove_txn ( txn, old_rsrc->main_data, NULL, NULL, gr_uri, NULL), fail); } } LSUP_Graph *tmp_gr; // Add new triples. LSUP_rc rc = LSUP_NOACTION; for (size_t i = 0; rsrc->user_data[i] != NULL; i++) { tmp_gr = LSUP_graph_new ( LSR_store, LSUP_graph_uri (rsrc->user_data[i]), NULL); if (UNLIKELY (!tmp_gr)) { rc = LSUP_MEM_ERR; goto fail; } PCHECK (LSUP_graph_copy_contents_txn ( txn, rsrc->user_data[i], tmp_gr, NULL, NULL, NULL), fail); LSUP_graph_free (tmp_gr); } // Update admin data. tmp_gr = LSUP_graph_new ( LSR_store, LSUP_graph_uri (rsrc->admin_data), NULL); if (UNLIKELY (!tmp_gr)) { rc = LSUP_MEM_ERR; goto fail; } PCHECK (LSUP_graph_copy_contents_txn ( txn, rsrc->admin_data, tmp_gr, NULL, NULL, NULL), fail); LSUP_graph_free (tmp_gr); // Update graph metadata. tmp_gr = LSUP_graph_new ( LSR_store, LSUP_graph_uri (rsrc->main_data), NULL); if (UNLIKELY (!tmp_gr)) { rc = LSUP_MEM_ERR; goto fail; } PCHECK (LSUP_graph_copy_contents_txn ( txn, rsrc->main_data, tmp_gr, NULL, NULL, NULL), fail); LSUP_graph_free (tmp_gr); PCHECK (LSUP_store_commit (LSR_store, txn), fail); /* * END txn */ return LSUP_OK; fail: LSUP_store_abort (LSR_store, txn); return rc; } LSUP_rc LSR_dres_delete (LSR_id id) { // TODO return LSUP_NOT_IMPL_ERR; } LSUP_rc LSR_dres_get (const uuid_t id, LSR_Dres **rsrc_p) { LSUP_rc rc = LSUP_OK; LSUP_Graph *main_gr = LSUP_graph_new (LSR_store, LSUP_default_ctx, NULL); if (!main_gr) return LSUP_DB_ERR; LSUP_Triple *spo = LSUP_triple_new ( LSR_id_to_urn (id, NULL), LSUP_iriref_new ("rdf:type", LSUP_default_nsm), LSUP_iriref_new ("lsup:Resource", LSUP_default_nsm) ); if (!LSUP_graph_contains (main_gr, spo)) { rc = LSUP_NORESULT; goto finally; } LSUP_term_free (spo->o); spo->o = LSUP_iriref_new ("lsup:DescriptiveResource", LSUP_default_nsm); if (!LSUP_graph_contains (main_gr, spo)) { log_error ("%s is not a descriptive resource.", spo->o->data); rc = LSUP_NORESULT; goto finally; } LSUP_term_free (spo->p); spo->p = LSUP_iriref_new ("foaf:primaryTopic", LSUP_default_nsm); size_t ct = 0, i = 0; // Find all graphs making up the resource. LSUP_GraphIterator *it = LSUP_graph_lookup ( main_gr, NULL, spo->p, spo->s, &ct); LSUP_Graph **data = calloc (sizeof (*data), ct + 1); while (LSUP_graph_iter_next (it, &spo)) { data[i] = LSUP_graph_new (LSR_store, spo->s, NULL); if (! data[i++]) break; // Last slot remains NULL (sentinel). } LSUP_graph_iter_free (it); rc = LSR_dres_new_multi (data, rsrc_p); i = 0; while (i < ct) LSUP_graph_free (data[i++]); free (data); finally: LSUP_triple_free (spo); LSUP_graph_free (main_gr); return rc; } LSUP_Graph * LSR_dres_metadata (const LSR_Dres *rsrc) { LSUP_Graph *res = LSUP_graph_new ( LSR_store, LSUP_graph_uri (rsrc->admin_data), NULL); LSUP_graph_copy (rsrc->admin_data, res); return res; } LSUP_Graph ** LSR_dres_user_data (const LSR_Dres *rsrc) { size_t ct = 0; while (rsrc->user_data[ct]) ct++; LSUP_Graph **res = malloc (sizeof (*res) * (ct + 1)); if (UNLIKELY (!res)) return NULL; for (size_t i = 0; i < ct; i++) { res[i] = LSUP_graph_new ( LSR_store, LSUP_graph_uri (rsrc->user_data[i]), NULL); PCHECK (LSUP_graph_copy (rsrc->user_data[i], res[i]), fail); } res[ct] = NULL; return res; fail: for (size_t i = 0; i < ct; i++) LSUP_graph_free (res[i]); return NULL; } LSUP_Graph * LSR_dres_triples (const LSR_Dres *rsrc) { LSUP_Graph *res = LSR_dres_metadata (rsrc); if (UNLIKELY (!res)) return NULL; LSUP_graph_set_uri (res, LSR_dres_urn (rsrc)); size_t ct = 0; while (rsrc->user_data[ct]) ct++; for (size_t i = 0; i < ct; i++) PCHECK (LSUP_graph_copy (rsrc->user_data[i], res), fail); return res; fail: LSUP_graph_free (res); return NULL; } LSUP_rc LSR_dres_update ( LSR_Dres *rsrc, LSUP_Term *const rm_data[][4], LSUP_Graph *const *add_data) { LSUP_rc rc = LSUP_NOACTION; LSUP_Term *rsrc_uri = LSR_id_to_urn (rsrc->id, NULL); size_t ct = 0; /* * REMOVE user data. */ bool is_new; if (rm_data) { size_t ct_loop, i, j; for (i = 0; rm_data[i] != NULL; i++) { LSUP_Buffer *sspoc[4] = {NULL}; for (j = 0; j < 4; j++) { if (rm_data[i][j]) sspoc[j] = LSUP_term_serialize (rm_data[i][j]); } LSR_store->sif->remove_fn ( LSR_store->data, sspoc[0], sspoc[1], sspoc[2], sspoc[3], NULL, &ct_loop); for (j = 0; j < 4; j++) LSUP_buffer_free (sspoc[j]); LOG_DEBUG ( "Removed %lu triples for %s", ct_loop, rm_data[i][0]->data); ct += ct_loop; // TODO if graph is empty after removal, remove it. } is_new = false; } else is_new = true; /* * ADD user data. * * Extensively using URIs relative to each graph, following * https://www.w3.org/TR/ldp-bp/#use-relative-uris * * Note: a URI such as `#doc1` in graph `urn:lsres:1234#__main` is * considered relative to `urn:lsres:1234` (`#__main` is the fragment of a * document). */ // Count graphs to be inserted and allocate space. ct = 0; while (add_data[ct]) ct++; rsrc->user_data = calloc (sizeof (*rsrc->user_data), ct + 1); if (UNLIKELY (! rsrc->user_data)) return LSUP_MEM_ERR; LSUP_Triple spo_s; LSUP_Triple *spo = &spo_s; LSUP_GraphIterator *lu_it, // Lookup iterator. *add_it; // Main graph add iterator. // Loop over input graphs. for (size_t i = 0; i < ct; i++) { const LSUP_Term *gr_uri = LSUP_graph_uri (add_data[i]); LOG_DEBUG ("Adding user data #%lu with uri: %s", i, gr_uri->data); LSUP_Term *rel_uri = LSUP_iriref_relative (rsrc_uri, gr_uri); if (strstr (rel_uri->data, "#__") == rel_uri->data) { log_error ("Fragment URI cannot start with double underscore."); rc = LSUP_VALUE_ERR; } LSUP_term_free (rel_uri); if (rc < 0) goto finally; rsrc->user_data[i] = LSUP_graph_new (NULL, gr_uri, NULL); LOG_DEBUG ( "User data graph (@%p): %s", LSUP_graph_uri(rsrc->user_data[i]), LSUP_graph_uri(rsrc->user_data[i])->data); add_it = LSUP_graph_add_init (rsrc->user_data[i]); lu_it = LSUP_graph_lookup (rsrc->user_data[i], NULL, NULL, NULL, NULL); // Loop over source graph triples. LSUP_Term *dest_s, *dest_p, *dest_o; LSUP_Triple *src_spo; while (LSUP_graph_iter_next (lu_it, &src_spo) == LSUP_OK) { dest_s = LSUP_IS_IRI (src_spo->s) ? LSUP_iriref_relative (rsrc_uri, src_spo->s) : src_spo->s; dest_p = LSUP_term_copy (src_spo->p); dest_o = LSUP_IS_IRI (src_spo->s) ? LSUP_iriref_relative (rsrc_uri, src_spo->s) : src_spo->s; LSUP_triple_init (spo, dest_s, dest_p, dest_o); // if the pred is managed, ignore the triple and send a warning. if (hashmap_get (LSR_managed_preds, spo->p)) { log_warn ( "Predicate %s is managed. Skipping triple.", dest_p->data); goto loop_end; } /* * If the subject or object is a resource, check if it exists; if * it does, add triple to user_data; if not, return an error. */ uuid_t id_tmp; LSUP_rc tmp_rc; // Check subject. if (LSR_IS_RSRC_IRI (dest_s)) { uuid_parse (dest_s->data + strlen (LSR_RSRC_NS), id_tmp); tmp_rc = LSR_dres_get (id_tmp, NULL); if (tmp_rc != LSUP_OK) { log_error ( "Referenced subject does not exist: %s", dest_s->data); rc = LSUP_VALUE_ERR; goto finally; } } // Check object. if (LSR_IS_RSRC_IRI (dest_o)) { uuid_parse (dest_o->data + strlen (LSR_RSRC_NS), id_tmp); tmp_rc = LSR_dres_get (id_tmp, NULL); if (tmp_rc != LSUP_OK) { log_error ( "Referenced object does not exist: %s", dest_o->data + strlen (LSR_RSRC_NS)); rc = LSUP_VALUE_ERR; goto finally; } } // RDF type check. if ( LSUP_term_equals ( gr_uri, LSUP_iriref_absolute (rsrc_uri, spo->s)) && LSUP_term_equals (LSR_rdf_t, spo->p) ) { // If the resource is a special type, handle specific workflow. if (hashmap_get (LSR_managed_types, spo->o)) { if ( !is_new && ( strcmp (spo->o->data, "lsup:List") == 0 || strcmp (spo->o->data, "lsup:ListItem") == 0 || strcmp (spo->o->data, "lsup:Set") == 0 || strcmp (spo->o->data, "lsup:Proxy") == 0 ) ) { // TODO handle system-managed types. } else { log_error ( "%s is a managed predicate and cannot " "be used on creation.", spo->o->data); rc = LSUP_VALUE_ERR; goto finally; } } } // Add triple to user_data. LSUP_graph_add_iter (add_it, spo); loop_end: if (dest_s != src_spo->s) LSUP_term_free (dest_s); if (dest_o != src_spo->o) LSUP_term_free (dest_o); } /* * UPDATE graph metadata. */ // Add user graph metadata to default graph. LSUP_GraphIterator *admin_add_it = LSUP_graph_add_init ( rsrc->main_data); dest_s = LSUP_iriref_new (gr_uri->data, NULL); dest_p = LSUP_iriref_new ("rdf:type", LSUP_default_nsm); dest_o = LSUP_iriref_new ("lsup:Metadata", LSUP_default_nsm); LSUP_triple_init (spo, dest_s, dest_p, dest_o); LSUP_graph_add_iter (admin_add_it, spo); LSUP_term_free (dest_o); dest_o = LSUP_iriref_new ("lsup:UserMetadata", LSUP_default_nsm); LSUP_triple_init (spo, dest_s, dest_p, dest_o); LSUP_graph_add_iter (admin_add_it, spo); LSUP_term_free (dest_o); LSUP_term_free (dest_p); // Relationship between data graph and resource. dest_p = LSUP_iriref_new ("foaf:primaryTopic", LSUP_default_nsm); dest_o = rsrc_uri; LSUP_triple_init (spo, dest_s, dest_p, dest_o); LSUP_graph_add_iter (admin_add_it, spo); LSUP_term_free (dest_p); LSUP_term_free (dest_s); LSUP_graph_iter_free (lu_it); LSUP_graph_add_done (add_it); LSUP_graph_add_done (admin_add_it); lu_it = add_it = admin_add_it = NULL; } finally: LSUP_term_free (rsrc_uri); if (lu_it) LSUP_graph_iter_free (lu_it); if (add_it) LSUP_graph_add_done (add_it); return rc; } /* * Inline function declarations. */ LSUP_Term *LSR_dres_urn (const LSR_Dres *rsrc);