/*
* 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 org.huahinframework.emanager.rest.service;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.wink.common.internal.utils.MediaTypeUtils;
import org.apache.wink.common.model.multipart.InMultiPart;
import org.apache.wink.common.model.multipart.InPart;
import org.huahinframework.emanager.amazonaws.elasticmapreduce.Config;
import org.huahinframework.emanager.amazonaws.elasticmapreduce.EMRProperties;
import org.huahinframework.emanager.queue.QueueUtils;
import org.huahinframework.emanager.rest.response.Response;
import org.huahinframework.emanager.util.S3Utils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3Client;
/**
*
*/
@Path("/queue")
public class QueueService {
private static final Log log = LogFactory.getLog(QueueService.class);
private static final String ARGUMENT_ERROR = "arguments error:[%s]";
private static final int LIST_QUEUE = 0;
private static final int LIST_RUNNING = 1;
private static final int METHOD_PUT = 0;
private static final int METHOD_POST = 1;
private static final String CONTENT_DISPOSITION = "Content-Disposition";
private static final String FORM_DATA_NAME_SCRIPT = "form-data; name=\"SCRIPT\"";
private static final String FORM_DATA_NAME_MAPPER = "form-data; name=\"MAPPER\"";
private static final String FORM_DATA_NAME_REDUCER = "form-data; name=\"REDUCER\"";
private static final String FORM_DATA_NAME_JAR = "form-data; name=\"JAR\"";
private static final String FORM_DATA_NAME_ARGUMENTS = "form-data; name=\"ARGUMENTS\"";
private static final String STEP_NAME = "STEP_NAME";
private static final String JAR = "jar";
private static final String MAIN_CLASS = "mainClass";
private static final String SCRIPT = "script";
private static final String INPUT = "input";
private static final String OUTPUT = "output";
private static final String MAPPER = "mapper";
private static final String REDUCER = "reducer";
private static final String ARGUMENTS = "arguments";
private static final String DELETE_ON_EXIT = "deleteOnExit";
private AmazonS3 s3;
private EMRProperties emrProperties;
@Path("/list")
@GET
@Produces(MediaType.APPLICATION_JSON)
public JSONArray list() {
return map2Array(LIST_QUEUE);
}
@Path("/runnings")
@GET
@Produces(MediaType.APPLICATION_JSON)
public JSONArray runnings() {
return map2Array(LIST_RUNNING);
}
/**
* @param type
* @param map
* @return JSONArray
*/
@SuppressWarnings("unchecked")
private JSONArray map2Array(int type) {
JSONArray jsonArray = null;
try {
Map<String, Config> queueMap = null;
switch (type) {
case LIST_QUEUE:
queueMap = QueueUtils.list();
break;
case LIST_RUNNING:
queueMap = QueueUtils.runnings();
break;
default:
break;
}
List<JSONObject> l = new ArrayList<JSONObject>();
for (Entry<String, Config> entry : queueMap.entrySet()) {
Config config = entry.getValue();
Map<String, Object> m = new HashMap<String, Object>();
m.put(Response.STEP_NAME, config.getName());
m.put(Response.TYPE, Config.getJobTypeName(config.getJobType()));
m.put(Response.CREATION_DATE, config.getDate().toString());
if (!isEmpty(config.getJobFlowId())) {
m.put(Response.JOB_FLOW, config.getJobFlowId());
}
l.add(new JSONObject(m));
}
jsonArray = new JSONArray(l);
} catch (Exception e) {
e.printStackTrace();
log.error(e);
Map<String, String> status = new HashMap<String, String>();
status.put(Response.STATUS, e.getMessage());
jsonArray = new JSONArray(Arrays.asList(status));
}
return jsonArray;
}
@Path("/describe/{" + STEP_NAME + "}")
@GET
@Produces(MediaType.APPLICATION_JSON)
public JSONObject describe(@PathParam(STEP_NAME) String stepName) {
Map<String, Object> status = new HashMap<String, Object>();
try {
Config config = QueueUtils.get(stepName);
status.put(Response.STEP_NAME, config.getName());
status.put(Response.STATUS, Config.getStatusName(config.getStatus()));
status.put(Response.TYPE, Config.getJobTypeName(config.getJobType()));
status.put(Response.CREATION_DATE, config.getDate().toString());
status.put(Response.DELETE_ON_EXIT, config.isDeleteOnExit());
if (!isEmpty(config.getJobFlowId())) {
status.put(Response.JOB_FLOW, config.getJobFlowId());
}
if (!isEmpty(config.getRun())) {
status.put(Response.SCRIPT, config.getRun());
}
if (!isEmpty(config.getMainClass())) {
status.put(Response.MAIN_CLASS, config.getMainClass());
}
if (config.getArgs() != null && config.getArgs().length >= 1) {
status.put(Response.ARGS, config.getArgs());
}
if (config.getArgMap() != null && !config.getArgMap().isEmpty()) {
for (Entry<String, String> entry : config.getArgMap().entrySet()) {
status.put(entry.getKey(), entry.getValue());
}
}
} catch (Exception e) {
e.printStackTrace();
log.error(e);
status.put(Response.STATUS, e.getMessage());
}
return new JSONObject(status);
}
@Path("/register/hive")
@PUT
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaTypeUtils.MULTIPART_FORM_DATA)
public JSONObject registerPutHive(InMultiPart inMP) {
return registerScripts(METHOD_PUT, Config.JOB_TYPE_HIVE, inMP);
}
@Path("/register/pig")
@PUT
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaTypeUtils.MULTIPART_FORM_DATA)
public JSONObject registerPutPig(InMultiPart inMP) {
return registerScripts(METHOD_PUT, Config.JOB_TYPE_PIG, inMP);
}
@Path("/register/hive")
@POST
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaTypeUtils.MULTIPART_FORM_DATA)
public JSONObject registerPostHive(InMultiPart inMP) {
return registerScripts(METHOD_POST, Config.JOB_TYPE_HIVE, inMP);
}
@Path("/register/pig")
@POST
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaTypeUtils.MULTIPART_FORM_DATA)
public JSONObject registerPostPig(InMultiPart inMP) {
return registerScripts(METHOD_POST, Config.JOB_TYPE_PIG, inMP);
}
/**
* @param method
* @param type
* @param inMP
* @return
*/
private JSONObject registerScripts(int method, int type, InMultiPart inMP) {
Map<String, String> status = new HashMap<String, String>();
status.put(Response.STATUS, "accepted");
File file = null;
JSONObject argument = null;
try {
while (inMP.hasNext()) {
InPart part = inMP.next();
for (String s : part.getHeaders().get(CONTENT_DISPOSITION)) {
if (s.startsWith(FORM_DATA_NAME_SCRIPT)) {
file = File.createTempFile("huahin", ".tmp");
createTempFile(part.getInputStream(), file);
break;
} else if (s.startsWith(FORM_DATA_NAME_ARGUMENTS)) {
argument = createJSON(part.getInputStream());
break;
}
}
}
if (method == METHOD_POST) {
if (file == null) {
deleteFile(file);
status.put(Response.STATUS, "arguments error");
return new JSONObject(status);
}
}
if (argument == null) {
deleteFile(file);
status.put(Response.STATUS, "arguments error");
return new JSONObject(status);
}
if (argument.isNull(SCRIPT)) {
deleteFile(file);
status.put(Response.STATUS, String.format(ARGUMENT_ERROR, "script not found"));
return new JSONObject(status);
}
Config config = new Config();
config.setJobType(type);
config.setRun(argument.getString(SCRIPT));
if (argument.has(ARGUMENTS)) {
JSONArray array = argument.getJSONArray(ARGUMENTS);
config.setArgs(new String[array.length()]);
for (int i = 0; i < array.length(); i++) {
config.getArgs()[i] = array.getString(i);
}
}
if (method == METHOD_POST) {
if (argument.has(DELETE_ON_EXIT)) {
config.setDeleteOnExit(argument.getBoolean(DELETE_ON_EXIT));
}
S3Utils.upload(s3, config.getRun(), file);
}
config = QueueUtils.registerQueue(config);
status.put(Response.STEP_NAME, config.getName());
} catch (Exception e) {
e.printStackTrace();
log.error(e);
status.put(Response.STATUS, e.getMessage());
}
deleteFile(file);
return new JSONObject(status);
}
@Path("/register/streaming")
@PUT
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaTypeUtils.MULTIPART_FORM_DATA)
public JSONObject registerPutStreaming(InMultiPart inMP) {
return registerStreaming(METHOD_PUT, inMP);
}
@Path("/register/streaming")
@POST
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaTypeUtils.MULTIPART_FORM_DATA)
public JSONObject registerPostStreaming(InMultiPart inMP) {
return registerStreaming(METHOD_POST, inMP);
}
/**
* @param inMP
* @return JSONObject
*/
public JSONObject registerStreaming(int method, InMultiPart inMP) {
Map<String, String> status = new HashMap<String, String>();
status.put(Response.STATUS, "accepted");
File mapperFile = null;
File reducerFile = null;
JSONObject argument = null;
try {
while (inMP.hasNext()) {
InPart part = inMP.next();
for (String s : part.getHeaders().get(CONTENT_DISPOSITION)) {
if (s.startsWith(FORM_DATA_NAME_MAPPER)) {
mapperFile = File.createTempFile("huahin", ".tmp");
createTempFile(part.getInputStream(), mapperFile);
break;
} else if (s.startsWith(FORM_DATA_NAME_REDUCER)) {
reducerFile = File.createTempFile("huahin", ".tmp");
createTempFile(part.getInputStream(), reducerFile);
break;
} else if (s.startsWith(FORM_DATA_NAME_ARGUMENTS)) {
argument = createJSON(part.getInputStream());
break;
}
}
}
if (method == METHOD_POST) {
if (mapperFile == null || reducerFile == null) {
deleteFile(mapperFile);
deleteFile(reducerFile);
status.put(Response.STATUS, "arguments error");
return new JSONObject(status);
}
}
if (argument == null) {
deleteFile(mapperFile);
deleteFile(reducerFile);
status.put(Response.STATUS, "arguments error");
return new JSONObject(status);
}
if (argument.isNull(INPUT)) {
deleteFile(mapperFile);
deleteFile(reducerFile);
status.put(Response.STATUS, String.format(ARGUMENT_ERROR, INPUT + " not found"));
return new JSONObject(status);
}
if (argument.isNull(OUTPUT)) {
deleteFile(mapperFile);
deleteFile(reducerFile);
status.put(Response.STATUS, String.format(ARGUMENT_ERROR, OUTPUT + " not found"));
return new JSONObject(status);
}
if (argument.isNull(MAPPER)) {
deleteFile(mapperFile);
deleteFile(reducerFile);
status.put(Response.STATUS, String.format(ARGUMENT_ERROR, MAPPER + " not found"));
return new JSONObject(status);
}
if (argument.isNull(REDUCER)) {
deleteFile(mapperFile);
deleteFile(reducerFile);
status.put(Response.STATUS, String.format(ARGUMENT_ERROR, REDUCER + " not found"));
return new JSONObject(status);
}
Map<String, String> argMap = new HashMap<String, String>();
for (String key : JSONObject.getNames(argument)) {
argMap.put(key, argument.getString(key));
}
Config config = new Config();
config.setJobType(Config.JOB_TYPE_STREAMING);
config.setArgMap(argMap);
if (method == METHOD_POST) {
if (argument.has(DELETE_ON_EXIT)) {
argMap.remove(DELETE_ON_EXIT);
config.setDeleteOnExit(argument.getBoolean(DELETE_ON_EXIT));
}
S3Utils.upload(s3, argMap.get(MAPPER), mapperFile);
S3Utils.upload(s3, argMap.get(REDUCER), reducerFile);
}
config = QueueUtils.registerQueue(config);
status.put(Response.STEP_NAME, config.getName());
} catch (Exception e) {
e.printStackTrace();
log.error(e);
status.put(Response.STATUS, e.getMessage());
}
deleteFile(mapperFile);
deleteFile(reducerFile);
return new JSONObject(status);
}
@Path("/register/customjar")
@PUT
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaTypeUtils.MULTIPART_FORM_DATA)
public JSONObject registerPutCustomJar(InMultiPart inMP) {
return registerCustomJar(METHOD_PUT, inMP);
}
@Path("/register/customjar")
@POST
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaTypeUtils.MULTIPART_FORM_DATA)
public JSONObject registerPostCustomJar(InMultiPart inMP) {
return registerCustomJar(METHOD_POST, inMP);
}
/**
* @param method
* @param inMP
* @return
*/
public JSONObject registerCustomJar(int method, InMultiPart inMP) {
Map<String, String> status = new HashMap<String, String>();
status.put(Response.STATUS, "accepted");
File file = null;
JSONObject argument = null;
try {
while (inMP.hasNext()) {
InPart part = inMP.next();
for (String s : part.getHeaders().get(CONTENT_DISPOSITION)) {
if (s.startsWith(FORM_DATA_NAME_JAR)) {
file = File.createTempFile("huahin", ".tmp");
createTempFile(part.getInputStream(), file);
break;
} else if (s.startsWith(FORM_DATA_NAME_ARGUMENTS)) {
argument = createJSON(part.getInputStream());
break;
}
}
}
if (method == METHOD_POST) {
if (file == null) {
deleteFile(file);
status.put(Response.STATUS, "arguments error");
return new JSONObject(status);
}
}
if (argument == null) {
deleteFile(file);
status.put(Response.STATUS, "arguments error");
return new JSONObject(status);
}
if (argument.isNull(JAR)) {
deleteFile(file);
status.put(Response.STATUS, String.format(ARGUMENT_ERROR, JAR + " not found"));
return new JSONObject(status);
}
Config config = new Config();
config.setJobType(Config.JOB_TYPE_CUSTOM_JAR);
config.setRun(argument.getString(JAR));
if (argument.has(MAIN_CLASS)) {
config.setMainClass(argument.getString(MAIN_CLASS));
}
if (argument.has(ARGUMENTS)) {
JSONArray array = argument.getJSONArray(ARGUMENTS);
config.setArgs(new String[array.length()]);
for (int i = 0; i < array.length(); i++) {
config.getArgs()[i] = array.getString(i);
}
}
if (method == METHOD_POST) {
if (argument.has(DELETE_ON_EXIT)) {
config.setDeleteOnExit(argument.getBoolean(DELETE_ON_EXIT));
}
S3Utils.upload(s3, config.getRun(), file);
}
config = QueueUtils.registerQueue(config);
status.put(Response.STEP_NAME, config.getName());
} catch (Exception e) {
e.printStackTrace();
log.error(e);
status.put(Response.STATUS, e.getMessage());
}
deleteFile(file);
return new JSONObject(status);
}
/**
* @return {@link JSONObject}
*/
@Path("/kill/{" + STEP_NAME + "}")
@DELETE
@Produces(MediaType.APPLICATION_JSON)
public JSONObject kill(@PathParam(STEP_NAME) String stepName) {
Map<String, String> status = new HashMap<String, String>();
try {
QueueUtils.removeQueue(stepName);
status.put(Response.STATUS, "Killed queue " + stepName);
} catch (Exception e) {
e.printStackTrace();
log.error(e);
status.put(Response.STATUS, e.getMessage());
}
return new JSONObject(status);
}
/**
* @param in
* @param file
* @throws IOException
*/
private void createTempFile(InputStream in, File file) throws IOException {
OutputStream out = new FileOutputStream(file);
byte[] buf = new byte[1024];
int len = 0;
while ((len = in.read(buf)) > 0) {
out.write(buf, 0, len);
}
out.close();
}
/**
* @param in
* @return JSONObject
* @throws IOException
* @throws JSONException
*/
private JSONObject createJSON(InputStream in)
throws IOException, JSONException {
BufferedReader reader =
new BufferedReader(new InputStreamReader(in, "UTF-8"));
StringBuilder sb = new StringBuilder();
String str;
while ((str = reader.readLine()) != null) {
sb.append(str);
}
return new JSONObject(sb.toString());
}
/**
* @param file
*/
private void deleteFile(File file) {
if (file != null) {
file.delete();
}
}
/**
*
*/
public void init() {
s3 = new AmazonS3Client(
new BasicAWSCredentials(emrProperties.getAccessKey(),
emrProperties.getSecretKey()));
if (!isEmpty(emrProperties.getS3Endpoint())) {
s3.setEndpoint(emrProperties.getS3Endpoint());
}
}
/**
*
*/
private boolean isEmpty(String s) {
return s == null || s.isEmpty();
}
/**
* @param emrProperties the emrProperties to set
*/
public void setEmrProperties(EMRProperties emrProperties) {
this.emrProperties = emrProperties;
}
}