package org.codelibs.elasticsearch.taste.rest;
import static org.elasticsearch.rest.RestStatus.OK;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import org.codelibs.elasticsearch.taste.exception.TasteException;
import org.codelibs.elasticsearch.taste.rest.handler.ActionHandler;
import org.codelibs.elasticsearch.taste.rest.handler.EvalItemsFromUserHandler;
import org.codelibs.elasticsearch.taste.rest.handler.GenTermValuesHandler;
import org.codelibs.elasticsearch.taste.rest.handler.ItemsFromItemHandler;
import org.codelibs.elasticsearch.taste.rest.handler.ItemsFromUserHandler;
import org.codelibs.elasticsearch.taste.rest.handler.SimilarUsersHandler;
import org.codelibs.elasticsearch.taste.service.TasteService;
import org.elasticsearch.client.Client;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.json.JsonXContent;
import org.elasticsearch.rest.BaseRestHandler;
import org.elasticsearch.rest.BytesRestResponse;
import org.elasticsearch.rest.RestChannel;
import org.elasticsearch.rest.RestController;
import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.search.lookup.SourceLookup;
import org.elasticsearch.threadpool.ThreadPool;
public class TasteActionRestAction extends BaseRestHandler {
private static final String THREAD_NAME_PREFIX = "Taste-";
private static final String GENERATE_TERM_VALUES = "generate_term_values";
private static final String EVALUATE_ITEMS_FROM_USER = "evaluate_items_from_user";
private static final String RECOMMENDED_ITEMS_FROM_ITEM = "recommended_items_from_item";
private static final String RECOMMENDED_ITEMS_FROM_USER = "recommended_items_from_user";
private static final String SIMILAR_USERS = "similar_users";
private final TasteService tasteService;
private final ThreadPool pool;
@Inject
public TasteActionRestAction(final Settings settings,
final RestController restController, final Client client, final ThreadPool pool,
final TasteService tasteService) {
super(settings, restController, client);
this.pool = pool;
this.tasteService = tasteService;
restController.registerHandler(RestRequest.Method.GET,
"/_taste/action", this);
restController.registerHandler(RestRequest.Method.GET,
"/_taste/action/{name}", this);
restController.registerHandler(RestRequest.Method.POST,
"/_taste/action/{action}", this);
restController.registerHandler(RestRequest.Method.DELETE,
"/_taste/action/{name}", this);
}
Map<String, Thread> handlerMap = new ConcurrentHashMap<>();
@Override
protected void handleRequest(final RestRequest request,
final RestChannel channel, final Client client) {
String name;
Map<String, Object> params;
switch (request.method()) {
case GET:
name = request.param("name");
params = new LinkedHashMap<>();
if (name == null) {
params.put("names", handlerMap.keySet());
} else {
params.put("name", name);
params.put("found", handlerMap.containsKey(name));
}
sendResponse(request, channel, params, true);
break;
case DELETE:
name = request.param("name");
params = new LinkedHashMap<>();
params.put("name", name);
boolean acknowledged;
if (handlerMap.containsKey(name)) {
final Thread thread = handlerMap.remove(name);
thread.interrupt();
acknowledged = true;
} else {
acknowledged = false;
}
sendResponse(request, channel, params, acknowledged);
break;
case POST:
final BytesReference content = request.content();
if (content == null) {
sendErrorResponse(channel, new TasteException(
"Invalid parameter. No request body."));
break;
}
final String action = request.param("action");
try {
final Map<String, Object> sourceMap = SourceLookup
.sourceAsMap(content);
if (RECOMMENDED_ITEMS_FROM_USER.equals(action)) {
final ItemsFromUserHandler handler = new ItemsFromUserHandler(
settings, sourceMap, client, pool, tasteService);
name = startThread(handler);
} else if (RECOMMENDED_ITEMS_FROM_ITEM.equals(action)) {
final ItemsFromItemHandler handler = new ItemsFromItemHandler(
settings, sourceMap, client, pool, tasteService);
name = startThread(handler);
} else if (SIMILAR_USERS.equals(action)) {
final SimilarUsersHandler handler = new SimilarUsersHandler(
settings, sourceMap, client, pool, tasteService);
name = startThread(handler);
} else if (EVALUATE_ITEMS_FROM_USER.equals(action)) {
final EvalItemsFromUserHandler handler = new EvalItemsFromUserHandler(
settings, sourceMap, client, pool, tasteService);
name = startThread(handler);
} else if (GENERATE_TERM_VALUES.equals(action)) {
final GenTermValuesHandler handler = new GenTermValuesHandler(
settings, sourceMap, client, pool);
name = startThread(handler);
} else {
throw new TasteException("Unknown action: " + action);
}
params = new LinkedHashMap<>();
params.put("name", name);
sendResponse(request, channel, params, true);
} catch (final Exception e) {
sendErrorResponse(channel, e);
}
break;
default:
sendErrorResponse(channel, new TasteException("Invalid request: "
+ request));
break;
}
}
protected String startThread(final ActionHandler handler) {
final String name = UUID.randomUUID().toString();
final Thread thread = new Thread(() -> {
try {
handler.execute();
} catch (final Exception e) {
logger.error("TasteThread {} is failed.", e, name);
} finally {
handlerMap.remove(name);
handler.close();
}
}, THREAD_NAME_PREFIX + name);
thread.start();
handlerMap.put(name, thread);
return name;
}
private void sendResponse(final RestRequest request,
final RestChannel channel, final Map<String, Object> params,
final boolean acknowledged) {
try {
final XContentBuilder builder = JsonXContent.contentBuilder();
if (request.hasParam("pretty")) {
builder.prettyPrint().lfAtEnd();
}
builder.startObject();
builder.field("acknowledged", acknowledged);
if (params != null) {
for (final Map.Entry<String, Object> entry : params.entrySet()) {
builder.field(entry.getKey(), entry.getValue());
}
}
builder.endObject();
channel.sendResponse(new BytesRestResponse(OK, builder));
} catch (final Exception e) {
sendErrorResponse(channel, e);
}
}
private void sendErrorResponse(final RestChannel channel, final Throwable t) {
try {
channel.sendResponse(new BytesRestResponse(channel, t));
} catch (final Exception e) {
logger.error("Failed to send a failure response.", e);
}
}
}