You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@rya.apache.org by ca...@apache.org on 2017/08/23 15:47:07 UTC

[2/3] incubator-rya git commit: RYA-292 Added owl:intersectionOf inference. Closes #206.

http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/e9488ff6/sail/src/main/java/org/apache/rya/rdftriplestore/inference/InferenceEngine.java
----------------------------------------------------------------------
diff --git a/sail/src/main/java/org/apache/rya/rdftriplestore/inference/InferenceEngine.java b/sail/src/main/java/org/apache/rya/rdftriplestore/inference/InferenceEngine.java
index 05b847b..a2f8d63 100644
--- a/sail/src/main/java/org/apache/rya/rdftriplestore/inference/InferenceEngine.java
+++ b/sail/src/main/java/org/apache/rya/rdftriplestore/inference/InferenceEngine.java
@@ -6,9 +6,9 @@
  * 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
@@ -25,8 +25,10 @@ import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Set;
 import java.util.Stack;
 import java.util.Timer;
@@ -34,6 +36,18 @@ import java.util.TimerTask;
 import java.util.TreeMap;
 import java.util.concurrent.ConcurrentHashMap;
 
+import org.apache.commons.lang3.mutable.MutableObject;
+import org.apache.log4j.Logger;
+import org.apache.rya.api.RdfCloudTripleStoreConfiguration;
+import org.apache.rya.api.persist.RyaDAO;
+import org.apache.rya.api.persist.RyaDAOException;
+import org.apache.rya.api.persist.utils.RyaDAOHelper;
+import org.apache.rya.api.persist.utils.RyaDaoQueryWrapper;
+import org.apache.tinkerpop.gremlin.structure.Direction;
+import org.apache.tinkerpop.gremlin.structure.Graph;
+import org.apache.tinkerpop.gremlin.structure.T;
+import org.apache.tinkerpop.gremlin.structure.Vertex;
+import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerGraph;
 import org.openrdf.model.Resource;
 import org.openrdf.model.Statement;
 import org.openrdf.model.URI;
@@ -45,21 +59,12 @@ import org.openrdf.model.vocabulary.OWL;
 import org.openrdf.model.vocabulary.RDF;
 import org.openrdf.model.vocabulary.RDFS;
 import org.openrdf.query.QueryEvaluationException;
-import org.apache.tinkerpop.gremlin.structure.Direction;
-import org.apache.tinkerpop.gremlin.structure.Edge;
-import org.apache.tinkerpop.gremlin.structure.Graph;
-import org.apache.tinkerpop.gremlin.structure.T;
-import org.apache.tinkerpop.gremlin.structure.VertexProperty;
-import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerGraph;
-import org.apache.tinkerpop.gremlin.structure.Vertex;
+import org.openrdf.rio.RDFHandlerException;
+import org.openrdf.rio.helpers.RDFHandlerBase;
 
-import com.google.common.collect.Iterators;
+import com.google.common.collect.Sets;
 
 import info.aduna.iteration.CloseableIteration;
