edu.unc.lib.dl.util.TripleStoreQueryServiceMulgaraImpl.java Source code

Java tutorial

Introduction

Here is the source code for edu.unc.lib.dl.util.TripleStoreQueryServiceMulgaraImpl.java

Source

/**
 * Copyright 2008 The University of North Carolina at Chapel Hill
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package edu.unc.lib.dl.util;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.StringReader;
import java.net.URI;
import java.net.URISyntaxException;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.xml.soap.MessageFactory;
import javax.xml.soap.SOAPBody;
import javax.xml.soap.SOAPConnection;
import javax.xml.soap.SOAPConnectionFactory;
import javax.xml.soap.SOAPElement;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPMessage;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.EndElement;
import javax.xml.stream.events.StartElement;
import javax.xml.stream.events.XMLEvent;

import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.codehaus.jackson.map.ObjectMapper;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.JDOMException;
import org.jdom2.input.DOMBuilder;
import org.jdom2.input.SAXBuilder;
import org.jdom2.output.XMLOutputter;
import org.w3c.dom.CDATASection;
import org.w3c.dom.NodeList;

import edu.unc.lib.dl.acl.util.UserRole;
import edu.unc.lib.dl.fedora.PID;
import edu.unc.lib.dl.xml.JDOMNamespaceUtil;

/**
 * Provides an adapter for querying and modifying the triple store.
 *
 * @author count0
 *
 */
public class TripleStoreQueryServiceMulgaraImpl implements TripleStoreQueryService {
    private static final Log log = LogFactory.getLog(TripleStoreQueryServiceMulgaraImpl.class);

    private String inferenceRulesModelUri;

    private String inferredRIModelUri;
    private String itqlEndpointURL;
    private String sparqlEndpointURL;
    private String name;
    private String pass;
    private String resourceIndexModelUri;
    private String serverModelUri;
    private HttpClient httpClient;
    private ObjectMapper mapper;
    private PID collections;

    private final MultiThreadedHttpConnectionManager multiThreadedHttpConnectionManager;

    public TripleStoreQueryServiceMulgaraImpl() {
        this.multiThreadedHttpConnectionManager = new MultiThreadedHttpConnectionManager();
        this.httpClient = new HttpClient(this.multiThreadedHttpConnectionManager);
        this.mapper = new ObjectMapper();
        this.collections = null;
    }

    public void destroy() {
        this.httpClient = null;
        this.mapper = null;
        this.multiThreadedHttpConnectionManager.shutdown();
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * edu.unc.lib.dl.services.TripleStoreService#fetchAllContents(edu.unc.lib
     * .dl.services.PID)
     */
    @Override
    public List<PID> fetchAllContents(PID key) {
        String query = String.format(
                "select $desc from <%1$s> where walk( <%3$s> <%2$s> $child and $child <%2$s> $desc);",
                this.getResourceIndexModelUri(), ContentModelHelper.Relationship.contains.getURI(), key.getURI());
        return this.lookupDigitalObjects(query);
    }

