package org.aplikator.server.persistence.search;
import static org.elasticsearch.node.NodeBuilder.nodeBuilder;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.aplikator.client.shared.data.PrimaryKey;
import org.aplikator.client.shared.data.RecordDTO;
import org.aplikator.client.shared.data.SearchResult;
import org.aplikator.client.shared.descriptor.EntityDTO;
import org.aplikator.server.Configurator;
import org.aplikator.server.DescriptorRegistry;
import org.aplikator.server.data.Context;
import org.aplikator.server.data.PersisterTriggers;
import org.aplikator.server.data.Record;
import org.aplikator.server.descriptor.Entity;
import org.aplikator.server.descriptor.Property;
import org.aplikator.server.descriptor.View;
import org.aplikator.server.persistence.Persister;
import org.aplikator.server.persistence.PersisterFactory;
import org.elasticsearch.action.ActionFuture;
import org.elasticsearch.action.admin.indices.create.CreateIndexRequestBuilder;
import org.elasticsearch.action.admin.indices.create.CreateIndexResponse;
import org.elasticsearch.action.admin.indices.exists.indices.IndicesExistsRequest;
import org.elasticsearch.action.admin.indices.exists.indices.IndicesExistsResponse;
import org.elasticsearch.action.get.GetAction;
import org.elasticsearch.action.get.GetRequestBuilder;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchRequestBuilder;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.Client;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.node.Node;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.jboss.errai.marshalling.client.Marshalling;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigValue;
/**
* @author eskymo
* <p/>
* ElasticSearch implements aplikators interface Search - it's an
* implementation for ElasticSearch engine
*/
public class ElasticSearch implements Search {
private static final Logger LOG = Logger.getLogger(ElasticSearch.class.getName());
private static final String SEARCH_INDEX = "index";
private Client client;
private Node server;
private Persister persister;
private String indexName;
/**
* Constructor gets the index and search engine parameters and establishes
* the connection to the search engine
*/
protected ElasticSearch() {
Config config = Configurator.get().getConfig();
Config esConfig = config.getConfig(config.getString(SEARCH_ENGINE));
Map<String, String> configurationMap = new HashMap<String, String>();
for (Map.Entry<String, ConfigValue> entry : esConfig.entrySet()) {
configurationMap.put(entry.getKey(), entry.getValue().unwrapped().toString());
LOG.info("Adding elastic search configuration property:" + entry.getKey() + ":" + entry.getValue().unwrapped().toString());
}
final Settings.Builder builder = Settings.settingsBuilder().put(configurationMap);
server = nodeBuilder().settings(builder).node();
server.start();
this.indexName = esConfig.getString(SEARCH_INDEX);
this.client = server.client();
client.admin().cluster().prepareHealth().setWaitForYellowStatus().setTimeout(TimeValue.timeValueMinutes(1)).execute().actionGet();
this.persister = PersisterFactory.getPersister();
// check if index exists - if not, then create one
ActionFuture<IndicesExistsResponse> indexExists = client.admin().indices().exists(new IndicesExistsRequest(indexName));
if (!indexExists.actionGet().isExists()) {
CreateIndexRequestBuilder indexBuilder = client.admin().indices().prepareCreate(indexName);
CreateIndexResponse response = indexBuilder.execute().actionGet();
LOG.fine("Create index acknowledged:" + response.isAcknowledged());
}
}
@SuppressWarnings("rawtypes")
private RecordDTO getReferencedRecord(RecordDTO rec, Property p) {
RecordDTO refRecord;
if (p.getRefferedThrough() != null) {
refRecord = getReferencedRecord(rec, p.getRefferedThrough());
if (refRecord != null && refRecord.getValue(p.getId()) instanceof RecordDTO) {
refRecord = (RecordDTO) refRecord.getValue(p.getId());
}
} else {
refRecord = (RecordDTO) rec.getValue(p.getId());
}
return refRecord;
}
/**
* Generates a Record from class CompleteRecord using the localization and
* triggers provided from context
*
* @param view
* @param completeRecord
* @param ctx
* @return
*/
@SuppressWarnings({"rawtypes", "unchecked"})
private RecordDTO generateRecord(View view, RecordDTO completeRecord, Context ctx) {
PersisterTriggers trigger = view.getPersisterTriggers();
// Creates new Record instance from PrimaryKey
RecordDTO rec = new RecordDTO(new PrimaryKey(completeRecord.getPrimaryKey().getEntityId(), completeRecord.getPrimaryKey().getId()));
// insert values into the Record based on View setup
for (int i = 0; i < view.getProperties().size(); i++) {
Property p = view.getProperties().get(i);
// if a property is a Reference then the value must be get from
// referencedRecord list from CompleteRecord instance
if (p.getRefferedThrough() != null) {
// Record refRecord = (Record) completeRecord.getValue(p
// .getRefferedThrough().getId());
RecordDTO refRecord = getReferencedRecord(completeRecord, p);
// Record refRecord = (Record) completeRecord.getValue(p
// .getRefferedThrough().getId());
if (refRecord != null) {
p.setValue(rec, (Serializable) refRecord.getValue(p.getId()));
} else {
p.setValue(rec, null);
}
} else {
p.setValue(rec, (Serializable) completeRecord.getValue(p.getId()));
}
}
if (trigger != null) {
trigger.onLoad(new Record(rec), view, ctx);
}
rec.resetDirtyFlags();
return rec;
}
/*
* (non-Javadoc)
*
* @see
* org.aplikator.server.persistence.search.Search#getPagefromSearch(org.
* aplikator.client.shared.descriptor.ViewDTO, java.lang.String, int, int,
* org.aplikator.server.data.Context)
*/
@Override
public SearchResult getPagefromSearch(String vdId, String searchArgument, int pageOffset, int pageSize, Context ctx) {
if (vdId == null) {
return getPagefromSearch(searchArgument, pageOffset, pageSize, ctx);
}
View view = (View) DescriptorRegistry.get().getDescriptionItem(vdId);
// if view is not supplied, call non-typed search accross all entities
SearchResult completeRecords = search(searchArgument, view.getEntity().getId(), pageOffset, pageSize);
List<RecordDTO> retval = new ArrayList<RecordDTO>();
try {
int recCounter = 0;
for (RecordDTO completeRecord : completeRecords.getRecords()) {
RecordDTO rec = generateRecord(view, completeRecord, ctx);
retval.add(rec);
recCounter++;
if (pageSize > 0 && recCounter >= pageSize) {
break;
}
}
} catch (Throwable th) {
LOG.log(Level.SEVERE, "Error in GETPAGEfromSearch", th);
}
return new SearchResult(retval, completeRecords.getCount());
}
@Override
public void index(Record record) {
String type = record.getPrimaryKey().getEntityId();
IndexRequest indexRequest = new IndexRequest(indexName, type);
String json = Marshalling.toJSON(record.getRecordDTO());
indexRequest.source(json);
IndexResponse indexResponse = client.index(indexRequest).actionGet();
LOG.fine("Indexed into " + indexResponse.getId());
}
@Override
public void index(EntityDTO entityDTO) {
/* Entity entity = (Entity) DescriptorRegistry.get().getDescriptionItem(entityDTO.getId());
Persister persisterIndex = PersisterFactory.getPersister();
Transaction tx = persisterIndex.beginTransaction();
try {
try {
client.prepareDeleteByQuery(indexName).setQuery(QueryBuilders.matchAllQuery()).setTypes(entityDTO.getId()).execute().get();
} catch (Exception e) {
LOG.severe("Problem in delete type:" + e.getMessage());
throw new IllegalStateException(e);
} //TODO: delete full index first?
List<PrimaryKey> PKs = persisterIndex.getEntityPKs(entity, null, null, tx);
for (PrimaryKey primaryKey : PKs) {
Record record = persisterIndex.getCompleteRecord(primaryKey, entity.getIndexTraverseLevel(), entity.isIndexIncludeCollections(), tx);
index(record);
}
} finally {
persisterIndex.commitTransaction(tx);
persisterIndex.close(tx);
}*/ //TODO call from APlikatorService instead of ApplikatorLoaderServlet, replace transaction with context
}
@Override
public SearchResult search(String searchArgument, String type, int offset, int size) {
List<RecordDTO> records = new ArrayList<RecordDTO>();
SearchResponse response = null;
SearchRequestBuilder searchBuilder = client.prepareSearch(indexName).setQuery(QueryBuilders.queryStringQuery(searchArgument));
if (type != null) {
searchBuilder.setTypes(type);
}
if (offset > 0) {
searchBuilder.setFrom(offset);
}
if (size > 0) {
searchBuilder.setSize(size);
}
try {
response = searchBuilder.execute().get();
} catch (Exception e) {
LOG.severe("Problem in search:" + e.getMessage());
throw new IllegalStateException(e);
}
SearchHits hits = response.getHits();
Iterator<SearchHit> hitIterator = hits.iterator();
while (hitIterator.hasNext()) {
SearchHit hit = hitIterator.next();
RecordDTO record = (RecordDTO) Marshalling.fromJSON(hit.getSourceAsString(), RecordDTO.class);
records.add(record);
}
return new SearchResult(records, hits.totalHits());
}
private List<String> searchIDs(String searchArgument) {
List<String> IDs = new ArrayList<String>();
SearchResponse response = null;
QueryBuilder builder = QueryBuilders.simpleQueryStringQuery("\"" + searchArgument + "\"").field("*.primaryKey").field("primaryKey");
SearchRequestBuilder searchBuilder = client.prepareSearch(indexName).setQuery(builder);
try {
response = searchBuilder.execute().get();
} catch (Exception e) {
LOG.severe("Problem in search:" + e.getMessage());
throw new IllegalStateException(e);
}
SearchHits hits = response.getHits();
Iterator<SearchHit> hitIterator = hits.iterator();
while (hitIterator.hasNext()) {
SearchHit hit = hitIterator.next();
IDs.add(hit.getId());
}
return IDs;
}
@Override
public SearchResult getPagefromSearch(String searchArgument, int pageOffset, int pageSize, Context ctx) {
ArrayList<RecordDTO> records = new ArrayList<RecordDTO>();
SearchResult completeRecords = search(searchArgument, pageOffset, pageSize);
for (RecordDTO completeRecord : completeRecords.getRecords()) {
// TODO - missing info from entity to create record preview
Entity entity = (Entity) DescriptorRegistry.get().getDescriptionItem(completeRecord.getPrimaryKey().getEntityId());
View view = entity.view();
RecordDTO rec = generateRecord(view, completeRecord, ctx);
records.add(rec);
}
return new SearchResult(records, completeRecords.getCount());
}
@Override
public SearchResult search(String searchArgument, int offset, int size) {
return search(searchArgument, null, offset, size);
}
@Override
public void update(PrimaryKey primaryKey, Context ctx) {
Entity entity = (Entity) DescriptorRegistry.get().getDescriptionItem(primaryKey.getEntityId());
String primaryKeyString = primaryKey.getSerializationString();
Set<PrimaryKey> processedPKs = new HashSet<PrimaryKey>();
List<String> listIDs = searchIDs(primaryKeyString);
if (listIDs.size() == 0) {
Record newRecord = persister.getCompleteRecord(primaryKey, entity.getIndexTraverseLevel(), entity.isIndexIncludeCollections(), ctx);
index(newRecord);
return;
}
for (String updatedID : listIDs) {
GetRequestBuilder getRequest = new GetRequestBuilder(client, GetAction.INSTANCE, indexName);
getRequest.setId(updatedID);
GetResponse getResponse = client.get(getRequest.request()).actionGet();
RecordDTO recordFound = (RecordDTO) Marshalling.fromJSON(getResponse.getSourceAsString(), RecordDTO.class);
if (recordFound == null) {//TODO find out when this happens (nested collections?)
continue;
}
client.prepareDelete(indexName, recordFound.getPrimaryKey().getEntityId(), updatedID).execute().actionGet();
if (processedPKs.contains(recordFound.getPrimaryKey())) {
// just in case record is indexed twice by mistake
continue;
}
entity = (Entity) DescriptorRegistry.get().getDescriptionItem(recordFound.getPrimaryKey().getEntityId());
Record newRecord = persister.getCompleteRecord(recordFound.getPrimaryKey(), entity.getIndexTraverseLevel(), entity.isIndexIncludeCollections(), ctx);
index(newRecord);
processedPKs.add(recordFound.getPrimaryKey());
}
}
@Override
public void insert(PrimaryKey primaryKey, Context ctx) {
Entity entity = (Entity) DescriptorRegistry.get().getDescriptionItem(primaryKey.getEntityId());
Record newRecord = persister.getCompleteRecord(primaryKey, entity.getIndexTraverseLevel(), entity.isIndexIncludeCollections(), ctx);
index(newRecord);
}
@Override
public void delete(PrimaryKey primaryKey) {
String primaryKeyString = primaryKey.getSerializationString();
List<String> listIDs = searchIDs(primaryKeyString);
for (String updatedID : listIDs) {
GetRequestBuilder getRequest = new GetRequestBuilder(client, GetAction.INSTANCE, indexName);
getRequest.setId(updatedID);
client.prepareDelete(indexName, primaryKey.getEntityId(), updatedID).execute().actionGet();
}
}
@Override
public void finish() {
}
}