Java tutorial
/* * Copyright Amherst College * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package edu.amherst.acdc.trellis.constraint; import static java.util.Collections.unmodifiableMap; import static java.util.Collections.unmodifiableSet; import static java.util.Objects.requireNonNull; import static java.util.Optional.of; import static java.util.Optional.ofNullable; import static java.util.stream.Collectors.groupingBy; import static java.util.stream.Collectors.mapping; import static java.util.stream.Collectors.toList; import static java.util.stream.Stream.empty; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Stream; import edu.amherst.acdc.trellis.spi.ConstraintService; import edu.amherst.acdc.trellis.vocabulary.ACL; import edu.amherst.acdc.trellis.vocabulary.LDP; import edu.amherst.acdc.trellis.vocabulary.RDF; import edu.amherst.acdc.trellis.vocabulary.Trellis; import org.apache.commons.rdf.api.Graph; import org.apache.commons.rdf.api.IRI; import org.apache.commons.rdf.api.RDFTerm; import org.apache.commons.rdf.api.Triple; /** * @author acoburn */ public class LdpConstraints implements ConstraintService { private static Predicate<Triple> indirectConstraints = triple -> triple.getPredicate().equals(LDP.contains); private static Predicate<Triple> directConstraints = indirectConstraints .or(triple -> triple.getPredicate().equals(LDP.insertedContentRelation)); private static Predicate<Triple> basicConstraints = directConstraints .or(triple -> triple.getPredicate().equals(LDP.membershipResource) || triple.getPredicate().equals(LDP.hasMemberRelation) || triple.getPredicate().equals(LDP.isMemberOfRelation)); private static final Map<IRI, Predicate<Triple>> typeMap = unmodifiableMap( new HashMap<IRI, Predicate<Triple>>() { { put(LDP.BasicContainer, basicConstraints); put(LDP.Container, basicConstraints); put(LDP.DirectContainer, directConstraints); put(LDP.IndirectContainer, indirectConstraints); put(LDP.NonRDFSource, basicConstraints); put(LDP.RDFSource, basicConstraints); } }); private static final Set<IRI> propertiesWithInDomainRange = unmodifiableSet(new HashSet<IRI>() { { add(ACL.accessControl); add(LDP.membershipResource); } }); private static final Set<IRI> propertiesWithUriRange = unmodifiableSet(new HashSet<IRI>() { { add(ACL.accessControl); add(LDP.membershipResource); add(LDP.hasMemberRelation); add(LDP.isMemberOfRelation); add(LDP.inbox); add(LDP.insertedContentRelation); } }); // Ensure that any LDP properties are appropriate for the interaction model private static Predicate<Triple> propertyFilter(final IRI model) { return of(model).filter(typeMap::containsKey).map(typeMap::get).orElse(basicConstraints); } // Ensure that RDF graphs adhere to the single-subject rule private static Predicate<Triple> subjectFilter(final IRI context) { return triple -> of(triple).map(Triple::getSubject).filter( iri -> !iri.equals(context) && !iri.ntriplesString().startsWith("<" + context.getIRIString() + "#")) .isPresent(); } // Don't allow LDP types to be set explicitly private static Predicate<Triple> typeFilter(final IRI model) { return triple -> of(triple).filter(t -> t.getPredicate().equals(RDF.type)).map(Triple::getObject) .map(RDFTerm::ntriplesString).filter(str -> str.startsWith("<" + LDP.uri)).isPresent(); } // Verify that the range of the property is a URI (if the property is in the above set) private static Predicate<Triple> uriRangeFilter = triple -> { return propertiesWithUriRange.contains(triple.getPredicate()) && !(triple.getObject() instanceof IRI); }; // Verify that the range of the property is in the repository domain private static Predicate<Triple> inDomainRangeFilter(final String domain) { return triple -> propertiesWithInDomainRange.contains(triple.getPredicate()) && !triple.getObject().ntriplesString().startsWith("<" + domain); } // Verify that the cardinality of the `propertiesWithUriRange` properties. Keep any whose cardinality is > 1 private static Predicate<Graph> checkCardinality = graph -> graph.stream() .filter(triple -> propertiesWithUriRange.contains(triple.getPredicate())) .collect(groupingBy(Triple::getPredicate, mapping(Triple::getObject, toList()))).entrySet().stream() .map(Map.Entry::getValue).map(List::size).anyMatch(val -> val > 1); private final String domain; /** * Create a LpdConstraints service * @param domain the repository domain */ public LdpConstraints(final String domain) { this.domain = domain; } private Function<Triple, Stream<IRI>> checkModelConstraints(final IRI model, final IRI context) { requireNonNull(model, "The interaction model must not be null!"); return triple -> of(triple).filter(propertyFilter(model)).map(t -> Stream.of(Trellis.InvalidProperty)) .orElseGet( () -> of(triple).filter(subjectFilter(context)).map(t -> Stream.of(Trellis.InvalidSubject)) .orElseGet(() -> of(triple).filter(typeFilter(model)) .map(t -> Stream.of(Trellis.InvalidType)) .orElseGet(() -> of(triple).filter(uriRangeFilter) .map(t -> Stream.of(Trellis.InvalidRange)) .orElseGet(() -> of(triple).filter(inDomainRangeFilter(domain)) .map(t -> Stream.of(Trellis.InvalidRange)) .orElse(empty()))))); } @Override public Optional<IRI> constrainedBy(final IRI model, final Graph graph, final IRI context) { return ofNullable(graph.stream().parallel().flatMap(checkModelConstraints(model, context)).findAny() .orElseGet(() -> of(graph).filter(checkCardinality).map(t -> Trellis.InvalidCardinality) .orElse(null))); } }