    @Override
    public List<PID> fetchChildren(PID key) {
        String query = String.format("select $child from <%1$s> where <%2$s> <%3$s> $child;",
                this.getResourceIndexModelUri(), key.getURI(), ContentModelHelper.Relationship.contains.getURI());
        return this.lookupDigitalObjects(query);
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * edu.unc.lib.dl.services.TripleStoreService#fetchAllContents(edu.unc.lib
     * .dl.services.PID)
     */
    @Override
    public Map<String, PID> fetchChildSlugs(PID parent) {
        Map<String, PID> result = new HashMap<String, PID>();
        String query = String.format(
                "select $slug $child from <%1$s> where <%2$s> <%3$s> $child and $child <%4$s> $slug;",
                this.getResourceIndexModelUri(), parent.getURI(), ContentModelHelper.Relationship.contains.getURI(),
                ContentModelHelper.CDRProperty.slug.getURI());
        List<List<String>> res = this.lookupStrings(query);
        for (List<String> list : res) {
            result.put(list.get(0), new PID(list.get(1)));
        }
        return result;
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * edu.unc.lib.dl.services.TripleStoreService#fetchByPredicateAndLiteral
     * (java.lang.String, java.lang.String)
     */
    @Override
    public List<PID> fetchByPredicateAndLiteral(String predicateURI, String literal) {
        String query = String.format("select $pid from <%1$s> where $pid <%2$s> '%3$s';",
                this.getResourceIndexModelUri(), predicateURI, literal);
        return this.lookupDigitalObjects(query);
    }

    @Override
    public List<PID> fetchByPredicateAndLiteral(String predicateURI, PID pidLiteral) {
        String query = String.format("select $pid from <%1$s> where $pid <%2$s> <%3$s>;",
                this.getResourceIndexModelUri(), predicateURI, pidLiteral.getURI());
        return this.lookupDigitalObjects(query);
    }

    @Override
    public List<String> fetchBySubjectAndPredicate(PID subject, String predicateURI) {
        String query = String.format("select $literal from <%1$s> where <%2$s> <%3$s> $literal;",
                this.getResourceIndexModelUri(), subject.getURI(), predicateURI);
        List<List<String>> res = this.lookupStrings(query);
        if (res == null)
            return null;
        List<String> literals = new ArrayList<String>();
        for (List<String> row : res) {
            if (row.size() > 0)
                literals.add(row.get(0));
        }
        return literals;
    }

    @Override
    public String fetchFirstBySubjectAndPredicate(PID subject, String predicateURI) {
        String query = String.format("select $literal from <%1$s> where <%2$s> <%3$s> $literal;",
                this.getResourceIndexModelUri(), subject.getURI(), predicateURI);

        return this.lookupFirstString(query);
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * edu.unc.lib.dl.services.TripleStoreService#fetchByRepositoryPath(java
     * .lang.String)
     */
    @Override
    public PID fetchByRepositoryPath(String path) {
        // TODO needs many CDRs one Fedora fix
        PID result = null;
        StringBuffer query = new StringBuffer();
        path = path.trim();
        List<String> slugs;
        if ("/".equals(path) || "REPOSITORY".equals(path)) {
            slugs = new ArrayList<String>();
            slugs.add("REPOSITORY");
        } else {
            slugs = Arrays.asList(path.split("/"));
        }
        String first = slugs.get(0);
        if ("".equals(first)) {
            slugs.set(0, "REPOSITORY");
        } else if (!"REPOSITORY".equals(first)) {
            List<String> newslugs = new ArrayList<String>();
            newslugs.addAll(slugs);
            newslugs.add(0, "REPOSITORY");
            slugs = newslugs;
        }
        query.append("select $r").append(slugs.size() - 1).append(" from <%1$s>");
        for (int i = 0; i < slugs.size(); i++) {
            if (i == 0) {
                query.append(" where $r").append(i).append(" <%3$s> '").append(slugs.get(i)).append("'");
            } else {
                query.append(" and $r").append(i - 1).append(" <%2$s> $r").append(i);
                query.append(" and $r").append(i).append(" <%3$s> '").append(slugs.get(i)).append("'");
            }
        }
        String q = String.format(query.append(";").toString(), this.getResourceIndexModelUri(),
                ContentModelHelper.Relationship.contains.getURI(), ContentModelHelper.CDRProperty.slug.getURI());
        List<PID> results = this.lookupDigitalObjects(q);
        if (results.size() == 1) {
            result = results.get(0);
        } else if (results.size() == 0) {
            result = null;
        } else {
            StringBuffer pidlist = new StringBuffer();
            for (PID pid : results) {
                pidlist.append(" ").append(pid.toString());
            }
            throw new IllegalRepositoryStateException(
                    "Multiple objects share the same path " + path + " PIDS:" + pidlist.toString());
        }
        return result;
    }

    private PID fetchCollectionsObject() {
        return fetchCollectionsObject(false);
    }

    private PID fetchCollectionsObject(boolean refresh) {
        if (refresh || collections == null) {
            collections = this.fetchByRepositoryPath("/Collections");
        }
        return collections;
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * edu.unc.lib.dl.services.TripleStoreService#fetchCollection(edu.unc.lib
     * .dl.services.PID)
     */
    public PID fetchCollection(PID key) {
        PID result = null;
        String query = String.format(
                "select $pid from <%1$s> where $keypid <%2$s> $pid and $keypid <http://mulgara.org/mulgara#is> <%3$s>;",
                this.getInferredModelUri(), ContentModelHelper.EntailedRelationship.isMemberOfCollection.getURI(),
                key.getURI());
        List<PID> response = this.lookupDigitalObjects(query);
        if (!response.isEmpty()) {
            if (response.size() > 1) {
                throw new IllegalRepositoryStateException(
                        "The repository is in an illegal state, an object is part of more than one collection.",
                        response);
            } else {
                result = response.get(0);
            }
        }
        return result;
    }

    @Override
    public PID fetchContainer(PID child) {
        String query = String.format("select $pid from <%1$s> where $pid <%2$s> <%3$s>;",
                this.getResourceIndexModelUri(), ContentModelHelper.Relationship.contains, child.getURI());
        List<PID> result = this.lookupDigitalObjects(query);
        if (result.size() > 1) {
            throw new IllegalRepositoryStateException(
                    "An objects seems to be contained by more than one object: " + child);
        } else if (result.size() == 0) {
            return null; // only the REPOSITORY object.
        } else {
            return result.get(0);
        }
    }

    @Override
    public List<PID> lookupAllContainersAbove(PID pid) {
        List<PID> result = new ArrayList<PID>();

        // construct path from contains relationships
        StringBuffer query = new StringBuffer();
        query.append("select $p <%2$s> $c from <%1$s>").append(" where walk( $p <%2$s> <%3$s> and $p <%2$s> $c);");
        String q = String.format(query.toString(), this.getResourceIndexModelUri(),
                ContentModelHelper.Relationship.contains.getURI(), pid.getURI());

        List<List<String>> response = this.lookupStrings(q);
        if (!response.isEmpty()) {
            // build a map of key:parent to val:child
            Map<String, String> parent2child = new HashMap<String, String>();
            for (List<String> solution : response) {
                parent2child.put(solution.get(0), solution.get(2));
            }
            // follow REPOSITORY through all parents, building path
            if (parent2child.containsKey(ContentModelHelper.Administrative_PID.REPOSITORY.getPID().getURI())) {
                result.add(ContentModelHelper.Administrative_PID.REPOSITORY.getPID());
            } else {
                throw new IllegalRepositoryStateException(
                        "The repository object should be in the parent tree for every CDR object.");
            }
            for (String step = ContentModelHelper.Administrative_PID.REPOSITORY.getPID().getURI(); parent2child
                    .containsKey(step); step = parent2child.get(step)) {
                result.add(new PID(parent2child.get(step)));
            }
        }
        return result;
    }

    @Deprecated
    public List<PID> fetchObjectReferences(List<PID> pids) {
        // this query can be very inefficient
        StringBuffer q = new StringBuffer();
        boolean first = true;
        if (pids.size() > 1) {
            q.append(" ( ");
        }
        for (PID pid : pids) {
            if (first) {
                first = false;
                q.append(" $obj <mulgara:is> <").append(pid.getURI()).append(">");
            } else {
                q.append(" or");
                q.append(" $obj <mulgara:is> <").append(pid.getURI()).append(">");
            }
        }
        if (pids.size() > 1) {
            q.append(" )");
        }

        String query = String.format(
                "select $pid from <%1$s> where $pid $rel $obj and $pid <%2$s> <%3$s> and %4$s ;",
                this.getResourceIndexModelUri(), ContentModelHelper.FedoraProperty.state,
                ContentModelHelper.FedoraProperty.Active, q.toString());
        return this.lookupDigitalObjects(query);
    }

    /**
     * Fetches a list of PIDs that depend on this object or it's descendants.
     *
     * @param pid
     *            the PID of the object
     * @return a list of dependent object PIDs
     */
    @Override
    public List<PID> fetchObjectReferences(PID pid) {
        List<PID> result = null;
        // fetch references to this pid
        String query = String.format("select $pid from <%1$s> where $pid $rel $obj and $obj <mulgara:is> <%2$s>;",
                this.getResourceIndexModelUri(), pid.getURI());
        result = this.lookupDigitalObjects(query);

        // fetch references to descendants
        String query2 = String.format(
                "select $pid from <%1$s> where $pid $rel $obj and walk( " + "<%2$s> <%3$s> $child"
                        + " and $child <%3$s> $obj);",
                this.getResourceIndexModelUri(), pid.getURI(), ContentModelHelper.Relationship.contains.getURI());
        result.addAll(this.lookupDigitalObjects(query2));
        return result;
    }

    @Override
    public String fetchState(PID pid) {
        String query = String.format("select $state from <%1$s> where <%2$s> <%3$s> $state;",
                this.getResourceIndexModelUri(), pid.getURI(), ContentModelHelper.FedoraProperty.Active.toString());
        return this.lookupFirstString(query);
    }

    public String getInferenceRulesModelUri() {
        return this.inferenceRulesModelUri;
    }

    public String getInferredModelUri() {
        return this.inferredRIModelUri;
    }

    public String getItqlEndpointURL() {
        return itqlEndpointURL;
    }

    public String getName() {
        return name;
    }

    public String getPass() {
        return pass;
    }

    private List<Element> getQuerySolutions(String response) {
        List<Element> result = null;
        Document r = null;
        StringReader sr = null;
        try {
            sr = new StringReader(response);
            r = new SAXBuilder().build(sr);
            result = r.getRootElement().getChild("query", JDOMNamespaceUtil.MULGARA_TQL_NS).getChildren("solution",
                    JDOMNamespaceUtil.MULGARA_TQL_NS);
            return result;
        } catch (IOException e) {
            log.error(response);
            throw new RuntimeException("IOException reading Mulgara answer string.", e);
        } catch (JDOMException e) {
            log.error(response);
            throw new RuntimeException("Unexpected error parsing Mulgara answer.", e);
        } finally {
            if (sr != null) {
                sr.close();
            }
        }
    }

    @SuppressWarnings({ "unchecked", "rawtypes" })
    private List<Element> extracted(List children) {
        return children;
    }

    @Override
    public String getResourceIndexModelUri() {
        return resourceIndexModelUri;
    }

    public String getServerModelUri() {
        return serverModelUri;
    }

    /*
     * (non-Javadoc)
     *
     * @see edu.unc.lib.dl.services.TripleStoreService#isContainer(edu.unc
     * .lib.dl.services.PID)
     */
    @Override
    public boolean isContainer(PID key) {
        String query = String.format(
                "select $keypid from <%1$s> where $keypid <%2$s> <%3$s> and $keypid <http://mulgara.org/mulgara#is> <%4$s>;",
                this.getResourceIndexModelUri(), ContentModelHelper.FedoraProperty.hasModel,
                ContentModelHelper.Model.CONTAINER, key.getURI());
        List<URI> response = this.lookupResources(query.toString());
        if (!response.isEmpty()) {
            if (response.size() > 0) {
                return true;
            }
        } else {
            return false;
        }
        return false;
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * edu.unc.lib.dl.services.TripleStoreService#lookupContentModel(edu.unc
     * .lib.dl.services.PID)
     */
    @Override
    public List<URI> lookupContentModels(PID key) {
        List<URI> result = null;
        String query = String.format(
                "select $resource from <%1$s> where $keypid <%2$s> $resource and $keypid <http://mulgara.org/mulgara#is> <%3$s>;",
                this.getResourceIndexModelUri(), ContentModelHelper.FedoraProperty.hasModel.getURI(), key.getURI());
        List<URI> response = this.lookupResources(query.toString());
        if (!response.isEmpty()) {
            if (response.size() == 0) {
                throw new IllegalRepositoryStateException(
                        "The repository is in an illegal state, an object has no content models.", key);
            } else {
                result = response;
            }
        }
        return result;
    }

    /**
     * Lookup a list of digital object ids. The query must return pairs of $pid
     * and $repositoryPath.
     *
     * @param query
     *            a query that returns resource values, one per row
     * @return a list of the URIs found
     * @throws RemoteException
     *             for communication failure
     */
    private List<PID> lookupDigitalObjects(String query) {
        List<PID> result = new ArrayList<PID>();
        String response = this.sendTQL(query);
        for (Element solution : getQuerySolutions(response)) {
            Object o = solution.getContent(0);
            if (o instanceof Element) {
                String res = ((Element) o).getAttributeValue("resource");
                if (res != null) {
                    result.add(new PID(res.substring(res.indexOf("/") + 1)));
                }
            }
        }
        return result;
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * edu.unc.lib.dl.services.TripleStoreService#lookupRepositoryPath(edu.unc
     * .lib.dl.services.PID)
     */
    @Override
    public String lookupRepositoryPath(PID key) {
        String result = null;
        // Walk the hierarchy and gather the slugs for each child as we go.
        StringBuffer squery = new StringBuffer();
        squery.append("select $p $c $slug from <%1$s>").append(" where walk( $p <%2$s> <%3$s> and $p <%2$s> $c)")
                .append(" and $c <%4$s> $slug;");
        String sq = String.format(squery.toString(), this.getResourceIndexModelUri(),
                ContentModelHelper.Relationship.contains.getURI(), key.getURI(),
                ContentModelHelper.CDRProperty.slug.getURI());
        List<List<String>> sresponse = this.lookupStrings(sq);

        if (!sresponse.isEmpty()) {
            Map<String, String> child2slug = new HashMap<String, String>();
            Map<String, String> parent2child = new HashMap<String, String>();
            for (List<String> solution : sresponse) {
                parent2child.put(solution.get(0), solution.get(1));
                child2slug.put(solution.get(1), solution.get(2));
            }

            StringBuffer sb = new StringBuffer();
            for (String step = ContentModelHelper.Administrative_PID.REPOSITORY.getPID().getURI(); parent2child
                    .containsKey(step); step = parent2child.get(step)) {
                String stepChild = parent2child.get(step);
                sb.append("/").append(child2slug.get(stepChild));
            }

            result = sb.toString();
        } else {
            return "/";
        }
        return result;
    }

    // USEFUL PUBLIC METHODS BELOW

    /**
     * Generates a list containing PathInfo objects for each hierarchical step
     * in the path leading up to and including PID key. If the object is an
     * orphan, then an empty list is returned
     */
    @Override
    public List<PathInfo> lookupRepositoryPathInfo(PID key) {
        List<PathInfo> result = new ArrayList<PathInfo>();

        // now get all the child slugs
        StringBuffer squery = new StringBuffer();
        squery.append("select $p $pid $slug $label from <%1$s>")
                .append(" where walk( $p <%2$s> <%3$s> and $p <%2$s> $pid)").append(" and $pid <%4$s> $slug")
                .append(" and $pid <%5$s> $label;");
        String sq = String.format(squery.toString(), this.getResourceIndexModelUri(),
                ContentModelHelper.Relationship.contains, key.getURI(), ContentModelHelper.CDRProperty.slug,
                ContentModelHelper.FedoraProperty.label);
        List<List<String>> sresponse = this.lookupStrings(sq);

        if (!sresponse.isEmpty()) {
            // add the REPOSITORY object path info
            PathInfo rootInfo = new PathInfo();
            rootInfo.setLabel("Repository Home");
            rootInfo.setPath("/");
            rootInfo.setSlug("REPOSITORY");
            rootInfo.setPid(ContentModelHelper.Administrative_PID.REPOSITORY.getPID());
            result.add(rootInfo);

            Map<String, PathInfo> parent2child = new HashMap<String, PathInfo>();

            // Build the PathInfo objects representing each tuple then store
            // them as the child of their parent.
            for (List<String> solution : sresponse) {
                PathInfo info = new PathInfo();
                info.setPid(new PID(solution.get(1)));
                info.setSlug(solution.get(2));
                info.setLabel(solution.get(3));
                parent2child.put(solution.get(0), info);
            }

            StringBuffer sb = new StringBuffer();
            // Now add the steps into the file result list in the correct walk
            // order.
            for (String step = ContentModelHelper.Administrative_PID.REPOSITORY.getPID().getURI(); parent2child
                    .containsKey(step); step = parent2child.get(step).getPid().getURI()) {
                PathInfo stepChild = parent2child.get(step);
                sb.append("/").append(stepChild.getSlug());
                stepChild.setPath(sb.toString());
                result.add(stepChild);
            }
        }
        return result;
    }

    /**
     * @param query
     *            a query that returns resource values, one per row
     * @return a list of the URIs found
     * @throws RemoteException
     *             for communication failure
     */
    private List<URI> lookupResources(String query) {
        List<URI> result = new ArrayList<URI>();
        String response = this.sendTQL(query);
        for (Element solution : getQuerySolutions(response)) {
            Object o = solution.getContent(0);
            if (o instanceof Element) {
                String res = ((Element) o).getAttributeValue("resource");
                if (res != null) {
                    try {
                        result.add(new URI(res));
                    } catch (URISyntaxException exception) {
                        throw new IllegalRepositoryStateException(
                                "Found an invalid resource URI in the resource index.");
                    }
                }
            }
        }
        return result;
    }

    /**
     * @param query
     *            a query that returns string values, literal or URI
     * @return a list of the literal strings found
     * @throws RemoteException
     *             for communication failure
     */
    private List<List<String>> lookupStrings(String query) {
        List<List<String>> result = new ArrayList<List<String>>();
        String response = this.sendTQL(query);
        for (Element solution : getQuerySolutions(response)) {
            List<String> row = new ArrayList<String>();
            for (Object o : solution.getChildren()) {
                if (o instanceof Element) {
                    Element el = (Element) o;
                    if (el.getAttributeValue("resource") != null) {
                        row.add(el.getAttributeValue("resource"));
                    } else {
                        row.add(el.getTextTrim());
                    }
                }
            }
            result.add(row);
        }
        return result;
    }

    private String lookupFirstString(String query) {
        String response = this.sendTQL(query);
        for (Element solution : getQuerySolutions(response)) {
            if (solution.getChildren().size() > 0) {
                Object o = solution.getChildren().get(0);
                if (o instanceof Element) {
                    Element el = (Element) o;
                    String resourceAttribute = el.getAttributeValue("resource");
                    if (resourceAttribute == null) {
                        return el.getTextTrim();
                    }
                    return resourceAttribute;
                }
            }
        }
        return null;
    }

    @Override
    public List<List<String>> queryResourceIndex(String query) {
        return this.lookupStrings(query);
    }

    private void reportSOAPFault(SOAPMessage reply) throws SOAPException {
        String error = reply.getSOAPBody().getFirstChild().getFirstChild().getNodeValue();
        throw new RuntimeException("There was a SOAP Fault from Mulgara: " + error);
    }

    private String sendTQL(String query) {
        log.debug(query);
        String result = null;
        SOAPMessage reply = null;
        SOAPConnection connection = null;
        try {
            // Next, create the actual message
            MessageFactory messageFactory = MessageFactory.newInstance();
            SOAPMessage message = messageFactory.createMessage();
            message.getMimeHeaders().setHeader("SOAPAction", "itqlbean:executeQueryToString");
            SOAPBody soapBody = message.getSOAPPart().getEnvelope().getBody();
            soapBody.addNamespaceDeclaration("xsd", JDOMNamespaceUtil.XSD_NS.getURI());
            soapBody.addNamespaceDeclaration("xsi", JDOMNamespaceUtil.XSI_NS.getURI());
            soapBody.addNamespaceDeclaration("itqlbean", this.getItqlEndpointURL());
            SOAPElement eqts = soapBody.addChildElement("executeQueryToString", "itqlbean");
            SOAPElement queryStr = eqts.addChildElement("queryString", "itqlbean");
            queryStr.setAttributeNS(JDOMNamespaceUtil.XSI_NS.getURI(), "xsi:type", "xsd:string");
            CDATASection queryCDATA = message.getSOAPPart().createCDATASection(query);
            queryStr.appendChild(queryCDATA);
            message.saveChanges();

            // First create the connection
            SOAPConnectionFactory soapConnFactory = SOAPConnectionFactory.newInstance();
            connection = soapConnFactory.createConnection();
            reply = connection.call(message, this.getItqlEndpointURL());

            if (reply.getSOAPBody().hasFault()) {
                reportSOAPFault(reply);
                if (log.isDebugEnabled()) {
                    // log the full soap body response
                    DOMBuilder builder = new DOMBuilder();
                    org.jdom2.Document jdomDoc = builder.build(reply.getSOAPBody().getOwnerDocument());
                    log.debug(new XMLOutputter().outputString(jdomDoc));
                }
            } else {
                NodeList nl = reply.getSOAPPart().getEnvelope().getBody().getElementsByTagNameNS("*",
                        "executeQueryToStringReturn");
                if (nl.getLength() > 0) {
                    result = nl.item(0).getFirstChild().getNodeValue();
                }
                log.debug(result);
            }
        } catch (SOAPException e) {
            log.error("Failed to prepare or send iTQL via SOAP", e);
            throw new RuntimeException("Cannot query triple store at " + this.getItqlEndpointURL(), e);
        } finally {
            try {
                connection.close();
            } catch (SOAPException e) {
                log.error("Failed to close SOAP connection", e);
                throw new RuntimeException(
                        "Failed to close SOAP connection for triple store at " + this.getItqlEndpointURL(), e);
            }
        }
        return result;
    }

    @Override
    public Map<?, ?> sendSPARQL(String query) {
        return sendSPARQL(query, "json");
    }

    @Override
    public Map<?, ?> sendSPARQL(String query, String format) {
        return sendSPARQL(query, format, 3);
    }

    public Map<?, ?> sendSPARQL(String query, String format, int retries) {
        PostMethod post = null;
        try {
            String postUrl = this.getSparqlEndpointURL();
            if (format != null) {
                postUrl += "?format=" + format;
            }
            post = new PostMethod(postUrl);
            post.setRequestHeader("Content-Type", "application/sparql-query");
            post.addParameter("query", query);

            log.debug("SPARQL URL: " + postUrl);
            log.debug("SPARQL Query: " + query);

            int statusCode = httpClient.executeMethod(post);
            if (statusCode != HttpStatus.SC_OK) {
                throw new RuntimeException("SPARQL POST method failed: " + post.getStatusLine());
            } else {
                log.debug("SPARQL POST method succeeded: " + post.getStatusLine());
                byte[] resultBytes = post.getResponseBody();
                log.debug(new String(resultBytes, "utf-8"));
                if (format != null && format.endsWith("json")) {
                    return (Map<?, ?>) mapper.readValue(new ByteArrayInputStream(resultBytes), Object.class);
                } else {
                    Map<String, String> resultMap = new HashMap<String, String>();
                    String resultString = new String(resultBytes, "utf-8");
                    resultMap.put("results", resultString);
                    return resultMap;
                }
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            if (post != null)
                post.releaseConnection();
        }
    }

    public void setItqlEndpointURL(String baseURL) {
        this.itqlEndpointURL = baseURL;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setPass(String pass) {
        this.pass = pass;
    }

    public void setServerModelUri(String serverModelUri) {
        this.serverModelUri = serverModelUri;
        this.resourceIndexModelUri = serverModelUri + "ri";
        this.inferredRIModelUri = serverModelUri + "ri_inferred";
        this.inferenceRulesModelUri = serverModelUri + "krules";
    }

    /**
     * @param query
     *            an ITQL command
     * @return the message returned by Mulgara
     * @throws RemoteException
     *             for communication failure
     */
    public String storeCommand(String query) {
        String result = null;
        String response = this.sendTQL(query);
        if (response != null) {
            XMLInputFactory factory = XMLInputFactory.newInstance();
            factory.setProperty(XMLInputFactory.IS_COALESCING, Boolean.TRUE);
            try (StringReader sr = new StringReader(response)) {
                XMLEventReader r = factory.createXMLEventReader(sr);
                boolean inMessage = false;
                StringBuffer message = new StringBuffer();
                while (r.hasNext()) {
                    XMLEvent e = r.nextEvent();
                    if (e.isStartElement()) {
                        StartElement s = e.asStartElement();
                        if ("message".equals(s.getName().getLocalPart())) {
                            inMessage = true;
                        }
                    } else if (e.isEndElement()) {
                        EndElement end = e.asEndElement();
                        if ("message".equals(end.getName().getLocalPart())) {
                            inMessage = false;
                        }
                    } else if (inMessage && e.isCharacters()) {
                        message.append(e.asCharacters().getData());
                    }
                }
                r.close();
                result = message.toString();
            } catch (XMLStreamException e) {
                e.printStackTrace();
            }
        }
        return result;
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * edu.unc.lib.dl.services.TripleStoreService#fetch(edu.unc.lib.dl.services
     * .PID)
     */
    @Override
    public PID verify(PID key) {
        PID result = null;
        String query = null;
        query = String.format(
                "select $pid $state from <%1$s> where $pid <http://mulgara.org/mulgara#is> <%2$s>"
                        + " and $pid <%3$s> $state;",
                this.getResourceIndexModelUri(), key.getURI(), ContentModelHelper.FedoraProperty.state);
        List<PID> response = this.lookupDigitalObjects(query);
        if (!response.isEmpty()) {
            if (response.size() > 1) {
                throw new IllegalRepositoryStateException(
                        "The repository is in an illegal state, multiple objects share a pid.", response);
            } else {
                result = response.get(0);
            }
        }
        return result;
    }

    @Override
    public boolean isSourceData(PID pid, String datastreamID) {
        String query = String.format(
                "select $pid $ds from <%1$s>" + " where $pid <%3$s> $ds"
                        + " and $pid <http://mulgara.org/mulgara#is> <%2$s>"
                        + " and $ds <http://mulgara.org/mulgara#is> <%4$s>;",
                this.getResourceIndexModelUri(), pid.getURI(), ContentModelHelper.CDRProperty.sourceData,
                pid.getURI() + "/" + datastreamID);
        List<List<String>> response = this.lookupStrings(query);
        if (!response.isEmpty()) {
            return true;
        }
        return false;
    }

    @Override
    public boolean allowIndexing(PID pid) {
        String query = String.format(
                "select ?pid from <%1$s> where {" + "?pid <%2$s> 'yes' " + "filter (?pid = <%3$s>) }",
                this.getResourceIndexModelUri(), ContentModelHelper.CDRProperty.allowIndexing.getURI(),
                pid.getURI());
        @SuppressWarnings({ "unchecked", "rawtypes" })
        List response = (List<Map>) ((Map) sendSPARQL(query).get("results")).get("bindings");

        if (!response.isEmpty()) {
            return true;
        }
        return false;
    }

    @Override
    public List<PID> fetchChildContainers(PID key) {
        String query = String.format(
                "select $child from <%1$s> where ( <%3$s> <%2$s> $child and $child <%4$s> <%5$s>);",
                this.getResourceIndexModelUri(), ContentModelHelper.Relationship.contains.getURI(), key.getURI(),
                ContentModelHelper.FedoraProperty.hasModel, ContentModelHelper.Model.CONTAINER);
        return this.lookupDigitalObjects(query);
    }

    @Override
    public List<String> fetchAllCollectionPaths() {
        // TODO needs many CDRs one Fedora fix
        List<String> result = new ArrayList<String>(256);

        PID collections = this.fetchCollectionsObject();

        // construct path from contains relationships
        StringBuffer query = new StringBuffer();
        query.append("select $p $sp $c $sc from <%1$s>").append(" where walk(<%2$s> <%3$s> $p ")
                .append("and $p <%3$s> $c) and $p <%4$s> $sp ").append("and $c <%4$s> $sc ")
                .append("and $p <%5$s> <%6$s> ").append("and $c <%5$s> <%6$s>;");

        String q = String.format(query.toString(), this.getResourceIndexModelUri(), collections.getURI(),
                ContentModelHelper.Relationship.contains.getURI(), ContentModelHelper.CDRProperty.slug,
                ContentModelHelper.FedoraProperty.hasModel, ContentModelHelper.Model.CONTAINER);

        List<List<String>> response = this.lookupStrings(q);

        if (!response.isEmpty()) {
            Map<String, String> pathsMap = new HashMap<String, String>();
            for (List<String> solution : response) {

                StringBuffer sb = new StringBuffer(256);

                String parent = pathsMap.get(solution.get(0));
                if (parent == null) {
                    pathsMap.put(solution.get(0), "/" + solution.get(1)); // add
                    // parent
                    // path
                    // to
                    // repository
                    parent = solution.get(1);
                }

                sb.append(parent).append("/");
                sb.append(solution.get(3));
                pathsMap.put(solution.get(2), sb.toString()); // add full child
                // path to
                // repository
            }

            result.addAll(pathsMap.values());
            Collections.sort(result);
        }

        return result;
    }

    @Override
    public String lookupLabel(String pid) {
        return lookupLabel(new PID(pid));
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * edu.unc.lib.dl.util.TripleStoreQueryService#lookupLabels(java.util.List)
     */
    @Override
    public String lookupLabel(PID pid) {
        String result = null;
        List<List<String>> label = this.queryResourceIndex(String.format(
                "select $label from <%1$s> where <%2$s> <%3$s> $label;", this.getResourceIndexModelUri(),
                pid.getURI(), ContentModelHelper.FedoraProperty.label.getURI()));
        if (!label.isEmpty() && !label.get(0).isEmpty()) {
            result = label.get(0).get(0);
        }
        return result;
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * edu.unc.lib.dl.util.TripleStoreQueryService#lookupSlug(edu.unc.lib.dl
     * .fedora.PID)
     */
    @Override
    public String lookupSlug(PID pid) {
        String result = null;
        List<List<String>> res = this.queryResourceIndex(String.format(
                "select $slug from <%1$s> where <%2$s> <%3$s> $slug;", this.getResourceIndexModelUri(),
                pid.getURI(), ContentModelHelper.CDRProperty.slug.getURI()));
        if (!res.isEmpty() && !res.get(0).isEmpty()) {
            result = res.get(0).get(0);
        }
        return result;
    }

    @Override
    public String lookupSourceMimeType(PID pid) {
        String result = null;
        List<List<String>> res = this.queryResourceIndex(String.format(
                "select $mimeType from <%1$s> where <%2$s> <%3$s> $mimeType;", this.getResourceIndexModelUri(),
                pid.getURI(), ContentModelHelper.CDRProperty.hasSourceMimeType.getURI()));
        if (!res.isEmpty() && !res.get(0).isEmpty()) {
            result = res.get(0).get(0);
        }
        return result;
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * edu.unc.lib.dl.util.TripleStoreQueryService#fetchChildPathInfo(edu.unc
     * .lib.dl.fedora.PID)
     */
    @Override
    public List<PathInfo> fetchChildPathInfo(PID parent) {
        String parentPath = this.lookupRepositoryPath(parent);

        List<PathInfo> result = new ArrayList<PathInfo>();

        String query = String.format(
                "select $slug $child $label from <%1$s> where <%2$s> <%3$s> $child and $child <%4$s> $slug and $child <%5$s> $label;",
                this.getResourceIndexModelUri(), parent.getURI(), ContentModelHelper.Relationship.contains.getURI(),
                ContentModelHelper.CDRProperty.slug.getURI(), ContentModelHelper.FedoraProperty.label);

        List<List<String>> res = this.lookupStrings(query);
        for (List<String> list : res) {
            String slug = list.get(0);
            StringBuilder sb = new StringBuilder(parentPath);
            sb.append('/');
            sb.append(slug);
            PathInfo p = new PathInfo();
            p.setSlug(slug);
            p.setPid(new PID(list.get(1)));
            p.setPath(sb.toString());
            p.setLabel(list.get(2));
            result.add(p);
        }
        return result;
    }

    /*
     * (non-Javadoc)
     *
     * @see edu.unc.lib.dl.util.TripleStoreQueryService#lookupPermissions
     * (edu.unc.lib.dl.fedora.PID)
     */
    @Override
    public Set<String[]> lookupGroupRoles(PID pid) {
        Set<String[]> result = new HashSet<String[]>();
        StringBuffer query = new StringBuffer();
        query.append("select $group $role from <%1$s>").append(" where <%2$s> $role $group").append(" and (")
                .append("       $role <mulgara:is> <%3$s>").append("       or $role <mulgara:is> <%4$s>")
                .append("       or $role <mulgara:is> <%5$s>").append("       or $role <mulgara:is> <%6$s>")
                .append("       or $role <mulgara:is> <%7$s>").append("       or $role <mulgara:is> <%8$s>")
                .append("       or $role <mulgara:is> <%9$s>").append(" );");
        String q = String.format(query.toString(), this.getResourceIndexModelUri(), pid.getURI(),
                UserRole.patron.getURI(), UserRole.observer.getURI(), UserRole.ingester.getURI(),
                UserRole.processor.getURI(), UserRole.curator.getURI(), UserRole.metadataPatron.getURI(),
                UserRole.accessCopiesPatron.getURI());
        List<List<String>> response = this.lookupStrings(q);
        if (!response.isEmpty()) {
            for (List<String> solution : response) {
                String group = solution.get(0);
                String role = solution.get(1);
                result.add(new String[] { group, role });
            }
        }
        return result;
    }

    /*
     * (non-Javadoc)
     *
     * @see edu.unc.lib.dl.util.TripleStoreQueryService#fetchAllTriples(PID pid)
     * (edu.unc.lib.dl.fedora.PID)
     */
    @Override
    public Map<String, List<String>> fetchAllTriples(PID pid) {
        Map<String, List<String>> result = new HashMap<String, List<String>>();

        // construct path from contains relationships
        StringBuffer query = new StringBuffer();
        query.append("select $permission $subject from <%1$s>").append(" where <%2$s> $permission $subject")
                .append(";");
        String q = String.format(query.toString(), this.getResourceIndexModelUri(), pid.getURI());

        List<List<String>> response = this.lookupStrings(q);
        if (!response.isEmpty()) {
            for (List<String> solution : response) {
                String permKey = solution.get(0);
                String subjectValue = solution.get(1);
                if (!result.containsKey(permKey)) {
                    result.put(permKey, new ArrayList<String>());
                }
                result.get(permKey).add(subjectValue);
            }
        }
        return result;
    }

    /*
     * (non-Javadoc)
     *
     * @see edu.unc.lib.dl.util.TripleStoreQueryService#lookupSinglePermission
     * (edu.unc.lib.dl.fedora.PID, String)
     */
    @Override
    public Map<String, List<String>> lookupSinglePermission(PID pid, String permission) {
        Map<String, List<String>> result = new HashMap<String, List<String>>();

        // construct path from contains relationships
        StringBuffer query = new StringBuffer();
        query.append("select $permission $subject from <%1$s>").append(" where <%2$s> $permission $subject")
                .append(" and (").append("       $permission <mulgara:is> <%3$s>")
                // inheritPermissions
                .append("       or $permission <mulgara:is> <%4$s> )").append(";");
        String q = String.format(query.toString(), this.getResourceIndexModelUri(), pid.getURI(),
                ContentModelHelper.CDRProperty.inheritPermissions.getURI(), permission);

        List<List<String>> response = this.lookupStrings(q);
        if (!response.isEmpty()) {
            for (List<String> solution : response) {
                String permKey = solution.get(0);
                String subjectValue = solution.get(1);
                if (!result.containsKey(permKey)) {
                    result.put(permKey, new ArrayList<String>());
                }
                result.get(permKey).add(subjectValue);
            }
        }
        return result;
    }

    /*
     * Used for access control
     */
    @Override
    public List<PID> lookupRepositoryAncestorPids(PID key) {

        // TODO needs many CDRs one Fedora fix
        List<PID> result = new ArrayList<PID>();

        // construct path from contains relationships
        StringBuffer query = new StringBuffer();
        query.append("select $p <%2$s> $c from <%1$s>").append(" where walk( $p <%2$s> <%3$s> and $p <%2$s> $c);");
        String q = String.format(query.toString(), this.getResourceIndexModelUri(),
                ContentModelHelper.Relationship.contains.getURI(), key.getURI());

        List<List<String>> response = this.lookupStrings(q);
        if (!response.isEmpty()) {
            // build a map of key:parent to val:child
            Map<String, String> parent2child = new HashMap<String, String>();
            for (List<String> solution : response) {
                parent2child.put(solution.get(0), solution.get(2));
            }

            // follow REPOSITORY through all children, building path in order
            for (String step = ContentModelHelper.Administrative_PID.REPOSITORY.getPID().getURI(); parent2child
                    .containsKey(step); step = parent2child.get(step)) {
                result.add(new PID(step));
            }
        }
        return result;
    }

    public String getSparqlEndpointURL() {
        return sparqlEndpointURL;
    }

    public void setSparqlEndpointURL(String sparqlEndpointURL) {
        this.sparqlEndpointURL = sparqlEndpointURL;
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * edu.unc.lib.dl.util.TripleStoreQueryService#getSourceData(edu.unc.lib
     * .dl.fedora.PID)
     */
    @Override
    public List<String> getSourceData(PID pid) {
        List<String> result = new ArrayList<String>();
        String query = String.format(
                "select $ds from <%1$s>" + " where $pid <%3$s> $ds"
                        + " and $pid <http://mulgara.org/mulgara#is> <%2$s>;",
                this.getResourceIndexModelUri(), pid.getURI(), ContentModelHelper.CDRProperty.sourceData.getURI());
        List<List<String>> response = this.lookupStrings(query);
        if (!response.isEmpty()) {
            for (List<String> entry : response) {
                result.add(entry.get(0));
            }
            log.debug("found " + result.size() + " source datastreams");
        }
        return result;
    }

    @Override
    public List<String> listDisseminators(PID pid) {
        List<String> result = new ArrayList<String>();
        String query = String.format(
                "select $ds from <%1$s>" + " where $pid <%3$s> $ds"
                        + " and $pid <http://mulgara.org/mulgara#is> <%2$s>;",
                this.getResourceIndexModelUri(), pid.getURI(),
                ContentModelHelper.FedoraProperty.disseminates.getURI());
        List<List<String>> response = this.lookupStrings(query);
        if (!response.isEmpty()) {
            for (List<String> entry : response) {
                result.add(entry.get(0));
            }
        }
        return result;
    }

    @Override
    public Map<String, String> fetchDisseminatorMimetypes(PID pid) {
        Map<String, String> result = new HashMap<String, String>();
        String query = String.format(
                "select $ds $mimetype from <%1$s>" + " where $pid <%3$s> $ds" + " and $ds <%4$s> $mimetype "
                        + " and $pid <http://mulgara.org/mulgara#is> <%2$s>;",
                this.getResourceIndexModelUri(), pid.getURI(),
                ContentModelHelper.FedoraProperty.disseminates.getURI(),
                ContentModelHelper.FedoraProperty.mimeType.getURI());
        List<List<String>> response = this.lookupStrings(query);
        if (!response.isEmpty()) {
            for (List<String> entry : response) {
                result.put(entry.get(0), entry.get(1));
            }
        }
        return result;
    }

    @Override
    public boolean isOrphaned(PID key) {
        PID collections = fetchCollectionsObject();
        StringBuffer query = new StringBuffer();
        query.append("select $p from <%1$s>").append(" where walk( $p <%2$s> <%3$s> and $p <%2$s> $c) ");
        query.append(" and <%4$s> <%2$s> $c;");
        String q = String.format(query.toString(), this.getResourceIndexModelUri(),
                ContentModelHelper.Relationship.contains.getURI(), key.getURI(), collections.getURI());

        List<List<String>> response = this.lookupStrings(q);
        return response.isEmpty() || response.get(0).isEmpty();
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * edu.unc.lib.dl.util.TripleStoreQueryService#fetchParentCollection(edu
     * .unc.lib.dl.fedora.PID)
     *
     * select $p from <#ri> where walk( $p
     * <http://cdr.unc.edu/definitions/1.0/base-model.xml#contains>
     * <info:fedora/uuid:4e349cbf-dda4-4d14-94eb-c2c27a59c06a> and $p
     * <http://cdr.unc.edu/definitions/1.0/base-model.xml#contains> $c) and $p
     * <fedora-model:hasModel> <info:fedora/cdr-model:Collection>;
     */
    @Override
    public PID fetchParentCollection(PID key) {
        PID result = null;
        StringBuffer query = new StringBuffer();
        query.append("select $p from <%1$s>").append(" where walk( $p <%2$s> <%3$s> and $p <%2$s> $c) ");
        query.append(" and $p <%4$s> <%5$s>;");
        String q = String.format(query.toString(), this.getResourceIndexModelUri(),
                ContentModelHelper.Relationship.contains.getURI(), key.getURI(),
                ContentModelHelper.FedoraProperty.hasModel.getURI(), ContentModelHelper.Model.COLLECTION.getURI());

        List<List<String>> response = this.lookupStrings(q);
        if (!response.isEmpty() && !response.get(0).isEmpty()) {
            String pid = response.get(0).get(0);
            result = new PID(pid);
        }
        return result;
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * edu.unc.lib.dl.util.TripleStoreQueryService#getSurrogateData(edu.unc.
     * lib.dl.fedora.PID)
     */
    @Override
    public List<String> getSurrogateData(PID pid) {
        List<String> result = new ArrayList<String>();
        String query = String.format(
                "select $ds from <%1$s>" + " where $pid <%3$s> $ds"
                        + " and ($pid <http://mulgara.org/mulgara#is> <%2$s> or <%2$s> <%4$s> $pid);",
                this.getResourceIndexModelUri(), pid.getURI(), ContentModelHelper.CDRProperty.sourceData.getURI(),
                ContentModelHelper.CDRProperty.hasSurrogate.getURI());
        List<List<String>> response = this.lookupStrings(query);
        if (!response.isEmpty()) {
            for (List<String> entry : response) {
                result.add(entry.get(0));
            }
        }
        log.debug("found " + result.size() + " source/surrogate datastreams for " + pid.getPid());
        return result;
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * edu.unc.lib.dl.util.TripleStoreQueryService#fetchPIDsSurrogateFor(edu
     * .unc.lib.dl.fedora.PID)
     */
    @Override
    public List<PID> fetchPIDsSurrogateFor(PID pid) {
        List<PID> result = new ArrayList<PID>();
        String query = String.format("select $pid from <%1$s> where $pid <%3$s> <%2$s>;",
                this.getResourceIndexModelUri(), pid.getURI(),
                ContentModelHelper.CDRProperty.hasSurrogate.getURI());
        List<List<String>> response = this.lookupStrings(query);
        if (!response.isEmpty()) {
            for (List<String> entry : response) {
                result.add(new PID(entry.get(0)));
            }
        }
        log.debug("found " + result.size() + " pid that have this one as a surrogate: " + pid.getPid());
        return result;
    }

    /*
     * (non-Javadoc)
     *
     * @see edu.unc.lib.dl.util.TripleStoreQueryService#
     * lookupRepositoryAncestorInheritance(edu.unc.lib.dl.fedora.PID)
     */
    @Override
    public Map<PID, ParentBond> lookupRepositoryAncestorInheritance(PID pid) {
        // TODO needs many CDRs one Fedora fix
        Map<PID, ParentBond> result = new HashMap<PID, ParentBond>();

        // construct path from contains relationships
        StringBuffer query = new StringBuffer();
        query.append("select $child $inheritsFrom $parent from <%1$s>")
                .append(" where walk( $parent <%2$s> <%3$s> and $parent <%2$s> $child)")
                .append(" and ($child <%4$s> $inheritsFrom")
                .append(" or $inheritsFrom <mulgara:is> <mulgara:null>);");
        String q = String.format(query.toString(), this.getResourceIndexModelUri(),
                ContentModelHelper.Relationship.contains.getURI(), pid.getURI(),
                ContentModelHelper.CDRProperty.inheritPermissions.getURI());

        List<List<String>> response = this.lookupStrings(q);
        if (!response.isEmpty()) {
            Set<PID> parentsNotInheritedFrom = new HashSet<PID>();
            for (List<String> solution : response) {
                ParentBond bond = new ParentBond();
                bond.parentPid = new PID(solution.get(2));
                if ("false".equals(solution.get(1).trim())) {
                    parentsNotInheritedFrom.add(bond.parentPid);
                }
                result.put(new PID(solution.get(0)), bond);
            }
            for (ParentBond bond : result.values()) {
                if (parentsNotInheritedFrom.contains(bond.parentPid)) {
                    bond.inheritsRoles = false;
                }
            }
        }
        return result;
    }

    @Override
    public Map<PID, String> fetchEmbargoes() {
        Map<PID, String> result = new HashMap<PID, String>();
        StringBuffer query = new StringBuffer();
        query.append("select $pid $embargoDate from <%1$s>").append(" where $pid <%2$s> $embargoDate;");
        String q = String.format(query.toString(), this.getResourceIndexModelUri(),
                ContentModelHelper.CDRProperty.embargoUntil.getURI());
        List<List<String>> response = this.lookupStrings(q);
        if (!response.isEmpty()) {
            for (List<String> row : response) {
                result.put(new PID(row.get(0)), row.get(1));
            }
        }
        return result;
    }

    @Override
    public Map<String, Map<String, String>> fetchVocabularyInfo() {
        Map<String, Map<String, String>> result = new HashMap<>();

        StringBuilder query = new StringBuilder();
        query.append("select $vocabPID $vocabURI $vocabType from <%1$s>").append(
                " where $vocabPID <%2$s> <%3$s> and $vocabPID <%4$s> $vocabURI and $vocabPID <%5$s> $vocabType;");

        String q = String.format(query.toString(), this.getResourceIndexModelUri(),
                ContentModelHelper.FedoraProperty.hasModel.getURI(), ContentModelHelper.Model.VOCABULARY,
                ContentModelHelper.CDRProperty.vocabularyUri.getURI(),
                ContentModelHelper.CDRProperty.vocabularyType.getURI());

        List<List<String>> response = this.lookupStrings(q);

        if (!response.isEmpty()) {
            for (List<String> row : response) {
                Map<String, String> values = new HashMap<>();
                values.put("vocabURI", row.get(1));
                values.put("vocabType", row.get(2));

                result.put(row.get(0), values);
            }
        }

        return result;
    }

    @Override
    public Map<String, Map<String, Set<String>>> fetchVocabularyMapping() {
        StringBuilder query = new StringBuilder();
        query.append("select $container $predicate $vocabUri from <%1$s>")
                .append(" where ($container <%2$s> $vocabUri")
                .append(" or $container <%3$s> $vocabUri or $container <%4$s> $vocabUri)")
                .append(" and $container $predicate $vocabPID;");

        String q = String.format(query.toString(), this.getResourceIndexModelUri(),
                ContentModelHelper.CDRProperty.indexValidTerms.getURI(),
                ContentModelHelper.CDRProperty.warnInvalidTerms.getURI(),
                ContentModelHelper.CDRProperty.replaceInvalidTerms.getURI());

        List<List<String>> response = this.lookupStrings(q);
        Map<String, Map<String, Set<String>>> result = new HashMap<>();

        // Store as collection > vocab > properties
        if (!response.isEmpty()) {
            for (List<String> row : response) {
                String containerPID = row.get(0);
                String predicate = row.get(1);
                String vocabURI = row.get(2);

                Map<String, Set<String>> vocabMap = result.get(containerPID);
                if (vocabMap == null) {
                    vocabMap = new HashMap<>();
                    result.put(containerPID, vocabMap);
                }

                Set<String> collectionConfig = vocabMap.get(vocabURI);
                if (collectionConfig == null) {
                    collectionConfig = new HashSet<>();
                    vocabMap.put(vocabURI, collectionConfig);
                }

                // Filter results down to just the vocabulary application level triples
                if (ContentModelHelper.CDRProperty.indexValidTerms.equals(predicate)
                        || ContentModelHelper.CDRProperty.warnInvalidTerms.equals(predicate)
                        || ContentModelHelper.CDRProperty.replaceInvalidTerms.equals(predicate))
                    collectionConfig.add(predicate);
            }
        }

        return result;
    }
}