/** * Copyright 2014 National University of Ireland, Galway. * * This file is part of the SIREn project. Project and contact information: * * https://github.com/rdelbru/SIREn * * 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 org.sindice.siren.qparser.json.dsl; import java.util.ArrayList; import java.util.List; import org.apache.lucene.queryparser.flexible.core.QueryNodeException; import org.apache.lucene.search.Query; import org.codehaus.jackson.map.ObjectMapper; import org.codehaus.jackson.node.ArrayNode; import org.codehaus.jackson.node.ObjectNode; import org.sindice.siren.qparser.json.dsl.QueryClause.Occur; import org.sindice.siren.qparser.json.parser.BoostPropertyParser; import org.sindice.siren.qparser.json.parser.ChildPropertyParser; import org.sindice.siren.qparser.json.parser.DescendantPropertyParser; import org.sindice.siren.qparser.json.parser.LevelPropertyParser; import org.sindice.siren.qparser.json.parser.OccurPropertyParser; import org.sindice.siren.qparser.json.parser.RangePropertyParser; import org.sindice.siren.qparser.json.parser.RootPropertyParser; import org.sindice.siren.qparser.json.parser.TwigPropertyParser; import org.sindice.siren.qparser.keyword.KeywordQueryParser; import org.sindice.siren.search.node.LuceneProxyNodeQuery; import org.sindice.siren.search.node.NodeBooleanClause; import org.sindice.siren.search.node.NodeQuery; /** * Class that represents a twig object of the JSON query syntax. */ public class TwigQuery extends AbstractNodeQuery { private boolean hasRoot = false; private String rootBooleanExpression; private final List<QueryClause> clauses; private final KeywordQueryParser parser; public TwigQuery(final ObjectMapper mapper, final KeywordQueryParser parser) { super(mapper); this.parser = parser; clauses = new ArrayList<QueryClause>(); } @Override public TwigQuery setLevel(final int level) { return (TwigQuery) super.setLevel(level); } @Override public TwigQuery setRange(final int lowerBound, final int upperBound) { return (TwigQuery) super.setRange(lowerBound, upperBound); } @Override public TwigQuery setBoost(final float boost) { return (TwigQuery) super.setBoost(boost); } /** * Set the ndoe boolean query expression for the root of the twig. * * @see org.sindice.siren.search.node.TwigQuery#addRoot(NodeQuery) */ void setRoot(final String booleanExpression) { this.rootBooleanExpression = booleanExpression; this.hasRoot = true; } /** * Adds a child clause with a * {@link NodeBooleanClause.Occur#MUST} operator. * <p> * Use this method for child clauses that must appear in the matching twigs. * * @see NodeBooleanClause.Occur#MUST * @see org.sindice.siren.search.node.TwigQuery#addChild(NodeQuery, NodeBooleanClause.Occur) */ public TwigQuery with(final AbstractNodeQuery child) { clauses.add(new BasicQueryClause(child, Occur.MUST)); return this; } /** * Adds a descendant clause with a * {@link NodeBooleanClause.Occur#MUST} operator. * <p> * Use this method for descendant clauses that must appear in the matching * twigs. * * @see NodeBooleanClause.Occur#MUST * @see org.sindice.siren.search.node.TwigQuery#addDescendant(int, NodeQuery, NodeBooleanClause.Occur) */ public TwigQuery with(final AbstractNodeQuery descendant, final int level) { clauses.add(new DescendantQueryClause(descendant, Occur.MUST, level)); return this; } /** * Adds a child clause with a * {@link NodeBooleanClause.Occur#MUST_NOT} operator. * <p> * Use this method for child clauses that must not appear in the matching * twigs. * * @see NodeBooleanClause.Occur#MUST_NOT * @see org.sindice.siren.search.node.TwigQuery#addChild(NodeQuery, NodeBooleanClause.Occur) */ public TwigQuery without(final AbstractNodeQuery child) { clauses.add(new BasicQueryClause(child, Occur.MUST_NOT)); return this; } /** * Adds a descendant clause with a * {@link NodeBooleanClause.Occur#MUST_NOT} operator. * <p> * Use this method for descendant clauses that must not appear in the matching * twigs. * * @see NodeBooleanClause.Occur#MUST_NOT * @see org.sindice.siren.search.node.TwigQuery#addDescendant(int, NodeQuery, NodeBooleanClause.Occur) */ public TwigQuery without(final AbstractNodeQuery descendant, final int level) { clauses.add(new DescendantQueryClause(descendant, Occur.MUST_NOT, level)); return this; } /** * Adds a child clause with a * {@link NodeBooleanClause.Occur#SHOULD} operator. * <p> * Use this method for child clauses that should appear in the matching * twigs. * * @see NodeBooleanClause.Occur#SHOULD * @see org.sindice.siren.search.node.TwigQuery#addChild(NodeQuery, NodeBooleanClause.Occur) */ public TwigQuery optional(final AbstractNodeQuery child) { clauses.add(new BasicQueryClause(child, Occur.SHOULD)); return this; } /** * Adds a descendant clause with a * {@link NodeBooleanClause.Occur#SHOULD} operator. * <p> * Use this method for descendant clauses that should appear in the matching * twigs. * * @see NodeBooleanClause.Occur#SHOULD * @see org.sindice.siren.search.node.TwigQuery#addDescendant(int, NodeQuery, NodeBooleanClause.Occur) */ public TwigQuery optional(final AbstractNodeQuery descendant, final int level) { clauses.add(new DescendantQueryClause(descendant, Occur.SHOULD, level)); return this; } @Override public Query toQuery(final boolean proxy) throws QueryNodeException { final org.sindice.siren.search.node.TwigQuery query = new org.sindice.siren.search.node.TwigQuery(); // parse and add root if (hasRoot) { query.addRoot((NodeQuery) parser.parse(rootBooleanExpression, "")); } // convert child and descendant clauses for (final QueryClause clause : clauses) { if (clause instanceof BasicQueryClause) { query.addChild((NodeQuery) clause.getQuery().toQuery(false),clause.getNodeBooleanOccur()); } else { final int level = ((DescendantQueryClause) clause).getLevel(); query.addDescendant(level, (NodeQuery) clause.getQuery().toQuery(false), clause.getNodeBooleanOccur()); } } // add level if (this.hasLevel()) { query.setLevelConstraint(this.getLevel()); } // add range if (this.hasRange()) { query.setNodeConstraint(this.getLowerBound(), this.getUpperBound()); } // add boost if (this.hasBoost()) { query.setBoost(this.getBoost()); } // should we wrap the query into a lucene proxy if (proxy) { return new LuceneProxyNodeQuery(query); } return query; } @Override ObjectNode toJson() { final ObjectNode obj = mapper.createObjectNode(); final ObjectNode twig = obj.putObject(TwigPropertyParser.TWIG_PROPERTY); if (hasRoot) { twig.put(RootPropertyParser.ROOT_PROPERTY, rootBooleanExpression); } if (this.hasLevel()) { twig.put(LevelPropertyParser.LEVEL_PROPERTY, this.getLevel()); } if (this.hasRange()) { final ArrayNode array = twig.putArray(RangePropertyParser.RANGE_PROPERTY); array.add(this.getLowerBound()); array.add(this.getUpperBound()); } if (this.hasBoost()) { twig.put(BoostPropertyParser.BOOST_PROPERTY, this.getBoost()); } ArrayNode childArray = null; ArrayNode descendantArray = null; for (final QueryClause clause : clauses) { if (clause instanceof BasicQueryClause) { if (!twig.has(ChildPropertyParser.CHILD_PROPERTY)) { // avoid to create an empty array in the JSON childArray = twig.putArray(ChildPropertyParser.CHILD_PROPERTY); } final ObjectNode e = childArray.addObject(); e.put(OccurPropertyParser.OCCUR_PROPERTY, clause.getOccur().toString()); e.putAll(clause.getQuery().toJson()); } else { if (!twig.has(DescendantPropertyParser.DESCENDANT_PROPERTY)) { // avoid to create an empty array in the JSON descendantArray = twig.putArray(DescendantPropertyParser.DESCENDANT_PROPERTY); } final ObjectNode e = descendantArray.addObject(); e.put(OccurPropertyParser.OCCUR_PROPERTY, clause.getOccur().toString()); e.put(LevelPropertyParser.LEVEL_PROPERTY, ((DescendantQueryClause) clause).getLevel()); e.putAll(clause.getQuery().toJson()); } } return obj; } }