/*
* 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 java.io.IOException;
import java.util.List;
import java.util.Map;
import javax.jcr.query.qom.Constraint;
import org.modeshape.common.util.CheckArg;
import org.modeshape.jcr.ExecutionContext;
import org.modeshape.jcr.api.index.IndexDefinition;
import org.modeshape.jcr.index.elasticsearch.client.EsClient;
import org.modeshape.jcr.index.elasticsearch.client.EsRequest;
import org.modeshape.jcr.spi.index.IndexConstraints;
import org.modeshape.jcr.spi.index.provider.ProvidedIndex;
/**
* Index stored in Elasticsearch.
*
* @author kulikov
*/
public class EsIndex implements ProvidedIndex {
private final String name;
private final String workspace;
private final EsIndexColumns columns;
private final Operations operations;
private final EsClient client;
/**
* Creates new index.
*
* @param client provides access to the elasticsearch functions.
* @param context Modeshape execution context.
* @param defn index definition.
* @param workspace workspace name where this index will be created.
*/
public EsIndex(EsClient client, ExecutionContext context, IndexDefinition defn, String workspace) {
this.client = client;
this.name = defn.getName();
this.workspace = workspace;
this.columns = new EsIndexColumns(context, defn);
this.operations = new Operations(context.getValueFactories(), columns);
this.createIndex();
}
/**
* Creates new index.
*
* @param client provides access to the elasticsearch functions.
* @param columns columns definition.
* @param context Modeshape execution context.
* @param name the name of the index
* @param workspace workspace name where this index will be created.
*/
protected EsIndex(EsClient client, EsIndexColumns columns, ExecutionContext context, String name, String workspace) {
this.client = client;
this.name = name;
this.workspace = workspace;
this.columns = columns;
this.operations = new Operations(context.getValueFactories(), columns);
this.createIndex();
}
/**
* Generates local index name.
*
* @return
*/
private String name() {
return name.toLowerCase() + "-" + workspace;
}
/**
* Executes create index action.
*/
private void createIndex() {
try {
client.createIndex(name(), workspace, columns.mappings(workspace));
client.flush(name());
} catch (IOException e) {
throw new EsIndexException(e);
}
}
@Override
public void add(String nodeKey, String propertyName, Object value) {
CheckArg.isNotNull(nodeKey, "nodeKey");
CheckArg.isNotNull(propertyName, "propertyName");
CheckArg.isNotNull(value, "value");
EsIndexColumn column = columns.column(propertyName);
assert column != null : "Unexpected column for the index " + name();
try {
EsRequest doc = findOrCreateDoc(nodeKey);
putValue(doc, column, value);
client.storeDocument(name(), workspace, nodeKey, doc);
} catch (IOException e) {
throw new EsIndexException(e);
}
}
@Override
public void add(String nodeKey, String propertyName, Object[] values) {
CheckArg.isNotNull(nodeKey, "nodeKey");
CheckArg.isNotNull(propertyName, "propertyName");
CheckArg.isNotNull(values, "values");
EsIndexColumn column = columns.column(propertyName);
assert column != null : "Unexpected column for the index " + name();
try {
EsRequest doc = findOrCreateDoc(nodeKey);
putValues(doc, column, values);
client.storeDocument(name(), workspace, nodeKey, doc);
} catch (IOException e) {
throw new EsIndexException(e);
}
}
@Override
public void remove(String nodeKey) {
CheckArg.isNotNull(nodeKey, "nodeKey");
try {
client.deleteDocument(name(), workspace, nodeKey);
} catch (IOException e) {
throw new EsIndexException(e);
}
}
@Override
public void remove(String nodeKey, String propertyName, Object value) {
CheckArg.isNotNull(nodeKey, "nodeKey");
CheckArg.isNotNull(propertyName, "propertyName");
try {
EsRequest doc = find(nodeKey);
if (doc == null) {
return;
}
doc.remove(propertyName);
client.storeDocument(name(), workspace, nodeKey, doc);
} catch (IOException e) {
throw new EsIndexException(e);
}
}
@Override
public void remove(String nodeKey, String propertyName, Object[] values) {
CheckArg.isNotNull(nodeKey, "nodeKey");
CheckArg.isNotNull(propertyName, "propertyName");
try {
EsRequest doc = find(nodeKey);
doc.remove(propertyName);
client.storeDocument(name(), workspace, nodeKey, doc);
} catch (Exception e) {
throw new EsIndexException(e);
}
}
/**
* Searches indexed node's properties by node key.
*
* @param nodeKey node key being indexed.
* @return list of stored properties as json document.
* @throws IOException
*/
private EsRequest find(String nodeKey) throws IOException {
return client.getDocument(name(), workspace, nodeKey);
}
/**
* Searches indexed node's properties by node key or creates new empty list.
*
* @param nodeKey node key being indexed.
* @return list of stored properties as json document or empty document
* if not found.
* @throws IOException
*/
private EsRequest findOrCreateDoc(String nodeKey) throws IOException {
EsRequest doc = client.getDocument(name(), workspace, nodeKey);
return doc != null ? doc : new EsRequest();
}
/**
* Appends specified value for the given column and related pseudo columns
* into list of properties.
*
* @param doc list of properties in json format
* @param column colum definition
* @param value column's value.
*/
private void putValue(EsRequest doc, EsIndexColumn column, Object value) {
Object columnValue = column.columnValue(value);
String stringValue = column.stringValue(value);
doc.put(column.getName(), columnValue);
doc.put(column.getLowerCaseFieldName(), stringValue.toLowerCase());
doc.put(column.getUpperCaseFieldName(), stringValue.toUpperCase());
doc.put(column.getLengthFieldName(), stringValue.length());
}
/**
* Appends specified values for the given column and related pseudo columns
* into list of properties.
*
* @param doc list of properties in json format
* @param column colum definition
* @param value column's value.
*/
private void putValues(EsRequest doc, EsIndexColumn column, Object[] value) {
Object[] columnValue = column.columnValues(value);
int[] ln = new int[columnValue.length];
String[] lc = new String[columnValue.length];
String[] uc = new String[columnValue.length];
for (int i = 0; i < columnValue.length; i++) {
String stringValue = column.stringValue(columnValue[i]);
lc[i] = stringValue.toLowerCase();
uc[i] = stringValue.toUpperCase();
ln[i] = stringValue.length();
}
doc.put(column.getName(), columnValue);
doc.put(column.getLowerCaseFieldName(), lc);
doc.put(column.getUpperCaseFieldName(), uc);
doc.put(column.getLengthFieldName(), ln);
}
@Override
public void commit() {
try {
client.refresh(name());
} catch (IOException e) {
throw new EsIndexException(e);
}
}
@Override
public String getName() {
return name;
}
@Override
public Results filter(IndexConstraints constraints, long cardinalityEstimate) {
EsRequest query = operations.createQuery(constraints.getConstraints(), constraints.getVariables());
return new SearchResults(client, name(), workspace, query);
}
@Override
public long estimateCardinality(List<Constraint> constraints, Map<String, Object> variables) {
EsRequest query = operations.createQuery(constraints, variables);
return new SearchResults(client, name(), workspace, query).getCardinality();
}
@Override
public long estimateTotalCount() {
try {
return client.count(name(), workspace);
} catch (IOException e) {
throw new EsIndexException(e);
}
}
@Override
public boolean requiresReindexing() {
return true;
}
@Override
public void clearAllData() {
try {
client.deleteAll(name(), workspace);
} catch (IOException e) {
throw new EsIndexException(e);
}
}
@Override
public void shutdown(boolean destroyed) {
if (destroyed) {
try {
client.deleteIndex(name());
} catch (Exception e) {
throw new EsIndexException(e);
}
}
}
}