package org.apache.tajo.webapp; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.util.StringUtils; import org.apache.tajo.QueryId; import org.apache.tajo.TajoProtos; import org.apache.tajo.catalog.CatalogUtil; import org.apache.tajo.catalog.TableDesc; import org.apache.tajo.client.QueryStatus; import org.apache.tajo.client.TajoClient; import org.apache.tajo.conf.TajoConf; import org.apache.tajo.ipc.ClientProtos; import org.apache.tajo.jdbc.TajoResultSet; import org.apache.tajo.util.JSPUtil; import org.codehaus.jackson.map.DeserializationConfig; import org.codehaus.jackson.map.ObjectMapper; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.OutputStream; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; /** * 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. */ public class QueryExecutorServlet extends HttpServlet { private static final Log LOG = LogFactory.getLog(QueryExecutorServlet.class); ObjectMapper om = new ObjectMapper(); //queryRunnerId -> QueryRunner private final Map<String, QueryRunner> queryRunners = new HashMap<String, QueryRunner>(); private TajoClient tajoClient; private ExecutorService queryRunnerExecutor = Executors.newFixedThreadPool(5); private QueryRunnerCleaner queryRunnerCleaner; @Override public void init(ServletConfig config) throws ServletException { om.getDeserializationConfig().disable( DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES); try { tajoClient = new TajoClient(new TajoConf()); queryRunnerCleaner = new QueryRunnerCleaner(); queryRunnerCleaner.start(); } catch (IOException e) { LOG.error(e.getMessage()); } } @Override public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String action = request.getParameter("action"); Map<String, Object> returnValue = new HashMap<String, Object>(); try { if(tajoClient == null) { errorResponse(response, "TajoClient not initialized"); return; } if(action == null || action.trim().isEmpty()) { errorResponse(response, "no action parameter."); return; } if("runQuery".equals(action)) { String query = request.getParameter("query"); if(query == null || query.trim().isEmpty()) { errorResponse(response, "No query parameter"); return; } String queryRunnerId = null; while(true) { synchronized(queryRunners) { queryRunnerId = "" + System.currentTimeMillis(); if(!queryRunners.containsKey(queryRunnerId)) { break; } try { Thread.sleep(100); } catch (InterruptedException e) { } } } QueryRunner queryRunner = new QueryRunner(queryRunnerId, query); try { queryRunner.sizeLimit = Integer.parseInt(request.getParameter("limitSize")); } catch (java.lang.NumberFormatException nfe) { queryRunner.sizeLimit = 1048576; } synchronized(queryRunners) { queryRunners.put(queryRunnerId, queryRunner); } queryRunnerExecutor.submit(queryRunner); returnValue.put("queryRunnerId", queryRunnerId); } else if("getQueryProgress".equals(action)) { synchronized(queryRunners) { String queryRunnerId = request.getParameter("queryRunnerId"); QueryRunner queryRunner = queryRunners.get(queryRunnerId); if(queryRunner == null) { errorResponse(response, "No query info:" + queryRunnerId); return; } if(queryRunner.error != null) { errorResponse(response, queryRunner.error); return; } SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); returnValue.put("progress", queryRunner.progress); returnValue.put("startTime", df.format(queryRunner.startTime)); returnValue.put("finishTime", queryRunner.finishTime == 0 ? "-" : df.format(queryRunner.startTime)); returnValue.put("runningTime", JSPUtil.getElapsedTime(queryRunner.startTime, queryRunner.finishTime)); } } else if("getQueryResult".equals(action)) { synchronized(queryRunners) { String queryRunnerId = request.getParameter("queryRunnerId"); QueryRunner queryRunner = queryRunners.get(queryRunnerId); if(queryRunner == null) { errorResponse(response, "No query info:" + queryRunnerId); return; } if(queryRunner.error != null) { errorResponse(response, queryRunner.error); return; } returnValue.put("numOfRows", queryRunner.numOfRows); returnValue.put("resultSize", queryRunner.resultSize); returnValue.put("resultData", queryRunner.queryResult); returnValue.put("resultColumns", queryRunner.columnNames); returnValue.put("runningTime", JSPUtil.getElapsedTime(queryRunner.startTime, queryRunner.finishTime)); } } else if("clearAllQueryRunner".equals(action)) { synchronized(queryRunners) { for(QueryRunner eachQueryRunner: queryRunners.values()) { eachQueryRunner.setStop(); } queryRunners.clear(); } } returnValue.put("success", "true"); writeHttpResponse(response, returnValue); } catch (Exception e) { LOG.error(e.getMessage(), e); errorResponse(response, e); } } private void errorResponse(HttpServletResponse response, Exception e) throws IOException { errorResponse(response, e.getMessage() + "\n" + StringUtils.stringifyException(e)); } private void errorResponse(HttpServletResponse response, String message) throws IOException { Map<String, Object> errorMessage = new HashMap<String, Object>(); errorMessage.put("success", "false"); errorMessage.put("errorMessage", message); writeHttpResponse(response, errorMessage); } private void writeHttpResponse(HttpServletResponse response, Map<String, Object> outputMessage) throws IOException { response.setContentType("text/html"); OutputStream out = response.getOutputStream(); out.write(om.writeValueAsBytes(outputMessage)); out.flush(); out.close(); } class QueryRunnerCleaner extends Thread { public void run() { List<QueryRunner> queryRunnerList; synchronized(queryRunners) { queryRunnerList = new ArrayList<QueryRunner>(queryRunners.values()); for(QueryRunner eachQueryRunner: queryRunnerList) { if(!eachQueryRunner.running.get() && (System.currentTimeMillis() - eachQueryRunner.finishTime > 180 * 1000)) { queryRunners.remove(eachQueryRunner.queryRunnerId); } } } } } class QueryRunner extends Thread { long startTime; long finishTime; String queryRunnerId; ClientProtos.SubmitQueryResponse queryRespons; AtomicBoolean running = new AtomicBoolean(true); AtomicBoolean stop = new AtomicBoolean(false); QueryId queryId; String query; long resultSize; int sizeLimit; long numOfRows; Exception error; AtomicInteger progress = new AtomicInteger(0); List<String> columnNames = new ArrayList<String>(); List<List<Object>> queryResult; public QueryRunner(String queryRunnerId, String query) { this.queryRunnerId = queryRunnerId; this.query = query; } public void setStop() { this.stop.set(true); this.interrupt(); } public void run() { startTime = System.currentTimeMillis(); try { queryRespons = tajoClient.executeQuery(query); if (queryRespons.getResultCode() == ClientProtos.ResultCode.OK) { QueryId queryId = null; try { queryId = new QueryId(queryRespons.getQueryId()); getQueryResult(queryId); } finally { if (queryId != null) { tajoClient.closeQuery(queryId); } } } else { LOG.error("queryRespons.getResultCode() not OK:" + queryRespons.getResultCode()); error = new Exception("queryRespons.getResultCode() not OK:" + queryRespons.getResultCode()); } } catch (Exception e) { LOG.error(e.getMessage(), e); error = e; } finally { running.set(false); finishTime = System.currentTimeMillis(); } } private void getQueryResult(QueryId tajoQueryId) { // query execute try { QueryStatus status = null; while (!stop.get()) { try { Thread.sleep(1000); } catch(InterruptedException e) { break; } status = tajoClient.getQueryStatus(tajoQueryId); if (status.getState() == TajoProtos.QueryState.QUERY_MASTER_INIT || status.getState() == TajoProtos.QueryState.QUERY_MASTER_LAUNCHED) { continue; } if (status.getState() == TajoProtos.QueryState.QUERY_RUNNING || status.getState() == TajoProtos.QueryState.QUERY_SUCCEEDED) { int progressValue = (int) (status.getProgress() * 100.0f); if(progressValue == 100) { progressValue = 99; } progress.set(progressValue); } if (status.getState() != TajoProtos.QueryState.QUERY_RUNNING && status.getState() != TajoProtos.QueryState.QUERY_NOT_ASSIGNED) { break; } } if(status == null) { LOG.error("Query Status is null"); error = new Exception("Query Status is null"); return; } if (status.getState() == TajoProtos.QueryState.QUERY_ERROR || status.getState() == TajoProtos.QueryState.QUERY_FAILED) { error = new Exception(status.getErrorMessage()); } else if (status.getState() == TajoProtos.QueryState.QUERY_KILLED) { LOG.info(queryId + " is killed."); error = new Exception(queryId + " is killed."); } else { if (status.getState() == TajoProtos.QueryState.QUERY_SUCCEEDED) { if (status.hasResult()) { ResultSet res = null; try { ClientProtos.GetQueryResultResponse response = tajoClient.getResultResponse(tajoQueryId); TableDesc desc = CatalogUtil.newTableDesc(response.getTableDesc()); tajoClient.getConf().setVar(TajoConf.ConfVars.USERNAME, response.getTajoUserName()); res = new TajoResultSet(tajoClient, queryId, tajoClient.getConf(), desc); ResultSetMetaData rsmd = res.getMetaData(); resultSize = desc.getStats().getNumBytes(); LOG.info("Tajo Query Result: " + desc.getPath() + "\n"); int numOfColumns = rsmd.getColumnCount(); for(int i = 0; i < numOfColumns; i++) { columnNames.add(rsmd.getColumnName(i + 1)); } queryResult = new ArrayList<List<Object>>(); if(sizeLimit < resultSize) { numOfRows = (long)((float)(desc.getStats().getNumRows()) * ((float)sizeLimit / (float)resultSize)); } else { numOfRows = desc.getStats().getNumRows(); } int rowCount = 0; boolean hasMoreData = false; while (res.next()) { if(rowCount > numOfRows) { hasMoreData = true; break; } List<Object> row = new ArrayList<Object>(); for(int i = 0; i < numOfColumns; i++) { row.add(res.getObject(i + 1).toString()); } queryResult.add(row); rowCount++; } } finally { if (res != null) { res.close(); } progress.set(100); } } else { error = new Exception(queryId + " no result"); } } } } catch (Exception e) { LOG.error(e.getMessage(), e); error = e; } } } }