Membership

From Linked Data Platform
Jump to: navigation, search

There is evidently confusion as to what is considered a "member" in LDP. Henry proposed a fixed ldp:member (or ldp:manages) arc tied to POST results. Following is a description of the current spec, including examples and SPARQL queries for querying and management of LDPCs.

1 Finding the members of a container

Per the LDP specification the members of a container are found by looking for the "membership triples". These can take different forms based on the setting of ldp:containerResource, and either ldp:containsRelation or ldp:containedByRelation.

The following SPARQL query returns the list of containers and their members, along with their related membership triple:

PREFIX ldp: <http://www.w3.org/ns/ldp#>

SELECT ?ldpc ?member ?subject ?predicate ?object
WHERE {
  {
      ?ldpc a ldp:SimpleContainer;
        ldp:member ?member.                  # membership triples
      BIND (?member AS ?object)              # the member is the object
      BIND (?ldpc AS ?subject)
      BIND (ldp:member AS ?predicate)
  } # container is of type DirectContainer or IndirectContainer
    UNION {
      ?ldpc ldp:containerResource ?subject;
            ldp:containsRelation ?predicate.
      ?subject ?predicate ?object.           # membership triple
      BIND (?object AS ?member)              # the member is the object
  } UNION {
      ?ldpc ldp:containerResource ?object;
            ldp:containedByRelation ?predicate.
      ?subject ?predicate ?object.           # membership triple
      BIND (?subject AS ?member)             # the member is the subject
  }
}

The query looks for a container and its "membership predicates". Depending on whether it is a SimpleContainer or one of DirectContainer and IndirectContainer the query changes. In the case of a SimpleContainer, the members are directly linked from the container using ldp:member. In the case of DirectContainer and IndirectContainer, depending on whether ldp:containsRelation or ldp:containedByRelation is used the container is the subject and the member the object of the membership triple, or the other way around. The query treats each case as part of a UNION and uses variables that are assigned accordingly to show how the membership triple is constructed from the different pieces of information found.

It should be noted that ldp:insertedContentRelation, which is used when creating a new member by POSTing to an IndirectContainer, plays no role in this at all. Indeed this piece of information is irrelevant to determining the members of a container.

Let's run this query against various examples.

1.1 Simple Example (C ldp:member R)

Using the new ldp:SimpleContainer:

@base <http://example.org/container1/>
@prefix dcterms: <http://purl.org/dc/terms/>.
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>.
@prefix ldp: <http://www.w3.org/ns/ldp#>.

<>
   a ldp:SimpleContainer;
   dcterms:title "A very simple container";
   ldp:member <member1>, <member2>, <member3>.

Results:

---------------------------------------------------------------------------------------------
| ldpc          | member               | subject       | predicate   | object               |
=============================================================================================
| <container1/> | <container1/member1> | <container1/> | ldp:member | <container1/member1> |
| <container1/> | <container1/member2> | <container1/> | ldp:member | <container1/member2> |
| <container1/> | <container1/member3> | <container1/> | ldp:member | <container1/member3> |
---------------------------------------------------------------------------------------------

Note: a bunch of prefixes were added to the query to simplify the output.

1.2 LDP Spec Example 3 (C rdfs:member R)

Using the new ldp:DirectContainer:

@base <http://example.org/container1/>
@prefix dcterms: <http://purl.org/dc/terms/>.
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>.
@prefix ldp: <http://www.w3.org/ns/ldp#>.

<>
   a ldp:DirectContainer;
   ldp:containerResource <> ;
   ldp:containsRelation rdfs:member;
   dcterms:title "A very simple container";
   rdfs:member <member1>, <member2>, <member3>.

Results:

---------------------------------------------------------------------------------------------
| ldpc          | member               | subject       | predicate   | object               |
=============================================================================================
| <container1/> | <container1/member1> | <container1/> | rdfs:member | <container1/member1> |
| <container1/> | <container1/member2> | <container1/> | rdfs:member | <container1/member2> |
| <container1/> | <container1/member3> | <container1/> | rdfs:member | <container1/member3> |
---------------------------------------------------------------------------------------------

Note: a bunch of prefixes were added to the query to simplify the output.

1.3 LDP Spec Example 5 (C1 o:asset R1. C2 o:liability R2)

Using the new ldp:DirectContainer:

@base <http://example.org/netWorth/nw1/>
@prefix ldp: <http://www.w3.org/ns/ldp#>.
@prefix dcterms: <http://purl.org/dc/terms/>.
@prefix o: <http://example.org/ontology/>.
<>
   a o:NetWorth;
   o:netWorthOf <http://example.org/users/JohnZSmith>;
   o:asset 
      <assetContainer/a1>,
      <assetContainer/a2>;
   o:liability 
      <liabilityContainer/l1>,
      <liabilityContainer/l2>,
      <liabilityContainer/l3>.

<assetContainer/>
   a ldp:DirectContainer;
   dcterms:title "The assets of JohnZSmith";
   ldp:containerResource <>;
   ldp:containsRelation o:asset.

<liabilityContainer/>
   a ldp:DirectContainer;
   dcterms:title "The liabilities of JohnZSmith";
   ldp:containerResource <>;
   ldp:containsRelation o:liability.

Results:

-------------------------------------------------------------------------------------------------------------------------------------------------------------
| ldpc                               | member                               | subject         | predicate            | object                               |
=============================================================================================================================================================
| <netWorth/nw1/assetContainer/>     | <netWorth/nw1/assetContainer/a1>     | <netWorth/nw1/> | <ontology/asset>     | <netWorth/nw1/assetContainer/a1>     |
| <netWorth/nw1/assetContainer/>     | <netWorth/nw1/assetContainer/a2>     | <netWorth/nw1/> | <ontology/asset>     | <netWorth/nw1/assetContainer/a2>     |
| <netWorth/nw1/liabilityContainer/> | <netWorth/nw1/liabilityContainer/l1> | <netWorth/nw1/> | <ontology/liability> | <netWorth/nw1/liabilityContainer/l1> |
| <netWorth/nw1/liabilityContainer/> | <netWorth/nw1/liabilityContainer/l2> | <netWorth/nw1/> | <ontology/liability> | <netWorth/nw1/liabilityContainer/l2> |
| <netWorth/nw1/liabilityContainer/> | <netWorth/nw1/liabilityContainer/l3> | <netWorth/nw1/> | <ontology/liability> | <netWorth/nw1/liabilityContainer/l3> |
-------------------------------------------------------------------------------------------------------------------------------------------------------------

1.4 SKOS Example (R skos:inScheme C)

Using the new ldp:DirectContainer:

@base <http://example.org/skosldpc/>
@prefix ldp: <http://www.w3.org/ns/ldp#>.
@prefix skos: <http://www.w3.org/2004/02/skos/core#>.

<> a ldp:DirectContainer, skos:ConceptScheme;
	ldp:containerResource <>;
	ldp:containedByRelation skos:inScheme;
	skos:prefLabel "Subject Heading".

</entry1> a skos:Concept;
	skos:inScheme <>.

Results:

-------------------------------------------------------------------
| ldpc        | member   | subject  | predicate     | object      |
===================================================================
| <skosldpc/> | <entry1> | <entry1> | skos:inScheme | <skosldpc/> |
-------------------------------------------------------------------

1.5 FOAF Example (C foaf:knows R)

Using the new ldp:IndirectContainer:

@base <http://example.org/arnaud>
@prefix ldp: <http://www.w3.org/ns/ldp#>.
@prefix foaf: <http://xmlns.com/foaf/0.1/> .

<> a ldp:IndirectContainer;
  ldp:containerResource <#me>;
  ldp:containsRelation foaf:knows;
  ldp:insertedContentRelation foaf:primaryTopic .

<#me> a foaf:Person;
  foaf:knows <http://example.org/steves#me>;
  foaf:knows <http://example.org/timbl#me>.

Results:

-------------------------------------------------------------------
| ldpc     | member      | subject     | predicate  | object      |
===================================================================
| <arnaud> | <steves#me> | <arnaud#me> | foaf:knows | <steves#me> |
| <arnaud> | <timbl#me>  | <arnaud#me> | foaf:knows | <timbl#me>  |
-------------------------------------------------------------------

2 Determining the membership triples to be added when a new member is created

Now that we've seen how one can find the members of a container, let's look at how new members get added to a container.

The following SPARQL query returns the membership triple corresponding to the new member when a new resource gets created by POSTing to a container.

# On POST of ?newResource to ?ldpc add membership triple as follows:
# In case of an SimpleContainer the new resource is simply added as
# the object of an ldp:member triple.
# In case of an DirectContainer the new resource is added as a member,
# either in object position or subject depending on the relation used.
# In case of an IndirectContainer it's the object of the corresponding
# triple in the newResource which gets added as the member.

PREFIX ldp: <http://www.w3.org/ns/ldp#>

