rewritten list view queries with UNIONs plus various and sundry other bugfixes and improvements; also probably new bugs

This commit is contained in:
bjl23 2011-01-27 20:56:44 +00:00
parent 881c3ca4fa
commit b5777a60ad
14 changed files with 238 additions and 61 deletions

View file

@ -15,6 +15,7 @@ import org.openrdf.model.URI;
import org.openrdf.model.impl.URIImpl;
import edu.cornell.mannlib.vitro.webapp.beans.Individual;
import edu.cornell.mannlib.vitro.webapp.beans.IndividualImpl;
import edu.cornell.mannlib.vitro.webapp.beans.Portal;
import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest;
import edu.cornell.mannlib.vitro.webapp.dao.IndividualDao;
@ -259,7 +260,7 @@ public class UrlBuilder {
}
public static String getIndividualProfileUrl(String individualUri, WebappDaoFactory wadf) {
Individual individual = wadf.getIndividualDao().getIndividualByURI(individualUri);
Individual individual = new IndividualImpl(individualUri);
return getIndividualProfileUrl(individual, individualUri, wadf);
}

View file

@ -45,6 +45,7 @@ import edu.cornell.mannlib.vitro.webapp.beans.IndividualImpl;
import edu.cornell.mannlib.vitro.webapp.beans.ObjectPropertyStatement;
import edu.cornell.mannlib.vitro.webapp.beans.VClass;
import edu.cornell.mannlib.vitro.webapp.dao.VitroVocabulary;
import edu.cornell.mannlib.vitro.webapp.dao.jena.IndividualSDB.IndividualNotFoundException;
import edu.cornell.mannlib.vitro.webapp.dao.jena.WebappDaoFactorySDB.SDBDatasetMode;
public class IndividualDaoSDB extends IndividualDaoJena {
@ -66,7 +67,12 @@ public class IndividualDaoSDB extends IndividualDaoJena {
}
protected Individual makeIndividual(String individualURI) {
return new IndividualSDB(individualURI, this.dwf, datasetMode, getWebappDaoFactory());
try {
return new IndividualSDB(individualURI, this.dwf, datasetMode, getWebappDaoFactory());
} catch (IndividualNotFoundException e) {
// If the individual does not exist, return null.
return null;
}
}
private static final Log log = LogFactory.getLog(IndividualDaoSDB.class.getName());

View file

@ -137,21 +137,21 @@ public class IndividualSDB extends IndividualImpl implements Individual {
"CONSTRUCT " +
"{ <"+individualURI+"> <" + RDFS.label.getURI() +
"> ?ooo. \n" +
"<"+individualURI+"> a ?type . \n" +
// "<"+individualURI+"> a ?type . \n" +
"<"+individualURI+"> <" + VitroVocabulary.MONIKER +
"> ?moniker \n" +
"} WHERE {" +
"{ GRAPH ?g { \n" +
"} WHERE { GRAPH <urn:x-arq:UnionGraph> {" +
"{ \n" +
"{ <"+individualURI+"> <" + RDFS.label.getURI() +
"> ?ooo } \n" +
"UNION { GRAPH ?h { <" +
"UNION { <" +
individualURI+"> <" + VitroVocabulary.MONIKER +
"> ?moniker } } \n" +
"} } \n" +
"UNION { GRAPH ?i { <"
+ individualURI + "> a ?type } } \n" +
WebappDaoFactorySDB.getFilterBlock(graphVars, datasetMode) +
"}";
"> ?moniker } \n" +
"} \n" +
// "UNION { <"
// + individualURI + "> a ?type } \n" +
// WebappDaoFactorySDB.getFilterBlock(graphVars, datasetMode) +
"} }";
model = QueryExecutionFactory.create(
QueryFactory.create(getStatements), dataset)
.execConstruct();
@ -163,6 +163,10 @@ public class IndividualSDB extends IndividualImpl implements Individual {
OntModel ontModel = ModelFactory.createOntologyModel(
OntModelSpec.OWL_MEM, model);
if (model.isEmpty()) {
throw new IndividualNotFoundException();
}
this.ind = ontModel.createOntResource(individualURI);
}
setUpURIParts(ind);
@ -182,6 +186,8 @@ public class IndividualSDB extends IndividualImpl implements Individual {
!SKIP_INITIALIZATION);
}
public class IndividualNotFoundException extends RuntimeException {}
private void setUpURIParts(OntResource ind) {
if (ind != null) {
if (ind.isAnon()) {
@ -304,8 +310,8 @@ public class IndividualSDB extends IndividualImpl implements Individual {
int portalid = FlagMathUtils.numeric2Portalid(numericPortal);
String portalTypeUri = VitroVocabulary.vitroURI +
"Flag1Value" + portalid + "Thing";
String Ask = "ASK { GRAPH ?g { <" + this.individualURI +
"> <" +RDF.type+ "> <" + portalTypeUri +">} }";
String Ask = "ASK { <" + this.individualURI +
"> <" +RDF.type+ "> <" + portalTypeUri +">} ";
if(!QueryExecutionFactory.create(
QueryFactory.create(Ask), dataset).execAsk()) {
return false;
@ -334,8 +340,8 @@ public class IndividualSDB extends IndividualImpl implements Individual {
getObjects =
"CONSTRUCT{<" + this.individualURI + "> <" +
RDF.type + "> ?object}" +
"WHERE{ GRAPH ?g { <" + this.individualURI + "> <" +
RDF.type + "> ?object} }";
"WHERE{ <" + this.individualURI + "> <" +
RDF.type + "> ?object }";
tempModel = QueryExecutionFactory.create(
QueryFactory.create(
getObjects), dataset).execConstruct();
@ -394,8 +400,8 @@ public class IndividualSDB extends IndividualImpl implements Individual {
getObjects =
"CONSTRUCT{<" + this.individualURI + "> <" +
RDF.type + "> ?object}" +
"WHERE{ GRAPH ?g { <" + this.individualURI + "> <" +
RDF.type + "> ?object} }";
"WHERE{ <" + this.individualURI + "> <" +
RDF.type + "> ?object }";
tempModel = QueryExecutionFactory.create(
QueryFactory.create(
getObjects), dataset).execConstruct();

View file

@ -50,19 +50,19 @@ public class ObjectPropertyStatementDaoSDB extends
return entity;
else {
Map<String, ObjectProperty> uriToObjectProperty = new HashMap<String,ObjectProperty>();
String[] graphVars = { "?g", "?h", "?i", "?j" };
//String[] graphVars = { "?g", "?h", "?i", "?j" };
String query = "CONSTRUCT { \n" +
" <" + entity.getURI() + "> ?p ?o . \n" +
" ?o a ?oType . \n" +
// " ?o a ?oType . \n" +
" ?o <" + RDFS.label.getURI() + "> ?oLabel . \n" +
" ?o <" + VitroVocabulary.MONIKER + "> ?oMoniker \n" +
"} WHERE { GRAPH ?g { \n" +
"} WHERE { GRAPH <urn:x-arq:UnionGraph> { \n" +
" <" + entity.getURI() + "> ?p ?o \n" +
" OPTIONAL { GRAPH ?h { ?o a ?oType } } \n" +
" OPTIONAL { GRAPH ?i { ?o <" + RDFS.label.getURI() + "> ?oLabel } } \n" +
" OPTIONAL { GRAPH ?j { ?o <" + VitroVocabulary.MONIKER + "> ?oMoniker } } \n" +
//" OPTIONAL { GRAPH ?h { ?o a ?oType } } \n" +
// " OPTIONAL { { ?o <" + RDFS.label.getURI() + "> ?oLabel } } \n" +
// " OPTIONAL { { ?o <" + VitroVocabulary.MONIKER + "> ?oMoniker } } \n" +
"} \n" +
WebappDaoFactorySDB.getFilterBlock(graphVars, datasetMode) +
//WebappDaoFactorySDB.getFilterBlock(graphVars, datasetMode) +
"}";
long startTime = System.currentTimeMillis();
Model m = null;

View file

@ -15,7 +15,9 @@ import org.apache.commons.logging.LogFactory;
import com.hp.hpl.jena.datatypes.xsd.XSDDatatype;
import com.hp.hpl.jena.ontology.Individual;
import com.hp.hpl.jena.ontology.OntModel;
import com.hp.hpl.jena.ontology.OntModelSpec;
import com.hp.hpl.jena.rdf.model.Literal;
import com.hp.hpl.jena.rdf.model.ModelFactory;
import com.hp.hpl.jena.rdf.model.Resource;
import com.hp.hpl.jena.rdf.model.Statement;
import com.hp.hpl.jena.shared.Lock;
@ -129,26 +131,44 @@ public class PropertyGroupDaoJena extends JenaBaseDao implements PropertyGroupDa
groupInd.setURI(group.getURI());
String groupURI = null;
try {
groupURI = (new WebappDaoFactoryJena(getOntModelSelector().getApplicationMetadataModel())).getIndividualDao().insertNewIndividual(groupInd);
OntModel unionForURIGeneration = ModelFactory.createOntologyModel(
OntModelSpec.OWL_MEM, ModelFactory.createUnion(
getOntModelSelector().getApplicationMetadataModel(),
getOntModelSelector().getFullModel()));
try {
groupURI = (new WebappDaoFactoryJena(unionForURIGeneration)).
getIndividualDao().insertNewIndividual(groupInd);
} catch (InsertException ie) {
throw new RuntimeException(InsertException.class.getName() + "Unable to insert property group "+groupURI, ie);
throw new RuntimeException(InsertException.class.getName() +
"Unable to insert property group " + groupURI, ie);
}
if (groupURI != null) {
getOntModel().enterCriticalSection(Lock.WRITE);
try {
com.hp.hpl.jena.ontology.Individual groupJenaInd = getOntModel().getIndividual(groupURI);
com.hp.hpl.jena.ontology.Individual groupJenaInd =
getOntModel().getIndividual(groupURI);
try {
groupJenaInd.addProperty(DISPLAY_RANK, Integer.toString(group.getDisplayRank()), XSDDatatype.XSDint);
groupJenaInd.addProperty(DISPLAY_RANK, Integer.toString(
group.getDisplayRank()), XSDDatatype.XSDint);
} catch (Exception e) {
log.error("error setting displayRank for "+groupInd.getURI());
log.error(
"error setting displayRank for "
+ groupInd.getURI());
}
if (group.getPublicDescription() != null && group.getPublicDescription().length()>0) {
if (group.getPublicDescription() != null
&& group.getPublicDescription().length()>0) {
try {
groupJenaInd.addProperty(PUBLIC_DESCRIPTION_ANNOT, group.getPublicDescription(), XSDDatatype.XSDstring);
groupJenaInd.addProperty(
PUBLIC_DESCRIPTION_ANNOT,
group.getPublicDescription(),
XSDDatatype.XSDstring);
} catch (Exception ex) {
log.error("error setting public description for "+groupInd.getURI());
log.error(
"error setting public description for "
+ groupInd.getURI());
}
}
} finally {

View file

@ -216,8 +216,15 @@ public class VClassGroupDaoJena extends JenaBaseDao implements VClassGroupDao {
groupInd.setVClassURI(CLASSGROUP.getURI());
String groupURI = null;
OntModel unionForURIGeneration = ModelFactory.createOntologyModel(
OntModelSpec.OWL_MEM, ModelFactory.createUnion(
getOntModelSelector().getApplicationMetadataModel(),
getOntModelSelector().getFullModel()));
try {
groupURI = (new WebappDaoFactoryJena(ontModel)).getIndividualDao().insertNewIndividual(groupInd);
groupURI = (new WebappDaoFactoryJena(unionForURIGeneration)).
getIndividualDao().insertNewIndividual(groupInd);
} catch (InsertException ie) {
throw new RuntimeException(InsertException.class.getName() + "Unable to insert class group "+groupURI, ie);
}

View file

@ -15,6 +15,7 @@ import com.hp.hpl.jena.iri.IRIFactory;
import com.hp.hpl.jena.iri.Violation;
import com.hp.hpl.jena.ontology.OntClass;
import com.hp.hpl.jena.ontology.OntModel;
import com.hp.hpl.jena.ontology.OntModelSpec;
import com.hp.hpl.jena.ontology.OntResource;
import com.hp.hpl.jena.query.DataSource;
import com.hp.hpl.jena.query.Dataset;
@ -175,22 +176,38 @@ public class WebappDaoFactoryJena implements WebappDaoFactory {
if (languageUniversalsModel.size()>0) {
this.ontModelSelector.getTBoxModel().addSubModel(languageUniversalsModel);
}
DataSource dataset = DatasetFactory.create();
dataset.addNamedModel(JenaDataSourceSetupBase.JENA_DB_MODEL,
(baseOntModelSelector != null)
? baseOntModelSelector.getFullModel()
: ontModelSelector.getFullModel());
if (inferenceOntModelSelector != null) {
dataset.addNamedModel(JenaDataSourceSetupBase.JENA_INF_MODEL,
inferenceOntModelSelector.getFullModel());
}
Model assertions = (baseOntModelSelector != null)
? baseOntModelSelector.getFullModel()
: ontModelSelector.getFullModel();
Model inferences = (inferenceOntModelSelector != null)
? inferenceOntModelSelector.getFullModel()
: null;
Dataset dataset = makeInMemoryDataset(assertions, inferences);
this.dwf = new StaticDatasetFactory(dataset);
}
public static Dataset makeInMemoryDataset(Model assertions, Model inferences) {
DataSource dataset = DatasetFactory.create();
OntModel union = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM);
if (assertions != null) {
dataset.addNamedModel(JenaDataSourceSetupBase.JENA_DB_MODEL, assertions);
union.addSubModel(assertions);
}
if (inferences != null) {
dataset.addNamedModel(JenaDataSourceSetupBase.JENA_INF_MODEL,
inferences);
union.addSubModel(inferences);
}
dataset.setDefaultModel(union);
dataset.addNamedModel("urn:x-arq:UnionGraph", union);
return dataset;
}
public WebappDaoFactoryJena(OntModelSelector ontModelSelector,
String defaultNamespace,
HashSet<String> nonuserNamespaces,

View file

@ -39,6 +39,7 @@ import edu.cornell.mannlib.vitro.webapp.dao.filtering.filters.FilterFactory;
import edu.cornell.mannlib.vitro.webapp.dao.filtering.filters.HiddenFromDisplayBelowRoleLevelFilter;
import edu.cornell.mannlib.vitro.webapp.dao.filtering.filters.VitroFilterUtils;
import edu.cornell.mannlib.vitro.webapp.dao.filtering.filters.VitroFilters;
import edu.cornell.mannlib.vitro.webapp.dao.jena.WebappDaoFactoryJena;
import edu.cornell.mannlib.vitro.webapp.flags.AuthFlag;
import edu.cornell.mannlib.vitro.webapp.flags.FlagException;
import edu.cornell.mannlib.vitro.webapp.flags.PortalFlag;
@ -203,9 +204,8 @@ public class VitroRequestPrep implements Filter {
// support for Dataset interface if using Jena in-memory model
if (vreq.getDataset() == null) {
DataSource dataset = DatasetFactory.create();
dataset.addNamedModel(JenaDataSourceSetupBase.JENA_DB_MODEL, vreq.getAssertionsOntModel());
dataset.addNamedModel(JenaDataSourceSetupBase.JENA_INF_MODEL, vreq.getInferenceOntModel());
Dataset dataset = WebappDaoFactoryJena.makeInMemoryDataset(
vreq.getAssertionsOntModel(), vreq.getInferenceOntModel());
vreq.setDataset(dataset);
}

View file

@ -206,13 +206,16 @@ public class JenaDataSourceSetupSDB extends JenaDataSourceSetupBase implements j
log.error("Unable to load application metadata model cache from DB", e);
}
log.info("Adding vitro application ontology");
// add the vitro ontologies to the tbox models
// add the vitroontologies to the tbox models
OntModel vitroTBoxModel = (new JenaBaseDaoCon()).getConstModel();
baseOms.getTBoxModel().addSubModel(vitroTBoxModel);
inferenceOms.getTBoxModel().addSubModel(vitroTBoxModel);
unionOms.getTBoxModel().addSubModel(vitroTBoxModel);
log.error("Setting up union models and DAO factories");
// create TBox + ABox union models and set up webapp DAO factories
OntModel baseUnion = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM,
ModelFactory.createUnion(baseOms.getABoxModel(), baseOms.getTBoxModel()));
@ -262,6 +265,8 @@ public class JenaDataSourceSetupSDB extends JenaDataSourceSetupBase implements j
// loadDataFromFilesystem(unionOms.getFullModel(), sce.getServletContext());
//}
log.info("Checking for user account data");
if (userAccountsModel.size() == 0) {
readOntologyFilesInPathSet(AUTHPATH, sce.getServletContext(), userAccountsModel);
if (userAccountsModel.size() == 0) {
@ -269,20 +274,28 @@ public class JenaDataSourceSetupSDB extends JenaDataSourceSetupBase implements j
}
}
log.info("Checking for minimal interface metadata");
ensureEssentialInterfaceData(unionOms.getApplicationMetadataModel(), sce, wadf);
//log.info("Setting up namespace mapper");
NamespaceMapper namespaceMapper = new NamespaceMapperJena(masterUnion, masterUnion, defaultNamespace);
sce.getServletContext().setAttribute("NamespaceMapper", namespaceMapper);
unionOms.getFullModel().getBaseModel().register(namespaceMapper);
sce.getServletContext().setAttribute("defaultNamespace", defaultNamespace);
log.info("SDB store ready for use");
makeModelMakerFromConnectionProperties(TripleStoreType.RDB);
VitroJenaModelMaker vjmm = getVitroJenaModelMaker();
setVitroJenaModelMaker(vjmm,sce);
makeModelMakerFromConnectionProperties(TripleStoreType.SDB);
VitroJenaSDBModelMaker vsmm = getVitroJenaSDBModelMaker();
setVitroJenaSDBModelMaker(vsmm,sce);
log.info("Model makers set up");
} catch (Throwable t) {
log.error("Throwable in " + this.getClass().getName(), t);
@ -587,7 +600,9 @@ public class JenaDataSourceSetupSDB extends JenaDataSourceSetupBase implements j
SimpleReasonerSetup.setRecomputeRequired(ctx);
} finally {
log.info("Adding indexes to SDB database tables.");
store.getTableFormatter().addIndexes();
log.info("Indexes created.");
}
}

View file

@ -9,6 +9,9 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.hp.hpl.jena.ontology.Individual;
import com.hp.hpl.jena.ontology.OntModel;
import com.hp.hpl.jena.rdf.listeners.StatementListener;
@ -26,6 +29,8 @@ import edu.cornell.mannlib.vitro.webapp.utils.NamespaceMapper;
public class NamespaceMapperJena extends StatementListener implements
NamespaceMapper {
private static final Log log = LogFactory.getLog(NamespaceMapperJena.class);
private HashMap<String,String> prefixToNamespaceMap;
private HashMap<String,List<String>> namespaceToPrefixMap;
@ -60,15 +65,23 @@ public class NamespaceMapperJena extends StatementListener implements
}
private static int LARGE_NS = 200;
private void rebuildNamespaceCache() {
HashMap<String,String> tempPrefixToNamespaceMap = new HashMap<String,String>();
HashMap<String,List<String>> tempNamespaceToPrefixMap = new HashMap<String,List<String>>();
metadataModel.enterCriticalSection(Lock.READ);
int nsCount = 0;
try {
// Iterate through all the namespace objects
ClosableIterator closeIt = metadataModel.listIndividuals(metadataModel.getResource(VitroVocabulary.NAMESPACE));
try {
for (Iterator namespaceIt = closeIt; namespaceIt.hasNext();) {
nsCount++;
if (nsCount == LARGE_NS) {
log.warn("Unusually large number of different namespaces encountered; " +
"namespace mapper setup may take some time.");
}
Individual namespaceInd = (Individual) namespaceIt.next();
String namespaceURI = null;
RDFNode node = namespaceInd.getPropertyValue(metadataModel.getProperty(VitroVocabulary.NAMESPACE_NAMESPACEURI));

View file

@ -44,6 +44,7 @@ public abstract class BaseObjectPropertyDataPostProcessor implements
/** Postprocessing that applies to the list as a whole - reordering, removing duplicates, etc. */
protected void processList(List<Map<String, String>> data) {
objectPropertyTemplateModel.removeLessSpecificSolutions(data);
objectPropertyTemplateModel.removeDuplicates(data);
}

View file

@ -9,6 +9,7 @@ import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -229,6 +230,67 @@ public abstract class ObjectPropertyTemplateModel extends PropertyTemplateModel
}
}
/**
* For performance reasons, we've had to rewrite SPARQL queries that would
* naturally use OPTIONAL blocks to use UNIONs instead. These UNION queries
* return a superset of the solutions returned by the originals. This
* method filters out the unwanted solutions with extra null values.
*
* This operation is polynomial time in the worst case, but should be linear
* with the actual queries used for list views. The ORDER BY clauses
* should minimize the seek distance for solution elimination.
*
* @param List<Map<String, String>> data
*/
protected void removeLessSpecificSolutions(List<Map<String, String>> data) {
List<Map<String, String>> redundantSolns =
new ArrayList<Map<String, String>>();
for (int i = 0; i < data.size(); i++) {
Map<String,String> soln = data.get(i);
boolean redundantSoln = false;
// seek forward
int j = i + 1;
while (!redundantSoln && (j < data.size())) {
redundantSoln = isEqualToOrLessSpecificThan(soln, data.get(j));
j++;
}
// loop back around
j = 0;
while (!redundantSoln && (j < i)) {
redundantSoln = isEqualToOrLessSpecificThan(soln, data.get(j));
j++;
}
if (redundantSoln) {
redundantSolns.add(soln);
}
}
data.removeAll(redundantSolns);
}
/**
* Returns true if soln1 is equal to or less specific (i.e., has more null
* values) than soln2
* @param List<Map<String, String>> soln1
* @param List<Map<String, String>> soln2
*/
private boolean isEqualToOrLessSpecificThan (Map<String, String> soln1,
Map<String, String> soln2) {
if (soln1.keySet().size() < soln2.keySet().size()) {
return true;
}
for (String key : soln1.keySet()) {
String value1 = soln1.get(key);
String value2 = soln2.get(key);
if (value2 == null && value1 != null) {
return false;
}
if (value1 != null && value2 != null && !value1.equals(value2)) {
return false;
}
}
return true;
}
/** The SPARQL query results may contain duplicate rows for a single object, if there are multiple solutions
* to the entire query. Remove duplicates here by arbitrarily selecting only the first row returned.

View file

@ -11,9 +11,21 @@
PREFIX rdfs: &lt;http://www.w3.org/2000/01/rdf-schema#&gt;
SELECT ?object ?name ?moniker {
GRAPH ?g1 { ?subject ?property ?object }
OPTIONAL { GRAPH ?g2 { ?object rdfs:label ?name } }
OPTIONAL { GRAPH ?g3 { ?object vitro:moniker ?moniker } }
{
?subject ?property ?object .
}
UNION {
?subject ?property ?object .
?object rdfs:label ?name
}
UNION {
?subject ?property ?object .
?object vitro:moniker ?moniker
} UNION {
?subject ?property ?object .
?object rdfs:label ?name .
?object vitro:moniker ?moniker .
}
}
</query-base>
@ -22,11 +34,28 @@
PREFIX rdfs: &lt;http://www.w3.org/2000/01/rdf-schema#&gt;
SELECT ?subclass ?object ?name ?moniker {
GRAPH ?g1 { ?subject ?property ?object
OPTIONAL { ?object a ?subclass }
{
?subject ?property ?object .
?object a ?subclass .
}
OPTIONAL { GRAPH ?g2 { ?object rdfs:label ?name } }
OPTIONAL { GRAPH ?g3 { ?object vitro:moniker ?moniker } }
UNION {
?subject ?property ?object .
?object a ?subclass .
?object rdfs:label ?name
}
UNION {
?subject ?property ?object .
?object a ?subclass .
?object vitro:moniker ?moniker
} UNION {
?subject ?property ?object .
?object a ?subclass .
?object rdfs:label ?name .
?object vitro:moniker ?moniker .
}
} ORDER BY ?subclass
</query-collated>

View file

@ -14,10 +14,10 @@
(afn:localname(?link) AS ?linkName)
?anchor
?url WHERE {
GRAPH ?g1 { ?subject ?property ?link }
OPTIONAL { GRAPH ?g2 { ?link vitro:linkAnchor ?anchor } }
OPTIONAL { GRAPH ?g3 { ?link vitro:linkURL ?url } }
OPTIONAL { GRAPH ?g4 { ?link vitro:linkDisplayRank ?rank } }
?subject ?property ?link
OPTIONAL { ?link vitro:linkAnchor ?anchor }
OPTIONAL { ?link vitro:linkURL ?url }
OPTIONAL { ?link vitro:linkDisplayRank ?rank }
} ORDER BY ?rank
</query-base>