-import org.apache.rya.api.RdfCloudTripleStoreConfiguration;
-import org.apache.rya.api.persist.RyaDAO;
-import org.apache.rya.api.persist.RyaDAOException;
-import org.apache.rya.api.persist.utils.RyaDAOHelper;
 
 /**
  * Will pull down inference relationships from dao every x seconds. <br>
@@ -67,6 +72,7 @@ import org.apache.rya.api.persist.utils.RyaDAOHelper;
  * Will cache relationships in Graph for later use. <br>
  */
 public class InferenceEngine {
+    private static final Logger log = Logger.getLogger(InferenceEngine.class);
 
     private Graph subClassOfGraph;
     private Graph subPropertyOfGraph;
@@ -78,15 +84,17 @@ public class InferenceEngine {
     private Map<Resource, Map<URI, Value>> hasValueByType;
     private Map<URI, Map<Resource, Value>> hasValueByProperty;
     private Map<Resource, Map<Resource, URI>> allValuesFromByValueType;
+    private final ConcurrentHashMap<Resource, List<Set<Resource>>> intersections = new ConcurrentHashMap<>();
 
-    private RyaDAO ryaDAO;
+    private RyaDAO<?> ryaDAO;
     private RdfCloudTripleStoreConfiguration conf;
+    private RyaDaoQueryWrapper ryaDaoQueryWrapper;
     private boolean initialized = false;
     private boolean schedule = true;
 
     private long refreshGraphSchedule = 5 * 60 * 1000; //5 min
     private Timer timer;
-	private HashMap<URI, List<URI>> propertyChainPropertyToChain = new HashMap<URI, List<URI>>();
+    private HashMap<URI, List<URI>> propertyChainPropertyToChain = new HashMap<>();
     public static final String URI_PROP = "uri";
 
     public void init() throws InferenceEngineException {
@@ -98,9 +106,10 @@ public class InferenceEngine {
             checkNotNull(conf, "Configuration is null");
             checkNotNull(ryaDAO, "RdfDao is null");
             checkArgument(ryaDAO.isInitialized(), "RdfDao is not initialized");
+            ryaDaoQueryWrapper = new RyaDaoQueryWrapper(ryaDAO, conf);
 
             if (schedule) {
-            	refreshGraph();
+                refreshGraph();
                 timer = new Timer(InferenceEngine.class.getName());
                 timer.scheduleAtFixedRate(new TimerTask() {
 
@@ -108,7 +117,7 @@ public class InferenceEngine {
                     public void run() {
                         try {
                             refreshGraph();
-                        } catch (InferenceEngineException e) {
+                        } catch (final InferenceEngineException e) {
                             throw new RuntimeException(e);
                         }
                     }
@@ -117,7 +126,7 @@ public class InferenceEngine {
             }
             refreshGraph();
             setInitialized(true);
-        } catch (RyaDAOException e) {
+        } catch (final RyaDAOException e) {
             throw new InferenceEngineException(e);
         }
     }
@@ -130,7 +139,7 @@ public class InferenceEngine {
     }
 
     public void refreshGraph() throws InferenceEngineException {
-        ValueFactory vf = ValueFactoryImpl.getInstance();
+        final ValueFactory vf = ValueFactoryImpl.getInstance();
         try {
             CloseableIteration<Statement, QueryEvaluationException> iter;
             //get all subclassof
@@ -146,20 +155,20 @@ public class InferenceEngine {
             iter = RyaDAOHelper.query(ryaDAO, null, OWL.UNIONOF, null, conf);
             try {
                 while (iter.hasNext()) {
-                    Statement st = iter.next();
-                    Value unionType = st.getSubject();
+                    final Statement st = iter.next();
+                    final Value unionType = st.getSubject();
                     // Traverse the list of types constituting the union
                     Value current = st.getObject();
                     while (current instanceof Resource && !RDF.NIL.equals(current)) {
-                        Resource listNode = (Resource) current;
+                        final Resource listNode = (Resource) current;
                         CloseableIteration<Statement, QueryEvaluationException> listIter = RyaDAOHelper.query(ryaDAO,
                                 listNode, RDF.FIRST, null, conf);
                         try {
                             if (listIter.hasNext()) {
-                                Statement firstStatement = listIter.next();
+                                final Statement firstStatement = listIter.next();
                                 if (firstStatement.getObject() instanceof Resource) {
-                                    Resource subclass = (Resource) firstStatement.getObject();
-                                    Statement subclassStatement = vf.createStatement(subclass, RDFS.SUBCLASSOF, unionType);
+                                    final Resource subclass = (Resource) firstStatement.getObject();
+                                    final Statement subclassStatement = vf.createStatement(subclass, RDFS.SUBCLASSOF, unionType);
                                     addStatementEdge(graph, RDFS.SUBCLASSOF.stringValue(), subclassStatement);
                                 }
                             }
@@ -192,11 +201,13 @@ public class InferenceEngine {
             addPredicateEdges(OWL.EQUIVALENTPROPERTY, Direction.BOTH, graph, RDFS.SUBPROPERTYOF.stringValue());
             subPropertyOfGraph = graph; //TODO: Should this be synchronized?
 
+            refreshIntersectionOf();
+
             iter = RyaDAOHelper.query(ryaDAO, null, RDF.TYPE, OWL.SYMMETRICPROPERTY, conf);
-            Set<URI> symProp = new HashSet();
+            final Set<URI> symProp = new HashSet<>();
             try {
                 while (iter.hasNext()) {
-                    Statement st = iter.next();
+                    final Statement st = iter.next();
                     symProp.add((URI) st.getSubject()); //safe to assume it is a URI?
                 }
             } finally {
@@ -207,10 +218,10 @@ public class InferenceEngine {
             symmetricPropertySet = symProp;
 
             iter = RyaDAOHelper.query(ryaDAO, null, RDF.TYPE, OWL.TRANSITIVEPROPERTY, conf);
-            Set<URI> transProp = new HashSet();
+            final Set<URI> transProp = new HashSet<>();
             try {
                 while (iter.hasNext()) {
-                    Statement st = iter.next();
+                    final Statement st = iter.next();
                     transProp.add((URI) st.getSubject());
                 }
             } finally {
@@ -221,10 +232,10 @@ public class InferenceEngine {
             transitivePropertySet = transProp;
 
             iter = RyaDAOHelper.query(ryaDAO, null, OWL.INVERSEOF, null, conf);
-            Map<URI, URI> invProp = new HashMap();
+            final Map<URI, URI> invProp = new HashMap<>();
             try {
                 while (iter.hasNext()) {
-                    Statement st = iter.next();
+                    final Statement st = iter.next();
                     invProp.put((URI) st.getSubject(), (URI) st.getObject());
                     invProp.put((URI) st.getObject(), (URI) st.getSubject());
                 }
@@ -234,153 +245,153 @@ public class InferenceEngine {
                 }
             }
             inverseOfMap = invProp;
-            
-            iter = RyaDAOHelper.query(ryaDAO, null, 
-            		vf.createURI("http://www.w3.org/2002/07/owl#propertyChainAxiom"),
-            		null, conf);
-            Map<URI,URI> propertyChainPropertiesToBNodes = new HashMap<URI, URI>();
-            propertyChainPropertyToChain = new HashMap<URI, List<URI>>();
+
+            iter = RyaDAOHelper.query(ryaDAO, null,
+                    vf.createURI("http://www.w3.org/2002/07/owl#propertyChainAxiom"),
+                    null, conf);
+            final Map<URI,URI> propertyChainPropertiesToBNodes = new HashMap<>();
+            propertyChainPropertyToChain = new HashMap<>();
             try {
-            	while (iter.hasNext()){
-            		Statement st = iter.next();
-            		propertyChainPropertiesToBNodes.put((URI)st.getSubject(), (URI)st.getObject());
-            	}
+                while (iter.hasNext()){
+                    final Statement st = iter.next();
+                    propertyChainPropertiesToBNodes.put((URI)st.getSubject(), (URI)st.getObject());
+                }
             } finally {
                 if (iter != null) {
                     iter.close();
                 }
             }
             // now for each property chain bNode, get the indexed list of properties associated with that chain
-            for (URI propertyChainProperty : propertyChainPropertiesToBNodes.keySet()){
-            	URI bNode = propertyChainPropertiesToBNodes.get(propertyChainProperty);
-            	// query for the list of indexed properties
-            	iter = RyaDAOHelper.query(ryaDAO, bNode, vf.createURI("http://www.w3.org/2000/10/swap/list#index"),
-            			null, conf);
-            	TreeMap<Integer, URI> orderedProperties = new TreeMap<Integer, URI>();
-            	// TODO refactor this.  Wish I could execute sparql
-            	try {
-            		while (iter.hasNext()){
-            		  Statement st = iter.next();
-            		  String indexedElement = st.getObject().stringValue();
-            		  System.out.println(indexedElement);
-            		  CloseableIteration<Statement, QueryEvaluationException>  iter2 = RyaDAOHelper.query(ryaDAO, vf.createURI(st.getObject().stringValue()), RDF.FIRST,
-                    			null, conf);
-            		  String integerValue = "";
-            		  Value anonPropNode = null;
-            		  Value propURI = null;
-            		  if (iter2 != null){
-            			  while (iter2.hasNext()){
-            				  Statement iter2Statement = iter2.next();
-            				  integerValue = iter2Statement.getObject().stringValue();
-            				  break;
-            			  }
-            			  iter2.close();
-            		  }
-            		  iter2 = RyaDAOHelper.query(ryaDAO, vf.createURI(st.getObject().stringValue()), RDF.REST,
-                  			null, conf);
-            		  if (iter2 != null){
-            			  while (iter2.hasNext()){
-            				  Statement iter2Statement = iter2.next();
-            				  anonPropNode = iter2Statement.getObject();
-            				  break;
-            			  }
-            			  iter2.close();
-            			  if (anonPropNode != null){
-            				  iter2 = RyaDAOHelper.query(ryaDAO, vf.createURI(anonPropNode.stringValue()), RDF.FIRST,
-                            			null, conf);
-            				  while (iter2.hasNext()){
-                				  Statement iter2Statement = iter2.next();
-                				  propURI = iter2Statement.getObject();
-                				  break;
-                			  }
-                			  iter2.close();
-            			  }
-            		  }
-            		  if (!integerValue.isEmpty() && propURI!=null) {
-            			  try {
-                			  int indexValue = Integer.parseInt(integerValue);
-                			  URI chainPropURI = vf.createURI(propURI.stringValue());
-                			  orderedProperties.put(indexValue, chainPropURI);
-            			  }
-            			  catch (Exception ex){
-            				  // TODO log an error here
-            				  
-            			  }
-            		  }
-            		}
-            	} finally{
-            		if (iter != null){
-            			iter.close();
-            		}
-            	}
-            	List<URI> properties = new ArrayList<URI>();
-            	for (Map.Entry<Integer, URI> entry : orderedProperties.entrySet()){
-            		properties.add(entry.getValue());
-            	}
-            	propertyChainPropertyToChain.put(propertyChainProperty, properties);
+            for (final URI propertyChainProperty : propertyChainPropertiesToBNodes.keySet()){
+                final URI bNode = propertyChainPropertiesToBNodes.get(propertyChainProperty);
+                // query for the list of indexed properties
+                iter = RyaDAOHelper.query(ryaDAO, bNode, vf.createURI("http://www.w3.org/2000/10/swap/list#index"),
+                        null, conf);
+                final TreeMap<Integer, URI> orderedProperties = new TreeMap<>();
+                // TODO refactor this.  Wish I could execute sparql
+                try {
+                    while (iter.hasNext()){
+                        final Statement st = iter.next();
+                        final String indexedElement = st.getObject().stringValue();
+                        log.info(indexedElement);
+                        CloseableIteration<Statement, QueryEvaluationException>  iter2 = RyaDAOHelper.query(ryaDAO, vf.createURI(st.getObject().stringValue()), RDF.FIRST,
+                                null, conf);
+                        String integerValue = "";
+                        Value anonPropNode = null;
+                        Value propURI = null;
+                        if (iter2 != null){
+                            while (iter2.hasNext()){
+                                final Statement iter2Statement = iter2.next();
+                                integerValue = iter2Statement.getObject().stringValue();
+                                break;
+                            }
+                            iter2.close();
+                        }
+                        iter2 = RyaDAOHelper.query(ryaDAO, vf.createURI(st.getObject().stringValue()), RDF.REST,
+                                null, conf);
+                        if (iter2 != null){
+                            while (iter2.hasNext()){
+                                final Statement iter2Statement = iter2.next();
+                                anonPropNode = iter2Statement.getObject();
+                                break;
+                            }
+                            iter2.close();
+                            if (anonPropNode != null){
+                                iter2 = RyaDAOHelper.query(ryaDAO, vf.createURI(anonPropNode.stringValue()), RDF.FIRST,
+                                        null, conf);
+                                while (iter2.hasNext()){
+                                    final Statement iter2Statement = iter2.next();
+                                    propURI = iter2Statement.getObject();
+                                    break;
+                                }
+                                iter2.close();
+                            }
+                        }
+                        if (!integerValue.isEmpty() && propURI!=null) {
+                            try {
+                                final int indexValue = Integer.parseInt(integerValue);
+                                final URI chainPropURI = vf.createURI(propURI.stringValue());
+                                orderedProperties.put(indexValue, chainPropURI);
+                            }
+                            catch (final Exception ex){
+                                // TODO log an error here
+
+                            }
+                        }
+                    }
+                } finally{
+                    if (iter != null){
+                        iter.close();
+                    }
+                }
+                final List<URI> properties = new ArrayList<>();
+                for (final Map.Entry<Integer, URI> entry : orderedProperties.entrySet()){
+                    properties.add(entry.getValue());
+                }
+                propertyChainPropertyToChain.put(propertyChainProperty, properties);
             }
-            
+
             // could also be represented as a list of properties (some of which may be blank nodes)
-            for (URI propertyChainProperty : propertyChainPropertiesToBNodes.keySet()){
-            	List<URI> existingChain = propertyChainPropertyToChain.get(propertyChainProperty);
-            	// if we didn't get a chain, try to get it through following the collection
-            	if ((existingChain == null) || existingChain.isEmpty()) {
-            		
-          		  CloseableIteration<Statement, QueryEvaluationException>  iter2 = RyaDAOHelper.query(ryaDAO, propertyChainPropertiesToBNodes.get(propertyChainProperty), RDF.FIRST,
-              			null, conf);
-          		  List<URI> properties = new ArrayList<URI>();
-          		  URI previousBNode = propertyChainPropertiesToBNodes.get(propertyChainProperty);
-            	  if (iter2.hasNext()) {
-            		  Statement iter2Statement = iter2.next();
-            		  Value currentPropValue = iter2Statement.getObject();
-            		  while ((currentPropValue != null) && (!currentPropValue.stringValue().equalsIgnoreCase(RDF.NIL.stringValue()))){
-                		  if (currentPropValue instanceof URI){
-                    		  iter2 = RyaDAOHelper.query(ryaDAO, vf.createURI(currentPropValue.stringValue()), RDF.FIRST,
-                          			null, conf);
-                			  if (iter2.hasNext()){
-                				  iter2Statement = iter2.next();
-                				  if (iter2Statement.getObject() instanceof URI){
-                					  properties.add((URI)iter2Statement.getObject());
-                				  }
-                			  }
-                			  // otherwise see if there is an inverse declaration
-                			  else {
-                				  iter2 = RyaDAOHelper.query(ryaDAO, vf.createURI(currentPropValue.stringValue()), OWL.INVERSEOF,
-                                			null, conf);
-                				  if (iter2.hasNext()){
-                    				  iter2Statement = iter2.next();
-                    				  if (iter2Statement.getObject() instanceof URI){
-                    					  properties.add(new InverseURI((URI)iter2Statement.getObject()));
-                    				  }
-                    			  }
-                			  }
-            				  // get the next prop pointer
-            				  iter2 = RyaDAOHelper.query(ryaDAO, previousBNode, RDF.REST,
-                            			null, conf);
-            				  if (iter2.hasNext()){
-                				  iter2Statement = iter2.next();
-                				  previousBNode = (URI)currentPropValue;
-                				  currentPropValue = iter2Statement.getObject();
-                			  }
-            				  else {
-            					  currentPropValue = null;
-            				  }
-                		  }
-                		  else {
-                		    currentPropValue = null;
-                		  }
-            			  
-            		  }
-                  	propertyChainPropertyToChain.put(propertyChainProperty, properties);
-            	  }
-            	}
+            for (final URI propertyChainProperty : propertyChainPropertiesToBNodes.keySet()){
+                final List<URI> existingChain = propertyChainPropertyToChain.get(propertyChainProperty);
+                // if we didn't get a chain, try to get it through following the collection
+                if ((existingChain == null) || existingChain.isEmpty()) {
+
+                    CloseableIteration<Statement, QueryEvaluationException>  iter2 = RyaDAOHelper.query(ryaDAO, propertyChainPropertiesToBNodes.get(propertyChainProperty), RDF.FIRST,
+                            null, conf);
+                    final List<URI> properties = new ArrayList<>();
+                    URI previousBNode = propertyChainPropertiesToBNodes.get(propertyChainProperty);
+                    if (iter2.hasNext()) {
+                        Statement iter2Statement = iter2.next();
+                        Value currentPropValue = iter2Statement.getObject();
+                        while ((currentPropValue != null) && (!currentPropValue.stringValue().equalsIgnoreCase(RDF.NIL.stringValue()))){
+                            if (currentPropValue instanceof URI){
+                                iter2 = RyaDAOHelper.query(ryaDAO, vf.createURI(currentPropValue.stringValue()), RDF.FIRST,
+                                        null, conf);
+                                if (iter2.hasNext()){
+                                    iter2Statement = iter2.next();
+                                    if (iter2Statement.getObject() instanceof URI){
+                                        properties.add((URI)iter2Statement.getObject());
+                                    }
+                                }
+                                // otherwise see if there is an inverse declaration
+                                else {
+                                    iter2 = RyaDAOHelper.query(ryaDAO, vf.createURI(currentPropValue.stringValue()), OWL.INVERSEOF,
+                                            null, conf);
+                                    if (iter2.hasNext()){
+                                        iter2Statement = iter2.next();
+                                        if (iter2Statement.getObject() instanceof URI){
+                                            properties.add(new InverseURI((URI)iter2Statement.getObject()));
+                                        }
+                                    }
+                                }
+                                // get the next prop pointer
+                                iter2 = RyaDAOHelper.query(ryaDAO, previousBNode, RDF.REST,
+                                        null, conf);
+                                if (iter2.hasNext()){
+                                    iter2Statement = iter2.next();
+                                    previousBNode = (URI)currentPropValue;
+                                    currentPropValue = iter2Statement.getObject();
+                                }
+                                else {
+                                    currentPropValue = null;
+                                }
+                            }
+                            else {
+                                currentPropValue = null;
+                            }
+
+                        }
+                        propertyChainPropertyToChain.put(propertyChainProperty, properties);
+                    }
+                }
             }
 
             refreshDomainRange();
 
             refreshPropertyRestrictions();
 
-        } catch (QueryEvaluationException e) {
+        } catch (final QueryEvaluationException e) {
             throw new InferenceEngineException(e);
         }
     }
@@ -396,13 +407,13 @@ public class InferenceEngine {
      * @param edgeName Label that will be given to all added edges
      * @throws QueryEvaluationException
      */
-    private void addPredicateEdges(URI predicate, Direction dir, Graph graph, String edgeName)
+    private void addPredicateEdges(final URI predicate, final Direction dir, final Graph graph, final String edgeName)
             throws QueryEvaluationException {
-        CloseableIteration<Statement, QueryEvaluationException> iter = RyaDAOHelper.query(ryaDAO,
+        final CloseableIteration<Statement, QueryEvaluationException> iter = RyaDAOHelper.query(ryaDAO,
                 null, predicate, null, conf);
         try {
             while (iter.hasNext()) {
-                Statement st = iter.next();
+                final Statement st = iter.next();
                 if (Direction.OUT.equals(dir) || Direction.BOTH.equals(dir)) {
                     addStatementEdge(graph, edgeName, st);
                 }
@@ -434,15 +445,15 @@ public class InferenceEngine {
      * @throws QueryEvaluationException
      */
     private void refreshDomainRange() throws QueryEvaluationException {
-        Map<URI, Set<URI>> domainByTypePartial = new ConcurrentHashMap<>();
-        Map<URI, Set<URI>> rangeByTypePartial = new ConcurrentHashMap<>();
+        final Map<URI, Set<URI>> domainByTypePartial = new ConcurrentHashMap<>();
+        final Map<URI, Set<URI>> rangeByTypePartial = new ConcurrentHashMap<>();
         // First, populate domain and range based on direct domain/range triples.
         CloseableIteration<Statement, QueryEvaluationException> iter = RyaDAOHelper.query(ryaDAO, null, RDFS.DOMAIN, null, conf);
         try {
             while (iter.hasNext()) {
-                Statement st = iter.next();
-                Resource property = st.getSubject();
-                Value domainType = st.getObject();
+                final Statement st = iter.next();
+                final Resource property = st.getSubject();
+                final Value domainType = st.getObject();
                 if (domainType instanceof URI && property instanceof URI) {
                     if (!domainByTypePartial.containsKey(domainType)) {
                         domainByTypePartial.put((URI) domainType, new HashSet<>());
@@ -458,9 +469,9 @@ public class InferenceEngine {
         iter = RyaDAOHelper.query(ryaDAO, null, RDFS.RANGE, null, conf);
         try {
             while (iter.hasNext()) {
-                Statement st = iter.next();
-                Resource property = st.getSubject();
-                Value rangeType = st.getObject();
+                final Statement st = iter.next();
+                final Resource property = st.getSubject();
+                final Value rangeType = st.getObject();
                 if (rangeType instanceof URI && property instanceof URI) {
                     if (!rangeByTypePartial.containsKey(rangeType)) {
                         rangeByTypePartial.put((URI) rangeType, new HashSet<>());
@@ -475,26 +486,26 @@ public class InferenceEngine {
         }
         // Then combine with the subclass/subproperty graphs and the inverse property map to compute
         // the closure of domain and range per class.
-        Set<URI> domainRangeTypeSet = new HashSet<>(domainByTypePartial.keySet());
+        final Set<URI> domainRangeTypeSet = new HashSet<>(domainByTypePartial.keySet());
         domainRangeTypeSet.addAll(rangeByTypePartial.keySet());
         // Extend to subproperties: make sure that using a more specific form of a property
         // still triggers its domain/range inferences.
         // Mirror for inverse properties: make sure that using the inverse form of a property
         // triggers the inverse domain/range inferences.
         // These two rules can recursively trigger one another.
-        for (URI domainRangeType : domainRangeTypeSet) {
-            Set<URI> propertiesWithDomain = domainByTypePartial.getOrDefault(domainRangeType, new HashSet<>());
-            Set<URI> propertiesWithRange = rangeByTypePartial.getOrDefault(domainRangeType, new HashSet<>());
+        for (final URI domainRangeType : domainRangeTypeSet) {
+            final Set<URI> propertiesWithDomain = domainByTypePartial.getOrDefault(domainRangeType, new HashSet<>());
+            final Set<URI> propertiesWithRange = rangeByTypePartial.getOrDefault(domainRangeType, new HashSet<>());
             // Since findParents will traverse the subproperty graph and find all indirect
             // subproperties, the subproperty rule does not need to trigger itself directly.
             // And since no more than one inverseOf relationship is stored for any property, the
             // inverse property rule does not need to trigger itself directly. However, each rule
             // can trigger the other, so keep track of how the inferred domains/ranges were
             // discovered so we can apply only those rules that might yield new information.
-            Stack<URI> domainViaSuperProperty  = new Stack<>();
-            Stack<URI> rangeViaSuperProperty  = new Stack<>();
-            Stack<URI> domainViaInverseProperty  = new Stack<>();
-            Stack<URI> rangeViaInverseProperty  = new Stack<>();
+            final Stack<URI> domainViaSuperProperty  = new Stack<>();
+            final Stack<URI> rangeViaSuperProperty  = new Stack<>();
+            final Stack<URI> domainViaInverseProperty  = new Stack<>();
+            final Stack<URI> rangeViaInverseProperty  = new Stack<>();
             // Start with the direct domain/range assertions, which can trigger any rule.
             domainViaSuperProperty.addAll(propertiesWithDomain);
             domainViaInverseProperty.addAll(propertiesWithDomain);
@@ -507,8 +518,8 @@ public class InferenceEngine {
                 // For a type c and property p, if c is a domain of p, then c is the range of any
                 // inverse of p. Would be redundant for properties discovered via inverseOf.
                 while (!domainViaSuperProperty.isEmpty()) {
-                    URI property = domainViaSuperProperty.pop();
-                    URI inverseProperty = findInverseOf(property);
+                    final URI property = domainViaSuperProperty.pop();
+                    final URI inverseProperty = findInverseOf(property);
                     if (inverseProperty != null && propertiesWithRange.add(inverseProperty)) {
                         rangeViaInverseProperty.push(inverseProperty);
                     }
@@ -516,8 +527,8 @@ public class InferenceEngine {
                 // For a type c and property p, if c is a range of p, then c is the domain of any
                 // inverse of p. Would be redundant for properties discovered via inverseOf.
                 while (!rangeViaSuperProperty.isEmpty()) {
-                    URI property = rangeViaSuperProperty.pop();
-                    URI inverseProperty = findInverseOf(property);
+                    final URI property = rangeViaSuperProperty.pop();
+                    final URI inverseProperty = findInverseOf(property);
                     if (inverseProperty != null && propertiesWithDomain.add(inverseProperty)) {
                         domainViaInverseProperty.push(inverseProperty);
                     }
@@ -525,8 +536,8 @@ public class InferenceEngine {
                 // For a type c and property p, if c is a domain of p, then c is also a domain of
                 // p's subproperties. Would be redundant for properties discovered via this rule.
                 while (!domainViaInverseProperty.isEmpty()) {
-                    URI property = domainViaInverseProperty.pop();
-                    Set<URI> subProperties = findParents(subPropertyOfGraph, property);
+                    final URI property = domainViaInverseProperty.pop();
+                    final Set<URI> subProperties = findParents(subPropertyOfGraph, property);
                     subProperties.removeAll(propertiesWithDomain);
                     propertiesWithDomain.addAll(subProperties);
                     domainViaSuperProperty.addAll(subProperties);
@@ -534,8 +545,8 @@ public class InferenceEngine {
                 // For a type c and property p, if c is a range of p, then c is also a range of
                 // p's subproperties. Would be redundant for properties discovered via this rule.
                 while (!rangeViaInverseProperty.isEmpty()) {
-                    URI property = rangeViaInverseProperty.pop();
-                    Set<URI> subProperties = findParents(subPropertyOfGraph, property);
+                    final URI property = rangeViaInverseProperty.pop();
+                    final Set<URI> subProperties = findParents(subPropertyOfGraph, property);
                     subProperties.removeAll(propertiesWithRange);
                     propertiesWithRange.addAll(subProperties);
                     rangeViaSuperProperty.addAll(subProperties);
@@ -551,23 +562,23 @@ public class InferenceEngine {
         // Once all properties have been found for each domain/range class, extend to superclasses:
         // make sure that the consequent of a domain/range inference goes on to apply any more
         // general classes as well.
-        for (URI subtype : domainRangeTypeSet) {
-            Set<URI> supertypes = findChildren(subClassOfGraph, subtype);
-            Set<URI> propertiesWithDomain = domainByTypePartial.getOrDefault(subtype, new HashSet<>());
-            Set<URI> propertiesWithRange = rangeByTypePartial.getOrDefault(subtype, new HashSet<>());
-            for (URI supertype : supertypes) {
+        for (final URI subtype : domainRangeTypeSet) {
+            final Set<URI> supertypes = getSuperClasses(subtype);
+            final Set<URI> propertiesWithDomain = domainByTypePartial.getOrDefault(subtype, new HashSet<>());
+            final Set<URI> propertiesWithRange = rangeByTypePartial.getOrDefault(subtype, new HashSet<>());
+            for (final URI supertype : supertypes) {
                 // For a property p and its domain c: all of c's superclasses are also domains of p.
                 if (!propertiesWithDomain.isEmpty() && !domainByTypePartial.containsKey(supertype)) {
                     domainByTypePartial.put(supertype, new HashSet<>());
                 }
-                for (URI property : propertiesWithDomain) {
+                for (final URI property : propertiesWithDomain) {
                     domainByTypePartial.get(supertype).add(property);
                 }
                 // For a property p and its range c: all of c's superclasses are also ranges of p.
                 if (!propertiesWithRange.isEmpty() && !rangeByTypePartial.containsKey(supertype)) {
                     rangeByTypePartial.put(supertype, new HashSet<>());
                 }
-                for (URI property : propertiesWithRange) {
+                for (final URI property : propertiesWithRange) {
                     rangeByTypePartial.get(supertype).add(property);
                 }
             }
@@ -578,11 +589,11 @@ public class InferenceEngine {
 
     private void refreshPropertyRestrictions() throws QueryEvaluationException {
         // Get a set of all property restrictions of any type
-        CloseableIteration<Statement, QueryEvaluationException> iter = RyaDAOHelper.query(ryaDAO, null, OWL.ONPROPERTY, null, conf);
-        Map<Resource, URI> restrictions = new HashMap<>();
+        final CloseableIteration<Statement, QueryEvaluationException> iter = RyaDAOHelper.query(ryaDAO, null, OWL.ONPROPERTY, null, conf);
+        final Map<Resource, URI> restrictions = new HashMap<>();
         try {
             while (iter.hasNext()) {
-                Statement st = iter.next();
+                final Statement st = iter.next();
                 restrictions.put(st.getSubject(), (URI) st.getObject());
             }
         } finally {
@@ -595,17 +606,17 @@ public class InferenceEngine {
         refreshAllValuesFromRestrictions(restrictions);
     }
 
-    private void refreshHasValueRestrictions(Map<Resource, URI> restrictions) throws QueryEvaluationException {
+    private void refreshHasValueRestrictions(final Map<Resource, URI> restrictions) throws QueryEvaluationException {
         hasValueByType = new HashMap<>();
         hasValueByProperty = new HashMap<>();
-        CloseableIteration<Statement, QueryEvaluationException> iter = RyaDAOHelper.query(ryaDAO, null, OWL.HASVALUE, null, conf);
+        final CloseableIteration<Statement, QueryEvaluationException> iter = RyaDAOHelper.query(ryaDAO, null, OWL.HASVALUE, null, conf);
         try {
             while (iter.hasNext()) {
-                Statement st = iter.next();
-                Resource restrictionClass = st.getSubject();
+                final Statement st = iter.next();
+                final Resource restrictionClass = st.getSubject();
                 if (restrictions.containsKey(restrictionClass)) {
-                    URI property = restrictions.get(restrictionClass);
-                    Value value = st.getObject();
+                    final URI property = restrictions.get(restrictionClass);
+                    final Value value = st.getObject();
                     if (!hasValueByType.containsKey(restrictionClass)) {
                         hasValueByType.put(restrictionClass, new HashMap<>());
                     }
@@ -623,22 +634,22 @@ public class InferenceEngine {
         }
     }
 
-    private void refreshAllValuesFromRestrictions(Map<Resource, URI> restrictions) throws QueryEvaluationException {
+    private void refreshAllValuesFromRestrictions(final Map<Resource, URI> restrictions) throws QueryEvaluationException {
         allValuesFromByValueType = new HashMap<>();
-        CloseableIteration<Statement, QueryEvaluationException> iter = RyaDAOHelper.query(ryaDAO, null, OWL.ALLVALUESFROM, null, conf);
+        final CloseableIteration<Statement, QueryEvaluationException> iter = RyaDAOHelper.query(ryaDAO, null, OWL.ALLVALUESFROM, null, conf);
         try {
             while (iter.hasNext()) {
-                Statement st = iter.next();
+                final Statement st = iter.next();
                 if (restrictions.containsKey(st.getSubject()) && st.getObject() instanceof URI) {
-                    URI property = restrictions.get(st.getSubject());
-                    URI valueClass = (URI) st.getObject();
+                    final URI property = restrictions.get(st.getSubject());
+                    final URI valueClass = (URI) st.getObject();
                     // Should also be triggered by subclasses of the property restriction
-                    Set<Resource> restrictionClasses = new HashSet<>();
+                    final Set<Resource> restrictionClasses = new HashSet<>();
                     restrictionClasses.add(st.getSubject());
                     if (st.getSubject() instanceof URI) {
-                        restrictionClasses.addAll(findParents(subClassOfGraph, (URI) st.getSubject()));
+                        restrictionClasses.addAll(getSubClasses((URI) st.getSubject()));
                     }
-                    for (Resource restrictionClass : restrictionClasses) {
+                    for (final Resource restrictionClass : restrictionClasses) {
                         if (!allValuesFromByValueType.containsKey(valueClass)) {
                             allValuesFromByValueType.put(valueClass, new HashMap<>());
                         }
@@ -653,136 +664,356 @@ public class InferenceEngine {
         }
     }
 
-    private static Vertex getVertex(Graph graph, Object id) {
-        Iterator<Vertex> it = graph.vertices(id.toString());
+    private void refreshIntersectionOf() throws QueryEvaluationException {
+        final Map<Resource, List<Set<Resource>>> intersectionsProp = new HashMap<>();
+
+        // First query for all the owl:intersectionOf's.
+        // If we have the following intersectionOf:
+        // :A owl:intersectionOf[:B, :C]
+        // It will be represented by triples following a pattern similar to:
+        // <:A> owl:intersectionOf _:bnode1 .
+        //  _:bnode1 rdf:first <:B> .
+        //  _:bnode1 rdf:rest _:bnode2 .
+        // _:bnode2 rdf:first <:C> .
+        // _:bnode2 rdf:rest rdf:nil .
+        ryaDaoQueryWrapper.queryAll(null, OWL.INTERSECTIONOF, null, new RDFHandlerBase() {
+            @Override
+            public void handleStatement(final Statement statement) throws RDFHandlerException {
+                final Resource type = statement.getSubject();
+                // head will point to a type that is part of the intersection.
+                final URI head = (URI) statement.getObject();
+                if (!intersectionsProp.containsKey(type)) {
+                    intersectionsProp.put(type, new ArrayList<Set<Resource>>());
+                }
+
+                // head should point to a list of items that forms the
+                // intersection.
+                try {
+                    final Set<Resource> intersection = new LinkedHashSet<>(getList(head));
+                    if (!intersection.isEmpty()) {
+                        // Add this intersection for this type. There may be more
+                        // intersections for this type so each type has a list of
+                        // intersection sets.
+                        intersectionsProp.get(type).add(intersection);
+                    }
+                } catch (final QueryEvaluationException e) {
+                    throw new RDFHandlerException("Error getting intersection list.", e);
+                }
+            }
+        });
+
+        intersections.clear();
+        for (final Entry<Resource, List<Set<Resource>>> entry : intersectionsProp.entrySet()) {
+            final Resource type = entry.getKey();
+            final List<Set<Resource>> intersectionList = entry.getValue();
+            final Set<Resource> otherTypes = new HashSet<>();
+            // Combine all of a type's intersections together.
+            for (final Set<Resource> intersection : intersectionList) {
+                otherTypes.addAll(intersection);
+            }
+            for (final Resource other : otherTypes) {
+                // :A intersectionOf[:B, :C] implies that
+                // :A subclassOf :B
+                // :A subclassOf :C
+                // So add each type that's part of the intersection to the
+                // subClassOf graph.
+                addSubClassOf(type, other);
+                for (final Set<Resource> intersection : intersectionList) {
+                    if (!intersection.contains(other)) {
+                        addIntersection(intersection, other);
+                    }
+                }
+            }
+            for (final Set<Resource> intersection : intersectionList) {
+                addIntersection(intersection, type);
+            }
+        }
+        for (final Entry<Resource, List<Set<Resource>>> entry : intersectionsProp.entrySet()) {
+            final Resource type = entry.getKey();
+            final List<Set<Resource>> intersectionList = entry.getValue();
+
+            final Set<URI> superClasses = getSuperClasses((URI) type);
+            for (final URI superClass : superClasses) {
+                // Add intersections to super classes if applicable.
+                // IF:
+                // :A intersectionOf[:B, :C]
+                // AND
+                // :A subclassOf :D
+                // Then we can infer:
+                // intersectionOf[:B, :C] subclassOf :D
+                for (final Set<Resource> intersection : intersectionList) {
+                    addIntersection(intersection, superClass);
+                }
+            }
+            // Check if other keys have any of the same intersections and infer
+            // the same subclass logic to them that we know from the current
+            // type. Propagating up through all the superclasses.
+            for (final Set<Resource> intersection : intersectionList) {
+                final Set<Resource> otherKeys = Sets.newHashSet(intersectionsProp.keySet());
+                otherKeys.remove(type);
+                for (final Resource otherKey : otherKeys) {
+                    if (intersectionsProp.get(otherKey).contains(intersection)) {
+                        addSubClassOf(otherKey, type);
+                        addSubClassOf(type, otherKey);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Queries for all items that are in a list of the form:
+     * <pre>
+     *     <:A> ?x _:bnode1 .
+     *     _:bnode1 rdf:first <:B> .
+     *     _:bnode1 rdf:rest _:bnode2 .
+     *     _:bnode2 rdf:first <:C> .
+     *     _:bnode2 rdf:rest rdf:nil .
+     * </pre>
+     * Where {@code :_bnode1} represents the first item in the list and
+     * {@code ?x} is some restriction on {@code <:A>}. This will return the
+     * list of resources, {@code [<:B>, <:C>]}.
+     * @param firstItem the first item in the list.
+     * @return the {@link List} of {@link Resource}s.
+     * @throws QueryEvaluationException
+     */
+    private List<Resource> getList(final URI firstItem) throws QueryEvaluationException {
+        URI head = firstItem;
+        final List<Resource> list = new ArrayList<>();
+        // Go through and find all bnodes that are part of the defined list.
+        while (!RDF.NIL.equals(head)) {
+            // rdf.first will point to a type item that is in the list.
+            ryaDaoQueryWrapper.queryFirst(head, RDF.FIRST, null, new RDFHandlerBase() {
+                @Override
+                public void handleStatement(final Statement statement) throws RDFHandlerException {
+                    // The object found in the query represents a type
+                    // that should be included in the list.
+                    final URI object = (URI) statement.getObject();
+                    list.add(object);
+                }
+            });
+            final MutableObject<URI> headHolder = new MutableObject<>();
+            // rdf.rest will point to the next bnode that's part of the list.
+            ryaDaoQueryWrapper.queryFirst(head, RDF.REST, null, new RDFHandlerBase() {
+                @Override
+                public void handleStatement(final Statement statement) throws RDFHandlerException {
+                    // This object is the next bnode head to look for.
+                    final URI object = (URI) statement.getObject();
+                    headHolder.setValue(object);
+                }
+            });
+            // As long as we get a new head there are more bnodes that are part
+            // of the list. Keep going until we reach rdf.nil.
+            if (headHolder.getValue() != null) {
+                head = headHolder.getValue();
+            } else {
+                head = RDF.NIL;
+            }
+        }
+        return list;
+    }
+
+    private void addSubClassOf(final Resource s, final Resource o) {
+        final Statement statement = new StatementImpl(s, RDFS.SUBCLASSOF, o);
+        final String edgeName = RDFS.SUBCLASSOF.stringValue();
+        addStatementEdge(subClassOfGraph, edgeName, statement);
+    }
+
+    private void addIntersection(final Set<Resource> intersection, final Resource type) {
+        if (type != null && intersection != null && !intersection.isEmpty()) {
+            List<Set<Resource>> intersectionList = intersections.get(type);
+            if (intersectionList == null) {
+                intersectionList = new ArrayList<>();
+            }
+            if (!intersectionList.contains(intersection)) {
+                intersectionList.add(intersection);
+            }
+            intersections.put(type, intersectionList);
+        }
+    }
+
+    private static Vertex getVertex(final Graph graph, final Object id) {
+        final Iterator<Vertex> it = graph.vertices(id.toString());
         if (it.hasNext()) {
             return it.next();
         }
         return null;
     }
 
-    private void addStatementEdge(Graph graph, String edgeName, Statement st) {
-        Resource subj = st.getSubject();
+    private static void addStatementEdge(final Graph graph, final String edgeName, final Statement st) {
+        final Resource subj = st.getSubject();
         Vertex a = getVertex(graph, subj);
         if (a == null) {
             a = graph.addVertex(T.id, subj.toString());
             a.property(URI_PROP, subj);
         }
-        Value obj = st.getObject();
+        final Value obj = st.getObject();
         Vertex b = getVertex(graph, obj);
         if (b == null) {
             b = graph.addVertex(T.id, obj.toString());
             b.property(URI_PROP, obj);
         }
         a.addEdge(edgeName, b);
-   }
+    }
+
+    /**
+     * Returns all super class types of the specified type based on the
+     * internal subclass graph.
+     * @param type the type {@link URI} to find super classes for.
+     * @return the {@link Set} of {@link URI} types that are super classes types
+     * of the specified {@code type}. Returns an empty set if nothing was found.
+     */
+    public Set<URI> getSuperClasses(final URI type) {
+        return findChildren(subClassOfGraph, type);
+    }
+
+    /**
+     * Returns all sub class types of the specified type based on the
+     * internal subclass graph.
+     * @param type the type {@link URI} to find sub classes for.
+     * @return the {@link Set} of {@link URI} types that are sub classes types
+     * of the specified {@code type}. Returns an empty set if nothing was found.
+     */
+    public Set<URI> getSubClasses(final URI type) {
+        return findParents(subClassOfGraph, type);
+    }
+
+    public static Set<URI> findParents(final Graph graph, final URI vertexId) {
+        return findParents(graph, vertexId, true);
+    }
+
+    public static Set<URI> findParents(final Graph graph, final URI vertexId, final boolean isRecursive) {
+        return findConnected(graph, vertexId, Direction.IN, isRecursive);
+    }
 
-    public Set<URI> findParents(Graph graph, URI vertexId) {
-        return findConnected(graph, vertexId, Direction.IN);
+    public static Set<URI> findChildren(final Graph graph, final URI vertexId) {
+        return findChildren(graph, vertexId, true);
     }
 
-    public Set<URI> findChildren(Graph graph, URI vertexId) {
-        return findConnected(graph, vertexId, Direction.OUT);
+    public static Set<URI> findChildren(final Graph graph, final URI vertexId, final boolean isRecursive) {
+        return findConnected(graph, vertexId, Direction.OUT, isRecursive);
     }
 
-    private Set<URI> findConnected(Graph graph, URI vertexId, Direction traversal) {
-        Set<URI> connected = new HashSet<>();
+    private static Set<URI> findConnected(final Graph graph, final URI vertexId, final Direction traversal, final boolean isRecursive) {
+        final Set<URI> connected = new HashSet<>();
         if (graph == null) {
             return connected;
         }
-        Vertex v = getVertex(graph, vertexId);
+        final Vertex v = getVertex(graph, vertexId);
         if (v == null) {
             return connected;
         }
-        addConnected(v, connected, traversal);
+        addConnected(v, connected, traversal, isRecursive);
         return connected;
     }
 
-    private static void addConnected(Vertex v, Set<URI> connected, Direction traversal) {
+    private static void addConnected(final Vertex v, final Set<URI> connected, final Direction traversal, final boolean isRecursive) {
         v.edges(traversal).forEachRemaining(edge -> {
-            Vertex ov = edge.vertices(traversal.opposite()).next();
-            Object o = ov.property(URI_PROP).value();
+            final Vertex ov = edge.vertices(traversal.opposite()).next();
+            final Object o = ov.property(URI_PROP).value();
             if (o != null && o instanceof URI) {
-                boolean contains = connected.contains(o);
+                final boolean contains = connected.contains(o);
                 if (!contains) {
                     connected.add((URI) o);
-                    addConnected(ov, connected, traversal);
+                    if (isRecursive) {
+                        addConnected(ov, connected, traversal, isRecursive);
+                    }
                 }
             }
         });
     }
 
-    public boolean isSymmetricProperty(URI prop) {
+    public boolean isSymmetricProperty(final URI prop) {
         return (symmetricPropertySet != null) && symmetricPropertySet.contains(prop);
     }
 
-    public URI findInverseOf(URI prop) {
+    public URI findInverseOf(final URI prop) {
         return (inverseOfMap != null) ? inverseOfMap.get(prop) : (null);
     }
 
-    public boolean isTransitiveProperty(URI prop) {
+    public boolean isTransitiveProperty(final URI prop) {
         return (transitivePropertySet != null) && transitivePropertySet.contains(prop);
     }
 
     /**
      * TODO: This chaining can be slow at query execution. the other option is to perform this in the query itself, but that will be constrained to how many levels we decide to go
      */
-    public Set<Statement> findTransitiveProperty(Resource subj, URI prop, Value obj, Resource... contxts) throws InferenceEngineException {
+    public Set<Statement> findTransitiveProperty(final Resource subj, final URI prop, final Value obj, final Resource... contxts) throws InferenceEngineException {
         if (transitivePropertySet.contains(prop)) {
-            Set<Statement> sts = new HashSet();
-            boolean goUp = subj == null;
+            final Set<Statement> sts = new HashSet<>();
+            final boolean goUp = subj == null;
             chainTransitiveProperty(subj, prop, obj, (goUp) ? (obj) : (subj), sts, goUp, contxts);
             return sts;
-        } else
+        } else {
             return null;
+        }
     }
 
     /**
      * TODO: This chaining can be slow at query execution. the other option is to perform this in the query itself, but that will be constrained to how many levels we decide to go
      */
-    public Set<Resource> findSameAs(Resource value, Resource... contxts) throws InferenceEngineException{
-		Set<Resource> sameAs = new HashSet<Resource>();
-		sameAs.add(value);
-		findSameAsChaining(value, sameAs, contxts);
-		return sameAs;
+    public Set<Resource> findSameAs(final Resource value, final Resource... contxts) throws InferenceEngineException{
+        final Set<Resource> sameAs = new HashSet<>();
+        sameAs.add(value);
+        findSameAsChaining(value, sameAs, contxts);
+        return sameAs;
+    }
+
+    public CloseableIteration<Statement, QueryEvaluationException> queryDao(final Resource subject, final URI predicate, final Value object, final Resource... contexts) throws QueryEvaluationException {
+        return RyaDAOHelper.query(ryaDAO, subject, predicate, object, conf, contexts);
     }
 
     /**
      * TODO: This chaining can be slow at query execution. the other option is to perform this in the query itself, but that will be constrained to how many levels we decide to go
      */
-    public void findSameAsChaining(Resource subj, Set<Resource> currentSameAs, Resource[] contxts) throws InferenceEngineException{
+    public void findSameAsChaining(final Resource subj, final Set<Resource> currentSameAs, final Resource[] contxts) throws InferenceEngineException{
+        CloseableIteration<Statement, QueryEvaluationException> subjIter = null;
+        CloseableIteration<Statement, QueryEvaluationException> objIter = null;
         try {
-			CloseableIteration<Statement, QueryEvaluationException> subjIter = RyaDAOHelper.query(ryaDAO, subj, OWL.SAMEAS, null, conf, contxts);
-			while (subjIter.hasNext()){
-				Statement st = subjIter.next();
-				if (!currentSameAs.contains(st.getObject())){
-					Resource castedObj = (Resource) st.getObject();
-					currentSameAs.add(castedObj);
-					findSameAsChaining(castedObj, currentSameAs, contxts);
-				}
-			}
-			subjIter.close();
-			CloseableIteration<Statement, QueryEvaluationException> objIter = RyaDAOHelper.query(ryaDAO, null, OWL.SAMEAS, subj, conf, contxts);
-			while (objIter.hasNext()){
-				Statement st = objIter.next();
-				if (!currentSameAs.contains(st.getSubject())){
-					Resource sameAsSubj = st.getSubject();
-					currentSameAs.add(sameAsSubj);
-					findSameAsChaining(sameAsSubj, currentSameAs, contxts);
-				}
-			}
-			objIter.close();
-		} catch (QueryEvaluationException e) {
-			throw new InferenceEngineException(e);
-		}
-
+            subjIter = queryDao(subj, OWL.SAMEAS, null, contxts);
+            while (subjIter.hasNext()){
+                final Statement st = subjIter.next();
+                if (!currentSameAs.contains(st.getObject())){
+                    final Resource castedObj = (Resource) st.getObject();
+                    currentSameAs.add(castedObj);
+                    findSameAsChaining(castedObj, currentSameAs, contxts);
+                }
+            }
+            objIter = queryDao(null, OWL.SAMEAS, subj, contxts);
+            while (objIter.hasNext()){
+                final Statement st = objIter.next();
+                if (!currentSameAs.contains(st.getSubject())){
+                    final Resource sameAsSubj = st.getSubject();
+                    currentSameAs.add(sameAsSubj);
+                    findSameAsChaining(sameAsSubj, currentSameAs, contxts);
+                }
+            }
+        } catch (final QueryEvaluationException e) {
+            throw new InferenceEngineException(e);
+        } finally {
+            if (subjIter != null) {
+                try {
+                    subjIter.close();
+                } catch (final QueryEvaluationException e) {
+                    throw new InferenceEngineException("Error while closing \"same as chaining\" statement subject iterator.", e);
+                }
+            }
+            if (objIter != null) {
+                try {
+                    objIter.close();
+                } catch (final QueryEvaluationException e) {
+                    throw new InferenceEngineException("Error while closing \"same as chaining\" statement object iterator.", e);
+                }
+            }
+        }
     }
 
-    protected void chainTransitiveProperty(Resource subj, URI prop, Value obj, Value core, Set<Statement> sts, boolean goUp, Resource[] contxts) throws InferenceEngineException {
+    protected void chainTransitiveProperty(final Resource subj, final URI prop, final Value obj, final Value core, final Set<Statement> sts, final boolean goUp, final Resource[] contxts) throws InferenceEngineException {
+        CloseableIteration<Statement, QueryEvaluationException> iter = null;
         try {
-            CloseableIteration<Statement, QueryEvaluationException> iter = RyaDAOHelper.query(ryaDAO, subj, prop, obj, conf, contxts);
+            iter = queryDao(subj, prop, obj, contxts);
             while (iter.hasNext()) {
-                Statement st = iter.next();
+                final Statement st = iter.next();
                 sts.add(new StatementImpl((goUp) ? (st.getSubject()) : (Resource) (core), prop, (!goUp) ? (st.getObject()) : (core)));
                 if (goUp) {
                     chainTransitiveProperty(null, prop, st.getSubject(), core, sts, goUp, contxts);
@@ -790,9 +1021,16 @@ public class InferenceEngine {
                     chainTransitiveProperty((Resource) st.getObject(), prop, null, core, sts, goUp, contxts);
                 }
             }
-            iter.close();
-        } catch (QueryEvaluationException e) {
+        } catch (final QueryEvaluationException e) {
             throw new InferenceEngineException(e);
+        } finally {
+            if (iter != null) {
+                try {
+                    iter.close();
+                } catch (final QueryEvaluationException e) {
+                    throw new InferenceEngineException("Error while closing \"chain transitive\" property statement iterator.", e);
+                }
+            }
         }
     }
 
@@ -800,23 +1038,24 @@ public class InferenceEngine {
         return initialized;
     }
 
-    public void setInitialized(boolean initialized) {
+    public void setInitialized(final boolean initialized) {
         this.initialized = initialized;
     }
 
-    public RyaDAO getRyaDAO() {
+    public RyaDAO<?> getRyaDAO() {
         return ryaDAO;
     }
 
-    public void setRyaDAO(RyaDAO ryaDAO) {
+    public void setRyaDAO(final RyaDAO<?> ryaDAO) {
         this.ryaDAO = ryaDAO;
+        this.ryaDaoQueryWrapper = new RyaDaoQueryWrapper(ryaDAO);
     }
 
     public RdfCloudTripleStoreConfiguration getConf() {
         return conf;
     }
 
-    public void setConf(RdfCloudTripleStoreConfiguration conf) {
+    public void setConf(final RdfCloudTripleStoreConfiguration conf) {
         this.conf = conf;
     }
 
@@ -828,10 +1067,10 @@ public class InferenceEngine {
         return propertyChainPropertyToChain;
     }
 
-    public List<URI> getPropertyChain(URI chainProp) {
-    	if (propertyChainPropertyToChain.containsKey(chainProp)){
-    		return propertyChainPropertyToChain.get(chainProp);
-    	}
+    public List<URI> getPropertyChain(final URI chainProp) {
+        if (propertyChainPropertyToChain.containsKey(chainProp)){
+            return propertyChainPropertyToChain.get(chainProp);
+        }
         return new ArrayList<URI>();
     }
 
@@ -843,7 +1082,7 @@ public class InferenceEngine {
         return refreshGraphSchedule;
     }
 
-    public void setRefreshGraphSchedule(long refreshGraphSchedule) {
+    public void setRefreshGraphSchedule(final long refreshGraphSchedule) {
         this.refreshGraphSchedule = refreshGraphSchedule;
     }
 
@@ -851,7 +1090,7 @@ public class InferenceEngine {
         return symmetricPropertySet;
     }
 
-    public void setSymmetricPropertySet(Set<URI> symmetricPropertySet) {
+    public void setSymmetricPropertySet(final Set<URI> symmetricPropertySet) {
         this.symmetricPropertySet = symmetricPropertySet;
     }
 
@@ -859,7 +1098,7 @@ public class InferenceEngine {
         return inverseOfMap;
     }
 
-    public void setInverseOfMap(Map<URI, URI> inverseOfMap) {
+    public void setInverseOfMap(final Map<URI, URI> inverseOfMap) {
         this.inverseOfMap = inverseOfMap;
     }
 
@@ -867,7 +1106,7 @@ public class InferenceEngine {
         return transitivePropertySet;
     }
 
-    public void setTransitivePropertySet(Set<URI> transitivePropertySet) {
+    public void setTransitivePropertySet(final Set<URI> transitivePropertySet) {
         this.transitivePropertySet = transitivePropertySet;
     }
 
@@ -875,7 +1114,7 @@ public class InferenceEngine {
         return schedule;
     }
 
-    public void setSchedule(boolean schedule) {
+    public void setSchedule(final boolean schedule) {
         this.schedule = schedule;
     }
 
@@ -889,17 +1128,17 @@ public class InferenceEngine {
      * @return For each relevant property, a set of values such that whenever a resource has that
      *      value for that property, it is implied to belong to the type.
      */
-    public Map<URI, Set<Value>> getHasValueByType(Resource type) {
-        Map<URI, Set<Value>> implications = new HashMap<>();
+    public Map<URI, Set<Value>> getHasValueByType(final Resource type) {
+        final Map<URI, Set<Value>> implications = new HashMap<>();
         if (hasValueByType != null) {
-            Set<Resource> types = new HashSet<>();
+            final Set<Resource> types = new HashSet<>();
             types.add(type);
             if (type instanceof URI) {
-                types.addAll(findParents(subClassOfGraph, (URI) type));
+                types.addAll(getSubClasses((URI) type));
             }
-            for (Resource relevantType : types) {
+            for (final Resource relevantType : types) {
                 if (hasValueByType.containsKey(relevantType)) {
-                    for (Map.Entry<URI, Value> propertyToValue : hasValueByType.get(relevantType).entrySet()) {
+                    for (final Map.Entry<URI, Value> propertyToValue : hasValueByType.get(relevantType).entrySet()) {
                         if (!implications.containsKey(propertyToValue.getKey())) {
                             implications.put(propertyToValue.getKey(), new HashSet<>());
                         }
@@ -922,17 +1161,17 @@ public class InferenceEngine {
      * @return A mapping from type (URIs or bnodes) to the set of any values that belonging to that
      *      type implies.
      */
-    public Map<Resource, Set<Value>> getHasValueByProperty(URI property) {
-        Map<Resource, Set<Value>> implications = new HashMap<>();
+    public Map<Resource, Set<Value>> getHasValueByProperty(final URI property) {
+        final Map<Resource, Set<Value>> implications = new HashMap<>();
         if (hasValueByProperty != null && hasValueByProperty.containsKey(property)) {
-            for (Map.Entry<Resource, Value> typeToValue : hasValueByProperty.get(property).entrySet()) {
-                Resource type = typeToValue.getKey();
+            for (final Map.Entry<Resource, Value> typeToValue : hasValueByProperty.get(property).entrySet()) {
+                final Resource type = typeToValue.getKey();
                 if (!implications.containsKey(type)) {
                     implications.put(type, new HashSet<>());
                 }
                 implications.get(type).add(typeToValue.getValue());
                 if (type instanceof URI) {
-                    for (URI subtype : findParents(subClassOfGraph, (URI) type)) {
+                    for (final URI subtype : getSubClasses((URI) type)) {
                         if (!implications.containsKey(subtype)) {
                             implications.put(subtype, new HashSet<>());
                         }
@@ -954,8 +1193,8 @@ public class InferenceEngine {
      * @return The set of properties with domain of that type, meaning that any triple whose
      *      predicate belongs to that set implies that the triple's subject belongs to the type.
      */
-    public Set<URI> getPropertiesWithDomain(URI domainType) {
-        Set<URI> properties = new HashSet<>();
+    public Set<URI> getPropertiesWithDomain(final URI domainType) {
+        final Set<URI> properties = new HashSet<>();
         if (domainByType.containsKey(domainType)) {
             properties.addAll(domainByType.get(domainType));
         }
@@ -972,8 +1211,8 @@ public class InferenceEngine {
      * @return The set of properties with range of that type, meaning that any triple whose
      *      predicate belongs to that set implies that the triple's object belongs to the type.
      */
-    public Set<URI> getPropertiesWithRange(URI rangeType) {
-        Set<URI> properties = new HashSet<>();
+    public Set<URI> getPropertiesWithRange(final URI rangeType) {
+        final Set<URI> properties = new HashSet<>();
         if (rangeByType.containsKey(rangeType)) {
             properties.addAll(rangeByType.get(rangeType));
         }
@@ -996,23 +1235,23 @@ public class InferenceEngine {
      *      subproperties) such that for any individual which belongs to the subject type, all
      *      values it has for any of those properties belong to the value type.
      */
-    public Map<Resource, Set<URI>> getAllValuesFromByValueType(Resource valueType) {
-        Map<Resource, Set<URI>> implications = new HashMap<>();
+    public Map<Resource, Set<URI>> getAllValuesFromByValueType(final Resource valueType) {
+        final Map<Resource, Set<URI>> implications = new HashMap<>();
         if (allValuesFromByValueType != null) {
             // Check for any subtypes which would in turn imply the value type
-            HashSet<Resource> valueTypes = new HashSet<>();
+            final HashSet<Resource> valueTypes = new HashSet<>();
             valueTypes.add(valueType);
             if (valueType instanceof URI) {
-                valueTypes.addAll(findParents(subClassOfGraph, (URI) valueType));
+                valueTypes.addAll(getSubClasses((URI) valueType));
             }
-            for (Resource valueSubType : valueTypes) {
+            for (final Resource valueSubType : valueTypes) {
                 if (allValuesFromByValueType.containsKey(valueSubType)) {
-                    Map<Resource, URI> restrictionToProperty = allValuesFromByValueType.get(valueSubType);
-                    for (Resource restrictionType : restrictionToProperty.keySet()) {
+                    final Map<Resource, URI> restrictionToProperty = allValuesFromByValueType.get(valueSubType);
+                    for (final Resource restrictionType : restrictionToProperty.keySet()) {
                         if (!implications.containsKey(restrictionType)) {
                             implications.put(restrictionType, new HashSet<>());
                         }
-                        URI property = restrictionToProperty.get(restrictionType);
+                        final URI property = restrictionToProperty.get(restrictionType);
                         implications.get(restrictionType).add(property);
                         // Also add subproperties that would in turn imply the property
                         implications.get(restrictionType).addAll(findParents(subPropertyOfGraph, property));
@@ -1022,4 +1261,24 @@ public class InferenceEngine {
         }
         return implications;
     }
+
+    /**
+     * For a given type, return all sets of types such that owl:intersectionOf
+     * restrictions on those properties could imply this type.  A type can have
+     * multiple intersections and each intersection is a set of types so a list
+     * of all the type sets is returned.
+     * @param type The type (URI or bnode) to check against the known
+     * intersections.
+     * @return A {@link List} of {@link Resource} type {@link Set}s that
+     * represents all the known intersections that imply the specified type.
+     * {@code null} is returned if no intersections were found for the specified
+     * type.
+     */
+    public List<Set<Resource>> getIntersectionsImplying(final Resource type) {
+        if (intersections != null) {
+            final List<Set<Resource>> intersectionList = intersections.get(type);
+            return intersectionList;
+        }
+        return null;
+    }
 }

http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/e9488ff6/sail/src/main/java/org/apache/rya/rdftriplestore/inference/InferenceEngineException.java
----------------------------------------------------------------------
diff --git a/sail/src/main/java/org/apache/rya/rdftriplestore/inference/InferenceEngineException.java b/sail/src/main/java/org/apache/rya/rdftriplestore/inference/InferenceEngineException.java
index 45bdfd7..af1805d 100644
--- a/sail/src/main/java/org/apache/rya/rdftriplestore/inference/InferenceEngineException.java
+++ b/sail/src/main/java/org/apache/rya/rdftriplestore/inference/InferenceEngineException.java
@@ -8,9 +8,9 @@ package org.apache.rya.rdftriplestore.inference;
  * 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
@@ -26,18 +26,20 @@ package org.apache.rya.rdftriplestore.inference;
  * Time: 11:03 AM
  */
 public class InferenceEngineException extends Exception {
+    private static final long serialVersionUID = 1L;
+
     public InferenceEngineException() {
     }
 
-    public InferenceEngineException(String s) {
+    public InferenceEngineException(final String s) {
         super(s);
     }
 
-    public InferenceEngineException(String s, Throwable throwable) {
+    public InferenceEngineException(final String s, final Throwable throwable) {
         super(s, throwable);
     }
 
-    public InferenceEngineException(Throwable throwable) {
+    public InferenceEngineException(final Throwable throwable) {
         super(throwable);
     }
 }

http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/e9488ff6/sail/src/main/java/org/apache/rya/rdftriplestore/inference/IntersectionOfVisitor.java
----------------------------------------------------------------------
diff --git a/sail/src/main/java/org/apache/rya/rdftriplestore/inference/IntersectionOfVisitor.java b/sail/src/main/java/org/apache/rya/rdftriplestore/inference/IntersectionOfVisitor.java
new file mode 100644
index 0000000..b4853c0
--- /dev/null
+++ b/sail/src/main/java/org/apache/rya/rdftriplestore/inference/IntersectionOfVisitor.java
@@ -0,0 +1,210 @@
+/*
+ * 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.rdftriplestore.inference;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.apache.log4j.Logger;
+import org.apache.rya.api.RdfCloudTripleStoreConfiguration;
+import org.openrdf.model.Resource;
+import org.openrdf.model.URI;
+import org.openrdf.model.vocabulary.RDF;
+import org.openrdf.query.algebra.StatementPattern;
+import org.openrdf.query.algebra.TupleExpr;
+import org.openrdf.query.algebra.Union;
+import org.openrdf.query.algebra.Var;
+
+/**
+ * Visitor for handling owl:intersectionOf inferencing on a node.
+ */
+public class IntersectionOfVisitor extends AbstractInferVisitor {
+    private static final Logger log = Logger.getLogger(IntersectionOfVisitor.class);
+
+    /**
+     * Creates a new instance of {@link IntersectionOfVisitor}.
+     * @param conf the {@link RdfCloudeTripleStoreConfiguration}.
+     * @param inferenceEngine the {@link InferenceEngine}.
+     */
+    public IntersectionOfVisitor(final RdfCloudTripleStoreConfiguration conf, final InferenceEngine inferenceEngine) {
+        super(conf, inferenceEngine);
+        include = conf.isInferIntersectionOf();
+    }
+
+    @Override
+    protected void meetSP(final StatementPattern node) throws Exception {
+        final StatementPattern currentNode = node.clone();
+        final Var subVar = node.getSubjectVar();
+        final Var predVar = node.getPredicateVar();
+        final Var objVar = node.getObjectVar();
+        final Var conVar = node.getContextVar();
+        if (predVar != null && objVar != null && objVar.getValue() != null && RDF.TYPE.equals(predVar.getValue()) && !EXPANDED.equals(conVar)) {
+            final List<Set<Resource>> intersections = inferenceEngine.getIntersectionsImplying((URI) objVar.getValue());
+            if (intersections != null && !intersections.isEmpty()) {
+                final List<TupleExpr> joins = new ArrayList<>();
+                for (final Set<Resource> intersection : intersections) {
+                    final Set<Resource> sortedIntersection = new TreeSet<>(new ResourceComparator());
+                    sortedIntersection.addAll(intersection);
+
+                    // Create a join tree of all statement patterns in the
+                    // current intersection.
+                    final TupleExpr joinTree = createJoinTree(new ArrayList<>(sortedIntersection), subVar, conVar);
+                    if (joinTree != null) {
+                        joins.add(joinTree);
+                    }
+                }
+
+                if (!joins.isEmpty()) {
+                    // Combine all the intersection join trees for the type
+                    // together into a union tree.  This will be a join tree if
+                    // only one intersection exists.
+                    final TupleExpr unionTree = createUnionTree(joins);
+                    // Union the above union tree of intersections with the
+                    // original node.
+                    final Union union = new InferUnion(unionTree, currentNode);
+                    node.replaceWith(union);
+                    log.trace("Replacing node with inferred intersection union: " + union);
+                }
+            }
+        }
+    }
+
+    /**
+     * Recursively creates a {@link TupleExpr} tree comprised of
+     * {@link InferJoin}s and {@link StatementPattern}s. The left arg is a
+     * {@link StatementPattern} and the right arg is either a
+     * {@link StatementPattern} if it's the final element or a nested
+     * {@link InferJoin}.<p>
+     * A list of {@code [:A, :B, :C, :D, :E]} with type {@code ?x} returns:
+     * <pre>
+     * InferJoin(
+     *     StatementPattern(?x, rdf:type, :A),
+     *     InferJoin(
+     *         StatementPattern(?x, rdf:type, :B),
+     *         InferJoin(
+     *             StatementPattern(?x, rdf:type, :C),
+     *             InferJoin(
+     *                 StatementPattern(?x, rdf:type, :D),
+     *                 StatementPattern(?x, rdf:type, :E)
+     *             )
+     *         )
+     *     )
+     * )
+     * </pre>
+     * @param intersection a {@link List} of {@link Resource}s.
+     * @param typeVar the type {@link Var} to use as the subject for the
+     * {@link StatementPattern}.
+     * @param conVar the {@link Var} to use as the context for the
+     * {@link StatementPattern}.
+     * @return the {@link TupleExpr} tree. Returns {@code null} if
+     * {@code intersection} is empty. Returns a {@link StatementPattern} if
+     * {@code intersection}'s size is 1.  Otherwise, returns an
+     * {@link InferJoin} which may contain more nested {@link InferJoin}s.
+     */
+    private static TupleExpr createJoinTree(final List<Resource> intersection, final Var typeVar, final Var conVar) {
+        if (intersection.isEmpty()) {
+            return null;
+        } else {
+            final Var predVar = new Var(RDF.TYPE.toString(), RDF.TYPE);
+            final Resource resource = intersection.get(0);
+            final Var valueVar = new Var(resource.toString(), resource);
+            final StatementPattern left = new StatementPattern(typeVar, predVar, valueVar, conVar);
+            if (intersection.size() == 1) {
+                return left;
+            } else {
+                final List<Resource> subList = intersection.subList(1, intersection.size());
+                final TupleExpr right = createJoinTree(subList, typeVar, conVar);
+                final InferJoin join = new InferJoin(left, right);
+                join.getProperties().put(InferConstants.INFERRED, InferConstants.TRUE);
+                return join;
+            }
+        }
+    }
+
+    /**
+     * Recursively creates a {@link TupleExpr} tree comprised of
+     * {@link InferUnion}s and {@link InferJoin}s. The left arg is a
+     * {@link InferJoin} and the right arg is either a {@link InferJoin} if it's
+     * the final element or a nested {@link InferUnion}.<p>
+     * A list of {@code [JoinA, JoinB, JoinC, JoinD, JoinE]} returns:
+     * <pre>
+     * InferUnion(
+     *     JoinA,
+     *     InferUnion(
+     *         JoinB,
+     *         InferUnion(
+     *             JoinC,
+     *             InferUnion(
+     *                 JoinD,
+     *                 JoinE
+     *             )
+     *         )
+     *     )
+     * )
+     * </pre>
+     * @param joins a {@link List} of {@link TupleExpr}s that is most likely
+     * comprised of {@link Join}s but may be a single {@link StatementPattern}.
+     * @return the {@link TupleExpr} tree. Returns {@code null} if
+     * {@code joins} is empty. Might return a {@link StatementPattern} if
+     * {@code joins}' size is 1.  Otherwise, returns an
+     * {@link InferUnion} which may contain more nested {@link InferUnion}s.
+     */
+    private static TupleExpr createUnionTree(final List<TupleExpr> joins) {
+        if (joins.isEmpty()) {
+            return null;
+        } else {
+            final TupleExpr left = joins.get(0);
+            if (joins.size() == 1) {
+                return left;
+            } else {
+                final List<TupleExpr> subList = joins.subList(1, joins.size());
+                final TupleExpr right = createUnionTree(subList);
+                return new InferUnion(left, right);
+            }
+        }
+    }
+
+    /**
+     * Sorts {@link Resource}s in ascending order based on their string values.
+     */
+    public static class ResourceComparator implements Comparator<Resource> {
+        private static final Comparator<String> NULL_SAFE_STRING_COMPARATOR =
+            Comparator.nullsFirst(String::compareTo);
+
+        private static final Comparator<Resource> RESOURCE_COMPARATOR =
+            Comparator.comparing(Resource::stringValue, NULL_SAFE_STRING_COMPARATOR);
+
+        @Override
+        public int compare(final Resource r1, final Resource r2) {
+            if (r1 == null && r2 != null) {
+                return -1;
+            }
+            if (r1 != null && r2 == null) {
+                return 1;
+            }
+            if (r1 == null && r2 == null) {
+                return 0;
+            }
+            return RESOURCE_COMPARATOR.compare(r1, r2);
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/e9488ff6/sail/src/main/java/org/apache/rya/rdftriplestore/inference/InverseURI.java
----------------------------------------------------------------------
diff --git a/sail/src/main/java/org/apache/rya/rdftriplestore/inference/InverseURI.java b/sail/src/main/java/org/apache/rya/rdftriplestore/inference/InverseURI.java
index 6ad9225..13c068a 100644
--- a/sail/src/main/java/org/apache/rya/rdftriplestore/inference/InverseURI.java
+++ b/sail/src/main/java/org/apache/rya/rdftriplestore/inference/InverseURI.java
@@ -1,4 +1,3 @@
-package org.apache.rya.rdftriplestore.inference;
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements.  See the NOTICE file
@@ -7,9 +6,9 @@ package org.apache.rya.rdftriplestore.inference;
  * 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
@@ -17,64 +16,49 @@ package org.apache.rya.rdftriplestore.inference;
  * specific language governing permissions and limitations
  * under the License.
  */
+package org.apache.rya.rdftriplestore.inference;
+
 import org.openrdf.model.URI;
-import org.openrdf.model.impl.URIImpl;
-/*
- * 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.
- */
+
 public class InverseURI implements URI {
+    private static final long serialVersionUID = 1L;
+
+    private final URI impl;
+
+    public InverseURI(final URI uri) {
+        this.impl = uri;
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((impl == null) ? 0 : impl.hashCode());
+        return result;
+    }
 
-	private URI impl;
-	
-	public InverseURI(URI uri) {
-		this.impl = uri;
-	}
+    @Override
+    public boolean equals(final Object obj) {
+        if (obj instanceof InverseURI){
+            return impl.equals(((InverseURI) obj).impl);
+        }
+        return false;
+    }
 
-	@Override
-	public int hashCode() {
-		final int prime = 31;
-		int result = 1;
-		result = prime * result + ((impl == null) ? 0 : impl.hashCode());
-		return result;
-	}
+    @Override
+    public String stringValue() {
+        return impl.stringValue();
+    }
 
-	@Override
-	public boolean equals(Object obj) {
-		if (obj instanceof InverseURI){
-			return impl.equals(((InverseURI) obj).impl);
-		}
-		return false;
-	}
+    @Override
+    public String getNamespace() {
+        return impl.getNamespace();
+    }
 
-	@Override
-	public String stringValue() {
-		return impl.stringValue();
-	}
+    @Override
+    public String getLocalName() {
+        return impl.getLocalName();
+    }
 
-	@Override
-	public String getNamespace() {
-		return impl.getNamespace();
-	}
 
-	@Override
-	public String getLocalName() {
-		return impl.getLocalName();
-	}
-	
-	
 }