1 /*******************************************************************************
\r
2 * Copyright (c) 2014, 2015 Association for Decentralized Information Management
\r
3 * in Industry THTH ry.
\r
4 * All rights reserved. This program and the accompanying materials
\r
5 * are made available under the terms of the Eclipse Public License v1.0
\r
6 * which accompanies this distribution, and is available at
\r
7 * http://www.eclipse.org/legal/epl-v10.html
\r
10 * Semantum Oy - initial API and implementation
\r
11 * Semantum Oy - improvements for Simantics issue #6053
\r
12 *******************************************************************************/
\r
13 package org.simantics.db.indexing;
\r
15 import java.io.Reader;
\r
16 import java.util.HashMap;
\r
17 import java.util.Map;
\r
18 import java.util.concurrent.atomic.AtomicReference;
\r
20 import org.apache.lucene.analysis.Analyzer;
\r
21 import org.apache.lucene.analysis.Tokenizer;
\r
22 import org.apache.lucene.analysis.core.KeywordAnalyzer;
\r
23 import org.apache.lucene.analysis.core.WhitespaceAnalyzer;
\r
24 import org.apache.lucene.analysis.miscellaneous.PerFieldAnalyzerWrapper;
\r
25 import org.apache.lucene.analysis.util.CharTokenizer;
\r
26 import org.apache.lucene.queryparser.classic.ParseException;
\r
27 import org.apache.lucene.queryparser.classic.QueryParser;
\r
28 import org.apache.lucene.search.NumericRangeQuery;
\r
29 import org.apache.lucene.search.Query;
\r
30 import org.apache.lucene.util.AttributeFactory;
\r
31 import org.apache.lucene.util.Version;
\r
32 import org.simantics.databoard.util.ObjectUtils;
\r
33 import org.simantics.utils.datastructures.Pair;
\r
35 public class Queries {
\r
37 static final class LowerCaseWhitespaceTokenizer extends CharTokenizer {
\r
39 * Construct a new WhitespaceTokenizer. * @param matchVersion Lucene version
\r
40 * to match See {@link <a href="#version">above</a>}
\r
43 * the input to split up into tokens
\r
45 public LowerCaseWhitespaceTokenizer(Version matchVersion, Reader in) {
\r
46 super(matchVersion, in);
\r
50 * Construct a new WhitespaceTokenizer using a given
\r
51 * {@link org.apache.lucene.util.AttributeSource.AttributeFactory}.
\r
54 * matchVersion Lucene version to match See
\r
55 * {@link <a href="#version">above</a>}
\r
57 * the attribute factory to use for this {@link Tokenizer}
\r
59 * the input to split up into tokens
\r
61 public LowerCaseWhitespaceTokenizer(Version matchVersion, AttributeFactory factory, Reader in) {
\r
62 super(matchVersion, factory, in);
\r
66 protected int normalize(int c) {
\r
67 return Character.toLowerCase(c);
\r
70 protected boolean isTokenChar(int c) {
\r
71 return !Character.isWhitespace(c);
\r
75 static final class LowerCaseWhitespaceAnalyzer extends Analyzer {
\r
77 private final Version matchVersion;
\r
80 * Creates a new {@link WhitespaceAnalyzer}
\r
81 * @param matchVersion Lucene version to match See {@link <a href="#version">above</a>}
\r
83 public LowerCaseWhitespaceAnalyzer(Version matchVersion) {
\r
84 this.matchVersion = matchVersion;
\r
88 protected TokenStreamComponents createComponents(final String fieldName, final Reader reader) {
\r
89 return new TokenStreamComponents(new LowerCaseWhitespaceTokenizer(matchVersion, reader));
\r
93 private static AtomicReference<Pair<Query, String>> queryCache = new AtomicReference<>();
\r
95 final static PerFieldAnalyzerWrapper analyzer = createAnalyzer();
\r
97 static PerFieldAnalyzerWrapper createAnalyzer() {
\r
99 Map<String,Analyzer> analyzerPerField = new HashMap<>();
\r
100 analyzerPerField.put("Model", new KeywordAnalyzer());
\r
101 analyzerPerField.put("Parent", new KeywordAnalyzer());
\r
102 analyzerPerField.put("Resource", new KeywordAnalyzer());
\r
103 analyzerPerField.put("GUID", new KeywordAnalyzer());
\r
105 PerFieldAnalyzerWrapper analyzer = new PerFieldAnalyzerWrapper(new LowerCaseWhitespaceAnalyzer(Version.LUCENE_4_9), analyzerPerField);
\r
110 static PerFieldAnalyzerWrapper getAnalyzer() {
\r
114 static Query parse(String search, IndexSchema schema) throws ParseException {
\r
115 Pair<Query, String> cachedQuery = queryCache.get();
\r
116 if (cachedQuery != null && search.equals(cachedQuery.second))
\r
117 return cachedQuery.first;
\r
119 //System.err.println("parse " + search + " (cached=" + (cachedQuery != null ? cachedQuery.second : "null") + ")" );
\r
120 CustomQueryParser parser = new CustomQueryParser(Version.LUCENE_4_9, "Name", getAnalyzer(), schema);
\r
121 Query query = parser.parse(search);
\r
123 queryCache.set(Pair.make(query, search));
\r
128 public static class CustomQueryParser extends QueryParser {
\r
130 protected final IndexSchema schema;
\r
132 public CustomQueryParser(Version version, String field, Analyzer analyzer, IndexSchema schema) {
\r
133 super(version, field, analyzer);
\r
134 this.schema = schema;
\r
135 setAllowLeadingWildcard(true);
\r
139 protected Query getRangeQuery(
\r
143 boolean startInclusive,
\r
144 boolean endInclusive) throws ParseException
\r
146 IndexSchema.Type type = schema.typeMap.get(field);
\r
147 if (IndexSchema.NUMERIC_TYPES.contains(type)) {
\r
148 boolean equalParts = ObjectUtils.objectEquals(part1, part2);
\r
152 Integer min = part1 != null ? ( Integer.valueOf(part1)) : null;
\r
153 Integer max = part2 != null ? (equalParts ? min : Integer.valueOf(part2)) : null;
\r
154 return NumericRangeQuery.newIntRange(field, min, max, startInclusive, endInclusive);
\r
157 Long min = part1 != null ? ( Long.valueOf(part1)) : null;
\r
158 Long max = part2 != null ? (equalParts ? min : Long.valueOf(part2)) : null;
\r
159 return NumericRangeQuery.newLongRange(field, min, max, startInclusive, endInclusive);
\r
162 Float min = part1 != null ? ( Float.valueOf(part1)) : null;
\r
163 Float max = part2 != null ? (equalParts ? min : Float.valueOf(part2)) : null;
\r
164 return NumericRangeQuery.newFloatRange(field, min, max, startInclusive, endInclusive);
\r
167 Double min = part1 != null ? ( Double.valueOf(part1)) : null;
\r
168 Double max = part2 != null ? (equalParts ? min : Double.valueOf(part2)) : null;
\r
169 return NumericRangeQuery.newDoubleRange(field, min, max, startInclusive, endInclusive);
\r
172 throw new ParseException("Unrecognized numeric field type '" + type + "' for field '" + field + "'");
\r
174 } catch (NumberFormatException e) {
\r
175 throw new ParseException(e.getMessage());
\r
178 return super.getRangeQuery(field, part1, part2, startInclusive, endInclusive);
\r