| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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 org.apache.rya.indexing.entity.query; |
| |
| import static java.util.Objects.requireNonNull; |
| |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Objects; |
| import java.util.Optional; |
| import java.util.Set; |
| import java.util.function.Consumer; |
| |
| import org.apache.rya.api.domain.RyaType; |
| import org.apache.rya.api.domain.RyaIRI; |
| import org.apache.rya.api.resolver.RdfToRyaConversions; |
| import org.apache.rya.indexing.entity.model.Entity; |
| import org.apache.rya.indexing.entity.model.Property; |
| import org.apache.rya.indexing.entity.model.Type; |
| import org.apache.rya.indexing.entity.model.TypedEntity; |
| import org.apache.rya.indexing.entity.storage.EntityStorage; |
| import org.apache.rya.indexing.entity.storage.EntityStorage.EntityStorageException; |
| import org.apache.rya.indexing.entity.storage.mongo.ConvertingCursor; |
| import org.apache.rya.indexing.entity.update.EntityIndexer; |
| import org.apache.rya.rdftriplestore.evaluation.ExternalBatchingIterator; |
| import org.eclipse.rdf4j.common.iteration.CloseableIteration; |
| import org.eclipse.rdf4j.model.impl.SimpleValueFactory; |
| import org.eclipse.rdf4j.model.vocabulary.RDF; |
| import org.eclipse.rdf4j.query.Binding; |
| import org.eclipse.rdf4j.query.BindingSet; |
| import org.eclipse.rdf4j.query.QueryEvaluationException; |
| import org.eclipse.rdf4j.query.algebra.StatementPattern; |
| import org.eclipse.rdf4j.query.algebra.Var; |
| import org.eclipse.rdf4j.query.algebra.evaluation.impl.ExternalSet; |
| import org.eclipse.rdf4j.query.algebra.evaluation.iterator.CollectionIteration; |
| import org.eclipse.rdf4j.query.impl.MapBindingSet; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import com.google.common.collect.ImmutableCollection; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.common.collect.ImmutableMap.Builder; |
| |
| import edu.umd.cs.findbugs.annotations.DefaultAnnotation; |
| import edu.umd.cs.findbugs.annotations.NonNull; |
| |
| /** |
| * Indexing Node for {@link Entity} expressions to be inserted into execution plan |
| * to delegate entity portion of query to {@link EntityIndexer}. |
| */ |
| @DefaultAnnotation(NonNull.class) |
| public class EntityQueryNode extends ExternalSet implements ExternalBatchingIterator { |
| |
| private static final Logger LOG = LoggerFactory.getLogger(EntityQueryNode.class); |
| |
| /** |
| * The {@link RyaIRI} that when used as the Predicate of a Statement Pattern indicates the Type of the Entities. |
| */ |
| private static final RyaIRI TYPE_ID_IRI = new RyaIRI(RDF.TYPE.toString()); |
| |
| // Provided at construction time. |
| private final Type type; |
| private final Collection<StatementPattern> patterns; |
| private final EntityStorage entities; |
| |
| // Information about the subject of the patterns. |
| private final boolean subjectIsConstant; |
| private final Optional<String> subjectConstant; |
| private final Optional<String> subjectVar; |
| |
| //since and EntityQueryNode exists in a single segment, all binding names are garunteed to be assured. |
| private final Set<String> bindingNames; |
| |
| // Information about the objects of the patterns. |
| private final ImmutableMap<RyaIRI, Var> objectVariables; |
| |
| // Properties of the Type found in the query |
| private final Set<Property> properties; |
| |
| |
| /** |
| * Constructs an instance of {@link EntityQueryNode}. |
| * |
| * @param type - The type of {@link Entity} this node matches. (not null) |
| * @param patterns - The query StatementPatterns that are solved using an |
| * Entity of the Type. (not null) |
| * @param entities - The {@link EntityStorage} that will be searched to match |
| * {@link BindingSet}s when evaluating a query. (not null) |
| */ |
| public EntityQueryNode(final Type type, final Collection<StatementPattern> patterns, final EntityStorage entities) throws IllegalStateException { |
| this.type = requireNonNull(type); |
| this.patterns = requireNonNull(patterns); |
| this.entities = requireNonNull(entities); |
| |
| bindingNames = new HashSet<>(); |
| properties = new HashSet<>(); |
| // Subject based preconditions. |
| verifySameSubjects(patterns); |
| |
| // Predicate based preconditions. |
| verifyAllPredicatesAreConstants(patterns); |
| verifyHasCorrectTypePattern(type, patterns); |
| verifyAllPredicatesPartOfType(type, patterns); |
| |
| // The Subject may either be constant or a variable. |
| final Var subject = patterns.iterator().next().getSubjectVar(); |
| subjectIsConstant = subject.isConstant(); |
| if(subjectIsConstant) { |
| subjectConstant = Optional.of( subject.getValue().toString() ); |
| subjectVar = Optional.empty(); |
| } else { |
| subjectConstant = Optional.empty(); |
| subjectVar = Optional.of( subject.getName() ); |
| } |
| |
| // Any constant that appears in the Object portion of the SP will be used to make sure they match. |
| final Builder<RyaIRI, Var> builder = ImmutableMap.builder(); |
| for(final StatementPattern sp : patterns) { |
| final Var object = sp.getObjectVar(); |
| final Var pred = sp.getPredicateVar(); |
| final RyaIRI predIRI = new RyaIRI(pred.getValue().stringValue()); |
| bindingNames.addAll(sp.getBindingNames()); |
| if(object.isConstant() && !pred.getValue().equals(RDF.TYPE)) { |
| final RyaType propertyType = RdfToRyaConversions.convertValue(object.getValue()); |
| properties.add(new Property(predIRI, propertyType)); |
| } |
| builder.put(predIRI, object); |
| } |
| objectVariables = builder.build(); |
| } |
| |
| @Override |
| public Set<String> getBindingNames() { |
| return bindingNames; |
| } |
| |
| @Override |
| public Set<String> getAssuredBindingNames() { |
| return bindingNames; |
| } |
| |
| /** |
| * Verify the Subject for all of the patterns is the same. |
| * |
| * @param patterns - The patterns to check. |
| * @throws IllegalStateException If all of the Subjects are not the same. |
| */ |
| private static void verifySameSubjects(final Collection<StatementPattern> patterns) throws IllegalStateException { |
| requireNonNull(patterns); |
| |
| final Iterator<StatementPattern> it = patterns.iterator(); |
| final Var subject = it.next().getSubjectVar(); |
| |
| while(it.hasNext()) { |
| final StatementPattern pattern = it.next(); |
| if(!pattern.getSubjectVar().equals(subject)) { |
| throw new IllegalStateException("At least one of the patterns has a different subject from the others. " + |
| "All subjects must be the same."); |
| } |
| } |
| } |
| |
| /** |
| * Verifies all of the Statement Patterns have Constants for their predicates. |
| * |
| * @param patterns - The patterns to check. (not null) |
| * @throws IllegalStateException A pattern has a variable predicate. |
| */ |
| private static void verifyAllPredicatesAreConstants(final Collection<StatementPattern> patterns) throws IllegalStateException { |
| requireNonNull(patterns); |
| |
| for(final StatementPattern pattern : patterns) { |
| if(!pattern.getPredicateVar().isConstant()) { |
| throw new IllegalStateException("The Predicate of a Statement Pattern must be constant. Pattern: " + pattern); |
| } |
| } |
| } |
| |
| /** |
| * Verifies a single Statement Pattern defines the Type of Entity this query node matches. |
| * |
| * @param type - The expected Type. (not null) |
| * @param patterns - The patterns to check. (not null) |
| * @throws IllegalStateException No Type or the wrong Type is specified by the patterns. |
| */ |
| private static void verifyHasCorrectTypePattern(final Type type, final Collection<StatementPattern> patterns) throws IllegalStateException { |
| requireNonNull(type); |
| requireNonNull(patterns); |
| |
| boolean typeFound = false; |
| |
| for(final StatementPattern pattern : patterns) { |
| final RyaIRI predicate = new RyaIRI(pattern.getPredicateVar().getValue().toString()); |
| |
| if(predicate.equals(TYPE_ID_IRI)) { |
| final RyaIRI typeId = new RyaIRI( pattern.getObjectVar().getValue().stringValue() ); |
| if(typeId.equals(type.getId())) { |
| typeFound = true; |
| } else { |
| throw new IllegalStateException("Statement Pattern encountred for a Type that does not match the expected Type." + |
| " Expected Type = '" + type.getId().getData() + "' Found Type = '" + typeId.getData() + "'"); |
| } |
| } |
| } |
| |
| if(!typeFound) { |
| throw new IllegalStateException("The collection of Statement Patterns that this node matches must define which Type they match."); |
| } |
| } |
| |
| /** |
| * Verify all of the patterns have predicates that match one of the Type's property names. |
| * |
| * @param type - The Type the patterns match. (not null) |
| * @param patterns - The patterns to check. |
| * @throws IllegalStateException If any of the non-type defining Statement Patterns |
| * contain a predicate that does not match one of the Type's property names. |
| */ |
| private static void verifyAllPredicatesPartOfType(final Type type, final Collection<StatementPattern> patterns) throws IllegalStateException { |
| requireNonNull(type); |
| requireNonNull(patterns); |
| |
| for(final StatementPattern pattern : patterns) { |
| // Skip TYPE patterns. |
| final RyaIRI predicate = new RyaIRI( pattern.getPredicateVar().getValue().toString() ); |
| if(predicate.equals(TYPE_ID_IRI)) { |
| continue; |
| } |
| |
| if(!type.getPropertyNames().contains(predicate)) { |
| throw new IllegalStateException("The Predicate of a Statement Pattern must be a property name for the Type. " + |
| "Type ID: '" + type.getId().getData() + "' Pattern: " + pattern); |
| } |
| } |
| } |
| |
| @Override |
| public CloseableIteration<BindingSet, QueryEvaluationException> evaluate(final Collection<BindingSet> bindingSets) throws QueryEvaluationException { |
| requireNonNull(bindingSets); |
| final List<BindingSet> list = new ArrayList<>(); |
| bindingSets.forEach(bindingSet -> { |
| try { |
| list.addAll(findBindings(bindingSet)); |
| } catch (final Exception e) { |
| LOG.error("Unable to evaluate bindingset.", e); |
| } |
| }); |
| |
| return new CollectionIteration<>(list); |
| } |
| |
| @Override |
| public CloseableIteration<BindingSet, QueryEvaluationException> evaluate(final BindingSet bindingSet) throws QueryEvaluationException { |
| requireNonNull(bindingSet); |
| return new CollectionIteration<>(findBindings(bindingSet)); |
| } |
| |
| private List<BindingSet> findBindings(final BindingSet bindingSet) throws QueryEvaluationException { |
| final MapBindingSet resultSet = new MapBindingSet(); |
| try { |
| final ConvertingCursor<TypedEntity> entitiesCursor; |
| final String subj; |
| // If the subject needs to be filled in, check if the subject variable is in the binding set. |
| if(subjectIsConstant) { |
| // if it is, fetch that value and then fetch the entity for the subject. |
| subj = subjectConstant.get(); |
| entitiesCursor = entities.search(Optional.of(new RyaIRI(subj)), type, properties); |
| } else { |
| entitiesCursor = entities.search(Optional.empty(), type, properties); |
| } |
| |
| while(entitiesCursor.hasNext()) { |
| final TypedEntity typedEntity = entitiesCursor.next(); |
| final ImmutableCollection<Property> properties = typedEntity.getProperties(); |
| //ensure properties match and only add properties that are in the statement patterns to the binding set |
| for(final RyaIRI key : objectVariables.keySet()) { |
| final Optional<RyaType> prop = typedEntity.getPropertyValue(new RyaIRI(key.getData())); |
| if(prop.isPresent()) { |
| final RyaType type = prop.get(); |
| final String bindingName = objectVariables.get(key).getName(); |
| resultSet.addBinding(bindingName, SimpleValueFactory.getInstance().createLiteral(type.getData())); |
| } |
| } |
| } |
| } catch (final EntityStorageException e) { |
| throw new QueryEvaluationException("Failed to evaluate the binding set", e); |
| } |
| bindingSet.forEach(new Consumer<Binding>() { |
| @Override |
| public void accept(final Binding binding) { |
| resultSet.addBinding(binding); |
| } |
| }); |
| final List<BindingSet> list = new ArrayList<>(); |
| list.add(resultSet); |
| return list; |
| } |
| |
| /** |
| * @return - The {@link StatementPattern}s that make up this {@link EntityQueryNode} |
| */ |
| public Collection<StatementPattern> getPatterns() { |
| return patterns; |
| } |
| |
| @Override |
| public int hashCode() { |
| return Objects.hash(subjectIsConstant, |
| type, |
| subjectVar, |
| subjectConstant, |
| getPatterns()); |
| } |
| |
| @Override |
| public boolean equals(final Object other) { |
| if(other instanceof EntityQueryNode) { |
| final EntityQueryNode otherNode = (EntityQueryNode)other; |
| |
| final boolean samePatterns = getPatterns().size() == otherNode.getPatterns().size(); |
| boolean match = true; |
| if(samePatterns) { |
| for(final StatementPattern sp : getPatterns()) { |
| if(!otherNode.getPatterns().contains(sp)) { |
| match = false; |
| break; |
| } |
| } |
| } |
| return Objects.equals(subjectIsConstant, otherNode.subjectIsConstant) && |
| Objects.equals(subjectVar, otherNode.subjectVar) && |
| Objects.equals(type, otherNode.type) && |
| Objects.equals(subjectConstant, otherNode.subjectConstant) |
| && samePatterns && match; |
| } |
| return false; |
| } |
| |
| @Override |
| public String toString() { |
| final StringBuilder sb = new StringBuilder(); |
| sb.append("Type: " + type.toString()); |
| sb.append("Statement Patterns:"); |
| sb.append("-------------------"); |
| for(final StatementPattern sp : getPatterns()) { |
| sb.append(sp.toString()); |
| } |
| return sb.toString(); |
| } |
| } |