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() { } }