/** property function that accesses a Solr server */ public class TextQueryPF extends PropertyFunctionBase { private static Logger log = LoggerFactory.getLogger(TextQueryPF.class); /* * (?uri ?score) :queryPF (property? "string" limit? score?) */ private TextIndex textIndex = null; private boolean warningIssued = false; public TextQueryPF() {} private String langArg = null; private static final Symbol cacheSymbol = Symbol.create("TextQueryPF.cache"); private static final int CACHE_SIZE = 10; @Override public void build( PropFuncArg argSubject, Node predicate, PropFuncArg argObject, ExecutionContext execCxt) { super.build(argSubject, predicate, argObject, execCxt); DatasetGraph dsg = execCxt.getDataset(); textIndex = chooseTextIndex(dsg); if (argSubject.isList()) { int size = argSubject.getArgListSize(); if (size != 2 && size != 3) { throw new QueryBuildException( "Subject has " + argSubject.getArgList().size() + " elements, not 2 or 3: " + argSubject); } } if (argObject.isList()) { List<Node> list = argObject.getArgList(); if (list.size() == 0) throw new QueryBuildException("Zero-length argument list"); if (list.size() > 4) throw new QueryBuildException("Too many arguments in list : " + list); // extract of extra lang arg if present and if is usable. // arg is removed from the list to avoid conflict with order and args length langArg = extractArg("lang", list); if (langArg != null && textIndex.getDocDef().getLangField() == null) log.warn("lang argument is ignored if langField not set in the index configuration"); } } private static TextIndex chooseTextIndex(DatasetGraph dsg) { Context c = dsg.getContext(); Object obj = dsg.getContext().get(TextQuery.textIndex); if (obj != null) { try { return (TextIndex) obj; } catch (ClassCastException ex) { Log.warn( TextQueryPF.class, "Context setting '" + TextQuery.textIndex + "'is not a TextIndex"); } } if (dsg instanceof DatasetGraphText) { DatasetGraphText x = (DatasetGraphText) dsg; return x.getTextIndex(); } Log.warn( TextQueryPF.class, "Failed to find the text index : tried context and as a text-enabled dataset"); return null; } private String extractArg(String prefix, List<Node> objArgs) { String value = null; int pos = 0; for (Node node : objArgs) { if (node.isLiteral()) { String arg = node.getLiteral().toString(); if (arg.startsWith(prefix + ":")) { value = arg.split(":")[1]; break; } } pos++; } if (value != null) objArgs.remove(pos); return value; } @Override public QueryIterator exec( Binding binding, PropFuncArg argSubject, Node predicate, PropFuncArg argObject, ExecutionContext execCxt) { if (textIndex == null) { if (!warningIssued) { Log.warn(getClass(), "No text index - no text search performed"); warningIssued = true; } // Not a text dataset - no-op return IterLib.result(binding, execCxt); } DatasetGraph dsg = execCxt.getDataset(); argSubject = Substitute.substitute(argSubject, binding); argObject = Substitute.substitute(argObject, binding); Node s = null; Node score = null; Node literal = null; if (argSubject.isList()) { // Length checked in build() s = argSubject.getArg(0); score = argSubject.getArg(1); if (!score.isVariable()) throw new QueryExecException("Hit score is not a variable: " + argSubject); if (argSubject.getArgListSize() > 2) { literal = argSubject.getArg(2); if (!literal.isVariable()) throw new QueryExecException("Hit literal is not a variable: " + argSubject); } } else { s = argSubject.getArg(); } if (s.isLiteral()) // Does not match return IterLib.noResults(execCxt); StrMatch match = objectToStruct(argObject, true); if (match == null) { // can't match return IterLib.noResults(execCxt); } // ---- QueryIterator qIter = (Var.isVar(s)) ? variableSubject(binding, s, score, literal, match, execCxt) : concreteSubject(binding, s, score, literal, match, execCxt); if (match.getLimit() >= 0) qIter = new QueryIterSlice(qIter, 0, match.getLimit(), execCxt); return qIter; } private QueryIterator resultsToQueryIterator( Binding binding, Node s, Node score, Node literal, Collection<TextHit> results, ExecutionContext execCxt) { Var sVar = Var.isVar(s) ? Var.alloc(s) : null; Var scoreVar = (score == null) ? null : Var.alloc(score); Var literalVar = (literal == null) ? null : Var.alloc(literal); Function<TextHit, Binding> converter = (TextHit hit) -> { if (score == null && literal == null) return sVar != null ? BindingFactory.binding(binding, sVar, hit.getNode()) : BindingFactory.binding(binding); BindingMap bmap = BindingFactory.create(binding); if (sVar != null) bmap.add(sVar, hit.getNode()); if (scoreVar != null) bmap.add(scoreVar, NodeFactoryExtra.floatToNode(hit.getScore())); if (literalVar != null) bmap.add(literalVar, hit.getLiteral()); return bmap; }; Iterator<Binding> bIter = Iter.map(results.iterator(), converter); QueryIterator qIter = new QueryIterPlainWrapper(bIter, execCxt); return qIter; } private QueryIterator variableSubject( Binding binding, Node s, Node score, Node literal, StrMatch match, ExecutionContext execCxt) { ListMultimap<String, TextHit> results = query(match.getProperty(), match.getQueryString(), match.getLimit(), execCxt); Collection<TextHit> r = results.values(); return resultsToQueryIterator(binding, s, score, literal, r, execCxt); } private QueryIterator concreteSubject( Binding binding, Node s, Node score, Node literal, StrMatch match, ExecutionContext execCxt) { if (!s.isURI()) { log.warn("Subject not a URI: " + s); return IterLib.noResults(execCxt); } String qs = match.getQueryString(); ListMultimap<String, TextHit> x = query(match.getProperty(), match.getQueryString(), -1, execCxt); if (x == null) // null return value - empty result return IterLib.noResults(execCxt); List<TextHit> r = x.get(s.getURI()); return resultsToQueryIterator(binding, s, score, literal, r, execCxt); } private ListMultimap<String, TextHit> query( Node property, String queryString, int limit, ExecutionContext execCxt) { // use the graph information in the text index if possible if (textIndex.getDocDef().getGraphField() != null && execCxt.getActiveGraph() instanceof GraphView) { GraphView activeGraph = (GraphView) execCxt.getActiveGraph(); if (!Quad.isUnionGraph(activeGraph.getGraphName())) { String uri = activeGraph.getGraphName() != null ? TextQueryFuncs.graphNodeToString(activeGraph.getGraphName()) : Quad.defaultGraphNodeGenerated.getURI(); String escaped = QueryParserBase.escape(uri); String qs2 = textIndex.getDocDef().getGraphField() + ":" + escaped; queryString = "(" + queryString + ") AND " + qs2; } } // for language-based search extension if (textIndex.getDocDef().getLangField() != null) { String field = textIndex.getDocDef().getLangField(); if (langArg != null) { String qs2 = !"none".equals(langArg) ? field + ":" + langArg : "-" + field + ":*"; queryString = "(" + queryString + ") AND " + qs2; } } Explain.explain(execCxt.getContext(), "Text query: " + queryString); if (log.isDebugEnabled()) log.debug("Text query: {} ({})", queryString, limit); String cacheKey = limit + " " + property + " " + queryString; Cache<String, ListMultimap<String, TextHit>> queryCache = (Cache<String, ListMultimap<String, TextHit>>) execCxt.getContext().get(cacheSymbol); if (queryCache == null) { /* doesn't yet exist, need to create it */ queryCache = CacheFactory.createCache(CACHE_SIZE); execCxt.getContext().put(cacheSymbol, queryCache); } final String queryStr = queryString; // final needed for the lambda function ListMultimap<String, TextHit> results = queryCache.getOrFill( cacheKey, () -> { List<TextHit> resultList = textIndex.query(property, queryStr, limit); ListMultimap<String, TextHit> resultMultimap = LinkedListMultimap.create(); for (TextHit result : resultList) { resultMultimap.put(result.getNode().getURI(), result); } return resultMultimap; }); return results; } /** * Deconstruct the node or list object argument and make a StrMatch The 'executionTime' flag * indciates whether this is for a build time static check, or for runtime execution. */ private StrMatch objectToStruct(PropFuncArg argObject, boolean executionTime) { EntityDefinition docDef = textIndex.getDocDef(); if (argObject.isNode()) { Node o = argObject.getArg(); if (!o.isLiteral()) { if (executionTime) log.warn("Object to text query is not a literal"); return null; } RDFDatatype dt = o.getLiteralDatatype(); if (dt != null && dt != XSDDatatype.XSDstring) { log.warn("Object to text query is not a string"); return null; } String qs = o.getLiteralLexicalForm(); return new StrMatch(null, qs, -1, 0); } List<Node> list = argObject.getArgList(); if (list.size() == 0 || list.size() > 3) throw new TextIndexException("Change in object list size"); Node predicate = null; String field = null; // Do not prepend the field name - rely on default field int idx = 0; Node x = list.get(0); // Property? if (x.isURI()) { predicate = x; idx++; if (idx >= list.size()) throw new TextIndexException("Property specificed but no query string : " + list); x = list.get(idx); field = docDef.getField(predicate); if (field == null) { log.warn("Predicate not indexed: " + predicate); return null; } } // String! if (!x.isLiteral()) { if (executionTime) log.warn("Text query string is not a literal " + list); return null; } if (x.getLiteralDatatype() != null && !x.getLiteralDatatype().equals(XSDDatatype.XSDstring)) { log.warn("Text query is not a string " + list); return null; } String queryString = x.getLiteralLexicalForm(); idx++; int limit = -1; float score = 0; if (idx < list.size()) { // Limit? x = list.get(idx); idx++; if (!x.isLiteral()) { if (executionTime) log.warn("Text query limit is not an integer " + x); return null; } int v = NodeFactoryExtra.nodeToInt(x); limit = (v < 0) ? -1 : v; } String qs = queryString; if (field != null) qs = field + ":" + qs; return new StrMatch(predicate, qs, limit, score); } class StrMatch { private final Node property; private final String queryString; private final int limit; private final float scoreLimit; public StrMatch(Node property, String queryString, int limit, float scoreLimit) { super(); this.property = property; this.queryString = queryString; this.limit = limit; this.scoreLimit = scoreLimit; } public Node getProperty() { return property; } public String getQueryString() { return queryString; } public int getLimit() { return limit; } public float getScoreLimit() { return scoreLimit; } } }
public class SpatialQuery { private static volatile boolean initialized = false; private static Object lock = new Object(); public static String NS = "http://jena.apache.org/spatial#"; public static String IRI = "http://jena.apache.org/#spatial"; public static final Symbol spatialIndex = Symbol.create(NS + "index"); public static final String PATH = "org.apache.jena.query.spatial"; private static String metadataLocation = "org/apache/jena/query/spatial/properties.xml"; private static Metadata metadata = new Metadata(metadataLocation); public static final String NAME = "ARQ Spatial Query"; public static final String VERSION = metadata.get(PATH + ".version", "unknown"); public static final String BUILD_DATE = metadata.get(PATH + ".build.datetime", "unset"); public static SpatialContext ctx = SpatialContext.GEO; // an optional feature for WKT literals, loaded when necessary, but not required public static final String JTS_SPATIAL_CONTEXT_FACTORY_CLASS = "com.spatial4j.core.context.jts.JtsSpatialContextFactory"; static { JenaSystem.init(); } public static void init() { if (initialized) return; synchronized (lock) { if (initialized) { JenaSystem.logLifecycle("SpatialQuery.init - skip"); return; } initialized = true; JenaSystem.logLifecycle("SpatialQuery.init - start"); SpatialAssembler.init(); SystemInfo sysInfo = new SystemInfo(IRI, PATH, VERSION, BUILD_DATE); SystemARQ.registerSubSystem(sysInfo); PropertyFunctionRegistry.get() .put( "http://jena.apache.org/spatial#withinCircle", new PropertyFunctionFactory() { @Override public PropertyFunction create(String uri) { return new IsWithinCirclePF(); } }); PropertyFunctionRegistry.get() .put( "http://jena.apache.org/spatial#nearby", new PropertyFunctionFactory() { @Override public PropertyFunction create(String uri) { return new IsNearByPF(); } }); PropertyFunctionRegistry.get() .put( "http://jena.apache.org/spatial#withinBox", new PropertyFunctionFactory() { @Override public PropertyFunction create(String uri) { return new IsWithinBoxPF(); } }); PropertyFunctionRegistry.get() .put( "http://jena.apache.org/spatial#intersectBox", new PropertyFunctionFactory() { @Override public PropertyFunction create(String uri) { return new IntersectsBoxPF(); } }); PropertyFunctionRegistry.get() .put( "http://jena.apache.org/spatial#north", new PropertyFunctionFactory() { @Override public PropertyFunction create(String uri) { return new NorthPF(); } }); PropertyFunctionRegistry.get() .put( "http://jena.apache.org/spatial#south", new PropertyFunctionFactory() { @Override public PropertyFunction create(String uri) { return new SouthPF(); } }); PropertyFunctionRegistry.get() .put( "http://jena.apache.org/spatial#east", new PropertyFunctionFactory() { @Override public PropertyFunction create(String uri) { return new EastPF(); } }); PropertyFunctionRegistry.get() .put( "http://jena.apache.org/spatial#west", new PropertyFunctionFactory() { @Override public PropertyFunction create(String uri) { return new WestPF(); } }); JenaSystem.logLifecycle("SpatialQuery.init - finish"); } } }