/* * 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.krakenapps.logdb.query; import java.io.IOException; import java.text.ParseException; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.atomic.AtomicInteger; import org.krakenapps.logdb.LogQuery; import org.krakenapps.logdb.LogQueryCallback; import org.krakenapps.logdb.LogQueryCommand; import org.krakenapps.logdb.LogQueryCommand.Status; import org.krakenapps.logdb.LogResultSet; import org.krakenapps.logdb.LogTimelineCallback; import org.krakenapps.logdb.SyntaxProvider; import org.krakenapps.logdb.query.command.Result; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class LogQueryImpl implements LogQuery { private Logger logger = LoggerFactory.getLogger(LogQueryImpl.class); private static AtomicInteger nextId = new AtomicInteger(1); private final int id = nextId.getAndIncrement(); private String queryString; private List<LogQueryCommand> commands = new ArrayList<LogQueryCommand>(); private Date lastStarted; private Result result; private Set<LogQueryCallback> logQueryCallbacks = new CopyOnWriteArraySet<LogQueryCallback>(); private Set<LogTimelineCallback> timelineCallbacks = new CopyOnWriteArraySet<LogTimelineCallback>(); public LogQueryImpl(SyntaxProvider syntaxProvider, String queryString) { this.queryString = queryString; for (String q : split(queryString)) { q = q.trim(); try { LogQueryCommand cmd = syntaxProvider.eval(this, q); commands.add(cmd); } catch (ParseException e) { throw new IllegalArgumentException("invalid query command: " + q); } } if (commands.isEmpty()) throw new IllegalArgumentException("empty query"); boolean setReducer = false; for (int i = 0; i < commands.size(); i++) { LogQueryCommand command = commands.get(i); if (i < commands.size() - 1) command.setNextCommand(commands.get(i + 1)); if (command.isReducer() && !setReducer && i > 0) { setReducer = true; commands.get(i - 1).setCallbackTimeline(true); } } if (!setReducer) commands.get(commands.size() - 1).setCallbackTimeline(true); } private static List<String> split(String query) { List<String> l = new ArrayList<String>(); StringBuilder sb = new StringBuilder(); char before = 0; boolean b = false; for (char c : query.toCharArray()) { if (c == '"' && before != '\\') { b = !b; sb.append(c); } else { if (c == '|' && !b) { l.add(sb.toString()); sb = new StringBuilder(); } else sb.append(c); } before = c; } if (sb.length() > 0) l.add(sb.toString()); return l; } @Override public void run() { if (!isEnd()) throw new IllegalStateException("already running"); lastStarted = new Date(); if (commands.isEmpty()) return; try { result = new Result(); result.setLogQuery(this); commands.get(commands.size() - 1).setNextCommand(result); for (LogQueryCallback callback : logQueryCallbacks) result.registerCallback(callback); logQueryCallbacks.clear(); logger.trace("kraken logdb: run query => {}", queryString); for (LogQueryCommand command : commands) command.init(); commands.get(0).start(); } catch (Exception e) { logger.error("kraken logdb: query failed - " + this, e); } } @Override public int getId() { return id; } @Override public String getQueryString() { return queryString; } @Override public boolean isEnd() { if (commands.size() == 0) return true; if (result == null) return true; if (commands.get(0).getStatus() == Status.Waiting) return true; return result.getStatus().equals(Status.End); } @Override public void purge() { // prevent deleted result file access caused by result check of query // callback or timeline callbacks clearTimelineCallbacks(); clearQueryCallbacks(); if (result != null) result.purge(); } @Override public void cancel() { if (result == null) return; if (result.getStatus() != Status.End && result.getStatus() != Status.Finalizing) result.eof(); for (int i = commands.size() - 1; i >= 0; i--) { LogQueryCommand command = commands.get(i); if (command.getStatus() != Status.End && command.getStatus() != Status.Finalizing) { command.eof(); } } } @Override public Date getLastStarted() { return lastStarted; } @Override public LogResultSet getResult() throws IOException { if (result != null) return result.getResult(); return null; } @Override public Long getResultCount() throws IOException { if (result == null) return null; LogResultSet rs = null; try { rs = result.getResult(); return rs.size(); } finally { if (rs != null) rs.close(); } } @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>>(); LogResultSet rs = getResult(); if (rs == null) return null; try { 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<LogQueryCommand> getCommands() { return commands; } @Override public Set<LogQueryCallback> getLogQueryCallback() { return Collections.unmodifiableSet(logQueryCallbacks); } @Override public void registerQueryCallback(LogQueryCallback callback) { logQueryCallbacks.add(callback); } @Override public void unregisterQueryCallback(LogQueryCallback callback) { logQueryCallbacks.add(callback); } @Override public void clearQueryCallbacks() { logQueryCallbacks.clear(); } @Override public Set<LogTimelineCallback> getTimelineCallbacks() { return Collections.unmodifiableSet(timelineCallbacks); } @Override public void registerTimelineCallback(LogTimelineCallback callback) { timelineCallbacks.add(callback); } @Override public void unregisterTimelineCallback(LogTimelineCallback callback) { timelineCallbacks.remove(callback); } @Override public void clearTimelineCallbacks() { timelineCallbacks.clear(); } @Override public String toString() { return "id=" + id + ", query=" + queryString; } }