CONSTRUCT { ?subject ?predicate ?object }
WHERE
{
 {
   ?ldpc a ldp:SimpleContainer.
   GRAPH ?newResource {
     ?a ?b ?c. 
   }
   BIND (?ldpc AS ?subject)
   BIND (ldp:member AS ?predicate)
   BIND (?newResource AS ?object)                             # the POSTed resource is the member
 } UNION {
   ?ldpc a ldp:DirectContainer;
        ldp:containerResource ?subject;
        ldp:containsRelation ?predicate.                      # ldp:containsRelation is used
   GRAPH ?newResource {
     ?a ?b ?c. 
   }
   BIND (?newResource AS ?object)                             # the POSTed resource is the member
 } UNION {
   ?ldpc a ldp:DirectContainer;
        ldp:containerResource ?object;
        ldp:containedByRelation ?predicate.                   # ldp:containedByRelation is used
   GRAPH ?newResource {
     ?a ?b ?c. 
   }
   BIND (?newResource AS ?subject)                            # the POSTed resource is the member
 } UNION {
   ?ldpc a ldp:IndirecContainer;
        ldp:containerResource ?subject;
        ldp:containsRelation ?predicate;                      # ldp:containsRelation is used
	ldp:insertedContentRelation ?insertedContentRelation. # the POSTed resource is not the member
   GRAPH ?newResource {
     ?newResource ?insertedContentRelation ?object.           # the object of this triple is the member
   }
 } UNION {
   ?ldpc a ldp:IndirectContainer;
        ldp:containerResource ?object;
        ldp:containedByRelation ?predicate;                   # ldp:containedByRelation is used
	ldp:insertedContentRelation ?insertedContentRelation. # the POSTed resource is not the member
   GRAPH ?newResource {
     ?newResource ?insertedContentRelation ?subject.          # the object of this triple is the member
   }
 }
}

This query is more complicated than the previous one because we now have to take into account one extra parameter: ldp:insertedContentRelation in case of an IndirectContainer.

In this case, instead of adding the resource that is created as a member, it's the object of the triple corresponding to the specified predicate in the created resource that is to be added as the new member.

Let's look at how this works in practice with a few more examples.

2.1 Simple Example

Let's consider again the simple example (C ldp:member R) and imagine that the following resource is created by POSTing to the container.

# <http://example.org/container1/member4>
# created by POSTing to the container <http://example.org/container1/>
@prefix dcterms: <http://purl.org/dc/terms/>.

<> dcterms:title "Yet another member".

Running the above SPARQL query tells us that the membership triple to be added is:

<http://example.org/container1/>
       ldp:member  <http://example.org/container1/member4> .

Which gives the following container:

@base <http://example.org/container1/>
@prefix dcterms: <http://purl.org/dc/terms/>.
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>.
@prefix ldp: <http://www.w3.org/ns/ldp#>.

<>
   a ldp:SimpleContainer;
   dcterms:title "A very simple container";
   ldp:member <member1>, <member2>, <member3>, <member4>.


2.2 LDP Spec Example 3

Similarly, let's consider again example 3 of the specification (C rdfs:member R) and imagine that the following resource is created by POSTing to the container.

# <http://example.org/container1/member4>
# created by POSTing to the container <http://example.org/container1/>
@prefix dcterms: <http://purl.org/dc/terms/>.

<> dcterms:title "Yet another member".

Running the above SPARQL query tells us that the membership triple to be added is:

<http://example.org/container1/>
       rdfs:member  <http://example.org/container1/member4> .

Which gives the following container:

@base <http://example.org/container1/>
@prefix dcterms: <http://purl.org/dc/terms/>.
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>.
@prefix ldp: <http://www.w3.org/ns/ldp#>.

<>
   a ldp:DirectContainer;
   ldp:containerResource <> ;
   ldp:containsRelation rdfs:member;
   dcterms:title "A very simple container";
   rdfs:member <member1>, <member2>, <member3>, <member4>.

2.3 FOAF Example

Considering the FOAF example discussed above (C foaf:knows R), now imagine that the following resource is created by POSTing to the container.

# <http://example.org/michel>
# created by POSTing to the container <http://example.org/arnaud>
@prefix foaf: <http://xmlns.com/foaf/0.1/> .

<> foaf:primaryTopic <#me>.

<#me> a foaf:Person;
   foaf:Name "Michel".

Running the above SPARQL query tells us that the membership triple to be added is:

<http://example.org/arnaud#me>
       foaf:knows  <http://example.org/michel#me> .

Which gives the following container:

