2020 */
2121package com .inrupt .client .solid ;
2222
23+ import com .inrupt .client .ValidationResult ;
2324import com .inrupt .client .vocabulary .LDP ;
2425import com .inrupt .client .vocabulary .RDF ;
2526import com .inrupt .rdf .wrapping .commons .ValueMappings ;
2627import com .inrupt .rdf .wrapping .commons .WrapperIRI ;
2728
2829import java .net .URI ;
30+ import java .util .ArrayList ;
2931import java .util .Collections ;
32+ import java .util .List ;
3033import java .util .Set ;
34+ import java .util .function .Predicate ;
3135import java .util .stream .Collectors ;
3236import java .util .stream .Stream ;
3337
3438import org .apache .commons .rdf .api .Dataset ;
3539import org .apache .commons .rdf .api .Graph ;
3640import org .apache .commons .rdf .api .IRI ;
3741import org .apache .commons .rdf .api .RDFTerm ;
42+ import org .apache .commons .rdf .api .Triple ;
3843
3944/**
4045 * A Solid Container Object.
@@ -77,35 +82,45 @@ public SolidContainer(final URI identifier, final Dataset dataset, final Metadat
7782 * @return the contained resources
7883 */
7984 public Set <SolidResource > getResources () {
80- final String container = getIdentifier (). toString ( );
85+ final String container = normalize ( getIdentifier ());
8186 // As defined by the Solid Protocol, containers always end with a slash.
8287 if (container .endsWith ("/" )) {
8388 final Node node = new Node (rdf .createIRI (getIdentifier ().toString ()), getGraph ());
8489 try (final Stream <Node .TypedNode > stream = node .getResources ()) {
85- return stream .flatMap (child -> {
86- final URI childLocation = URI .create (child .getIRIString ()).normalize ();
87- // Solid containment is based on URI path hierarchy,
88- // so all child resources must start with the URL of the parent
89- if (childLocation .toString ().startsWith (container )) {
90- final String relativePath = childLocation .toString ().substring (container .length ());
91- final String normalizedPath = relativePath .endsWith ("/" ) ?
92- relativePath .substring (0 , relativePath .length () - 1 ) : relativePath ;
93- // Solid containment occurs via direct decent,
94- // so any recursively contained resources must not be included
95- if (!normalizedPath .isEmpty () && !normalizedPath .contains ("/" )) {
96- final Metadata .Builder builder = Metadata .newBuilder ();
97- getMetadata ().getStorage ().ifPresent (builder ::storage );
98- child .getTypes ().forEach (builder ::type );
99- return Stream .of (new SolidResourceReference (childLocation , builder .build ()));
100- }
101- }
102- return Stream .empty ();
90+ return stream .filter (child -> verifyContainmentIri (container , child )).map (child -> {
91+ final Metadata .Builder builder = Metadata .newBuilder ();
92+ getMetadata ().getStorage ().ifPresent (builder ::storage );
93+ child .getTypes ().forEach (builder ::type );
94+ return new SolidResourceReference (URI .create (child .getIRIString ()), builder .build ());
10395 }).collect (Collectors .collectingAndThen (Collectors .toSet (), Collections ::unmodifiableSet ));
10496 }
10597 }
10698 return Collections .emptySet ();
10799 }
108100
101+ @ Override
102+ public ValidationResult validate () {
103+ // Get the normalized container URI
104+ final String container = normalize (getIdentifier ());
105+ final List <String > messages = new ArrayList <>();
106+ // Verify that the container URI path ends with a slash
107+ if (!container .endsWith ("/" )) {
108+ messages .add ("Container URI does not end in a slash" );
109+ }
110+
111+ // Verify that all ldp:contains triples align with Solid expectations
112+ getGraph ().stream (null , rdf .createIRI (LDP .contains .toString ()), null )
113+ .collect (Collectors .partitioningBy (verifyContainmentTriple (container )))
114+ .get (false ) // we are only concerned with the invalid triples
115+ .forEach (triple -> messages .add ("Invalid containment triple: " + triple .getSubject ().ntriplesString () +
116+ " ldp:contains " + triple .getObject ().ntriplesString () + " ." ));
117+
118+ if (messages .isEmpty ()) {
119+ return new ValidationResult (true );
120+ }
121+ return new ValidationResult (false , messages );
122+ }
123+
109124 /**
110125 * Retrieve the resources contained in this SolidContainer.
111126 *
@@ -117,6 +132,49 @@ public Stream<SolidResource> getContainedResources() {
117132 return getResources ().stream ();
118133 }
119134
135+ static String normalize (final IRI iri ) {
136+ return normalize (URI .create (iri .getIRIString ()));
137+ }
138+
139+ static String normalize (final URI uri ) {
140+ return uri .normalize ().toString ().split ("#" )[0 ].split ("\\ ?" )[0 ];
141+ }
142+
143+ static Predicate <Triple > verifyContainmentTriple (final String container ) {
144+ final IRI subject = rdf .createIRI (container );
145+ return triple -> {
146+ if (!triple .getSubject ().equals (subject )) {
147+ // Out-of-domain containment triple subject
148+ return false ;
149+ }
150+ if (triple .getObject () instanceof IRI ) {
151+ return verifyContainmentIri (container , (IRI ) triple .getObject ());
152+ }
153+ // Non-URI containment triple object
154+ return false ;
155+ };
156+ }
157+
158+ static boolean verifyContainmentIri (final String container , final IRI object ) {
159+ if (!object .getIRIString ().startsWith (container )) {
160+ // Out-of-domain containment triple object
161+ return false ;
162+ } else {
163+ final String relativePath = object .getIRIString ().substring (container .length ());
164+ final String normalizedPath = relativePath .endsWith ("/" ) ?
165+ relativePath .substring (0 , relativePath .length () - 1 ) : relativePath ;
166+ if (normalizedPath .isEmpty ()) {
167+ // Containment triple subject and object cannot be the same
168+ return false ;
169+ }
170+ if (normalizedPath .contains ("/" )) {
171+ // Containment cannot skip intermediate nodes
172+ return false ;
173+ }
174+ }
175+ return true ;
176+ }
177+
120178 @ SuppressWarnings ("java:S2160" ) // Wrapper equality is correctly delegated to underlying node
121179 static final class Node extends WrapperIRI {
122180 private final IRI ldpContains = rdf .createIRI (LDP .contains .toString ());
0 commit comments