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