/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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 com.github.fhuss.storm.elasticsearch.state; import java.io.IOException; import java.util.Collection; import java.util.LinkedList; import java.util.List; import java.util.Map; import org.elasticsearch.ElasticsearchException; import org.elasticsearch.action.bulk.BulkRequestBuilder; import org.elasticsearch.action.index.IndexRequestBuilder; import org.elasticsearch.action.search.SearchRequestBuilder; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.client.Client; import org.elasticsearch.search.SearchHit; import org.elasticsearch.search.sort.SortBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import storm.trident.state.State; import storm.trident.state.StateFactory; import storm.trident.tuple.TridentTuple; import backtype.storm.task.IMetricsContext; import backtype.storm.topology.FailedException; import com.github.fhuss.storm.elasticsearch.ClientFactory; import com.github.fhuss.storm.elasticsearch.Document; import com.github.fhuss.storm.elasticsearch.handler.BulkResponseHandler; import com.github.fhuss.storm.elasticsearch.mapper.TridentTupleMapper; /** * Simple {@link State} implementation for Elasticsearch. * * @author fhussonnois */ public class ESIndexState<T> implements State { public static final Logger LOGGER = LoggerFactory.getLogger(ESIndexState.class); private Client client; private ValueSerializer<T> serializer; public ESIndexState(Client client, ValueSerializer<T> serializer) { this.client = client; this.serializer = serializer; } @Override public void beginCommit(Long aLong) { } @Override public void commit(Long aLong) { } public void bulkUpdateIndices(List<TridentTuple> inputs, TridentTupleMapper<Document<T>> mapper, BulkResponseHandler handler) { BulkRequestBuilder bulkRequest = client.prepareBulk(); for (TridentTuple input : inputs) { Document<T> doc = mapper.map(input); byte[] source = serializeSourceOrFail(doc); IndexRequestBuilder request = client.prepareIndex(doc.getName(), doc.getType(), doc.getId()).setSource(source); if(doc.getParentId() != null) { request.setParent(doc.getParentId()); } bulkRequest.add(request); } if( bulkRequest.numberOfActions() > 0) { try { handler.handle(bulkRequest.execute().actionGet()); } catch(ElasticsearchException e) { LOGGER.error("error while executing bulk request to elasticsearch"); throw new FailedException("Failed to store data into elasticsearch", e); } } } protected byte[] serializeSourceOrFail(Document<T> doc) { try { return serializer.serialize(doc.getSource()); } catch (IOException e) { LOGGER.error("Error while serializing document source", e); throw new FailedException("Failed to serialize source as byte[]", e); } } public Collection<T> searchQuery(String query, List<String> indices, List<String> types) { return searchQuery(query, indices, types, 10); } public Collection<T> searchQuery(String query, List<String> indices, List<String> types, int size) { SearchResponse response = buildSearchQuery(query, indices, types, size).execute().actionGet(); return buildResult(response); } public Collection<T> searchSortedAndFirstNQuery(String query, List<String> indices, List<String> types, SortBuilder sortBuilder, int firstN) { SearchResponse response = buildSearchQuery(query, indices, types, firstN) .addSort(sortBuilder) .setQuery(query).execute().actionGet(); return buildResult(response); } private SearchRequestBuilder buildSearchQuery(String query, List<String> indices, List<String> types, int size) { return client.prepareSearch() .setIndices(indices.toArray(new String[indices.size()])) .setTypes(types.toArray(new String[types.size()])) .setSize(size) .setQuery(query); } private List<T> buildResult(SearchResponse response) { List<T> result = new LinkedList<>(); for(SearchHit hit : response.getHits()) { try { result.add(serializer.deserialize(hit.source())); } catch (IOException e) { LOGGER.error("Error while trying to deserialize data from json source"); } } return result; } public static class Factory<T> implements StateFactory { private ClientFactory clientFactory; private ValueSerializer<T> serializer; public Factory(ClientFactory clientFactory, Class<T> clazz ) { this.clientFactory = clientFactory; this.serializer = new ValueSerializer.NonTransactionalValueSerializer<>(clazz); } @Override public State makeState(Map map, IMetricsContext iMetricsContext, int i, int i2) { return new ESIndexState<>(makeClient(map), serializer); } protected Client makeClient(Map map) { return clientFactory.makeClient(map); } } }