/*
* Copyright 2013 Eediom Inc.
*
* 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.client;
import java.io.Closeable;
import java.io.IOException;
import java.net.SocketException;
import java.nio.ByteBuffer;
import java.text.ParseException;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicReference;
import java.util.zip.DataFormatException;
import java.util.zip.Inflater;
import org.araqne.api.PrimitiveConverter;
import org.araqne.codec.EncodingRule;
import org.araqne.codec.FastEncodingRule;
import org.araqne.logdb.client.http.WebSocketTransport;
import org.araqne.logdb.client.http.impl.StreamingResultDecoder;
import org.araqne.logdb.client.http.impl.StreamingResultEncoder;
import org.araqne.logdb.client.http.impl.TrapListener;
import org.araqne.websocket.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* <p>
* 쿼리 실행, 로그 수집 설정, 트랜스포머 설정, 테이블, 인덱스, 스트림 쿼리 설정, 예약된 쿼리 설정, 계정 관리 등 로그프레소를
* 원격으로 제어하는데 필요한 모든 기능을 제공합니다.
* </p>
*
* <p>
* 아래는 접속과 쿼리를 수행하는 간단한 예시입니다:
* </p>
*
* <pre>
* {@link LogDbClient} client = null;
* {@link LogCursor} cursor = null;
*
* try {
* client = new LogDbClient();
* client.connect("localhost", 8888, "araqne", "");
* cursor = client.query("logdb tables");
*
* while (cursor.hasNext()) {
* System.out.println(cursor.next());
* }
* } finally {
* if (cursor != null)
* cursor.close();
*
* if (client != null)
* client.close();
* }
* </pre>
*
* @since 0.5.0
* @author xeraph@eediom.com
*
*/
public class LogDbClient implements TrapListener, Closeable {
private static final int MAX_THROTTLE_PERMIT = 100000;
private Logger logger = LoggerFactory.getLogger(LogDbClient.class);
private LogDbTransport transport;
private LogDbSession session;
private int fetchSize = 10000;
private ConcurrentMap<Integer, LogQuery> queries = new ConcurrentHashMap<Integer, LogQuery>();
private ConcurrentMap<Integer, StreamingResultSet> streamCallbacks = new ConcurrentHashMap<Integer, StreamingResultSet>();
private Locale locale = Locale.getDefault();
private StreamingResultDecoder streamingDecoder;
private AtomicReference<Flusher> flusher = new AtomicReference<Flusher>(null);
private StreamingResultEncoder streamingEncoder;
private Semaphore inputThrottler = new Semaphore(MAX_THROTTLE_PERMIT);
// private int counter = 0;
private int insertBatchSize = 3500;
// milliseconds
private int indexFlushInterval = 1000;
// table name to row list mappings
private Map<String, List<QueuedRows>> flushBuffers = new HashMap<String, List<QueuedRows>>();
private CopyOnWriteArraySet<FailureListener> failureListeners = new CopyOnWriteArraySet<FailureListener>();
public LogDbClient() {
this(new WebSocketTransport());
}
public LogDbClient(LogDbTransport transport) {
this.transport = transport;
int poolSize = Math.min(8, Runtime.getRuntime().availableProcessors());
this.streamingDecoder = new StreamingResultDecoder("Streaming Result Decoder", poolSize);
this.streamingEncoder = new StreamingResultEncoder("Streaming Result Incoder", poolSize);
}
public Locale getLocale() {
return locale;
}
public void setLocale(Locale locale) {
checkNotNull("locale", locale);
this.locale = locale;
}
/**
* 현재 접속된 세션 개체를 반환합니다.
*
* @since 0.9.1
*/
public LogDbSession getSession() {
return session;
}
/**
* 현재 커서가 서버에서 한 번에 조회하는 행의 갯수를 조회합니다. 기본값은 10000입니다.
*
* @since 0.6.0
*/
public int getFetchSize() {
return fetchSize;
}
/**
* 커서가 서버에서 한 번에 조회하는 행의 갯수를 설정합니다. 이 수치가 클수록 RPC 통신 횟수가 줄어들지만, 반대로 클라이언트와
* 서버의 메모리 소모와 RPC 통신 시의 소요 시간이 증가합니다. 반대로 너무 작으면 RPC 통신 회수가 증가하므로 쿼리 결과를
* 가져오는 속도가 느려질 수 있습니다.
*
* @param fetchSize
* RPC 호출마다 가져오는 행의 갯수
* @since 0.6.0
*/
public void setFetchSize(int fetchSize) {
this.fetchSize = fetchSize;
}
public int getInsertFetchSize() {
return insertBatchSize;
}
public void setInserFetchSize(int insertFetchSize) {
if (insertFetchSize < 0 || insertFetchSize > 200000)
throw new IllegalArgumentException("InsertFetchSize should be > 0 and < 200000");
this.insertBatchSize = insertFetchSize;
}
// index flush interval (ms)
public int getIndexFlushInterval() {
return indexFlushInterval;
}
public void setIndexFlushInterval(int millisec) {
if (millisec < 0)
throw new IllegalArgumentException("Index flush interval should be greater than 0");
this.indexFlushInterval = millisec;
if (flusher.get() != null)
flusher.get().signal();
}
/**
* 연결 해제 상태 여부를 조회합니다.
*
* @return 접속한 적이 없거나, 기존 연결이 닫힌 경우 true를 반환합니다.
*/
public boolean isClosed() {
return session == null || session.isClosed();
}
/**
* 현재 세션에서 실행 중인 로그 쿼리 목록을 조회합니다. 포어그라운드와 백그라운드 실행 중인 모든 쿼리 정보가 반환됩니다.
*
* @return 현재 세션에서 실행 중인 로그 쿼리 목록이 LogQuery 개체의 리스트로 반환됩니다.
*/
public List<LogQuery> getQueries() throws IOException {
Message resp = rpc("org.araqne.logdb.msgbus.LogQueryPlugin.queries");
@SuppressWarnings("unchecked")
List<Map<String, Object>> l = (List<Map<String, Object>>) resp.getParameters().get("queries");
for (Map<String, Object> q : l) {
int queryId = (Integer) q.get("id");
LogQuery query = queries.get(queryId);
if (query == null) {
query = new LogQuery(this, queryId, (String) q.get("query_string"));
LogQuery old = queries.putIfAbsent(queryId, query);
if (old != null)
query = old;
}
parseQueryStatus(q, query);
}
return new ArrayList<LogQuery>(queries.values());
}
/**
* 특정 쿼리 ID에 대응하는 쿼리 실행 정보를 조회합니다. 호출 시마다 RPC 호출이 발생하면서 쿼리 상태 정보를 갱신합니다. 지정된
* 쿼리 ID가 존재하지 않거나 액세스 권한이 없는 경우 예외가 발생합니다.
*
* @param id
* 쿼리 ID
* @return 갱신된 쿼리 실행 정보를 담고 있는 LogQuery 개체가 반환됩니다.
*/
public LogQuery getQuery(int id) throws IOException {
Map<String, Object> params = new HashMap<String, Object>();
params.put("id", id);
try {
Message resp = rpc("org.araqne.logdb.msgbus.LogQueryPlugin.queryStatus", params);
Map<String, Object> q = resp.getParameters();
int queryId = (Integer) q.get("id");
LogQuery query = queries.get(queryId);
if (query == null) {
query = new LogQuery(this, queryId, (String) q.get("query_string"));
LogQuery old = queries.putIfAbsent(queryId, query);
if (old != null)
query = old;
}
parseQueryStatus(q, query);
} catch (MessageException t) {
if (!t.getMessage().startsWith("msgbus-handler-not-found"))
throw t;
}
return queries.get(id);
}
@SuppressWarnings("unchecked")
private void parseQueryStatus(Map<String, Object> q, LogQuery query) {
List<LogQueryCommand> commands = new ArrayList<LogQueryCommand>();
List<Map<String, Object>> cl = (List<Map<String, Object>>) q.get("commands");
for (Map<String, Object> cm : cl) {
commands.add(parseCommand(cm));
}
long stamp = 0;
if (q.containsKey("stamp"))
stamp = Long.parseLong(q.get("stamp").toString());
query.setCommands(commands);
boolean end = (Boolean) q.get("is_end");
boolean eof = end;
if (q.containsKey("is_eof"))
eof = (Boolean) q.get("is_eof");
boolean cancelled = false;
if (q.containsKey("is_cancelled"))
cancelled = (Boolean) q.get("is_cancelled");
if (eof) {
if (!query.getCommands().get(0).getStatus().equalsIgnoreCase("Waiting"))
query.updateStatus("Ended", stamp);
if (cancelled)
query.updateStatus("Cancelled", stamp);
} else if (end) {
query.updateStatus("Stopped", stamp);
} else {
query.updateStatus("Running", stamp);
}
if (q.containsKey("background"))
query.setBackground((Boolean) q.get("background"));
query.setElapsed(toLong(q.get("elapsed")));
Long startTime = toLong(q.get("start_time"));
Long finishTime = toLong(q.get("finish_time"));
if (startTime != null && startTime != 0)
query.setStartTime(new Date(startTime));
if (finishTime != null && finishTime != 0)
query.setFinishTime(new Date(finishTime));
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ssZ");
if (q.get("last_started") != null)
query.setLastStarted(df.parse((String) q.get("last_started"), new ParsePosition(0)));
List<Object> subQueries = (List<Object>) q.get("sub_queries");
if (subQueries != null) {
for (Object o : subQueries) {
SubQuery subQuery = parseSubQuery((Map<String, Object>) o);
query.getSubQueries().add(subQuery);
}
}
}
@SuppressWarnings("unchecked")
private SubQuery parseSubQuery(Map<String, Object> m) {
SubQuery q = new SubQuery();
q.setId((Integer) m.get("id"));
List<Object> l = (List<Object>) m.get("commands");
if (l != null) {
for (Object o : l)
q.getCommands().add(parseCommand((Map<String, Object>) o));
}
return q;
}
@SuppressWarnings("unchecked")
private LogQueryCommand parseCommand(Map<String, Object> m) {
LogQueryCommand c = new LogQueryCommand();
c.setName((String) m.get("name"));
c.setStatus((String) m.get("status"));
c.setPushCount(toLong(m.get("push_count")));
c.setCommand((String) m.get("command"));
List<Object> l = (List<Object>) m.get("commands");
if (l != null) {
for (Object o : l)
c.getCommands().add(parseCommand((Map<String, Object>) o));
}
return c;
}
private Long toLong(Object v) {
if (v == null)
return null;
if (v instanceof Integer)
return (long) (Integer) v;
if (v instanceof Long)
return (Long) v;
return null;
}
/**
* 로그프레소 서버에 접속을 시도합니다. 잘못된 IP 주소, DNS 조회 실패, 방화벽 문제 혹은 암호 실패 등의 원인으로 접속 실패
* 시 IOException 예외가 발생합니다.
*
* @param host
* 도메인 혹은 IP 주소
* @param loginName
* DB 계정
* @param password
* DB 암호
*/
public void connect(String host, String loginName, String password) throws IOException {
connect(host, 8888, loginName, password);
}
public void connect(String host, int port, String loginName, String password) throws IOException {
connect(host, port, loginName, password, 0, 10000);
}
public void connect(String host, int port, String loginName, String password, int connectTimeout) throws IOException {
connect(host, port, loginName, password, connectTimeout, 10000);
}
/**
* 로그프레소 서버에 접속을 시도합니다. 잘못된 IP 주소, DNS 조회 실패, 방화벽 문제 혹은 암호 실패 등의 원인으로 접속 실패
* 시 IOException 예외가 발생합니다.
*
* @param host
* 도메인 혹은 IP 주소
* @param port
* 포트 번호
* @param loginName
* DB 계정
* @param password
* DB 암호
*/
public void connect(String host, int port, String loginName, String password, int connectTimeout, int readTimeout)
throws IOException {
this.session = transport.newSession(host, port, connectTimeout, readTimeout);
try {
this.session.login(loginName, password, true);
this.session.addListener(this);
} catch (IOException e) {
this.session.close();
this.session = null;
throw e;
} catch (LoginFailureException e) {
this.session.close();
this.session = null;
throw e;
} catch (Throwable t) {
this.session.close();
this.session = null;
throw new IllegalStateException(t);
}
}
/**
* 로그 저장 설정 개체 목록을 조회합니다.
*
* @return 저장 설정 개체의 리스트
*/
@SuppressWarnings("unchecked")
public List<ArchiveConfig> listArchiveConfigs() throws IOException {
List<ArchiveConfig> configs = new ArrayList<ArchiveConfig>();
Message resp = rpc("com.logpresso.core.msgbus.ArchivePlugin.getConfigs");
List<Map<String, Object>> l = (List<Map<String, Object>>) resp.get("configs");
for (Map<String, Object> m : l) {
configs.add(parseArchiveConfig(m));
}
return configs;
}
/**
* 지정된 로그 수집기 이름과 대응하는 저장 설정을 조회합니다. 로그 수집기가 존재하지 않는 경우 예외가 발생합니다.
*
* @param loggerName
* 이름공간\이름 형식의 로그 수집기 이름 (NULL 허용 안 함)
* @return 지정된 로그 이름과 대응하는 저장 설정 개체
*/
@SuppressWarnings("unchecked")
public ArchiveConfig getArchiveConfig(String loggerName) throws IOException {
Map<String, Object> params = new HashMap<String, Object>();
params.put("logger", loggerName);
Message resp = rpc("com.logpresso.core.msgbus.ArchivePlugin.getConfig", params);
Map<String, Object> m = (Map<String, Object>) resp.getParameters().get("config");
return parseArchiveConfig(m);
}
@SuppressWarnings("unchecked")
private ArchiveConfig parseArchiveConfig(Map<String, Object> m) {
ArchiveConfig c = new ArchiveConfig();
c.setLoggerName((String) m.get("logger"));
c.setTableName((String) m.get("table"));
c.setHost((String) m.get("host"));
c.setPrimaryLogger((String) m.get("primary_logger"));
c.setBackupLogger((String) m.get("backup_logger"));
c.setEnabled((Boolean) m.get("enabled"));
c.setMetadata((Map<String, String>) m.get("metadata"));
return c;
}
/**
* 새 로그 저장 설정을 생성합니다. 로그 수집기에서 수집되는 모든 로그를 지정된 테이블에 저장하도록 설정합니다. 로그 수집기 이름이
* 중복되는 경우에 예외가 발생합니다. 테이블이 존재하지 않으면 저장 설정의 메타데이터를 이용하여 테이블이 자동 생성됩니다.
*
* @param config
* 로그 저장 설정 (NULL 허용 안 함)
*/
public void createArchiveConfig(ArchiveConfig config) throws IOException {
Map<String, Object> params = new HashMap<String, Object>();
params.put("logger", config.getLoggerName());
params.put("table", config.getTableName());
params.put("host", config.getHost());
params.put("enabled", config.isEnabled());
params.put("metadata", config.getMetadata());
rpc("com.logpresso.core.msgbus.ArchivePlugin.createConfig", params);
}
/**
* 지정된 로그 수집기 이름으로 된 저장 설정을 삭제합니다. 지정된 로그 수집기 이름의 설정이 존재하지 않으면 예외가 발생합니다.
*
* @param loggerName
* 이름공간\이름 형식의 로그 수집기 이름 (NULL 허용 안 함)
*/
public void removeArchiveConfig(String loggerName) throws IOException {
Map<String, Object> params = new HashMap<String, Object>();
params.put("logger", loggerName);
rpc("com.logpresso.core.msgbus.ArchivePlugin.removeConfig", params);
}
/**
* DB 계정 목록을 조회합니다.
*
* @return 계정정보 목록
*/
@SuppressWarnings("unchecked")
public List<AccountInfo> listAccounts() throws IOException {
Message resp = rpc("org.araqne.logdb.msgbus.ManagementPlugin.listAccounts");
List<AccountInfo> accounts = new ArrayList<AccountInfo>();
List<Object> l = (List<Object>) resp.get("accounts");
for (Object o : l) {
Map<String, Object> m = (Map<String, Object>) o;
List<Object> pl = (List<Object>) m.get("privileges");
AccountInfo account = new AccountInfo();
String loginName = (String) m.get("login_name");
account.setLoginName(loginName);
for (Object o2 : pl) {
Map<String, Object> m2 = (Map<String, Object>) o2;
String tableName = (String) m2.get("table_name");
Privilege p = new Privilege(loginName, tableName);
account.getPrivileges().add(p);
}
accounts.add(account);
}
return accounts;
}
/**
* 새로운 DB 계정을 생성합니다. 관리자 계정이 아니거나 계정 이름이 중복된 경우 예외가 발생합니다.
*
* @param account
* 새로 생성할 계정 정보 (NULL 허용 안 함)
*/
public void createAccount(AccountInfo account) throws IOException {
Map<String, Object> params = new HashMap<String, Object>();
params.put("login_name", account.getLoginName());
params.put("password", account.getPassword());
rpc("org.araqne.logdb.msgbus.ManagementPlugin.createAccount", params);
}
/**
* 기존 DB 계정을 삭제합니다. 관리자 계정이 아니거나 계정이 존재하지 않는 경우 예외가 발생합니다.
*
* @param loginName
* 삭제할 계정 이름 (NULL 허용 안 함)
*/
public void removeAccount(String loginName) throws IOException {
Map<String, Object> params = new HashMap<String, Object>();
params.put("login_name", loginName);
rpc("org.araqne.logdb.msgbus.ManagementPlugin.removeAccount", params);
}
/**
* DB 계정의 암호를 변경합니다. 관리자 계정이거나 자신의 계정 암호인 경우에만 변경 가능하고, 그 외의 경우에는 예외가 발생합니다.
* 존재하지 않는 계정인 경우에도 예외가 발생합니다.
*
* @param loginName
* DB 계정 이름 (NULL 허용 안 함)
* @param password
* 새 암호 (NULL 허용 안 함)
*/
public void changePassword(String loginName, String password) throws IOException {
Map<String, Object> params = new HashMap<String, Object>();
params.put("login_name", loginName);
params.put("password", password);
rpc("org.araqne.logdb.msgbus.ManagementPlugin.changePassword", params);
}
/**
* 계정에 테이블 접근 권한을 부여합니다. 관리자 계정이 아니거나, 권한을 부여할 계정이 존재하지 않거나, 테이블이 존재하지 않는 경우
* 예외가 발생합니다.
*
* @param privilege
* 권한을 부여할 계정과 테이블 매핑 (NULL 허용 안 함)
*/
public void grantPrivilege(Privilege privilege) throws IOException {
Map<String, Object> params = new HashMap<String, Object>();
params.put("login_name", privilege.getLoginName());
params.put("table_name", privilege.getTableName());
rpc("org.araqne.logdb.msgbus.ManagementPlugin.grantPrivilege", params);
}
/**
* 계정의 테이블 접근 권한을 박탈합니다. 관리자 계정이 아니거나, 권한을 박탈할 계정이 존재하지 않거나, 테이블이 존재하지 않는 경우
* 예외가 발생합니다.
*
* @param privilege
* 권한을 박탈할 계정과 테이블 매핑 (NULL 허용 안 함)
*/
public void revokePrivilege(Privilege privilege) throws IOException {
Map<String, Object> params = new HashMap<String, Object>();
params.put("login_name", privilege.getLoginName());
params.put("table_name", privilege.getTableName());
rpc("org.araqne.logdb.msgbus.ManagementPlugin.revokePrivilege", params);
}
/**
* 보안 그룹 목록을 조회합니다. 보안 그룹에 속한 계정 목록과 테이블 권한 정보는 조회되지 않습니다.
*
* @since 1.1.3
*/
@SuppressWarnings("unchecked")
public List<SecurityGroupInfo> listSecurityGroups() throws IOException {
List<SecurityGroupInfo> groups = new ArrayList<SecurityGroupInfo>();
Message resp = rpc("org.araqne.logdb.msgbus.ManagementPlugin.listSecurityGroups");
List<Object> l = (List<Object>) resp.get("security_groups");
for (Object o : l) {
groups.add(parseSecurityGroup((Map<String, Object>) o));
}
return groups;
}
/**
* 보안 그룹을 조회합니다. 계정 목록과 테이블 권한 정보를 포함합니다.
*
* @param guid
* 보안그룹 식별자
*/
@SuppressWarnings("unchecked")
public SecurityGroupInfo getSecurityGroup(String guid) throws IOException {
Map<String, Object> params = new HashMap<String, Object>();
params.put("guid", guid);
Message resp = rpc("org.araqne.logdb.msgbus.ManagementPlugin.getSecurityGroup", params);
Map<String, Object> o = (Map<String, Object>) resp.get("group");
if (o == null)
return null;
return parseSecurityGroup(o);
}
/**
* 새 보안 그룹을 생성합니다.
*
* @throws IOException
*
* @since 1.1.3
*/
public void createSecurityGroup(SecurityGroupInfo group) throws IOException {
Map<String, Object> params = buildSecurityGroupRequest(group);
rpc("org.araqne.logdb.msgbus.ManagementPlugin.createSecurityGroup", params);
}
/**
* 보안 그룹 설정을 수정합니다.
*
* @since 1.1.3
*/
public void updateSecurityGroup(SecurityGroupInfo group) throws IOException {
Map<String, Object> params = buildSecurityGroupRequest(group);
rpc("org.araqne.logdb.msgbus.ManagementPlugin.updateSecurityGroup", params);
}
private Map<String, Object> buildSecurityGroupRequest(SecurityGroupInfo group) {
checkNotNull("name", group.getName());
checkNotNull("guid", group.getGuid());
Map<String, Object> m = new HashMap<String, Object>();
m.put("guid", group.getGuid());
m.put("name", group.getName());
m.put("description", group.getDescription());
m.put("accounts", group.getAccounts());
m.put("table_names", group.getReadableTables());
return m;
}
/**
* 보안 그룹을 삭제합니다.
*
* @param guid
* 보안그룹 식별자
* @since 1.1.3
*/
public void removeSecurityGroup(String guid) throws IOException {
Map<String, Object> params = new HashMap<String, Object>();
params.put("group_guids", Arrays.asList(guid));
rpc("org.araqne.logdb.msgbus.ManagementPlugin.removeSecurityGroups", params);
}
@SuppressWarnings("unchecked")
private SecurityGroupInfo parseSecurityGroup(Map<String, Object> o) {
List<String> accounts = (List<String>) o.get("accounts");
List<String> readableTables = (List<String>) o.get("table_names");
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ssZ");
SecurityGroupInfo g = new SecurityGroupInfo();
g.setGuid((String) o.get("guid"));
g.setName((String) o.get("name"));
g.setDescription((String) o.get("description"));
if (accounts != null)
g.setAccounts(new HashSet<String>(accounts));
if (readableTables != null)
g.setReadableTables(new HashSet<String>(readableTables));
g.setCreated(df.parse((String) o.get("created"), new ParsePosition(0)));
g.setUpdated(df.parse((String) o.get("updated"), new ParsePosition(0)));
return g;
}
/**
* 인덱스 토크나이저 유형 목록을 조회합니다. 일반적으로 createIndex() 할 때 사용자에게 설정 가능한 인덱스 토크나이저
* 목록을 보여주기 위해서 호출합니다. 관리자 권한이 없는 경우 예외가 발생합니다.
*
* @return 인덱스 토크나이저 유형 목록이 리스트로 반환됩니다.
*/
@SuppressWarnings("unchecked")
public List<IndexTokenizerFactoryInfo> listIndexTokenizerFactories() throws IOException {
Map<String, Object> params = new HashMap<String, Object>();
params.put("locale", locale.getLanguage());
Message resp = rpc("com.logpresso.index.msgbus.ManagementPlugin.listIndexTokenizerFactories", params);
List<IndexTokenizerFactoryInfo> l = new ArrayList<IndexTokenizerFactoryInfo>();
for (Object o : (List<Object>) resp.getParameters().get("factories")) {
IndexTokenizerFactoryInfo f = parseIndexTokenizerFactory(o);
l.add(f);
}
return l;
}
/**
* 지정된 이름을 가진 인덱스 토크나이저 유형을 조회합니다. 관리자 권한이 없는 경우 예외가 발생합니다.
*
* @param name
* 인덱스 토크나이저 유형 이름 (NULL 허용 안 함)
* @return 지정된 이름을 가진 인덱스 토크나이저 유형 개체를 반환합니다.
*/
public IndexTokenizerFactoryInfo getIndexTokenizerFactory(String name) throws IOException {
Map<String, Object> params = new HashMap<String, Object>();
params.put("name", name);
params.put("locale", locale.getLanguage());
Message resp = rpc("com.logpresso.index.msgbus.ManagementPlugin.getIndexTokenizerFactoryInfo", params);
return parseIndexTokenizerFactory(resp.getParameters().get("factory"));
}
@SuppressWarnings("unchecked")
private IndexTokenizerFactoryInfo parseIndexTokenizerFactory(Object o) {
Map<String, Object> m = (Map<String, Object>) o;
IndexTokenizerFactoryInfo f = new IndexTokenizerFactoryInfo();
f.setName((String) m.get("name"));
f.setConfigSpecs(parseIndexConfigList((List<Object>) m.get("config_specs")));
return f;
}
@SuppressWarnings("unchecked")
private List<IndexConfigSpec> parseIndexConfigList(List<Object> l) {
List<IndexConfigSpec> specs = new ArrayList<IndexConfigSpec>();
for (Object o : l) {
Map<String, Object> m = (Map<String, Object>) o;
IndexConfigSpec spec = new IndexConfigSpec();
spec.setKey((String) m.get("key"));
spec.setName((String) m.get("name"));
spec.setDescription((String) m.get("description"));
spec.setRequired((Boolean) m.get("required"));
specs.add(spec);
}
return specs;
}
/**
* 인덱스 목록을 반환합니다. 관리자 권한이 없는 경우 예외가 발생합니다.
*
* @param tableName
* 테이블 이름 (NULL 허용), NULL인 경우 전체 인덱스 목록을, 테이블 이름이 지정된 경우에는 테이블
* 이름으로 필터링된 결과를 반환합니다.
* @return 인덱스 정보 개체의 리스트를 반환합니다.
*/
@SuppressWarnings("unchecked")
public List<IndexInfo> listIndexes(String tableName) throws IOException {
Map<String, Object> params = new HashMap<String, Object>();
params.put("table", tableName);
Message resp = rpc("com.logpresso.index.msgbus.ManagementPlugin.listIndexes", params);
List<IndexInfo> indexes = new ArrayList<IndexInfo>();
List<Object> l = (List<Object>) resp.getParameters().get("indexes");
for (Object o : l) {
Map<String, Object> m = (Map<String, Object>) o;
IndexInfo indexInfo = getIndexInfo(m);
indexes.add(indexInfo);
}
return indexes;
}
/**
* 주어진 테이블 및 인덱스 이름과 일치하는 인덱스 정보를 반환합니다. 관리자 권한이 없거나 해당 인덱스가 존재하지 않는 경우 예외가
* 발생합니다.
*
* @param tableName
* 테이블 이름 (NULL 허용 안 함)
* @param indexName
* 인덱스 이름 (NULL 허용 안 함)
* @return 주어진 테이블 및 인덱스 이름과 일치하는 인덱스 정보 개체를 반환합니다.
*/
@SuppressWarnings("unchecked")
public IndexInfo getIndexInfo(String tableName, String indexName) throws IOException {
Map<String, Object> params = new HashMap<String, Object>();
params.put("table", tableName);
params.put("index", indexName);
Message resp = rpc("com.logpresso.index.msgbus.ManagementPlugin.getIndexInfo", params);
return getIndexInfo((Map<String, Object>) resp.getParameters().get("index"));
}
/**
* 특정 인덱스에 데이터가 입력될 때 풀텍스트 토큰이 어떻게 추출되는지 시험합니다. 정상적으로 인덱스가 설정되었는지, 의도한대로
* 풀텍스트 인덱싱이 진행되는지 확인할 용도로 사용합니다.
*
* @param tableName
* 테이블 이름 (NULL 허용 안 함)
* @param indexName
* 인덱스 이름 (NULL 허용 안 함)
* @param data
* 키/값 쌍으로 구성된 원본 데이터 (NULL 허용 안 함), 일반적으로 텍스트 파일에서 데이터를 읽어오거나
* 시스로그와 같은 경우 원본 데이터가 line 키와 값으로 구성됩니다. SNMP 트랩의 경우 OID와 변수 바인딩
* 목록, 윈도우 이벤트 로그의 경우 이벤트 로그 필드 이름과 값 목록이 데이터 원본 키/값 쌍으로 반영됩니다.
* @return 추출된 풀텍스트 토큰 문자열 집합
* @since 0.8.1
*/
@SuppressWarnings("unchecked")
public Set<String> testIndexTokenizer(String tableName, String indexName, Map<String, Object> data) throws IOException {
Map<String, Object> params = new HashMap<String, Object>();
params.put("table", tableName);
params.put("index", indexName);
params.put("data", data);
Message resp = rpc("com.logpresso.index.msgbus.ManagementPlugin.testIndexTokenizer", params);
return new HashSet<String>((List<String>) resp.getParameters().get("tokens"));
}
@SuppressWarnings("unchecked")
private IndexInfo getIndexInfo(Map<String, Object> m) {
IndexInfo index = new IndexInfo();
index.setTableName((String) m.get("table"));
index.setIndexName((String) m.get("index"));
index.setTokenizerName((String) m.get("tokenizer_name"));
index.setTokenizerConfigs((Map<String, String>) m.get("tokenizer_configs"));
try {
SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd HH:mm:ssZ");
String s = (String) m.get("min_index_day");
if (s != null)
index.setMinIndexDay(f.parse(s));
} catch (ParseException e) {
}
index.setUseBloomFilter((Boolean) m.get("use_bloom_filter"));
index.setBloomFilterCapacity0((Integer) m.get("bf_lv0_capacity"));
index.setBloomFilterErrorRate0((Double) m.get("bf_lv0_error_rate"));
index.setBloomFilterCapacity1((Integer) m.get("bf_lv1_capacity"));
index.setBloomFilterErrorRate1((Double) m.get("bf_lv1_error_rate"));
index.setBasePath((String) m.get("base_path"));
index.setBuildPastIndex((Boolean) m.get("build_past_index"));
return index;
}
/**
* 새 인덱스를 생성합니다. 관리자 권한이 없거나, 테이블이 존재하지 않거나, 인덱스 이름이 중복되는 경우 예외가 발생합니다.
*
* @param info
* 새 인덱스를 생성하는데 필요한 설정 (NULL 허용 안 함)
*/
public void createIndex(IndexInfo info) throws IOException {
Map<String, Object> params = new HashMap<String, Object>();
params.put("table", info.getTableName());
params.put("index", info.getIndexName());
params.put("tokenizer_name", info.getTokenizerName());
params.put("tokenizer_configs", info.getTokenizerConfigs());
params.put("base_path", info.getBasePath());
params.put("use_bloom_filter", info.isUseBloomFilter());
params.put("bf_lv0_capacity", info.getBloomFilterCapacity0());
params.put("bf_lv0_error_rate", info.getBloomFilterErrorRate0());
params.put("bf_lv1_capacity", info.getBloomFilterCapacity1());
params.put("bf_lv1_error_rate", info.getBloomFilterErrorRate1());
params.put("min_index_day", info.getMinIndexDay());
params.put("build_past_index", info.isBuildPastIndex());
rpc("com.logpresso.index.msgbus.ManagementPlugin.createIndex", params);
}
/**
* 인덱스를 삭제합니다. 진행 중인 백그라운드 배치 인덱스 생성 작업은 취소됩니다. 관리자 권한이 없거나 테이블이나 인덱스가 존재하지
* 않는 경우 예외가 발생합니다.
*
* @param tableName
* 테이블 이름 (NULL 허용 안 함)
* @param indexName
* 인덱스 이름 (NULL 허용 안 함)
*/
public void dropIndex(String tableName, String indexName) throws IOException {
Map<String, Object> params = new HashMap<String, Object>();
params.put("table", tableName);
params.put("index", indexName);
rpc("com.logpresso.index.msgbus.ManagementPlugin.dropIndex", params);
}
/**
* 액세스 가능한 전체 테이블 목록을 반환합니다.
*
* @return 테이블 정보를 담은 TableInfo 개체의 리스트
*/
@SuppressWarnings("unchecked")
public List<TableSchemaInfo> listTables() throws IOException {
Message resp = rpc("org.araqne.logdb.msgbus.ManagementPlugin.listTables");
List<TableSchemaInfo> tables = new ArrayList<TableSchemaInfo>();
Map<String, Object> schemaMap = (Map<String, Object>) resp.get("schemas");
if (schemaMap != null) {
for (String tableName : schemaMap.keySet()) {
Map<String, Object> schema = (Map<String, Object>) schemaMap.get(tableName);
tables.add(parseSchema(schema));
}
} else {
// support backward-compatibility
Map<String, Object> metadataMap = (Map<String, Object>) resp.get("tables");
Map<String, Object> fieldsMap = (Map<String, Object>) resp.get("fields");
for (String tableName : metadataMap.keySet()) {
Map<String, Object> params = (Map<String, Object>) metadataMap.get(tableName);
List<Object> fields = (List<Object>) fieldsMap.get(tableName);
TableSchemaInfo tableInfo = getTableInfo(tableName, params, fields);
tables.add(tableInfo);
}
}
return tables;
}
@SuppressWarnings("unchecked")
private TableSchemaInfo parseSchema(Map<String, Object> schema) {
TableSchemaInfo s = new TableSchemaInfo();
s.setName((String) schema.get("name"));
if (schema.get("id") != null)
s.setId((Integer) schema.get("id"));
s.setMetadata((Map<String, String>) schema.get("metadata"));
s.setPrimaryStorage(parseStorageConfig((Map<String, Object>) schema.get("primary_storage")));
s.setReplicaStorage(parseStorageConfig((Map<String, Object>) schema.get("replica_storage")));
List<Map<String, Object>> l = (List<Map<String, Object>>) schema.get("secondary_storages");
if (l != null) {
List<StorageEngineConfig> secondaryStorages = new ArrayList<StorageEngineConfig>();
for (Map<String, Object> m : l)
secondaryStorages.add(parseStorageConfig(m));
s.setSecondaryStorages(secondaryStorages);
}
List<String> fieldList = (List<String>) schema.get("fields");
if (fieldList != null) {
List<FieldInfo> fields = new ArrayList<FieldInfo>();
for (String def : fieldList)
fields.add(FieldInfo.parse(def));
s.setFieldDefinitions(fields);
}
return s;
}
@SuppressWarnings("unchecked")
private StorageEngineConfig parseStorageConfig(Map<String, Object> m) {
if (m == null)
return null;
List<TableConfig> configs = new ArrayList<TableConfig>();
// parse configs
Map<String, Object> configMap = (Map<String, Object>) m.get("configs");
for (String key : configMap.keySet()) {
Object o = configMap.get(key);
if (o instanceof String) {
configs.add(new TableConfig(key, o.toString()));
} else {
TableConfig c = new TableConfig();
c.setKey(key);
for (Object s : (List<Object>) o)
c.getValues().add(s.toString());
configs.add(c);
}
}
StorageEngineConfig c = new StorageEngineConfig();
c.setType((String) m.get("type"));
c.setBasePath((String) m.get("base_path"));
c.setConfigs(configs);
return c;
}
/**
* 특정 테이블 정보를 반환합니다. 테이블이 존재하지 않거나, 액세스 권한이 없는 경우 예외가 발생합니다.
*
* @param tableName
* 조회할 테이블 이름 (NULL 허용 안 함)
* @return 테이블 이름과 대응되는 테이블 정보
*/
@SuppressWarnings("unchecked")
public TableSchemaInfo getTableInfo(String tableName) throws IOException {
Map<String, Object> params = new HashMap<String, Object>();
params.put("table", tableName);
Message resp = rpc("org.araqne.logdb.msgbus.ManagementPlugin.getTableInfo", params);
Map<String, Object> schema = (Map<String, Object>) resp.get("schema");
if (schema != null) {
return parseSchema(schema);
} else {
// support backward compatibility
return getTableInfo(tableName, (Map<String, Object>) resp.get("table"), (List<Object>) resp.get("fields"));
}
}
private TableSchemaInfo getTableInfo(String tableName, Map<String, Object> params, List<Object> fields) {
Map<String, String> metadata = new HashMap<String, String>();
for (Entry<String, Object> pair : params.entrySet())
metadata.put(pair.getKey(), pair.getValue() == null ? null : pair.getValue().toString());
List<FieldInfo> fieldDefs = null;
if (fields != null) {
fieldDefs = new ArrayList<FieldInfo>();
for (Object o : fields) {
@SuppressWarnings("unchecked")
Map<String, Object> m = (Map<String, Object>) o;
FieldInfo f = new FieldInfo();
f.setType((String) m.get("type"));
f.setName((String) m.get("name"));
f.setLength((Integer) m.get("length"));
fieldDefs.add(f);
}
}
TableSchemaInfo t = new TableSchemaInfo(tableName, metadata);
t.setFieldDefinitions(fieldDefs);
return t;
}
/***
* 테이블 스키마를 설정합니다. 필드 정의는 실제 데이터 적재 및 쿼리에 제약을 가하지 않으며, UI 지원용으로만 사용됩니다.
*
* @param tableName
* 테이블 이름
* @param fields
* 필드 정의 목록
* @since 0.9.0 and logdb 2.0.3
*/
public void setTableFields(String tableName, List<FieldInfo> fields) throws IOException {
if (tableName == null)
throw new IllegalArgumentException("table name cannot be null");
List<Object> l = null;
if (fields != null) {
l = new ArrayList<Object>();
for (FieldInfo f : fields) {
Map<String, Object> m = new HashMap<String, Object>();
m.put("name", f.getName());
m.put("type", f.getType());
m.put("length", f.getLength());
l.add(m);
}
}
Map<String, Object> params = new HashMap<String, Object>();
params.put("table", tableName);
params.put("fields", l);
rpc("org.araqne.logdb.msgbus.ManagementPlugin.setTableFields", params);
}
/**
* 테이블 메타데이터를 설정합니다. 관리자 권한이 없는 경우 예외가 발생합니다.
*
* @param tableName
* 테이블 이름 (NULL 허용 안 함)
* @param config
* 키/값 쌍으로 설정할 테이블 메타데이터 목록을 지정합니다. (NULL 허용 안 함)
* @throws IOException
*/
public void setTableMetadata(String tableName, Map<String, String> config) throws IOException {
if (tableName == null)
throw new IllegalArgumentException("table name cannot be null");
Map<String, Object> params = new HashMap<String, Object>();
params.put("table", tableName);
params.put("metadata", config);
rpc("org.araqne.logdb.msgbus.ManagementPlugin.setTableMetadata", params);
}
/**
* 테이블 메타데이터를 삭제합니다. 관리자 권한이 없는 경우 예외가 발생합니다.
*
* @param tableName
* 테이블 이름 (NULL 허용 안 함)
* @param keySet
* 삭제할 메타데이터 키 문자열 목록을 지정합니다. (NULL 허용 안 함)
*/
public void unsetTableMetadata(String tableName, Set<String> keySet) throws IOException {
Map<String, Object> params = new HashMap<String, Object>();
params.put("table", tableName);
params.put("keys", keySet);
rpc("org.araqne.logdb.msgbus.ManagementPlugin.unsetTableMetadata", params);
}
@SuppressWarnings("unchecked")
public List<StorageEngineInfo> listStorageEngines() throws IOException {
Message resp = rpc("org.araqne.logdb.msgbus.ManagementPlugin.getStorageEngines");
List<StorageEngineInfo> l = new ArrayList<StorageEngineInfo>();
List<Map<String, Object>> engines = (List<Map<String, Object>>) resp.get("engines");
for (Map<String, Object> engine : engines) {
String name = (String) engine.get("type");
List<StorageEngineConfigSpec> primaryConfigSpecs = parseStorageConfigSpecs(
(List<Object>) engine.get("primary_config_specs"));
List<StorageEngineConfigSpec> replicaConfigSpecs = parseStorageConfigSpecs(
(List<Object>) engine.get("replica_config_specs"));
l.add(new StorageEngineInfo(name, primaryConfigSpecs, replicaConfigSpecs));
}
return l;
}
@SuppressWarnings("unchecked")
private List<StorageEngineConfigSpec> parseStorageConfigSpecs(List<Object> specs) {
if (specs == null)
return null;
List<StorageEngineConfigSpec> l = new ArrayList<StorageEngineConfigSpec>();
for (Object o : specs) {
Map<String, Object> m = (Map<String, Object>) o;
StorageEngineConfigSpec spec = new StorageEngineConfigSpec();
spec.setKey((String) m.get("key"));
spec.setType((String) m.get("type"));
spec.setOptional((Boolean) m.get("optional"));
spec.setUpdatable((Boolean) m.get("updatable"));
spec.setDisplayName((String) m.get("display_name"));
spec.setEnums((String) m.get("enums"));
l.add(spec);
}
return l;
}
/**
* 경고: createTable(테이블이름, 타입)으로 된 새 메소드를 사용하세요. 이 메소드는 곧 폐기됩니다.새 테이블을 생성합니다.
* 새 테이블을 생성합니다. 관리자 권한이 없거나 테이블 이름이 중복되는 경우 예외가 발생합니다.
*
* @param tableName
* 테이블 이름 (NULL 허용 안 함)
*/
@Deprecated
public void createTable(String tableName) throws IOException {
Map<String, Object> params = new HashMap<String, Object>();
params.put("table", tableName);
params.put("type", "v3p");
try {
rpc("org.araqne.logdb.msgbus.ManagementPlugin.createTable", params);
} catch (MessageException e) {
if (e.getMessage().contains("not supported engine")) {
params.put("type", "v2");
rpc("org.araqne.logdb.msgbus.ManagementPlugin.createTable", params);
} else
throw e;
}
}
/**
* 경고: createTable(테이블이름, 타입)으로 된 새 메소드를 사용하세요. 이 메소드는 곧 폐기됩니다.새 테이블을 생성합니다.
* 관리자 권한이 없거나 테이블 이름이 중복되는 경우 예외가 발생합니다.
*
* @param tableName
* 테이블 이름 (NULL 허용 안 함)
* @param metadata
* 테이블 초기 메타데이터 설정 (NULL 허용)
*/
@Deprecated
public void createTable(String tableName, Map<String, String> metadata) throws IOException {
Map<String, Object> params = new HashMap<String, Object>();
params.put("table", tableName);
params.put("type", "v3p");
params.put("metadata", metadata);
try {
rpc("org.araqne.logdb.msgbus.ManagementPlugin.createTable", params);
} catch (MessageException e) {
if (e.getMessage().contains("not supported engine")) {
params.put("type", "v2");
rpc("org.araqne.logdb.msgbus.ManagementPlugin.createTable", params);
} else
throw e;
}
}
/**
* 새 테이블을 생성합니다. 관리자 권한이 없거나 테이블 이름이 중복되는 경우 예외가 발생합니다.
*
* @param tableName
* 테이블 이름
* @param type
* 주 스토리지 엔진 타입
* @throws IOException
*/
public void createTable(String tableName, String type) throws IOException {
createTable(new TableSchemaInfo(tableName, type));
}
/**
* 새 테이블을 생성합니다. 관리자 권한이 없거나 테이블 이름이 중복되는 경우 예외가 발생합니다.
*
* @param schema
* 테이블 스키마. 테이블 이름을 비롯하여 스토리지 엔진과 메타데이터 상세 설정이 포함됩니다.
*/
public void createTable(TableSchemaInfo schema) throws IOException {
rpc("org.araqne.logdb.msgbus.ManagementPlugin.createTable", schema.toMap());
}
/**
* 테이블을 삭제합니다. 모든 원본 데이터와 인덱스 데이터가 삭제되고 되돌릴 수 없습니다. 관리자 권한이 없는 경우 예외가 발생합니다.
*
* @param tableName
* 테이블 이름 (NULL 허용 안 함)
*/
public void dropTable(String tableName) throws IOException {
Map<String, Object> params = new HashMap<String, Object>();
params.put("table", tableName);
rpc("org.araqne.logdb.msgbus.ManagementPlugin.dropTable", params);
}
/**
* 로그 수집기 유형 목록을 조회합니다. 일반적으로 createLogger()를 할 때 사용자에게 사용 가능한 로그 수집기 목록을
* 보여주기 위해서 호출합니다.
*
* @return 로그 수집기 유형 개체를 리스트로 반환합니다.
*/
@SuppressWarnings("unchecked")
public List<LoggerFactoryInfo> listLoggerFactories() throws IOException {
Map<String, Object> params = new HashMap<String, Object>();
params.put("locale", locale.getLanguage());
Message resp = rpc("org.araqne.log.api.msgbus.LoggerPlugin.getLoggerFactories", params);
List<LoggerFactoryInfo> factories = new ArrayList<LoggerFactoryInfo>();
List<Object> l = (List<Object>) resp.get("factories");
for (Object o : l) {
parseLoggerFactoryInfo(factories, o);
}
return factories;
}
/**
* 지정된 이름을 가진 로그 수집기 유형 정보를 반환합니다. 로그 수집기를 생성하는데 필요한 설정 명세 목록을 포함하여 반환합니다.
* 해당 이름의 로그 수집기 유형이 존재하지 않는 경우 예외가 발생합니다.
*
* @param factoryName
* 로그 수집기 유형 이름 (NULL 허용 안 함)
* @return 지정된 이름을 가진 로그 수집기 유형 개체
*/
@SuppressWarnings("unchecked")
public LoggerFactoryInfo getLoggerFactoryInfo(String factoryName) throws IOException {
List<LoggerFactoryInfo> factories = listLoggerFactories();
LoggerFactoryInfo found = null;
for (LoggerFactoryInfo f : factories) {
if (f.getNamespace().equals("local") && f.getName().equals(factoryName)) {
found = f;
break;
}
}
if (found == null)
throw new IllegalStateException("logger factory not found: " + factoryName);
Map<String, Object> params = new HashMap<String, Object>();
params.put("factory", factoryName);
params.put("locale", locale.getLanguage());
Message resp2 = rpc("org.araqne.log.api.msgbus.LoggerPlugin.getFactoryOptions", params);
List<ConfigSpec> configSpecs = parseConfigList((List<Object>) resp2.get("options"));
found.setConfigSpecs(configSpecs);
return found;
}
@SuppressWarnings("unchecked")
private void parseLoggerFactoryInfo(List<LoggerFactoryInfo> factories, Object o) {
Map<String, Object> m = (Map<String, Object>) o;
LoggerFactoryInfo f = new LoggerFactoryInfo();
f.setFullName((String) m.get("full_name"));
f.setDisplayName((String) m.get("display_name"));
f.setNamespace((String) m.get("namespace"));
f.setName((String) m.get("name"));
f.setDescription((String) m.get("description"));
factories.add(f);
}
/**
* 파서 유형 목록을 조회합니다. 일반적으로 createParser() 매개변수 설정에 필요한 파서 유형 목록을 사용자에게 표시하기
* 위해 호출합니다.
*
* @return 파서 유형 목록 개체의 리스트를 반환합니다.
*/
@SuppressWarnings("unchecked")
public List<ParserFactoryInfo> listParserFactories() throws IOException {
Map<String, Object> params = new HashMap<String, Object>();
params.put("locale", locale.getLanguage());
Message resp = rpc("org.araqne.log.api.msgbus.LoggerPlugin.getParserFactories", params);
List<Object> l = (List<Object>) resp.get("factories");
List<ParserFactoryInfo> parsers = new ArrayList<ParserFactoryInfo>();
for (Object o : l) {
Map<String, Object> m = (Map<String, Object>) o;
ParserFactoryInfo f = new ParserFactoryInfo();
f.setName((String) m.get("name"));
f.setDisplayName((String) m.get("display_name"));
f.setDescription((String) m.get("description"));
f.setConfigSpecs(parseConfigList((List<Object>) m.get("options")));
parsers.add(f);
}
return parsers;
}
/**
* 설정 명세 목록이 포함된 파서 유형 정보를 조회합니다. 지정된 이름의 파서 유형이 존재하지 않는 경우 예외가 발생합니다.
*
* @param name
* 파서 유형 이름 (NULL 허용 안 함)
* @return 지정된 이름의 파서 유형 정보 개체를 반환합니다.
*/
@SuppressWarnings("unchecked")
public ParserFactoryInfo getParserFactoryInfo(String name) throws IOException {
Map<String, Object> params = new HashMap<String, Object>();
params.put("factory_name", name);
Message resp = rpc("com.logpresso.core.msgbus.ParserPlugin.getParserFactoryInfo", params);
Map<String, Object> m = (Map<String, Object>) resp.get("factory");
ParserFactoryInfo f = new ParserFactoryInfo();
f.setName((String) m.get("name"));
f.setDisplayName((String) m.get("display_name"));
f.setDescription((String) m.get("description"));
f.setConfigSpecs(parseConfigList((List<Object>) m.get("options")));
return f;
}
@SuppressWarnings("unchecked")
private List<ConfigSpec> parseConfigList(List<Object> l) {
List<ConfigSpec> specs = new ArrayList<ConfigSpec>();
for (Object o : l) {
Map<String, Object> m = (Map<String, Object>) o;
ConfigSpec spec = new ConfigSpec();
spec.setName((String) m.get("name"));
spec.setDescription((String) m.get("description"));
spec.setDisplayName((String) m.get("display_name"));
spec.setType((String) m.get("type"));
spec.setRequired((Boolean) m.get("required"));
spec.setDefaultValue((String) m.get("default_value"));
specs.add(spec);
}
return specs;
}
/**
* 파서 목록을 조회합니다.
*
* @deprecated Use listParsers() instead
* @return 파서 설정 개체의 리스트가 반환됩니다.
*/
@Deprecated
public List<ParserInfo> getParsers() throws IOException {
return listParsers();
}
/**
* 파서 목록을 조회합니다.
*
* @return 파서 설정 개체의 리스트가 반환됩니다.
*/
@SuppressWarnings("unchecked")
public List<ParserInfo> listParsers() throws IOException {
Message resp = rpc("com.logpresso.core.msgbus.ParserPlugin.getParsers");
List<Object> l = (List<Object>) resp.get("parsers");
List<ParserInfo> parsers = new ArrayList<ParserInfo>();
for (Object o : l) {
parsers.add(parseParserInfo((Map<String, Object>) o));
}
return parsers;
}
/**
* 지정된 이름의 파서 정보를 조회합니다.
*
* @param name
* 파서 이름 (NULL 허용 안 함)
* @return 지정된 이름의 파서 정보 개체를 반환합니다. 지정된 이름의 파서가 존재하지 않는 경우 NULL이 반환됩니다.
*/
public ParserInfo getParser(String name) throws IOException {
Map<String, Object> params = new HashMap<String, Object>();
params.put("name", name);
Message resp = rpc("com.logpresso.core.msgbus.ParserPlugin.getParser", params);
@SuppressWarnings("unchecked")
Map<String, Object> m = (Map<String, Object>) resp.get("parser");
return parseParserInfo(m);
}
@SuppressWarnings("unchecked")
private ParserInfo parseParserInfo(Map<String, Object> m) {
ParserInfo p = new ParserInfo();
p.setName((String) m.get("name"));
p.setFactoryName((String) m.get("factory_name"));
p.setConfigs((Map<String, String>) m.get("configs"));
// since 0.9.0 and logpresso-core 0.8.0
if (m.get("fields") != null) {
List<FieldInfo> l = new ArrayList<FieldInfo>();
for (Object o : (List<Object>) m.get("fields"))
l.add(PrimitiveConverter.parse(FieldInfo.class, o));
p.setFieldDefinitions(l);
}
return p;
}
/**
* 새 파서를 생성합니다. 파서 유형이 존재하지 않는 경우, 필수적인 파서 설정이 누락된 경우, 파서 이름이 중복된 경우 예외가
* 발생합니다.
*
* @param parser
* 새 파서 설정 (NULL 허용 안 함)
*/
public void createParser(ParserInfo parser) throws IOException {
Map<String, Object> params = new HashMap<String, Object>();
params.put("name", parser.getName());
params.put("factory_name", parser.getFactoryName());
params.put("configs", parser.getConfigs());
rpc("com.logpresso.core.msgbus.ParserPlugin.createParser", params);
}
/**
* 파서를 삭제합니다. 지정된 이름의 파서가 존재하지 않는 경우 예외가 발생합니다.
*
* @param name
* 삭제할 파서 이름 (NULL 허용 안 함)
*/
public void removeParser(String name) throws IOException {
Map<String, Object> params = new HashMap<String, Object>();
params.put("name", name);
rpc("com.logpresso.core.msgbus.ParserPlugin.removeParser", params);
}
/**
* 특정한 파서의 동작을 시험합니다. 지정된 파서가 존재하지 않거나 파싱이 실패하는 경우 예외가 발생합니다.
*
* @param parserName
* 파서 이름 (NULL 허용 안 함)
* @param data
* 원본 데이터 키/값 쌍 (NULL 허용 안 함), 일반적으로 텍스트 파일에서 수집되는 데이터 혹은 시스로그의
* 경우 line 키/값 쌍으로 표현됩니다.
* @return 파싱된 키/값 쌍의 리스트가 반환됩니다. 대부분(V1 파서)은 데이터 원본과 파싱된 결과가 1:1로 대응되지만,
* 넷플로우처럼 다수의 레코드가 단일 패킷에 팩킹된 경우 (V2 파서) 다수의 파싱 결과를 얻을 수 있습니다.
* @since 0.8.1
*/
@SuppressWarnings("unchecked")
public List<Map<String, Object>> testParser(String parserName, Map<String, Object> data) throws IOException {
Map<String, Object> params = new HashMap<String, Object>();
params.put("name", parserName);
params.put("data", data);
Message resp = rpc("com.logpresso.core.msgbus.ParserPlugin.testParser", params);
return (List<Map<String, Object>>) resp.get("rows");
}
/**
* 트랜스포머 유형 목록을 조회합니다. 일반적으로 createTransformer()를 호출하기 전에 사용자에게 가능한 트랜스포머 유형
* 목록을 표시하기 위해 호출합니다.
*
* @return 트랜스포머 유형 개체의 리스트가 반환됩니다.
*/
@SuppressWarnings("unchecked")
public List<TransformerFactoryInfo> listTransformerFactories() throws IOException {
Map<String, Object> params = new HashMap<String, Object>();
params.put("locale", locale.getLanguage());
Message resp = rpc("com.logpresso.core.msgbus.TransformerPlugin.listTransformerFactories", params);
List<Object> l = (List<Object>) resp.get("factories");
List<TransformerFactoryInfo> factories = new ArrayList<TransformerFactoryInfo>();
for (Object o : l) {
Map<String, Object> m = (Map<String, Object>) o;
TransformerFactoryInfo f = new TransformerFactoryInfo();
f.setName((String) m.get("name"));
f.setDisplayName((String) m.get("display_name"));
f.setDescription((String) m.get("description"));
f.setConfigSpecs(parseConfigList((List<Object>) m.get("options")));
factories.add(f);
}
return factories;
}
/**
* 지정된 이름의 트랜스포머 유형 정보를 조회합니다. 트랜스포머 설정 명세 목록을 조회하는데 사용합니다. 지정된 이름의 트랜스포머 유형
* 이름이 존재하지 않으면 예외가 발생합니다.
*
* @param name
* 트랜스포머 유형 이름 (NULL 허용 안 함)
* @return 지정된 이름의 트랜스포머 유형 정보 개체
*/
@SuppressWarnings("unchecked")
public TransformerFactoryInfo getTransformerFactoryInfo(String name) throws IOException {
Map<String, Object> params = new HashMap<String, Object>();
params.put("factory_name", name);
params.put("locale", locale.getLanguage());
Message resp = rpc("com.logpresso.core.msgbus.TransformerPlugin.getTransformerFactoryInfo", params);
Map<String, Object> m = (Map<String, Object>) resp.get("factory");
TransformerFactoryInfo f = new TransformerFactoryInfo();
f.setName((String) m.get("name"));
f.setDisplayName((String) m.get("display_name"));
f.setDescription((String) m.get("description"));
f.setConfigSpecs(parseConfigList((List<Object>) m.get("options")));
return f;
}
/**
* 트랜스포머 목록을 조회합니다.
*
* @return 트랜스포머 개체의 리스트를 반환합니다.
* @deprecated Use listTransformers() instead.
*/
@Deprecated
public List<TransformerInfo> getTransformers() throws IOException {
return listTransformers();
}
/**
* 트랜스포머 목록을 조회합니다.
*
* @return 트랜스포머 개체의 리스트를 반환합니다.
*/
@SuppressWarnings("unchecked")
public List<TransformerInfo> listTransformers() throws IOException {
Message resp = rpc("com.logpresso.core.msgbus.TransformerPlugin.getTransformers");
List<Object> l = (List<Object>) resp.get("transformers");
List<TransformerInfo> transformers = new ArrayList<TransformerInfo>();
for (Object o : l) {
transformers.add(parseTransformerInfo((Map<String, Object>) o));
}
return transformers;
}
/**
* 지정된 이름의 트랜스포머 설정을 조회합니다. 지정된 이름의 트랜스포머가 존재하지 않는 경우 예외가 발생합니다.
*
* @param name
* 트랜스포머 이름 (NULL 허용 안 함)
* @return 트랜스포머 설정 정보 개체
*/
public TransformerInfo getTransformer(String name) throws IOException {
Map<String, Object> params = new HashMap<String, Object>();
params.put("name", name);
Message resp = rpc("com.logpresso.core.msgbus.TransformerPlugin.getTransformer", params);
@SuppressWarnings("unchecked")
Map<String, Object> m = (Map<String, Object>) resp.get("transformer");
return parseTransformerInfo(m);
}
@SuppressWarnings("unchecked")
private TransformerInfo parseTransformerInfo(Map<String, Object> m) {
TransformerInfo p = new TransformerInfo();
p.setName((String) m.get("name"));
p.setFactoryName((String) m.get("factory_name"));
p.setConfigs((Map<String, String>) m.get("configs"));
return p;
}
/**
* 새 트랜스포머 설정을 추가합니다. 트랜스포머 이름이 중복되거나, 트랜스포머 유형이 존재하지 않거나, 필수적인 설정이 누락된 경우
* 예외가 발생합니다.
*
* @param transformer
* 새 트랜스포머 설정 (NULL 허용 안 함)
*/
public void createTransformer(TransformerInfo transformer) throws IOException {
Map<String, Object> params = new HashMap<String, Object>();
params.put("name", transformer.getName());
params.put("factory_name", transformer.getFactoryName());
params.put("configs", transformer.getConfigs());
rpc("com.logpresso.core.msgbus.TransformerPlugin.createTransformer", params);
}
/**
* 트랜스포머를 삭제합니다. 지정된 이름의 트랜스포머가 존재하지 않는 경우 예외가 발생합니다.
*
* @param name
* 삭제할 트랜스포머 이름 (NULL 허용 안 함)
*/
public void removeTransformer(String name) throws IOException {
Map<String, Object> params = new HashMap<String, Object>();
params.put("name", name);
rpc("com.logpresso.core.msgbus.TransformerPlugin.removeTransformer", params);
}
/**
* 로그 수집기 목록을 반환합니다.
*
* @return 로그 수집기 정보 개체 리스트를 반환합니다.
*/
public List<LoggerInfo> listLoggers() throws IOException {
return listLoggers(null);
}
/**
* 로그 수집기 목록을 반환합니다.
*
* @param loggerNames
* 정보를 조회할 로거 이름 목록
* @return 로그 수집기 정보 개체 리스트를 반환합니다.
*/
@SuppressWarnings("unchecked")
public List<LoggerInfo> listLoggers(List<String> loggerNames) throws IOException {
Map<String, Object> params = new HashMap<String, Object>();
params.put("logger_names", loggerNames);
Message resp = rpc("org.araqne.log.api.msgbus.LoggerPlugin.getLoggers", params);
List<Object> l = (List<Object>) resp.get("loggers");
List<LoggerInfo> loggers = new ArrayList<LoggerInfo>();
for (Object o : l) {
Map<String, Object> m = (Map<String, Object>) o;
LoggerInfo lo = decodeLoggerInfo(m);
loggers.add(lo);
}
return loggers;
}
/**
* 특정한 로그 수집기 정보를 조회합니다. 이 때 로그 수집기의 수집상태 정보는 포함되지 않습니다. Retrieve specific
* logger information with config using RPC call. States will not returned
* because logger states' size can be very large.
*
* @param loggerName
* 새 로그 수집기 생성에 필요한 설정 (NULL 허용 안 함)
* @since 0.8.6
*/
public LoggerInfo getLogger(String loggerName) throws IOException {
return getLogger(loggerName, false);
}
/**
* 특정한 로그 수집기 정보를 조회합니다.
*
* @param loggerName
* 조회 대상 로그 수집기 이름
* @param includeStates
* 로거 수집상태 정보 포함 여부
* @since 0.8.6
*/
@SuppressWarnings("unchecked")
public LoggerInfo getLogger(String loggerName, boolean includeStates) throws IOException {
Map<String, Object> params = new HashMap<String, Object>();
params.put("logger_name", loggerName);
params.put("include_configs", true);
params.put("include_states", includeStates);
Message resp = rpc("org.araqne.log.api.msgbus.LoggerPlugin.getLogger", params);
Map<String, Object> m = (Map<String, Object>) resp.get("logger");
if (m == null)
return null;
return decodeLoggerInfo(m);
}
@SuppressWarnings("unchecked")
private LoggerInfo decodeLoggerInfo(Map<String, Object> m) {
LoggerInfo lo = new LoggerInfo();
lo.setNamespace((String) m.get("namespace"));
lo.setName((String) m.get("name"));
if (m.get("enabled") != null)
lo.setEnabled((Boolean) m.get("enabled"));
lo.setFactoryName((String) m.get("factory_full_name"));
lo.setDescription((String) m.get("description"));
lo.setPassive((Boolean) m.get("is_passive"));
lo.setInterval((Integer) m.get("interval"));
lo.setStartTime((String) m.get("start_time"));
lo.setEndTime((String) m.get("end_time"));
lo.setStatus((String) m.get("status"));
lo.setLastStartAt(parseDate((String) m.get("last_start")));
lo.setLastRunAt(parseDate((String) m.get("last_run")));
lo.setLastLogAt(parseDate((String) m.get("last_log")));
lo.setLogCount(Long.valueOf(m.get("log_count").toString()));
if (m.get("log_volume") != null)
lo.setLogVolume(Long.valueOf(m.get("log_volume").toString()));
if (m.get("drop_volume") != null)
lo.setDropVolume(Long.valueOf(m.get("drop_volume").toString()));
Object dropCount = m.get("drop_count");
if (dropCount != null)
lo.setDropCount(Long.valueOf(dropCount.toString()));
Object updateCount = m.get("update_count");
if (updateCount != null)
lo.setUpdateCount(Long.valueOf(updateCount.toString()));
lo.setConfigs((Map<String, String>) m.get("configs"));
lo.setStates((Map<String, Object>) m.get("states"));
return lo;
}
private Date parseDate(String s) {
if (s == null)
return null;
try {
SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd HH:mm:ssZ");
return f.parse(s);
} catch (ParseException e) {
return null;
}
}
/**
* 새 로그 수집기를 생성합니다. 로그 수집기 이름이 중복되거나, 로그 수집기 유형의 설정 명세에서 필수로 표시된 설정이 입력되지 않은
* 경우 예외가 발생합니다.
*
* @param logger
* 새 로그 수집기 생성에 필요한 설정 (NULL 허용 안 함)
* @throws IOException
*/
public void createLogger(LoggerInfo logger) throws IOException {
Map<String, Object> params = new HashMap<String, Object>();
params.put("factory", logger.getFactoryName());
params.put("namespace", logger.getNamespace());
params.put("name", logger.getName());
params.put("description", logger.getDescription());
params.put("options", logger.getConfigs());
rpc("org.araqne.log.api.msgbus.LoggerPlugin.createLogger", params);
}
/**
* 로그 수집기를 삭제합니다. 로그 수집기가 존재하지 않거나 아직 실행 중인 경우 예외가 발생합니다.
*
* @param fullName
* 이름공간\이름 형식의 로그 수집기 이름 (NULL 허용 안 함)
*/
public void removeLogger(String fullName) throws IOException {
Map<String, Object> params = new HashMap<String, Object>();
params.put("logger", fullName);
rpc("org.araqne.log.api.msgbus.LoggerPlugin.removeLogger", params);
}
/**
* 로그 수집기를 시작합니다. 패시브 속성의 로그 수집기인 경우 수집 주기를 무시합니다. 로거가 이미 시작된 경우 예외가 발생합니다.
*
* @param fullName
* 이름공간\이름 형식의 로그 수집기 이름 (NULL 허용 안 함)
* @param interval
* 밀리세컨드 단위의 로그 수집 주기, 패시브 로그 수집기인 경우 무시됨.
*/
public void startLogger(String fullName, int interval) throws IOException {
Map<String, Object> params = new HashMap<String, Object>();
params.put("logger", fullName);
params.put("interval", interval);
rpc("org.araqne.log.api.msgbus.LoggerPlugin.startLogger", params);
}
/**
* 로그 수집기를 정지합니다. 패시브 속성의 로그 수집기인 경우 최대 대기 시간을 무시합니다.
*
* @param fullName
* 이름공간\이름 형식의 로그 수집기 이름 (NULL 허용 안 함)
* @param waitTime
* 액티브 로그 수집기의 정지를 기다리는 밀리세컨드 단위의 최대 대기 시간
*/
public void stopLogger(String fullName, int waitTime) throws IOException {
Map<String, Object> params = new HashMap<String, Object>();
params.put("logger", fullName);
params.put("wait_time", waitTime);
rpc("org.araqne.log.api.msgbus.LoggerPlugin.stopLogger", params);
}
/**
* 기존의 JDBC 프로파일 설정 목록을 조회합니다. DB 관리자 권한이 없는 경우 예외가 발생합니다. 프로파일 정보 조회 시 암호
* 문자열은 반환하지 않습니다.
*
* @return JDBC 프로파일 설정 목록
*/
@SuppressWarnings("unchecked")
public List<JdbcProfileInfo> listJdbcProfiles() throws IOException {
List<JdbcProfileInfo> l = new ArrayList<JdbcProfileInfo>();
Message resp = rpc("org.logpresso.jdbc.JdbcProfilePlugin.getProfiles");
List<Object> profiles = (List<Object>) resp.get("profiles");
for (Object o : profiles) {
Map<String, Object> m = (Map<String, Object>) o;
JdbcProfileInfo info = new JdbcProfileInfo();
info.setName((String) m.get("name"));
info.setConnectionString((String) m.get("connection_string"));
info.setReadOnly((Boolean) m.get("readonly"));
info.setUser((String) m.get("user"));
l.add(info);
}
return l;
}
/**
* 새 JDBC 프로파일을 생성합니다. 이미 동일한 이름의 JDBC 프로파일이 존재하거나, DB 관리자 권한이 없는 경우 예외가
* 발생합니다.
*
* @param profile
* 새 JDBC 프로파일 설정 (NULL 허용 안 함)
*/
public void createJdbcProfile(JdbcProfileInfo profile) throws IOException {
checkNotNull("profile", profile);
checkNotNull("profile.name", profile.getName());
checkNotNull("profile.connectionString", profile.getConnectionString());
checkNotNull("profile.user", profile.getUser());
Map<String, Object> params = new HashMap<String, Object>();
params.put("name", profile.getName());
params.put("connection_string", profile.getConnectionString());
params.put("readonly", profile.isReadOnly());
params.put("user", profile.getUser());
params.put("password", profile.getPassword());
rpc("org.logpresso.jdbc.JdbcProfilePlugin.createProfile", params);
}
/**
* 기존의 JDBC 프로파일을 삭제합니다. 지정된 이름의 JDBC 프로파일이 존재하지 않거나, DB 관리자 권한이 없는 경우 예외가
* 발생합니다.
*
* @param name
* 삭제할 대상 JDBC 프로파일 이름
*/
public void removeJdbcProfile(String name) throws IOException {
checkNotNull("name", name);
Map<String, Object> params = new HashMap<String, Object>();
params.put("name", name);
rpc("org.logpresso.jdbc.JdbcProfilePlugin.removeProfile", params);
}
/**
* 쿼리 실행 결과를 커서 개체로 반환합니다. 쿼리 실행이 완전히 끝날 때까지 스레드가 차단(blocking)되며, 커서를 모두
* 순회하거나 닫기 전까지 쿼리가 유지됩니다. 권한이 없거나, 쿼리 문법이 틀린 경우 예외가 발생합니다. 스레드를 차단하지 않고 쿼리
* 실행 상태를 폴링하면서 부분적인 쿼리 결과를 최대한 빨리 가져오고 싶은 경우(pipelining)에는 아래에 설명되는
* createQuery(), startQuery(), stopQuery(), removeQuery(), getResult() 메소드
* 조합을 사용하십시오.
*
* @param queryString
* 쿼리 문자열 (NULL 허용 안 함)
* @return 쿼리 결과를 조회할 수 있는 커서가 반환됩니다.
*/
public LogCursor query(String queryString) throws IOException {
int id = createQuery(queryString);
startQuery(id);
LogQuery q = queries.get(id);
q.waitUntil(null);
if (q.getStatus().equals("Cancelled")) {
String errorMsg = "";
if (q.getErrorCode() != null)
errorMsg = String.format(", error LOGPRESSO-%05d [%s]", q.getErrorCode(), q.getErrorDetail());
throw new IllegalStateException(
"query cancelled, id [" + q.getId() + "] query string [" + queryString + "]" + errorMsg);
}
long total = q.getLoadedCount();
return new LogCursorImpl(id, 0L, total, true, fetchSize);
}
private class LogCursorImpl implements LogCursor {
private int id;
private long offset;
private long limit;
private boolean removeOnClose;
private long p;
private Map<String, Object> cached;
private Long currentCacheOffset;
private Long nextCacheOffset;
private int fetchUnit;
private Map<String, Object> prefetch;
public LogCursorImpl(int id, long offset, long limit, boolean removeOnClose, int fetchUnit) {
this.id = id;
this.offset = offset;
this.limit = limit;
this.removeOnClose = removeOnClose;
this.p = offset;
this.nextCacheOffset = offset;
this.fetchUnit = fetchUnit;
}
@SuppressWarnings("unchecked")
@Override
public boolean hasNext() {
if (prefetch != null)
return true;
if (p < offset || p >= offset + limit)
return false;
try {
if (cached == null || p >= currentCacheOffset + fetchUnit) {
cached = getResult(id, nextCacheOffset, fetchUnit);
currentCacheOffset = nextCacheOffset;
nextCacheOffset += fetchUnit;
}
int relative = (int) (p - currentCacheOffset);
List<Object> l = (List<Object>) cached.get("result");
if (relative >= l.size())
return false;
prefetch = (Map<String, Object>) l.get(relative);
p++;
return true;
} catch (IOException e) {
logger.error("araqne logdb client: cannot fetch log query result", e);
return false;
}
}
@Override
public Map<String, Object> next() {
if (!hasNext())
throw new NoSuchElementException("end of log cursor");
Map<String, Object> m = prefetch;
prefetch = null;
return m;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
@Override
public void close() throws IOException {
if (removeOnClose)
removeQuery(id);
}
}
/**
* 스트림 쿼리 목록을 조회합니다.
*
* @since 0.9.5
*/
public List<StreamQueryStatus> listStreamQueries() throws IOException {
Message resp = rpc("com.logpresso.query.msgbus.StreamQueryPlugin.getStreamQueries");
@SuppressWarnings("unchecked")
List<Object> l = (List<Object>) resp.get("stream_queries");
List<StreamQueryStatus> statuses = new ArrayList<StreamQueryStatus>();
for (Object o : l) {
StreamQueryStatus status = parseStreamQueryStatus(o);
statuses.add(status);
}
return statuses;
}
/**
* @since 0.9.5
*/
@SuppressWarnings("unchecked")
public StreamQueryStatus getStreamQuery(String name) throws IOException {
checkNotNull("name", name);
Map<String, Object> params = new HashMap<String, Object>();
params.put("name", name);
Message resp = rpc("com.logpresso.query.msgbus.StreamQueryPlugin.getStreamQuery", params);
Map<String, Object> o = (Map<String, Object>) resp.get("stream_query");
if (o == null)
return null;
return parseStreamQueryStatus(o);
}
@SuppressWarnings("unchecked")
private StreamQueryStatus parseStreamQueryStatus(Object o) {
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ssZ");
StreamQueryStatus status = new StreamQueryStatus();
Map<String, Object> m = (Map<String, Object>) o;
Map<String, Object> c = (Map<String, Object>) m.get("config");
StreamQueryInfo query = new StreamQueryInfo();
query.setName((String) c.get("name"));
query.setDescription((String) c.get("description"));
query.setInterval((Integer) c.get("interval"));
query.setQueryString((String) c.get("query"));
query.setOwner((String) c.get("owner"));
String sourceType = (String) c.get("source_type");
query.setSourceType(sourceType);
if (sourceType.equals("table"))
query.setSources((List<String>) c.get("table"));
else if (sourceType.equals("logger"))
query.setSources((List<String>) c.get("logger"));
else if (sourceType.equals("stream"))
query.setSources((List<String>) c.get("stream"));
query.setEnabled((Boolean) c.get("is_enabled"));
query.setCreated(df.parse((String) c.get("created"), new ParsePosition(0)));
query.setModified(df.parse((String) c.get("modified"), new ParsePosition(0)));
status.setStreamQuery(query);
status.setInputCount(Long.parseLong(m.get("input_count").toString()));
status.setLastRefresh(df.parse((String) m.get("last_refresh"), new ParsePosition(0)));
status.setRunning((Boolean) m.get("is_running"));
return status;
}
/**
* 스트림 쿼리를 생성합니다. 스트림 쿼리 이름이 중복되는 경우 예외가 발생합니다. logger, table, stream 이외의
* 데이터 원본 타입이 지정된 경우 예외가 발생합니다. 새로고침 주기가 음수인 경우 예외가 발생합니다.
*
* @since 0.9.5
*/
public void createStreamQuery(StreamQueryInfo query) throws IOException {
Map<String, Object> params = buildStreamQueryParams(query);
rpc("com.logpresso.query.msgbus.StreamQueryPlugin.createStreamQuery", params);
}
/**
* 스트림 쿼리를 수정합니다. 스트림 쿼리가 존재하지 않는 경우 예외가 발생합니다. logger, table, stream 이외의
* 데이터 원본 타입이 지정된 경우 예외가 발생합니다. 새로고침 주기가 음수인 경우 예외가 발생합니다.
*
* @since 1.0.0
*/
public void updateStreamQuery(StreamQueryInfo query) throws IOException {
Map<String, Object> params = buildStreamQueryParams(query);
rpc("com.logpresso.query.msgbus.StreamQueryPlugin.updateStreamQuery", params);
}
private Map<String, Object> buildStreamQueryParams(StreamQueryInfo query) {
Map<String, Object> params = new HashMap<String, Object>();
params.put("name", query.getName());
params.put("description", query.getDescription());
params.put("interval", query.getInterval());
params.put("source_type", query.getSourceType());
params.put("sources", query.getSources());
params.put("query", query.getQueryString());
params.put("is_enabled", query.isEnabled());
return params;
}
/**
* 지정된 이름의 스트림 쿼리를 삭제합니다. 지정된 스트림 쿼리가 존재하지 않거나, 소유자가 아닌 경우 예외가 발생합니다.
*
* @param name
* 스트림 쿼리 이름
* @since 0.9.5
*/
public void removeStreamQuery(String name) throws IOException {
Map<String, Object> params = new HashMap<String, Object>();
params.put("name", name);
rpc("com.logpresso.query.msgbus.StreamQueryPlugin.removeStreamQuery", params);
}
/**
* 예약된 쿼리 목록을 조회합니다. 자신이 설정한 예약된 쿼리 목록만 조회됩니다.
*
* @since 0.9.5
*/
public List<ScheduledQueryInfo> listScheduledQueries() throws IOException {
Message resp = rpc("com.logpresso.core.msgbus.ScheduledQueryPlugin.getScheduledQueries");
@SuppressWarnings("unchecked")
List<Object> l = (List<Object>) resp.get("scheduled_queries");
List<ScheduledQueryInfo> queries = new ArrayList<ScheduledQueryInfo>();
for (Object o : l) {
queries.add(parseScheduledQuery(o));
}
return queries;
}
/**
* 지정된 GUID를 가진 예약된 쿼리 설정을 조회합니다. 예약된 쿼리가 존재하지 않거나 조회 권한이 없는 경우 예외가 발생합니다.
*
* @since 0.9.5
*/
public ScheduledQueryInfo getScheduledQuery(String guid) throws IOException {
checkNotNull("guid", guid);
Map<String, Object> params = new HashMap<String, Object>();
params.put("guid", guid);
Message resp = rpc("com.logpresso.core.msgbus.ScheduledQueryPlugin.getScheduledQuery", params);
return parseScheduledQuery(resp.get("scheduled_query"));
}
private ScheduledQueryInfo parseScheduledQuery(Object o) {
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ssZ");
@SuppressWarnings("unchecked")
Map<String, Object> m = (Map<String, Object>) o;
ScheduledQueryInfo query = new ScheduledQueryInfo();
query.setGuid((String) m.get("guid"));
query.setTitle((String) m.get("title"));
query.setCronSchedule((String) m.get("cron_schedule"));
query.setOwner((String) m.get("owner"));
query.setQueryString((String) m.get("query"));
query.setSaveResult((Boolean) m.get("use_save_result"));
query.setUseAlert((Boolean) m.get("use_alert"));
query.setSkipWhileRunning((Boolean) m.get("skip_while_running"));
query.setBootstrapQuery((Boolean) m.get("bootstrap_query"));
query.setAlertQuery((String) m.get("alert_query"));
query.setSuppressInterval((Integer) m.get("suppress_interval"));
query.setMailProfile((String) m.get("mail_profile"));
query.setMailFrom((String) m.get("mail_from"));
query.setMailTo((String) m.get("mail_to"));
query.setMailSubject((String) m.get("mail_subject"));
query.setEnabled((Boolean) m.get("is_enabled"));
query.setCreated(df.parse((String) m.get("created_at"), new ParsePosition(0)));
return query;
}
/**
* 예약된 쿼리를 생성합니다. 예약된 쿼리의 GUID가 중복되는 경우 예외가 발생합니다.
*
* @since 0.9.5
*/
public void createScheduledQuery(ScheduledQueryInfo query) throws IOException {
Map<String, Object> params = buildScheduledQueryParams(query);
rpc("com.logpresso.core.msgbus.ScheduledQueryPlugin.createScheduledQuery", params);
}
/**
* 예약된 쿼리를 수정합니다. 지정된 GUID의 예약된 쿼리가 존재하지 않으면 예외가 발생합니다.
*
* @since 0.9.5
*/
public void updateScheduledQuery(ScheduledQueryInfo query) throws IOException {
Map<String, Object> params = buildScheduledQueryParams(query);
rpc("com.logpresso.core.msgbus.ScheduledQueryPlugin.updateScheduledQuery", params);
}
private Map<String, Object> buildScheduledQueryParams(ScheduledQueryInfo query) {
Map<String, Object> params = new HashMap<String, Object>();
params.put("guid", query.getGuid());
params.put("title", query.getTitle());
params.put("cron_schedule", query.getCronSchedule());
params.put("query", query.getQueryString());
params.put("save_result", query.isSaveResult());
params.put("use_alert", query.isUseAlert());
params.put("alert_query", query.getAlertQuery());
params.put("skip_while_running", query.isSkipWhileRunning());
params.put("bootstrap_query", query.isBootstrapQuery());
params.put("suppress_interval", query.getSuppressInterval());
params.put("mail_profile", query.getMailProfile());
params.put("mail_from", query.getMailFrom());
params.put("mail_to", query.getMailTo());
params.put("mail_subject", query.getMailSubject());
params.put("is_enabled", query.isEnabled());
return params;
}
/**
* 예약된 쿼리를 삭제합니다. 지정된 GUID의 예약된 쿼리가 존재하지 않으면 예외가 발생합니다.
*
* @since 0.9.5
*/
public void removeScheduledQuery(String guid) throws IOException {
Map<String, Object> params = new HashMap<String, Object>();
params.put("guid", guid);
rpc("com.logpresso.core.msgbus.ScheduledQueryPlugin.removeScheduledQuery", params);
}
/**
* 프로시저 목록을 조회합니다.
*
* @since 1.1.3
*/
@SuppressWarnings("unchecked")
public List<ProcedureInfo> listProcedures() throws IOException {
Message resp = rpc("com.logpresso.core.msgbus.ProcedurePlugin.getProcedures");
List<Object> l = (List<Object>) resp.get("procedures");
List<ProcedureInfo> procedures = new ArrayList<ProcedureInfo>();
for (Object o : l) {
procedures.add(parseProcedure((Map<String, Object>) o));
}
return procedures;
}
@SuppressWarnings("unchecked")
private ProcedureInfo parseProcedure(Map<String, Object> m) {
List<ProcedureParameterInfo> parameters = new ArrayList<ProcedureParameterInfo>();
for (Map<String, Object> o : (List<Map<String, Object>>) m.get("parameters")) {
ProcedureParameterInfo pp = new ProcedureParameterInfo();
pp.setKey((String) o.get("key"));
pp.setType((String) o.get("type"));
pp.setName((String) o.get("name"));
pp.setDescription((String) o.get("description"));
parameters.add(pp);
}
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ssZ");
ProcedureInfo p = new ProcedureInfo();
p.setName((String) m.get("name"));
p.setDescription((String) m.get("description"));
p.setQueryString((String) m.get("query_string"));
p.setParameters(parameters);
p.setOwner((String) m.get("owner"));
// support backward compatibility
if (m.get("grants") != null)
p.setGrantLogins(new HashSet<String>((List<String>) m.get("grants")));
if (m.get("grant_groups") != null)
p.setGrantGroups(new HashSet<String>((List<String>) m.get("grant_groups")));
p.setCreated(df.parse((String) m.get("created"), new ParsePosition(0)));
p.setModified(df.parse((String) m.get("modified"), new ParsePosition(0)));
return p;
}
/**
* 프로시저를 생성합니다. 프로시저 이름이 중복되는 경우 예외가 발생합니다.
*
* @since 1.1.3
*/
public void createProcedure(ProcedureInfo procedure) throws IOException {
Map<String, Object> params = buildProcedureRequest(procedure);
rpc("com.logpresso.core.msgbus.ProcedurePlugin.createProcedure", params);
}
/**
* 프로시저를 수정합니다. 프로시저가 존재하지 않는 경우 예외가 발생합니다. 현재 세션이 관리자나 프로시저 소유자가 아닌 경우 예외가
* 발생합니다.
*
* @since 1.1.3
*/
public void updateProcedure(ProcedureInfo procedure) throws IOException {
Map<String, Object> params = buildProcedureRequest(procedure);
rpc("com.logpresso.core.msgbus.ProcedurePlugin.updateProcedure", params);
}
private Map<String, Object> buildProcedureRequest(ProcedureInfo p) {
checkNotNull("name", p.getName());
checkNotNull("query string", p.getQueryString());
checkNotNull("procedure paramter list", p.getParameters());
List<Object> parameters = new ArrayList<Object>();
for (ProcedureParameterInfo pp : p.getParameters()) {
checkNotNull("procedure paramter key", pp.getKey());
checkNotNull("procedure paramter type", pp.getType());
Map<String, Object> o = new HashMap<String, Object>();
o.put("key", pp.getKey());
o.put("type", pp.getType());
o.put("name", pp.getName());
o.put("description", pp.getDescription());
parameters.add(o);
}
Map<String, Object> m = new HashMap<String, Object>();
m.put("name", p.getName());
m.put("description", p.getDescription());
m.put("query_string", p.getQueryString());
m.put("parameters", parameters);
m.put("grants", p.getGrantLogins());
m.put("grant_groups", p.getGrantGroups());
return m;
}
/**
* 프로시저를 삭제합니다. 존재하지 않는 프로시저를 삭제하려고 시도하는 경우 예외가 발생합니다. 현재 세션이 관리자나 프로시저 소유자가
* 아닌 경우 예외가 발생합니다.
*
* @param name
* 프로시저 이름
* @since 1.1.3
*/
public void removeProcedure(String name) throws IOException {
Map<String, Object> params = new HashMap<String, Object>();
params.put("name", name);
rpc("com.logpresso.core.msgbus.ProcedurePlugin.removeProcedure", params);
}
/**
* 주어진 쿼리 문자열을 사용하여 쿼리를 생성합니다. 권한이 없거나 문법이 틀린 경우 예외가 발생합니다.
*
* @param queryString
* 쿼리 문자열 (NULL 허용 안 함)
* @return 새로 생성된 쿼리 ID가 반환됩니다.
*/
public int createQuery(String queryString) throws IOException {
return createQuery(queryString, null, null);
}
/**
* 주어진 쿼리 문자열을 사용하여 스트리밍 쿼리를 생성합니다. 권한이 없거나 문법이 틀린 경우 예외가 발생합니다.
*
* @param queryString
* 쿼리 문자열 (NULL 허용 안 함)
* @param rs
* 쿼리 결과 스트리밍에 사용할 콜백 인스턴스, NULL인 경우 스트리밍 모드로 전환되지 않습니다.
* @return 새로 생성된 쿼리 ID가 반환됩니다.
* @since 0.9.1
*/
public int createQuery(String queryString, StreamingResultSet rs) throws IOException {
return createQuery(queryString, rs, null);
}
/**
* 주어진 쿼리 문자열을 사용하여 스트리밍 쿼리를 생성합니다. 권한이 없거나 문법이 틀린 경우 예외가 발생합니다.
*
* @param queryString
* 쿼리 문자열 (NULL 허용 안 함)
* @param rs
* 쿼리 결과 스트리밍에 사용할 콜백 인스턴스, NULL인 경우 스트리밍 모드로 전환되지 않습니다.
* @param queryContext
* 쿼리 컨텍스트, 가령 프로시저에서 메인 쿼리의 쿼리 컨텍스트를 서브 쿼리로 전달하는데 사용됩니다.
* @return 새로 생성된 쿼리 ID가 반환됩니다.
* @since 0.9.1
*/
public int createQuery(String queryString, StreamingResultSet rs, Map<String, Object> queryContext) throws IOException {
String queryContextEncoded = null;
if (queryContext != null) {
ByteBuffer bb = new FastEncodingRule().encode(queryContext);
queryContextEncoded = new String(Base64.encode(bb.array()));
}
Map<String, Object> params = new HashMap<String, Object>();
params.put("query", queryString);
params.put("source", "java-client");
params.put("context", queryContextEncoded);
Message resp = rpc("org.araqne.logdb.msgbus.LogQueryPlugin.createQuery", params);
int id = resp.getInt("id");
session.registerTrap("logdb-query-" + id);
session.registerTrap("logdb-query-timeline-" + id);
if (rs != null) {
streamCallbacks.put(id, rs);
session.registerTrap("logdb-query-result-" + id);
}
queries.putIfAbsent(id, new LogQuery(this, id, queryString));
return id;
}
/**
* 지정된 쿼리를 시작시킵니다. 주어진 ID에 대응하는 쿼리가 없거나 액세스 권한이 없는 경우 예외가 발생합니다.
*
* @param id
* 쿼리 ID
*/
public void startQuery(int id) throws IOException {
verifyQueryId(id);
Map<String, Object> params = new HashMap<String, Object>();
params.put("id", id);
params.put("streaming", streamCallbacks.containsKey(id));
rpc("org.araqne.logdb.msgbus.LogQueryPlugin.startQuery", params);
}
/**
* 지정된 쿼리를 정지(취소)시킵니다. 주어진 ID에 대응하는 쿼리가 없거나 액세스 권한이 없는 경우 예외가 발생합니다. 정지되기 직전
* 시점까지의 쿼리 결과는 removeQuery()를 호출하기 전까지 getResult()를 사용하여 조회할 수 있습니다.
*
* @param id
* 쿼리 ID
*/
public void stopQuery(int id) throws IOException {
verifyQueryId(id);
Map<String, Object> params = new HashMap<String, Object>();
params.put("id", id);
rpc("org.araqne.logdb.msgbus.LogQueryPlugin.stopQuery", params);
}
/**
* 지정된 쿼리를 삭제합니다. 서버의 임시 쿼리 결과 파일이 삭제됩니다. 이후에는 getResult()를 사용하여 쿼리 결과를 조회할
* 수 없습니다. 주어진 ID에 대응하는 쿼리가 없거나 액세스 권한이 없는 경우 예외가 발생합니다.
*
* @param id
* 쿼리 ID
*/
public void removeQuery(int id) throws IOException {
verifyQueryId(id);
StreamingResultSet rs = streamCallbacks.remove(id);
if (rs != null)
session.unregisterTrap("logdb-query-result-" + id);
session.unregisterTrap("logdb-query-" + id);
session.unregisterTrap("logdb-query-timeline-" + id);
Map<String, Object> params = new HashMap<String, Object>();
params.put("id", id);
rpc("org.araqne.logdb.msgbus.LogQueryPlugin.removeQuery", params);
queries.remove(id);
}
public void addFailureListener(FailureListener listener) {
failureListeners.add(listener);
}
public void removeFailureListener(FailureListener listener) {
failureListeners.remove(listener);
}
private static class QueuedRows implements Future<Integer> {
private List<Row> rows;
CountDownLatch l = new CountDownLatch(1);
private volatile Throwable t;
private Flusher flusher;
public QueuedRows(List<Row> rows, Flusher flusher) {
this.rows = rows;
this.flusher = flusher;
}
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
throw new UnsupportedOperationException();
}
@Override
public boolean isCancelled() {
return false;
}
@Override
public boolean isDone() {
return false;
}
public void setDone() {
l.countDown();
}
public void setDone(Throwable t) {
this.t = t;
l.countDown();
}
@Override
public Integer get() throws InterruptedException, ExecutionException {
flusher.await(this);
if (t != null)
throw new ExecutionException(t);
else
return rows.size();
}
@Override
public Integer get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
if (flusher.await(this, timeout, unit)) {
if (t != null)
throw new ExecutionException(t);
else
return rows.size();
} else
throw new TimeoutException();
}
public List<Row> getRows() {
return rows;
}
}
/**
* 지정된 테이블에 행을 입력합니다.
*
* @param tableName
* 테이블 이름
* @param rows
* 행 목록
* @since 0.9.5
*/
public Future<Integer> insert(String tableName, List<Row> rows) {
for (Row row : rows) {
if (row.get("_time") == null || !(row.get("_time") instanceof Date))
row.put("_time", new Date());
}
QueuedRows ret = null;
// buffering
if (flusher.get() == null) {
if (flusher.compareAndSet(null, new Flusher())) {
flusher.get().start();
}
}
boolean acResult = inputThrottler.tryAcquire(rows.size());
if (!acResult) {
flusher.get().signal();
while (true) {
try {
inputThrottler.acquire(rows.size());
break;
} catch (InterruptedException e) {
if (isClosed())
break;
}
}
}
synchronized (flushBuffers) {
if (!flushBuffers.containsKey(tableName))
flushBuffers.put(tableName, new ArrayList<QueuedRows>());
QueuedRows qr = new QueuedRows(rows, flusher.get());
flushBuffers.get(tableName).add(qr);
ret = qr;
// counter += rows.size();
}
// count over -> flush
if (inputThrottler.availablePermits() <= MAX_THROTTLE_PERMIT * 0.8) {
flusher.get().signal();
}
return ret;
}
/**
* 지정된 테이블에 행을 입력합니다.
*
* @param tableName
* 테이블 이름
* @param row
* 입력할 행
* @since 0.9.5
*/
public Future<Integer> insert(String tableName, Row row) {
if (row.get("_time") == null || !(row.get("_time") instanceof Date))
row.put("_time", new Date());
QueuedRows ret = null;
// buffering
if (flusher.get() == null) {
if (flusher.compareAndSet(null, new Flusher())) {
flusher.get().start();
}
}
boolean acResult = inputThrottler.tryAcquire();
if (!acResult) {
flusher.get().signal();
while (true) {
try {
inputThrottler.acquire();
break;
} catch (InterruptedException e) {
if (isClosed())
break;
}
}
}
synchronized (flushBuffers) {
if (!flushBuffers.containsKey(tableName))
flushBuffers.put(tableName, new ArrayList<QueuedRows>());
QueuedRows qr = new QueuedRows(Arrays.asList(row), flusher.get());
flushBuffers.get(tableName).add(qr);
ret = qr;
}
// count over -> flush
if (inputThrottler.availablePermits() != MAX_THROTTLE_PERMIT) {
flusher.get().signal();
}
return ret;
}
public class Flusher implements Runnable {
Thread th;
ConcurrentHashMap<QueuedRows, QueuedRows> wCalls = new ConcurrentHashMap<QueuedRows, QueuedRows>();
public void start() {
synchronized (this) {
if (th == null) {
th = new Thread(this, String.format("Insert flush thread"));
th.start();
}
}
}
public boolean await(QueuedRows r, long timeout, TimeUnit unit) throws InterruptedException {
try {
long start = System.currentTimeMillis();
long end = start + TimeUnit.MILLISECONDS.convert(timeout, unit);
wCalls.put(r, r);
signal();
while (true) {
if (!running) {
r.setDone(new SocketException("closed"));
return true;
}
if (r.l.await(50, TimeUnit.MILLISECONDS))
return true;
if (System.currentTimeMillis() >= end)
return false;
}
} finally {
wCalls.remove(r, r);
}
}
public void await(QueuedRows r) throws InterruptedException {
try {
wCalls.put(r, r);
signal();
while (true) {
if (!running) {
r.setDone(new SocketException("closed"));
break;
}
if (r.l.await(50, TimeUnit.MILLISECONDS))
break;
}
} finally {
wCalls.remove(r, r);
}
}
volatile boolean running = true;
@Override
public void run() {
while (running) {
try {
long started = System.nanoTime();
flushInternal();
long nextWaitMillis = indexFlushInterval - (System.nanoTime() - started) / 1000000L;
if (inputThrottler.availablePermits() == MAX_THROTTLE_PERMIT && wCalls.size() == 0 && nextWaitMillis > 0)
synchronized (this) {
this.wait(nextWaitMillis);
}
} catch (InterruptedException e) {
}
}
// give one more chance to flush
flushInternal();
}
void shutdown() {
running = false;
synchronized (this) {
this.notifyAll();
}
while (!flushBuffers.isEmpty()) {
try {
th.join();
} catch (InterruptedException e) {
}
}
}
public void signal() {
synchronized (this) {
this.notifyAll();
}
}
}
/**
* 현재 대기 중인 쓰기 버퍼를 비우고 RPC 통신을 통해 로그프레소 테이블에 기록합니다.
*
* @since 0.9.5
*/
public void flush() {
}
private void flushInternal() {
if (inputThrottler.availablePermits() == MAX_THROTTLE_PERMIT)
return;
Map<String, List<QueuedRows>> binsMap = null;
synchronized (flushBuffers) {
binsMap = new HashMap<String, List<QueuedRows>>(flushBuffers);
flushBuffers.clear();
}
int counter = 0;
for (Map.Entry<String, List<QueuedRows>> entry : binsMap.entrySet()) {
for (QueuedRows rows : entry.getValue()) {
counter += rows.getRows().size();
}
}
inputThrottler.release(counter);
for (Map.Entry<String, List<QueuedRows>> entry : binsMap.entrySet()) {
String tableName = entry.getKey();
List<QueuedRows> items = entry.getValue();
try {
Iterator<QueuedRows> it = items.iterator();
while (it.hasNext()) {
List<Object> l = new ArrayList<Object>(items.size());
List<QueuedRows> currItems = new ArrayList<QueuedRows>();
while (it.hasNext()) {
QueuedRows rows = it.next();
for (Row row : rows.getRows()) {
l.add(row.map());
}
currItems.add(rows);
if (l.size() >= insertBatchSize)
break;
}
List<Map<String, Object>> bins = streamingEncoder.encode(l, false);
Map<String, Object> params = new HashMap<String, Object>();
params.put("table", entry.getKey());
params.put("bins", bins);
rpc("org.araqne.logdb.msgbus.LogQueryPlugin.insertBatch", params);
for (QueuedRows rows : currItems) {
rows.setDone();
}
}
} catch (Throwable t) {
logger.debug("araqne logdb client: cannot insert data", t);
for (QueuedRows rows : items) {
rows.setDone(t);
for (FailureListener c : failureListeners) {
try {
c.onInsertFailure(tableName, rows.getRows(), t);
} catch (Throwable t2) {
logger.debug("araqne logdb client: insert failure callback should not throw any exception", t2);
}
}
}
}
}
}
/**
* 특정 쿼리에 대해서 주어진 쿼리 결과 갯수가 조회 가능할 때까지 현재 스레드를 대기(blocking) 합니다. 주어진 쿼리 결과
* 갯수를 채우지 못하더라도 쿼리가 완료 혹은 취소되면 스레드 대기 상태가 풀립니다. 이 메소드를 이용하면 매번 getQuery()를
* 사용하여 서버에 폴링하지 않더라도 원하는 시점까지 대기할 수 있으며 서버 부하도 감소합니다.
*
* @param id
* 쿼리 ID
* @param count
* 쿼리 결과 행 갯수, null을 넘기는 경우 쿼리 완료 혹은 취소 시까지 대기합니다.
*/
public void waitUntil(int id, Long count) {
verifyQueryId(id);
queries.get(id).waitUntil(count);
}
/**
* 쿼리 결과를 조회합니다. 주어진 offset 갯수만큼 건너뛰고, 최대 limit 갯수만큼 쿼리 결과를 조회합니다. 쿼리가 존재하지
* 않거나 액세스 권한이 없는 경우 예외가 발생합니다.
*
* @param id
* 쿼리 ID
* @param offset
* 건너뛸 결과 행 갯수
* @param limit
* 가져올 최대 행 갯수. 너무 큰 값을 넘기면 서버나 클라이언트에서 메모리 고갈이 발생할 수 있습니다. 일반적으로
* 10000 내외의 값을 사용하여 페이징 조회합니다.
* @return Map 타입으로 아래와 같은 항목들을 반환합니다. result: Map 타입의 결과 행의 List, count: 전체
* 쿼리 결과 행 갯수, 쿼리가 실행 중인 경우 getResult() 호출 시점까지의 쿼리 결과 행 갯수를 반환하며 쿼리
* 완료 시까지 계속 증가할 수 있습니다. fields: 쿼리 문자열에 fields 쿼리 커맨드를 사용한 경우, 출력
* 필드 순서를 정렬하는데 사용할 수 있도록 필드 이름 목록을 반환합니다.
*/
public Map<String, Object> getResult(int id, long offset, int limit) throws IOException {
verifyQueryId(id);
Map<String, Object> params = new HashMap<String, Object>();
params.put("id", id);
params.put("offset", offset);
params.put("limit", limit);
params.put("binary_encode", true);
Message resp = rpc("org.araqne.logdb.msgbus.LogQueryPlugin.getResult", params);
if (resp.getParameters().size() == 0)
throw new MessageException("query-not-found", "", resp.getParameters());
// support backward compatibility
if (!resp.getParameters().containsKey("uncompressed_size"))
return resp.getParameters();
// decompress and decode
int uncompressedSize = (Integer) resp.getParameters().get("uncompressed_size");
String binary = (String) resp.getParameters().get("binary");
return decodeBinary(binary, uncompressedSize);
}
private Map<String, Object> decodeBinary(String binary, int uncompressedSize) {
byte[] b = Base64.decode(binary);
byte[] uncompressed = new byte[uncompressedSize];
uncompress(uncompressed, b);
Map<String, Object> m = EncodingRule.decodeMap(ByteBuffer.wrap(uncompressed));
Object[] resultArray = (Object[]) m.get("result");
m.put("result", Arrays.asList(resultArray));
return m;
}
private void uncompress(byte[] output, byte[] b) {
Inflater inflater = new Inflater();
inflater.setInput(b, 0, b.length);
try {
inflater.inflate(output);
inflater.reset();
} catch (DataFormatException e) {
throw new IllegalStateException(e);
} finally {
inflater.end();
}
}
private void verifyQueryId(int id) {
if (!queries.containsKey(id))
throw new MessageException("query-not-found", "query [" + id + "] does not exist", null);
}
/**
* 접속을 끊고 할당된 자원을 정리합니다.
*/
public void close() throws IOException {
if (inputThrottler.availablePermits() != MAX_THROTTLE_PERMIT)
flush();
if (flusher.get() != null)
flusher.get().shutdown();
if (session != null)
session.close();
if (streamingDecoder != null) {
streamingDecoder.close();
streamingDecoder = null;
}
if (streamingEncoder != null) {
streamingEncoder.close();
streamingEncoder = null;
}
}
@Override
public void onTrap(Message msg) {
String method = msg.getMethod();
long stamp = 0;
if (msg.containsKey("stamp"))
stamp = Long.parseLong(msg.get("stamp").toString());
if (method.startsWith("logdb-query-timeline-")) {
int id = msg.getInt("id");
LogQuery q = queries.get(id);
q.updateCount(msg.getLong("count"), stamp);
if (msg.getString("type").equals("eof"))
q.updateStatus("Ended", stamp);
} else if (method.startsWith("logdb-query-result-")) {
handleStreamingResult(msg);
} else if (method.startsWith("logdb-query-")) {
int id = msg.getInt("id");
LogQuery q = queries.get(id);
if (msg.getString("type").equals("eof")) {
q.updateCount(msg.getLong("total_count"), stamp);
if (msg.get("error_code") != null) {
q.setErrorCode((Integer) msg.get("error_code"));
q.setErrorDetail((String) msg.get("error_detail"));
q.updateStatus("Cancelled", stamp);
} else {
q.updateStatus("Ended", stamp);
}
} else if (msg.getString("type").equals("page_loaded")) {
q.updateCount(msg.getLong("count"), stamp);
q.updateStatus("Running", stamp);
} else if (msg.getString("type").equals("status_change")) {
q.updateCount(msg.getLong("count"), stamp);
q.updateStatus(msg.getString("status"), stamp);
}
}
}
@SuppressWarnings("unchecked")
private void handleStreamingResult(Message msg) {
List<Map<String, Object>> chunks = (List<Map<String, Object>>) msg.get("bins");
boolean last = msg.getBoolean("last");
boolean lastCalled = false;
int queryId = Integer.valueOf(msg.getMethod().substring("logdb-query-result-".length()));
StreamingResultSet rs = null;
LogQuery query = null;
try {
query = queries.get(queryId);
rs = streamCallbacks.get(queryId);
ArrayList<Row> rows = null;
List<Object> l = null;
if (chunks != null)
l = streamingDecoder.decode(chunks);
else
l = (List<Object>) msg.get("rows");
rows = new ArrayList<Row>(l.size());
for (Object o : l)
rows.add(new Row((Map<String, Object>) o));
if (query != null && rs != null) {
rs.onRows(query, rows, last);
if (last)
lastCalled = true;
}
} catch (ExecutionException e) {
logger.error("araqne logdb client: cannot decode streaming result", e);
if (query != null && rs != null && last && !lastCalled)
rs.onRows(query, new ArrayList<Row>(), true);
} catch (Throwable t) {
if (query != null && rs != null && last && !lastCalled)
rs.onRows(query, new ArrayList<Row>(), true);
}
}
@Override
public void onClose(Throwable t) {
for (LogQuery q : queries.values())
q.updateStatus("Cancelled", Long.MAX_VALUE);
}
private void checkNotNull(String name, Object o) {
if (o == null)
throw new IllegalArgumentException(name + " parameter should be not null");
}
private Message rpc(String method, int timeout) throws IOException, TimeoutException {
if (session == null)
throw new IOException("not connected yet, use connect()");
return session.rpc(method, timeout);
}
private Message rpc(String method) throws IOException {
try {
return rpc(method, 0);
} catch (TimeoutException e) {
throw new IllegalStateException(e);
}
}
private Message rpc(String method, Map<String, Object> params, int timeout) throws IOException, TimeoutException {
if (session == null)
throw new IOException("not connected yet, use connect()");
return session.rpc(method, params, timeout);
}
private Message rpc(String method, Map<String, Object> params) throws IOException {
try {
return rpc(method, params, 0);
} catch (TimeoutException e) {
throw new IllegalStateException(e);
}
}
public Map<String, Object> getNodeByGuid(String instanceGuid) throws IOException {
Map<String, Object> params = new HashMap<String, Object>();
params.put("instance_guid", instanceGuid);
Message resp = rpc("com.logpresso.query.msgbus.FederationPlugin.getNodeByGuid", params);
@SuppressWarnings("unchecked")
Map<String, Object> nodeInfo = (Map<String, Object>) resp.get("node");
return nodeInfo;
}
public String getInstanceGuid(int timeout) throws IOException, TimeoutException {
Message resp = rpc("org.araqne.logdb.msgbus.ManagementPlugin.getInstanceGuid", timeout);
String l = (String) resp.get("instance_guid");
return l;
}
public PeerStatus getPeerStatus(String instanceGuid, int timeout) throws IOException, TimeoutException {
Map<String, Object> params = new HashMap<String, Object>();
params.put("instance_guid", instanceGuid);
Message resp = rpc("com.logpresso.query.msgbus.FederationPlugin.getPeerStatus", params, timeout);
return new PeerStatus(resp.get("peer_status"));
}
}