/* * Copyright (C) 2014 Jörg Prante * * 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.xbib.elasticsearch.knapsack; import com.google.common.collect.ImmutableList; import org.elasticsearch.ElasticsearchException; import org.elasticsearch.action.admin.indices.recovery.RecoveryResponse; import org.elasticsearch.action.get.GetResponse; import org.elasticsearch.client.Client; import org.elasticsearch.common.component.AbstractLifecycleComponent; import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.inject.Injector; import org.elasticsearch.common.logging.ESLogger; import org.elasticsearch.common.logging.ESLoggerFactory; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.indices.IndexAlreadyExistsException; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import static org.elasticsearch.common.xcontent.ToXContent.EMPTY_PARAMS; import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; import static org.elasticsearch.common.xcontent.XContentFactory.xContent; import static org.elasticsearch.common.xcontent.XContentParser.Token.START_ARRAY; import static org.elasticsearch.common.xcontent.XContentParser.Token.END_ARRAY; import static org.elasticsearch.common.xcontent.XContentType.JSON; public class KnapsackService extends AbstractLifecycleComponent<KnapsackService> { private final static ESLogger logger = ESLoggerFactory.getLogger(KnapsackService.class.getSimpleName()); public static final String INDEX_NAME = ".knapsack"; private static final String MAPPING_NAME = "knapsack"; private static final String EXPORT_NAME = "export"; private static final String IMPORT_NAME = "import"; private final Injector injector; private ExecutorService executor; private List<Future<?>> tasks; @Inject public KnapsackService(Settings settings, Injector injector) { super(settings); this.injector = injector; } @Override protected void doStart() throws ElasticsearchException { this.tasks = new ArrayList<>(); this.executor = newExecutorService(); } @Override protected void doStop() throws ElasticsearchException { } @Override protected void doClose() throws ElasticsearchException { int size = tasks.size(); if (size > 0) { for (Future<?> f : tasks) { if (!f.isDone()) { logger.info("aborting knapsack task {}", f); boolean b = f.cancel(true); if (!b) { logger.error("knapsack task {} could not be cancelled", f); } } } tasks.clear(); } logger.info("knapsack shutdown..."); executor.shutdown(); try { this.executor.awaitTermination(5, TimeUnit.SECONDS); } catch (InterruptedException e) { throw new ElasticsearchException(e.getMessage()); } if (!executor.isShutdown()) { logger.info("knapsack shutdown now"); executor.shutdownNow(); try { this.executor.awaitTermination(5, TimeUnit.SECONDS); } catch (InterruptedException e) { throw new ElasticsearchException(e.getMessage()); } } logger.info("knapsack shutdown complete"); } protected ExecutorService newExecutorService() { return Executors.newFixedThreadPool(4); } public List<KnapsackState> getImports() throws IOException { return get(IMPORT_NAME); } public void addImport(KnapsackState newImport) throws IOException { add(IMPORT_NAME, getImports(), newImport); } public void removeImport(KnapsackState targetImport) throws IOException { remove(IMPORT_NAME, getImports(), targetImport); } public List<KnapsackState> getExports() throws IOException { return get(EXPORT_NAME); } public void addExport(KnapsackState newExport) throws IOException { add(EXPORT_NAME, getExports(), newExport); } public void removeExport(KnapsackState targetExport) throws IOException { remove(EXPORT_NAME, getExports(), targetExport); } private void add(String name, List<KnapsackState> values, KnapsackState targetValue) throws IOException { logger.debug("add: {} -> {}", name, values); if (values == null) { values = Collections.emptyList(); } put(name, generate(ImmutableList.<KnapsackState>builder() .addAll(values) .add(targetValue) .build())); } private void remove(String name, List<KnapsackState> values, KnapsackState targetValue) throws IOException { logger.debug("remove: {} -> {}", name, values); ImmutableList.Builder<KnapsackState> updatedValues = ImmutableList.builder(); for (KnapsackState value : values) { if (!value.equals(targetValue)) { updatedValues.add(value); } } put(name, generate(updatedValues.build())); } private List<KnapsackState> get(String name) throws IOException { ImmutableList.Builder<KnapsackState> builder = ImmutableList.builder(); try { logger.debug("get knapsack states: {}", name); final Client client = injector.getInstance(Client.class); createIndexIfNotExist(client); GetResponse getResponse = client.prepareGet(INDEX_NAME, MAPPING_NAME, name).execute().actionGet(); if (!getResponse.isExists()) { return builder.build(); } XContentParser parser = xContent(JSON).createParser(getResponse.getSourceAsBytes()); while (parser.nextToken() != START_ARRAY) { // forward } while (parser.nextToken() != END_ARRAY) { KnapsackState state = new KnapsackState(); builder.add(state.fromXContent(parser)); } return builder.build(); } catch (Throwable t) { logger.error("get settings failed", t); return null; } } private void put(final String name, final XContentBuilder builder) { try { logger.debug("put knapsack state: {} -> {}", name, builder.string()); final Client client = injector.getInstance(Client.class); createIndexIfNotExist(client); client.prepareIndex(INDEX_NAME, MAPPING_NAME, name) .setSource(builder) .setRefresh(true) .execute().actionGet(); } catch (Throwable t) { logger.error("update settings failed", t); } } private void remove(final String name) { try { logger.debug("remove: {}", name); final Client client = injector.getInstance(Client.class); createIndexIfNotExist(client); client.prepareDelete(INDEX_NAME, MAPPING_NAME, name) .setRefresh(true) .execute().actionGet(); } catch (Throwable t) { logger.error("remove failed", t); } } private static XContentBuilder generate(List<KnapsackState> values) throws IOException { XContentBuilder builder = jsonBuilder(); builder.startObject(); builder.startArray("array"); for (KnapsackState value : values) { value.toXContent(builder, EMPTY_PARAMS); } builder.endArray(); builder.endObject(); return builder; } private void createIndexIfNotExist(Client client) { try { client.admin().indices().prepareCreate(INDEX_NAME).execute().actionGet(); RecoveryResponse response = client.admin().indices().prepareRecoveries(INDEX_NAME).execute().actionGet(); int shards = response.getTotalShards(); client.admin().cluster().prepareHealth(INDEX_NAME) .setWaitForActiveShards(shards) .setWaitForYellowStatus() .execute().actionGet(); } catch (IndexAlreadyExistsException e) { // ignore } } public void submit(Runnable runnable) { Iterator<Future<?>> it = tasks.iterator(); while (it.hasNext()) { Future<?> f = it.next(); if (f.isDone()) { it.remove(); } } Future<?> f = executor.submit(runnable); tasks.add(f); } public void abort(boolean reset) { doClose(); this.executor = newExecutorService(); if (reset) { remove(EXPORT_NAME); remove(IMPORT_NAME); } } }