/*
* ModeShape (http://www.modeshape.org)
*
* 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.modeshape.jcr.index.elasticsearch;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import javax.jcr.PropertyType;
import javax.jcr.query.qom.Constraint;
import javax.jcr.query.qom.JoinCondition;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.node.Node;
import org.elasticsearch.node.NodeBuilder;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.modeshape.common.util.FileUtil;
import org.modeshape.jcr.ExecutionContext;
import org.modeshape.jcr.api.query.qom.Operator;
import org.modeshape.jcr.cache.NodeKey;
import org.modeshape.jcr.index.elasticsearch.client.EsClient;
import org.modeshape.jcr.query.model.And;
import org.modeshape.jcr.query.model.Between;
import org.modeshape.jcr.query.model.Comparison;
import org.modeshape.jcr.query.model.FullTextSearch;
import org.modeshape.jcr.query.model.Length;
import org.modeshape.jcr.query.model.Literal;
import org.modeshape.jcr.query.model.LowerCase;
import org.modeshape.jcr.query.model.Not;
import org.modeshape.jcr.query.model.Or;
import org.modeshape.jcr.query.model.PropertyExistence;
import org.modeshape.jcr.query.model.PropertyValue;
import org.modeshape.jcr.query.model.SelectorName;
import org.modeshape.jcr.query.model.SetCriteria;
import org.modeshape.jcr.query.model.UpperCase;
import org.modeshape.jcr.spi.index.IndexConstraints;
import org.modeshape.jcr.spi.index.provider.Filter;
import org.modeshape.jcr.spi.index.provider.Filter.Results;
import org.modeshape.jcr.value.Name;
import org.modeshape.jcr.value.ValueFactories;
/**
*
* @author kulikov
*/
public class EsIndexTest {
private final static ExecutionContext context = new ExecutionContext();
private final static EsIndexColumn def1 = new EsIndexColumn(context, "field1", PropertyType.STRING);
private final static EsIndexColumn def2 = new EsIndexColumn(context, "field2", PropertyType.DECIMAL);
private final static EsIndexColumn def3 = new EsIndexColumn(context, "field3", PropertyType.STRING);
private final static EsIndexColumn def4 = new EsIndexColumn(context, "mixinTypes", PropertyType.NAME);
private final static EsIndexColumn def5 = new EsIndexColumn(context, "myfield", PropertyType.STRING);
private static Node esNode;
private static EsClient client;
private static EsIndex index;
public EsIndexTest() {
}
@BeforeClass
public static void setUpClass() throws Exception {
FileUtil.delete("target/data");
EsIndexColumns cols = new EsIndexColumns(def1, def2, def3, def4, def5);
esNode = NodeBuilder.nodeBuilder().local(false)
.settings(Settings.settingsBuilder().put("path.home", "target/data"))
.node();
client = new EsClient("localhost", 9200);
index = new EsIndex(client, cols, context, "test", "workspace");
initialIndex();
Thread.currentThread().sleep(1000);
}
@AfterClass
public static void tearDownClass() {
index.shutdown(true);
esNode.close();
FileUtil.delete("target/data");
}
private static void initialIndex() {
index.add(key("key1"), def1.getName(), "node1 - value1");
index.add(key("key1"), def2.getName(), 1);
index.add(key("key1"), def3.getName(), new String[]{"a", "b", "c"});
index.add(key("key2"), def1.getName(), "node2 - value2");
index.add(key("key2"), def2.getName(), 2);
index.add(key("key2"), def3.getName(), new String[]{"d", "e", "f"});
index.add(key("key3"), def1.getName(), "node3 - value3");
index.add(key("key3"), def2.getName(), 3);
index.add(key("key3"), def3.getName(), new String[]{"h", "i", "g"});
index.add(key("key4"), def1.getName(), "node4 - value4");
index.add(key("key4"), def2.getName(), 4);
index.add(key("key4"), def3.getName(), new String[]{"j", "k", "l"});
index.add(key("key5"), def1.getName(), "node5 - value5");
index.add(key("key5"), def2.getName(), 5);
index.add(key("key5"), def3.getName(), new String[]{"m", "n", "o"});
index.add(key("key6"), def1.getName(), "the quick Brown fox jumps over to the dog in at the gate");
index.add(key("key6"), def2.getName(), 55);
index.add(key("key6"), def3.getName(), new String[]{"m", "n", "o"});
index.add(key("key7"), def5.getName(), "asd-sdf-dfg");
index.commit();
}
/**
* Test of reindexNode method, of class EsIndexImpl.
*/
@Test
public void shouldSupportBetweenConstraint() {
//range including bounds
Between c1 = new Between(propertyValue(def2), new Literal(2), new Literal(4));
validate(c1, "key2", "key3", "key4");
//range excluding bounds
Between c2 = new Between(propertyValue(def2), new Literal(2), new Literal(4), false, false);
validate(c2, "key3");
}
@Test
public void shouldSupportComparisonConstraint() {
Comparison gt = new Comparison(propertyValue(def2), Operator.GREATER_THAN, new Literal(2));
validate(gt, "key3", "key4", "key5", "key6");
Comparison gte = new Comparison(propertyValue(def2), Operator.GREATER_THAN_OR_EQUAL_TO, new Literal(2));
validate(gte, "key2", "key3", "key4", "key5", "key6");
Comparison eq = new Comparison(propertyValue(def2), Operator.EQUAL_TO, new Literal(3));
validate(eq, "key3");
Comparison lt = new Comparison(propertyValue(def2), Operator.LESS_THAN, new Literal(2));
validate(lt, "key1");
Comparison lte = new Comparison(propertyValue(def2), Operator.LESS_THAN_OR_EQUAL_TO, new Literal(2));
validate(lte, "key1", "key2");
Comparison ne = new Comparison(propertyValue(def2), Operator.NOT_EQUAL_TO, new Literal(2));
validate(ne, "key1", "key3", "key4", "key5", "key6", "key7");
Comparison like = new Comparison(propertyValue(def2), Operator.LIKE, new Literal("value3"));
validate(like, "key3");
}
@Test
public void shouldSupportSetConstraint() {
SetCriteria sc = new SetCriteria(propertyValue(def3), new Literal("a"));
validate(sc, "key1");
SetCriteria sc1 = new SetCriteria(propertyValue(def2), new Literal(3), new Literal(4));
validate(sc1, "key3", "key4");
}
@Test
public void shouldSupportFullTextSearchConstraint() {
validate(fullTextSearch("node1 - value1"), "key1");
validate(fullTextSearch("the quick Brown fox jumps over to the dog in at the gate"), "key6");
validate(fullTextSearch("the quick Dog"), "key6");
}
@Test
public void shouldSupportPropertyExistanceConstraint() {
PropertyExistence pe = new PropertyExistence(new SelectorName("test"), def2.getName());
validate(pe, "key1", "key2", "key3", "key4", "key5", "key6");
}
@Test
public void testOrConstraint() {
Comparison eq1 = new Comparison(propertyValue(def2), Operator.EQUAL_TO, new Literal(3));
Comparison eq2 = new Comparison(propertyValue(def2), Operator.EQUAL_TO, new Literal(2));
validate(new Or(eq1, eq2), "key2", "key3");
}
@Test
public void testAndConstraint() {
Comparison gt = new Comparison(propertyValue(def2), Operator.GREATER_THAN, new Literal(2));
Comparison lt = new Comparison(propertyValue(def2), Operator.LESS_THAN, new Literal(4));
validate(new And(gt, lt), "key3");
}
@Test
public void testNotConstraint() {
Comparison ne = new Comparison(propertyValue(def2), Operator.NOT_EQUAL_TO, new Literal(3));
validate(new Not(ne), "key3");
}
@Test
public void shouldSupportMultipleValues() {
SetCriteria sc = new SetCriteria(propertyValue(def3),
new Literal[]{new Literal("a"), new Literal("e")});
validate(sc, "key1", "key2");
}
@Test
public void shouldSupportLowerCase() {
LowerCase lowerCase = new LowerCase(propertyValue(def1));
Comparison eq = new Comparison(lowerCase, Operator.EQUAL_TO, new Literal("value3"));
validate(eq, "key3");
}
@Test
public void shouldSupportUpperCase() {
UpperCase upperCase = new UpperCase(propertyValue(def1));
Comparison eq = new Comparison(upperCase, Operator.EQUAL_TO, new Literal("VALUE3"));
validate(eq, "key3");
}
@Test
public void shouldSupportLength() {
Length len = new Length(propertyValue(def2));
Comparison eq = new Comparison(len, Operator.EQUAL_TO, new Literal(1));
validate(eq, "key1", "key2", "key3", "key4", "key5");
}
@Test
public void shouldNotTokenizeOnIntrawordDelimeter() {
SetCriteria sc = new SetCriteria(propertyValue(def5), new Literal("asd-sdf-dfg"));
validate(sc, "key7");
}
@Test
public void shouldConvertValuesToCoreTypes() {
ExecutionContext ctx = new ExecutionContext();
Name n1 = ctx.getValueFactories().getNameFactory().create("mix:title");
Name n2 = ctx.getValueFactories().getNameFactory().create("mix:titl");
index.add(key("key8"), "mixinTypes", new Object[]{n1});
index.commit();
SetCriteria sc = new SetCriteria(propertyValue(def4), new Literal(n1), new Literal(n2));
validate(sc, "key8");
}
private void validate(Constraint constraint, String... keys) {
Results results = index.filter(constraints(constraint), keys.length);
Filter.ResultBatch batch = results.getNextBatch(100);
assertTrue(checkResults(batch.keys(), keys));
}
private PropertyValue propertyValue(EsIndexColumn column) {
return new PropertyValue(new SelectorName("test"), column.getName());
}
private FullTextSearch fullTextSearch(String query) {
return new FullTextSearch(new SelectorName("test"), query);
}
protected IndexConstraints constraints(final Constraint constraint) {
return new IndexConstraints() {
@Override
public Collection<Constraint> getConstraints() {
return Collections.singletonList(constraint);
}
@Override
public Map<String, Object> getParameters() {
return Collections.emptyMap();
}
@Override
public ValueFactories getValueFactories() {
return context.getValueFactories();
}
@Override
public Map<String, Object> getVariables() {
return Collections.emptyMap();
}
@Override
public boolean hasConstraints() {
return true;
}
@Override
public Collection<JoinCondition> getJoinConditions() {
return Collections.emptyList();
}
};
}
private boolean checkResults(Iterable<NodeKey> res, String... expected) {
int count = 0;
for (NodeKey k : res) {
++count;
for (int i = 0; i < expected.length; i++) {
String s = key(expected[i]);
if (s.equals(k.toString())) {
return true;
}
}
}
assertEquals(expected.length, count);
return false;
}
private static final String NODE_KEY_PREFIX = "12345671234567-";
protected static String key(String value) {
return NODE_KEY_PREFIX + value;
}
}