package org.infinispan.cli.interpreter;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import org.antlr.runtime.ANTLRStringStream;
import org.antlr.runtime.CharStream;
import org.antlr.runtime.CommonTokenStream;
import org.infinispan.cli.interpreter.codec.CodecRegistry;
import org.infinispan.cli.interpreter.logging.Log;
import org.infinispan.cli.interpreter.result.EmptyResult;
import org.infinispan.cli.interpreter.result.Result;
import org.infinispan.cli.interpreter.result.ResultKeys;
import org.infinispan.cli.interpreter.session.Session;
import org.infinispan.cli.interpreter.session.SessionImpl;
import org.infinispan.cli.interpreter.statement.Statement;
import org.infinispan.commons.api.BasicCacheContainer;
import org.infinispan.factories.annotations.Inject;
import org.infinispan.factories.annotations.Start;
import org.infinispan.factories.annotations.Stop;
import org.infinispan.factories.scopes.Scope;
import org.infinispan.factories.scopes.Scopes;
import org.infinispan.jmx.annotations.MBean;
import org.infinispan.jmx.annotations.ManagedAttribute;
import org.infinispan.jmx.annotations.ManagedOperation;
import org.infinispan.manager.EmbeddedCacheManager;
import org.infinispan.util.TimeService;
import org.infinispan.util.logging.LogFactory;
@Scope(Scopes.GLOBAL)
@MBean(objectName = "Interpreter", description = "Interpreter component which executes CLI operations")
public class Interpreter {
private static final Log log = LogFactory.getLog(Interpreter.class, Log.class);
private static final long DEFAULT_SESSION_REAPER_WAKEUP_INTERVAL = 60000l; // in millis
private static final long DEFAULT_SESSION_TIMEOUT = 360000l; // in millis
private EmbeddedCacheManager cacheManager;
private ScheduledExecutorService executor;
private long sessionReaperWakeupInterval = DEFAULT_SESSION_REAPER_WAKEUP_INTERVAL;
private long sessionTimeout = DEFAULT_SESSION_TIMEOUT;
private CodecRegistry codecRegistry;
private TimeService timeService;
private final Map<String, Session> sessions = new ConcurrentHashMap<String, Session>();
private ScheduledFuture<?> sessionReaperTask;
public Interpreter() {
}
@Inject
public void initialize(final EmbeddedCacheManager cacheManager, TimeService timeService) {
this.cacheManager = cacheManager;
this.codecRegistry = new CodecRegistry(cacheManager.getCacheManagerConfiguration().classLoader());
this.timeService = timeService;
}
@Start
public void start() {
this.executor = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() {
@Override
public Thread newThread(final Runnable r) {
return new Thread(r, "Interpreter");
}
});
sessionReaperTask = executor.scheduleWithFixedDelay(new ScheduledTask(), sessionReaperWakeupInterval, sessionReaperWakeupInterval, TimeUnit.MILLISECONDS);
}
@Stop
public void stop() {
if (sessionReaperTask != null) {
sessionReaperTask.cancel(true);
}
executor.shutdownNow();
}
@ManagedOperation(description = "Creates a new interpreter session")
public String createSessionId(String cacheName) {
String sessionId = UUID.randomUUID().toString();
SessionImpl session = new SessionImpl(codecRegistry, cacheManager, sessionId, timeService);
sessions.put(sessionId, session);
if (cacheName != null) {
session.setCurrentCache(cacheName);
}
return sessionId;
}
public long getSessionReaperWakeupInterval() {
return sessionReaperWakeupInterval;
}
public void setSessionReaperWakeupInterval(final long sessionReaperWakeupInterval) {
this.sessionReaperWakeupInterval = sessionReaperWakeupInterval;
}
public long getSessionTimeout() {
return sessionTimeout;
}
public void setSessionTimeout(final long sessionTimeout) {
this.sessionTimeout = sessionTimeout;
}
void expireSessions() {
long timeBoundary = timeService.time() - sessionTimeout * 1000000l;
for (Iterator<Session> i = sessions.values().iterator(); i.hasNext();) {
Session session = i.next();
if (timeBoundary - session.getTimestamp() > 0) {
i.remove();
if (log.isDebugEnabled()) {
log.debugf("Removed expired interpreter session %s", session.getId());
}
}
}
}
@ManagedOperation(description = "Parses and executes IspnCliQL statements")
public Map<String, String> execute(final String sessionId, final String s) throws Exception {
Session session = null;
ClassLoader oldCL = SecurityActions.setThreadContextClassLoader(cacheManager.getCacheManagerConfiguration().classLoader());
Map<String, String> response = new HashMap<String, String>();
try {
session = validateSession(sessionId);
CharStream stream = new ANTLRStringStream(s);
IspnCliQLLexer lexer = new IspnCliQLLexer(stream);
CommonTokenStream tokens = new CommonTokenStream(lexer);
IspnCliQLParser parser = new IspnCliQLParser(tokens);
parser.statements();
if (parser.hasParserErrors()) {
throw new ParseException(parser.getParserErrors());
}
StringBuilder output = new StringBuilder();
for (Statement stmt : parser.statements) {
Result result = stmt.execute(session);
if (result != EmptyResult.RESULT) {
output.append(result.getResult());
}
}
if (output.length() > 0) {
response.put(ResultKeys.OUTPUT.toString(), output.toString());
}
} catch (Throwable t) {
log.interpreterError(t);
response.put(ResultKeys.ERROR.toString(), t.getMessage());
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
t.printStackTrace(pw);
response.put(ResultKeys.STACKTRACE.toString(), sw.toString());
} finally {
if (session != null) {
session.reset();
response.put(ResultKeys.CACHE.toString(), session.getCurrentCacheName());
}
SecurityActions.setThreadContextClassLoader(oldCL);
}
return response;
}
private Session validateSession(final String sessionId) {
if (sessionId == null) {
Session session = new SessionImpl(codecRegistry, cacheManager, null, timeService);
session.setCurrentCache(BasicCacheContainer.DEFAULT_CACHE_NAME);
return session;
}
if (!sessions.containsKey(sessionId)) {
throw log.invalidSession(sessionId);
}
return sessions.get(sessionId);
}
@ManagedAttribute(description = "Retrieves a list of caches for the cache manager")
public String[] getCacheNames() {
Set<String> cacheNames = new HashSet<String>(cacheManager.getCacheNames());
cacheNames.add(BasicCacheContainer.DEFAULT_CACHE_NAME);
return cacheNames.toArray(new String[0]);
}
class ScheduledTask implements Runnable {
@Override
public void run() {
expireSessions();
}
}
}