/* * 2012-3 Red Hat Inc. and/or its affiliates and other contributors. * * 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.overlord.rtgov.activity.store.elasticsearch; import org.elasticsearch.action.admin.indices.refresh.RefreshRequestBuilder; import org.elasticsearch.action.bulk.BulkRequestBuilder; import org.elasticsearch.action.bulk.BulkResponse; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.action.search.SearchType; import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.index.query.BoolQueryBuilder; import org.elasticsearch.index.query.QueryBuilder; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.search.SearchHit; import org.overlord.commons.services.ServiceClose; import org.overlord.commons.services.ServiceInit; import org.overlord.rtgov.activity.model.ActivityType; import org.overlord.rtgov.activity.model.ActivityUnit; import org.overlord.rtgov.activity.model.Context; import org.overlord.rtgov.activity.server.ActivityStore; import org.overlord.rtgov.activity.server.QuerySpec; import org.overlord.rtgov.activity.util.ActivityUtil; import org.overlord.rtgov.common.elasticsearch.ElasticsearchClient; import org.overlord.rtgov.common.util.RTGovProperties; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; /** * This class provides the Elasticsearch implementation of the activityStore * CRUD operations are provided by ElasticSearchKeyValueStore. * User: imk@redhat.com * Date: 20/04/14 * Time: 23:32 */ @SuppressWarnings("deprecation") public class ElasticsearchActivityStore implements ActivityStore { private static final Logger LOG = Logger.getLogger(ElasticsearchActivityStore.class.getName()); private static String ACTIVITYSTORE_UNIT_INDEX = "ActivityStore.Elasticsearch.index"; private static String ACTIVITYSTORE_UNIT_TYPE = "ActivityStore.Elasticsearch.type"; private static String ACTIVITYSTORE_RESPONSE_SIZE = "ActivityStore.Elasticsearch.responseSize"; private static String ACTIVITYSTORE_TIMEOUT = "ActivityStore.Elasticsearch.timeout"; private static int DEFAULT_RESPONSE_SIZE = 100000; private static long DEFAULT_TIMEOUT = 10000L; private int _responseSize; private long _timeout; private ElasticsearchClient _client=new ElasticsearchClient(); /** * The default constructor. */ public ElasticsearchActivityStore() { } /** * Initialize the situation store. */ @ServiceInit public void init() { _client.setIndex(RTGovProperties.getProperty(ACTIVITYSTORE_UNIT_INDEX, "rtgov")); _client.setType(RTGovProperties.getProperty(ACTIVITYSTORE_UNIT_TYPE, "activity")); _responseSize = RTGovProperties.getPropertyAsInteger(ACTIVITYSTORE_RESPONSE_SIZE, DEFAULT_RESPONSE_SIZE); _timeout = RTGovProperties.getPropertyAsLong(ACTIVITYSTORE_TIMEOUT, DEFAULT_TIMEOUT); try { _client.init(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } /** * This method persists the activity unit in the Elasticsearch repository. * * @param id The id * @param activityUnit The activity unit * @throws Exception Failed to persist */ protected void persist(BulkRequestBuilder localBulkRequestBuilder, String id, ActivityUnit activityUnit) throws Exception { List<ActivityType> activityTypes = activityUnit.getActivityTypes(); // Temporarily clear the list of activities, while the activity unit part is stored activityUnit.setActivityTypes(Collections.<ActivityType>emptyList()); localBulkRequestBuilder.add(_client.getElasticsearchClient().prepareIndex(_client.getIndex(), _client.getType(), id).setSource(ElasticsearchClient.convertTypeToJson(activityUnit))); activityUnit.setActivityTypes(activityTypes); // Persist activity types for (int i = 0; i < activityTypes.size(); i++) { ActivityType activityType = activityTypes.get(i); localBulkRequestBuilder.add(_client.getElasticsearchClient().prepareIndex(_client.getIndex(), _client.getType() + "type", id + "-" + i).setParent(id).setSource( ElasticsearchClient.convertTypeToJson(activityType))); } } /** * @param activities The list of activity events to store * @throws Exception if any activities cannot be stored */ public void store(List<ActivityUnit> activities) throws Exception { if (LOG.isLoggable(Level.FINEST)) { LOG.finest("Store=" + new String(ActivityUtil.serializeActivityUnitList(activities))); } BulkRequestBuilder localBulkRequestBuilder = _client.getElasticsearchClient().prepareBulk(); for (int i=0; i < activities.size(); i++) { ActivityUnit activityUnit=activities.get(i); persist(localBulkRequestBuilder, activityUnit.getId(), activityUnit); } BulkResponse bulkItemResponses = localBulkRequestBuilder.execute().actionGet(); if (bulkItemResponses.hasFailures()) { LOG.severe(" Bulk Documents{" + activities.size() + "} could not be created for index [" + _client.getIndex() + "/" + _client.getType() + "/"); if (LOG.isLoggable(Level.FINEST)) { LOG.finest("FAILED MESSAGES. " + bulkItemResponses.buildFailureMessage()); } throw new Exception(" Bulk Documents{" + activities.size() + "} could not be created for index [" + _client.getIndex() + "/" + _client.getType() + "/ \n " + bulkItemResponses.buildFailureMessage()); } else { if (LOG.isLoggable(Level.FINEST)) { LOG.finest("Success storing " + activities.size() + " items to [" + _client.getIndex() + "/" + _client.getType() + "]"); } } } /** * @param id The activity unit id * @return ActivityUnit as activity unit or null. * @throws Exception when activity unit cannot be got from elastic search. */ public ActivityUnit getActivityUnit(String id) throws Exception { if (LOG.isLoggable(Level.FINEST)) { LOG.finest("Get activity unit for id["+id+"]"); } if (id == null) { return null; } else { String jsonDoc = _client.get(id); if (jsonDoc != null) { ActivityUnit ret=ElasticsearchClient.<ActivityUnit>convertJsonToType(jsonDoc, ActivityUnit.class); // Retrieve the activity types associated with the activity unit SearchResponse response=_client.getElasticsearchClient().prepareSearch(_client.getIndex()) .setTypes(_client.getType()+"type") .setSearchType(SearchType.DFS_QUERY_THEN_FETCH) .setTimeout(TimeValue.timeValueMillis(_timeout)) .setSize(_responseSize) .setQuery(QueryBuilders.matchQuery("unitId", id)) .execute().actionGet(); // Using iterator instead of using index, as caused out of range exception, // so not sure if results are unstable for (SearchHit hit : response.getHits()) { ret.getActivityTypes().add(ElasticsearchClient.convertJsonToType(hit.getSourceAsString(), ActivityType.class)); } if (ret.getActivityTypes().size() > 0) { // Sort the entries Collections.<ActivityType>sort(ret.getActivityTypes(), new Comparator<ActivityType>() { public int compare(ActivityType at1, ActivityType at2) { return at1.getUnitIndex()-at2.getUnitIndex(); } }); } if (LOG.isLoggable(Level.FINEST)) { LOG.finest("Return reconstructed activity unit for id["+id+"]="+ActivityUtil.objectToJSONString(ret)); } return ret; } else { return null; } } } /** * @param context The context value * @return List of activityTypes * @throws Exception in the event of a failure */ public List<ActivityType> getActivityTypes(Context context) throws Exception { if (context == null) { throw new Exception(java.util.PropertyResourceBundle.getBundle( "activity-store-elasticsearch.Messages").getString("ACTIVITY-STORE-ELASTICSEARCH-4")); } RefreshRequestBuilder refreshRequestBuilder = _client.getElasticsearchClient().admin().indices().prepareRefresh(_client.getIndex()); _client.getElasticsearchClient().admin().indices().refresh(refreshRequestBuilder.request()).actionGet(); if (LOG.isLoggable(Level.FINEST)) { LOG.finest("getActivityTypes=" + context); } QueryBuilder b2 = QueryBuilders.nestedQuery("context", QueryBuilders.boolQuery() .must(QueryBuilders.matchQuery("context.value", context.getValue())).must(QueryBuilders.matchQuery("context.type", context.getType())) ); SearchResponse response = _client.getElasticsearchClient().prepareSearch( _client.getIndex()).setTypes(_client.getType() + "type") .setSearchType(SearchType.DFS_QUERY_THEN_FETCH) .setTimeout(TimeValue.timeValueMillis(_timeout)) .setSize(_responseSize) .setQuery(b2).execute().actionGet(); if (response.isTimedOut()) { throw new Exception(MessageFormat.format( java.util.PropertyResourceBundle.getBundle( "activity-store-elasticsearch.Messages").getString("ACTIVITY-STORE-ELASTICSEARCH-3"), _client.getIndex(), _client.getType(), b2.toString() )); } List<ActivityType> list = new ArrayList<ActivityType>(); for (SearchHit searchHitFields : response.getHits()) { list.add(ElasticsearchClient.<ActivityType>convertJsonToType(searchHitFields.getSourceAsString(), ActivityType.class)); } if (LOG.isLoggable(Level.FINEST)) { LOG.finest("Returning activity list for context '"+context+"': " +new String(ActivityUtil.serializeActivityTypeList(list))); } return list; } /** * @param context The optional context value * @param from The 'from' timestamp * @param to The 'to' timestamp * @return List of actvitiyTypes * @throws Exception in the event of timeout. */ public List<ActivityType> getActivityTypes(Context context, long from, long to) throws Exception { // If default time range, then use the alternate method based on querying just the context if (from == 0 && to == 0) { return getActivityTypes(context); } RefreshRequestBuilder refreshRequestBuilder = _client.getElasticsearchClient().admin().indices().prepareRefresh(_client.getIndex()); _client.getElasticsearchClient().admin().indices().refresh(refreshRequestBuilder.request()).actionGet(); if (LOG.isLoggable(Level.FINEST)) { LOG.finest("getActivityTypes=" + context); } if (to == 0) { to = System.currentTimeMillis(); } BoolQueryBuilder b2 = QueryBuilders.boolQuery() .must(QueryBuilders.rangeQuery("timestamp").from(from).to(to)); if (context != null) { b2 = b2.must( QueryBuilders.nestedQuery("context", // Path QueryBuilders.boolQuery() .must(QueryBuilders.matchQuery("context.value", context.getValue())) .must(QueryBuilders.matchQuery("context.type", context.getType())) ) ); } SearchResponse response = _client.getElasticsearchClient().prepareSearch( _client.getIndex()).setTypes(_client.getType() + "type") .setSearchType(SearchType.DFS_QUERY_THEN_FETCH) .setTimeout(TimeValue.timeValueMillis(_timeout)) .setSize(_responseSize) .setQuery(b2).execute().actionGet(); if (response.isTimedOut()) { throw new Exception(MessageFormat.format( java.util.PropertyResourceBundle.getBundle( "activity-store-elasticsearch.Messages").getString("ACTIVITY-STORE-ELASTICSEARCH-3"), _client.getIndex(), _client.getType(), b2.toString() )); } List<ActivityType> list = new ArrayList<ActivityType>(); for (SearchHit searchHitFields : response.getHits()) { list.add(ElasticsearchClient.<ActivityType>convertJsonToType(searchHitFields.getSourceAsString(), ActivityType.class)); } return list; } /** * Elasticsearch query DSL. further language reference can be found under * http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl.html#query-dsl * No validation is performed against query and should format be incorrect api simply fails fast and returns a exception * * @param query The query. Of type elastic search Query DSL * @return null * @throws Exception UnsupportedOperationException */ @Deprecated public List<ActivityType> query(QuerySpec query) throws Exception { throw new UnsupportedOperationException("Query method not support by Elasticsearch Actvitystore"); } /** * This method returns the client. * * @return The client */ protected ElasticsearchClient getClient() { return (_client); } /** * Close the situation store. */ @ServiceClose public void close() { if (_client != null) { _client.close(); _client = null; } } }