/* * Copyright 2011 Future Systems * * 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.araqne.logdb; import java.io.IOException; import java.util.ArrayList; import java.util.Date; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import org.araqne.logdb.QueryCommand.Status; import org.araqne.logdb.impl.QueryHelper; import org.araqne.logdb.query.engine.QueryTaskScheduler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class DefaultQuery implements Query { private Logger logger = LoggerFactory.getLogger(DefaultQuery.class); private Logger resultTracer = LoggerFactory.getLogger("query-result-trace"); private static AtomicInteger nextId = new AtomicInteger(1); private final int id = nextId.getAndIncrement(); private QueryContext context; private String queryString; private List<QueryCommand> commands; private Date lastStarted; private QueryResult result; private QueryStopReason stopReason; private Throwable cause; private RunMode runMode = RunMode.FOREGROUND; private QueryCallbacks callbacks = new QueryCallbacks(); // task scheduler which consider dependency private QueryTaskScheduler scheduler; private AtomicLong stamp = new AtomicLong(1); private List<String> fieldOrder; private AtomicBoolean closed = new AtomicBoolean(); private CountDownLatch stopLatch = new CountDownLatch(1); private Query parent; private List<Query> dependencies = new ArrayList<Query>(); public DefaultQuery(QueryContext context, String queryString, List<QueryCommand> commands, QueryResultFactory resultFactory) { this.context = context; this.queryString = queryString; this.commands = commands; this.scheduler = new QueryTaskScheduler(this, commands); for (QueryCommand cmd : commands) { if (cmd instanceof FieldOrdering) { FieldOrdering f = (FieldOrdering) cmd; if (f.getFieldOrder() != null) fieldOrder = f.getFieldOrder(); } cmd.setQuery(this); } createResult(resultFactory); // sub query is built in reversed order if (context != null) context.getQueries().add(0, this); } public void createResult(QueryResultFactory resultFactory) { if (resultFactory == null) throw new IllegalStateException("query [id " + id + "]'s result factory is null"); if (result != null) throw new IllegalStateException("query [id " + id + "]'s result is already openned"); try { if (resultTracer.isDebugEnabled()) { String currentLogin = null; if (context != null && context.getSession() != null) currentLogin = context.getSession().getLoginName(); resultTracer.debug("araqne logdb: open query result for query [{}:{}], session [{}]", new Object[] { id, queryString, currentLogin }); } QueryResultConfig config = new QueryResultConfig(); config.setQuery(this); result = resultFactory.createResult(config); } catch (IOException e) { if (resultTracer.isDebugEnabled()) { resultTracer.debug("araqne logdb: delete query result for query [" + id + ":" + queryString + "], run mode [" + runMode + "] by exception", e); } result.closeWriter(); result.purge(); throw new IllegalStateException("cannot create result, maybe disk full", e); } } public void preRun() { QueryHelper.setJoinDependencies(this); // connect all pipe QueryCommand last = null; for (QueryCommand cmd : commands) { if (last != null) last.setOutput(new QueryCommandPipe(cmd)); last = cmd; } try { this.getResult().openWriter(); commands.get(commands.size() - 1).setOutput(result); logger.trace("araqne logdb: run query => {}", queryString); for (QueryCommand command : commands) { command.setStatus(Status.Waiting); command.tryStart(); command.setStatus(Status.Running); } } catch (IOException e) { throw new IllegalStateException("can not open result file", e); } } @Override public void run() { try { lastStarted = new Date(); if (commands.isEmpty()) return; preRun(); scheduler.run(); } catch (Throwable t) { logger.error("araqne logdb: query failed - " + this, t); stop(t); // call postRun(), all other tasks are already cancelled scheduler.run(); } } @Override public void postRun() { stop(QueryStopReason.End); } @Override public boolean isAccessible(Session session) { Session querySession = getContext().getSession(); if (runMode == RunMode.FOREGROUND) return querySession.equals(session); else return querySession.getLoginName().equals(session.getLoginName()); } @Override public QueryContext getContext() { return context; } @Override public boolean isCancelled() { return stopReason != null && stopReason != QueryStopReason.End && stopReason != QueryStopReason.PartialFetch; } @Override public RunMode getRunMode() { return runMode; } @Override public void setRunMode(RunMode runMode, QueryContext context) { this.runMode = runMode; if (context != null) this.context = context; } @Override public int getId() { return id; } @Override public String getQueryString() { return queryString; } @Override public boolean isStarted() { return scheduler.isStarted(); } @Override public long getStartTime() { return scheduler.getStartTime(); } @Override public boolean isFinished() { return stopLatch.getCount() == 0; } @Override public void awaitFinish() { try { stopLatch.await(); } catch (InterruptedException e) { } } @Override public long getFinishTime() { return scheduler.getFinishTime(); } @Override public void purge() { // prevent deleted result file access caused by result check of query // callback or timeline callbacks stop(); try { stopLatch.await(); } catch (InterruptedException e) { logger.error("stopLatch failed", e); } callbacks.getStatusCallbacks().clear(); if (result != null) { if (resultTracer.isDebugEnabled()) { resultTracer.debug( "araqne logdb: delete query result for query [{}:{}], run mode [{}], stop reason [{}], cause [{}]", new Object[] { id, queryString, runMode, stopReason, cause }); } result.purge(); } } @Override public QueryStopReason getStopReason() { return stopReason; } @Override public Throwable getCause() { return cause; } @Override public void stop() { stop(QueryStopReason.End); } @Override public void cancel(QueryStopReason reason) { stop(reason); } @Override public void cancel(Throwable cause) { stop(cause); } @Override public void stop(QueryStopReason reason) { // stop() at onPush() can cause deadlock without this guard. if (reason != QueryStopReason.End) { if (stopReason == null) stopReason = reason; // cancel tasks scheduler.cancel(); return; } if (!closed.compareAndSet(false, true)) return; try { if (stopReason == null) stopReason = reason; // stop tasks scheduler.stop(); // send eof and close result writer for (QueryCommand cmd : commands) { if (cmd.getStatus() == Status.Finalizing || cmd.getStatus() == Status.End) continue; cmd.setStatus(Status.Finalizing); try { cmd.tryClose(stopReason); } catch (Throwable t) { logger.error("araqne logdb: cannot close command " + cmd.getName(), t); } cmd.setStatus(Status.End); } try { if (result != null) result.closeWriter(); } catch (Throwable t) { logger.error("araqne logdb: cannot close query result", t); } } finally { stopLatch.countDown(); } } @Override public void stop(Throwable cause) { // onClose callback can fail (e.g. sort) even if driver is ended if (this.cause == null && cause != null) { this.cause = cause; this.stopReason = QueryStopReason.CommandFailure; } stop(QueryStopReason.CommandFailure); } @Override public Date getLastStarted() { return lastStarted; } @Override public Long getElapsedTime() { long end = System.currentTimeMillis(); if (result != null && result.getEofDate() != null) end = result.getEofDate().getTime(); if (getLastStarted() != null) return end - getLastStarted().getTime(); return null; } @Override public QueryResult getResult() { return result; } @Override public QueryResultSet getResultSet() throws IOException { if (result == null) return null; result.syncWriter(); return result.getResultSet(); } @Override public Long getResultCount() throws IOException { return result.getCount(); } @Override public List<Map<String, Object>> getResultAsList() throws IOException { return getResultAsList(0, Integer.MAX_VALUE); } @Override public List<Map<String, Object>> getResultAsList(long offset, int limit) throws IOException { LinkedList<Map<String, Object>> l = new LinkedList<Map<String, Object>>(); QueryResultSet rs = null; try { rs = getResultSet(); if (rs == null) return null; long p = 0; long count = 0; while (rs.hasNext()) { if (count >= limit) break; Map<String, Object> m = rs.next(); if (p++ < offset) continue; l.add(m); count++; } } finally { rs.close(); } return l; } @Override public List<QueryCommand> getCommands() { return commands; } @Override public QueryCallbacks getCallbacks() { return callbacks; } @Override public long getNextStamp() { return stamp.incrementAndGet(); } @Override public List<String> getFieldOrder() { return fieldOrder; } @Override public boolean isRunnable() { for (Query q : dependencies) if (!q.isFinished()) return false; return true; } @Override public void addDependency(Query query) { dependencies.add(query); } @Override public String toString() { return "id=" + id + ", query=" + queryString; } }