@base <http://example.org/arnaud>
@prefix ldp: <http://www.w3.org/ns/ldp#>.
@prefix foaf: <http://xmlns.com/foaf/0.1/> .

<> a ldp:IndirectContainer;
  ldp:containerResource <#me>;
  ldp:containsRelation foaf:knows;
  ldp:insertedContentRelation foaf:primaryTopic .

<#me> a foaf:Person;
  foaf:knows <http://example.org/steves#me>;
  foaf:knows <http://example.org/timbl#me>;
  foaf:knows <http://example.org/michel#me>.

3 Keeping track of the created resources

In the simple example and LDP Spec Example 3, the (information) resource that is created and the resource that is added as the member of the container are the same: <http://example.org/container1/member4>.

In a case where all resources are added this way, one can therefore get the list of created resources by simply looking at the list of members.

This is not true in a case of an IndirectContainer, as in the FOAF example above. In that case, the resource created is <http://example.org/michel> and the resource that is added as member is <http://example.org/michel#me>. Therefore the list of members doesn't give the list of created resources.

This is what was referred as the "lost link" and to address it the WG agreed to require that in this case an ldp:created link be also added pointing to the created resource. This was later renamed ldp:memberResource as part of the decision to adopt Arnaud's container proposal.

3.1 Determining the ldp:memberResource triples to be added when a new resource is created

The following SPARQL query tells us what triple needs to be added:

# Add a link from the container to the POSTed resource when it is not the member

PREFIX ldp: <http://www.w3.org/ns/ldp#>

CONSTRUCT { ?ldpc ldp:memberResource ?newResource }
WHERE
{
   ?ldpc a ldp:IndirectContainer;
	ldp:insertedContentRelation ?insertedContentRelation.
   GRAPH ?newResource {
     ?newResource ?insertedContentRelation ?c. 
   }
}

For the FOAF example this returns:

<http://example.org/arnaud>
       ldp:memberResource  <http://example.org/michel> .

Which gives us the following container:

@base <http://example.org/arnaud>
@prefix ldp: <http://www.w3.org/ns/ldp#>.
@prefix foaf: <http://xmlns.com/foaf/0.1/> .

<> a ldp:IndirectContainer;
  ldp:containerResource <#me>;
  ldp:containsRelation foaf:knows;
  ldp:insertedContentRelation foaf:primaryTopic ;
  ldp:memberResource <http://example.org/steves> ;    # link to the information resource
  ldp:memberResource <http://example.org/timbl> ;     # link to the information resource
  ldp:memberResource <http://example.org/michel> .    # link to the information resource

<#me> a foaf:Person;
  foaf:knows <http://example.org/steves#me>;          # link to the member resource
  foaf:knows <http://example.org/timbl#me>;           # link to the member resource
  foaf:knows <http://example.org/michel#me>.          # link to the member resource

3.2 Finding the list of created resources associated with a container

We can then use the following query to find the list of created resources associated with a container:

PREFIX ldp: <http://www.w3.org/ns/ldp#>

SELECT ?ldpc ?resource
WHERE {
  {
      ?ldpc ldp:member ?resource.                      # the information resource is the member
  } UNION {
      ?ldpc a ldp:DirectContainer;
        ldp:containerResource ?containerResource;
        ldp:containsRelation ?containsRelation.
      ?containerResource ?containsRelation ?resource.  # the information resource is the member
  } UNION {
      ?ldpc a ldp:DirectContainer;
        ldp:containerResource ?containerResource;
        ldp:containedByRelation ?containedByRelation.  # the information resource is the member
      ?resource ?containedByRelation ?containerResource.
  } UNION {
      ?ldpc a ldp:IndirectContainer;
        ldp:memberResource ?resource.                  # the information resource is linked via ldp:memberResource
  }
}

Running this query against the LDP Spec Example 3 (C rdfs:member R) gives the following:

----------------------------------------
| ldpc          | resource             |
========================================
| <container1/> | <container1/member1> |
| <container1/> | <container1/member2> |
| <container1/> | <container1/member3> |
----------------------------------------

Running this query against the FOAF Example (C foaf:knows R) gives the following:

-----------------------
| ldpc     | resource |
=======================
| <arnaud> | <steves> |
| <arnaud> | <timbl>  |
| <arnaud> | <michel> |
-----------------------

3.3 Impact of making ldp:memberResource mandatory in all cases

If ldp:memberResource were mandatory in all cases the query to find the created resources associated with a container would simply become:

PREFIX ldp: <http://www.w3.org/ns/ldp#>

SELECT ?ldpc ?resource
WHERE {
      ?ldpc a ldp:Container;
        ldp:memberResource ?resource.                     # the information resource is linked via ldp:memberResource
}

The query to find which ldp:memberResource triple needs to be added when creating a resource simply becomes:

PREFIX ldp: <http://www.w3.org/ns/ldp#>

CONSTRUCT { ?ldpc ldp:memberResource ?newResource }
WHERE
{
   ?ldpc a ldp:Container.
   GRAPH ?newResource {
     ?a ?b ?c. 
   }
}

This simplifies queries at the cost of extra arcs. LDP Spec Example 3 becomes:

@base <http://example.org/container1/>
@prefix dcterms: <http://purl.org/dc/terms/>.
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>.
@prefix ldp: <http://www.w3.org/ns/ldp#>.

<>
   a ldp:DirectContainer;
   ldp:containerResource <> ;
   ldp:containsRelation rdfs:member;
   dcterms:title "A very simple container";
   rdfs:member <member1>, <member2>, <member3>;
   ldp:memberResource <member1>, <member2>, <member3>.

This approach doubles the size of large containers. The WG introduced ldp:created, now called ldp:memberResource, as optional (see June 2013 discussion) except where necessary to prevent lost links (see Nov 2013 discussion).

4 Implied xyz relationship

Some WG members have been looking for a way to precisely defined the relationship that exists between a container and the information resources (http resources or documents) associated with its members. Some actually consider this to be the relationship that should define membership but we will leave that aside for now.

Without trying to change what the spec currently defines as membership, we can define an xyz relationship that links a container to the information resources associated with its members. These information resources can be created by POSTing to a container and in the case of a SimpleContainer and DirectContainer they are listed as members. In the case of an IndirectContainer however these resources are not listed as members. They are the information resources which are created when POSTing and in which the server looked for the resource to be added as member, using ldp:insertedContentRelation.

This relation is actually the same as what is being discussed in the previous section about keeping track of the created resource. It can be materialized with the following SPARQL expression:

PREFIX ldp: <http://www.w3.org/ns/ldp#>

CONSTRUCT { ?ldpc ldp:xyz ?resource }
WHERE {
  {
      ?ldpc ldp:member ?resource.                      # the information resource is the member
  } UNION {
      ?ldpc a ldp:DirectContainer;
        ldp:containerResource ?containerResource;
        ldp:containsRelation ?containsRelation.
      ?containerResource ?containsRelation ?resource.  # the information resource is the member
  } UNION {
      ?ldpc a ldp:DirectContainer;
        ldp:containerResource ?containerResource;
        ldp:containedByRelation ?containedByRelation.  # the information resource is the member
      ?resource ?containedByRelation ?containerResource.
  } UNION {
      ?ldpc a ldp:IndirectContainer;
        ldp:memberResource ?resource.                  # the information resource is linked via ldp:memberResource
  }
}

There is no harm for a client to infer this relationship. However, as previously pointed out, if one were to require this triple, the number of triples would simply double in number in some cases.

It is tempting to think that ldp:xyz should simply be ldp:member but that would lead to a contradiction in the case of an IndirectContainer where this relationship links to an information resource which is not the member.

Instead the most logical thing to do is to think of ldp:memberResource as ldp:xyz. Indeed, in the case of an IndirectContainer ldp:memberResource does provide the link to the information resource. For that matter, it could be renamed ldp:memberInformationResource if one wanted to favor accuracy over convenience.

Defining ldp:memberResource as the ldp:xyz relationship the above query would then become:

PREFIX ldp: <http://www.w3.org/ns/ldp#>

CONSTRUCT { ?ldpc ldp:memberResource ?resource }
WHERE {
  {
      ?ldpc ldp:member ?resource.                      # the information resource is the member
  } UNION {
      ?ldpc a ldp:DirectContainer;                     # the information resource is the member
        ldp:containerResource ?containerResource;
        ldp:containsRelation ?containsRelation.
      ?containerResource ?containsRelation ?resource.  # the information resource is the member
  } UNION {
      ?ldpc a ldp:DirectContainer;
        ldp:containerResource ?containerResource;
        ldp:containedByRelation ?containedByRelation.  # the information resource is the member
      ?resource ?containedByRelation ?containerResource.
  } # IndirectContainer already provides the relationship
}

5 Binary/non-RDF resources

This is a placeholder to discuss the case of binary/non-RDF resources which comes with its own set of rules because when a binary is POSTed to a container, two resources are created: one for the binary blob, and one for the metadata.