/*
* Copyright (C) 2013 Andrey Chaschev.
*
* 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 bear.core;
import bear.console.ConsoleCallback;
import bear.context.AbstractContext;
import bear.main.event.PartyFinishedEventToUI;
import bear.main.phaser.SettableFuture;
import bear.maven.LoggingBooter;
import bear.plugins.Plugin;
import bear.plugins.sh.*;
import bear.session.Address;
import bear.session.DynamicVariable;
import bear.session.SshAddress;
import bear.session.Variables;
import bear.task.*;
import chaschev.util.Exceptions;
import com.google.common.base.Optional;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.appender.FileAppender;
import org.apache.logging.log4j.core.filter.ThreadContextMapFilter;
import org.apache.logging.log4j.core.helpers.KeyValuePair;
import org.apache.logging.log4j.core.layout.PatternLayout;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.slf4j.MDC;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import static bear.session.Variables.*;
import static org.apache.logging.log4j.core.Filter.Result.ACCEPT;
import static org.apache.logging.log4j.core.Filter.Result.DENY;
/**
* @author Andrey Chaschev chaschev@gmail.com
*/
public class SessionContext extends AbstractContext {
public static final DateTimeFormatter TIME_FORMATTER = DateTimeFormat.forPattern("HH:mm:ss:SSS");
// public final GlobalContext globalContext;
public final SystemEnvironmentPlugin.SystemSessionDef sysDef;
public GenericUnixLocalEnvironmentPlugin localSysEnv;
public GenericUnixRemoteEnvironmentPlugin remoteSysEnv;
public final SystemSession sys;
public final SessionRunner runner;
public Bear bear;
public Address address;
// protected CompositeTaskRunContext taskRunContext;
public static final org.apache.logging.log4j.Logger ui = LogManager.getLogger("fx");
protected Task<Object, TaskResult<?>> currentTask;
protected ExecutionContext executionContext = new ExecutionContext();
protected GlobalTaskRunner globalRunner;
public final String id = randomId();
protected Thread thread;
public SessionContext(GlobalContext global, Address address, SessionRunner runner) {
super(global, address.getName());
///this can be extracted into newContext(aClass, parent, Object... fields)
this.runner = runner;
global.wire(this); //sets bear, global and the SystemEnvironment plugin =)
this.global = global;
layer.put(bear.sessionHostname, address.getName());
layer.put(bear.sessionAddress, address.getAddress());
this.address = wire(address);
////////
// this can be extracted into init
sysDef = ((address instanceof SshAddress) ? remoteSysEnv : localSysEnv).getTaskDef();
sys = (SystemSession) (Task) sysDef.singleTaskSupplier().createNewSession(this, null, sysDef);
this.setName(address.getName());
if (address instanceof SshAddress) {
SshAddress a = (SshAddress) address;
layer.putConst(bear.sshUsername, a.username);
layer.putConst(bear.sshPassword, a.password);
}
runner.set$(this);
currentTask = new Task<Object, TaskResult<?>>(null, TaskDef.ROOT, this);
}
public synchronized void foo() {
// this can be any thread-specific string
String threadName = Thread.currentThread().getName();
org.apache.logging.log4j.core.Logger coreLogger
= (org.apache.logging.log4j.core.Logger) org.apache.logging.log4j.LogManager.getLogger(threadName);
String appenderName = threadName + "-file-appender";
MDC.put("threadId", threadName);
if(!global.var(bear.printHostsToConsole)){
MDC.put("hideFromConsole", "yes");
}
if(!global.var(bear.printHostsToBearLog)){
MDC.put("hideFromBearLog", "yes");
}
if (coreLogger.getAppenders().containsKey(appenderName)) {
return;
}
//will reject messages coming from other threads
ThreadContextMapFilter filter = ThreadContextMapFilter.createFilter(
new KeyValuePair[]{new KeyValuePair("threadId", name)}, "or",
ACCEPT.name(),
DENY.name());
try {
FileAppender fileAppender = FileAppender.createAppender(
".bear/logs/" + threadName + ".log",
"false",
null,
appenderName,
null,
null,
null,
PatternLayout.createLayout("%d{HH:mm:ss.S} [%thread{10}] %c{1.} - %msg%n", null, null, null, null),
filter,
null,
null,
null
);
fileAppender.start();
LoggingBooter.addLog4jAppender(LogManager.getRootLogger(), fileAppender, Level.DEBUG, filter, true);
} catch (Exception e) {
throw new RuntimeException(e);
}
// logger.info("This message will only end up in {}.log!", threadName);
// LogManager.getLogger("fx").info("This message will only end up in {}.log!", threadName);
}
public static String randomId() {
return RandomStringUtils.randomAlphanumeric(6);
}
public Thread getThread() {
return thread;
}
public void setThread(Thread thread) {
this.thread = thread;
thread.setName(threadName());
foo();
}
public void whenPhaseStarts(BearScriptPhase<Object, TaskResult<?>> phase, BearScriptRunner.ShellSessionContext shellSessionContext) {
StringBuilder phaseSB = executionContext.phaseText.getDefaultValue();
phaseSB.setLength(0);
executionContext.phaseText.fireExternalModification();
executionContext.phaseName = phase.getName();
executionContext.phaseId.defaultTo(phase.id);
}
public void whenSessionComplete(GlobalTaskRunner globalTaskRunner) {
thread = null;
DynamicVariable<TaskExecutionContext> execCtx = executionContext.rootExecutionContext;
TaskExecutionContext execCtx2 = execCtx.getDefaultValue();
TaskResult<?> myLastResult = runner.getMyLastResult();
boolean isOk = myLastResult.ok();
globalTaskRunner.stats.getDefaultValue().addArrival(isOk);
globalTaskRunner.stats.fireExternalModification();
globalTaskRunner.arrivedCount.getDefaultValue().incrementAndGet();
globalTaskRunner.arrivedCount.fireExternalModification();
BearMain.ui.info(new PartyFinishedEventToUI(getName(), execCtx2.getDuration(), myLastResult, runner.getMyLastRollbackResult()));
}
public void cancel() {
if (thread == null) {
throw new IllegalStateException("not running or already cancelled");
}
thread.interrupt();
}
public void terminate() {
if (thread == null) {
throw new IllegalStateException("not running or already cancelled");
}
ui.info("terminating...");
thread.interrupt();
}
public boolean isRunning() {
return executionContext.isRunning();
}
public ConsoleCallback sshCallback() {
return SystemEnvironmentPlugin.println(var(bear.sshPassword));
}
public SettableFuture<TaskResult<?>> future(String taskDefName, String sessionName) {
return globalRunner.future(taskDefName, sessionName);
}
public <I, O extends TaskResult<?>> SettableFuture<O> future(TaskDef<I, O> taskDef, String sessionName) {
return (SettableFuture<O>) globalRunner.future(taskDef.getName(), sessionName);
}
public <I, O extends TaskResult<?>> SettableFuture<O> future(NamedCallable<I, O> namedCallable, String sessionName) {
return (SettableFuture<O>) globalRunner.future(namedCallable.getName(), sessionName);
}
public <I, O extends TaskResult<?>> O getPreviousResult(TaskDef<I, O> taskDef) {
return (O) getPreviousResult(taskDef.getName());
}
public <I, O extends TaskResult<?>> O getPreviousResult(NamedCallable<I, O> callable) {
return (O) getPreviousResult(callable.getName());
}
public TaskResult<?> getPreviousResult(String taskDefName) {
try {
return globalRunner.future(taskDefName, getName()).get();
} catch (InterruptedException e) {
throw new IllegalStateException("future must be completed when retrieving result in the same session");
} catch (ExecutionException e) {
throw Exceptions.runtime(e.getCause());
}
}
public static class ExecutionHistoryEntry {
}
public static class ExecutionHistory {
protected Map<TaskDef<Object, TaskResult<?>>, TaskResult<?>> resultMap = new HashMap<TaskDef<Object, TaskResult<?>>, TaskResult<?>>();
}
public class ExecutionContext {
public final DateTime startedAt = new DateTime();
public final DynamicVariable<String> phaseId = undefined();
public final DynamicVariable<StringBuilder> text = newVar(new StringBuilder(8192)).desc("text appended in session");
public final DynamicVariable<StringBuilder> phaseText = newVar(new StringBuilder(8192)).desc("text appended in session");
public final DynamicVariable<String> textAppended = dynamic(String.class).desc("text appended in session").defaultTo("");
public final DynamicVariable<TaskExecutionContext> rootExecutionContext = dynamic(TaskExecutionContext.class);
public final DynamicVariable<Task> currentTask = dynamic(Task.class);
public final DynamicVariable<CommandContext> currentCommand = dynamic(CommandContext.class);
public String phaseName;
public void textAdded(String textAdded) {
updateBuffer(textAdded, text);
updateBuffer(textAdded, phaseText);
textAppended.defaultTo(textAdded);
}
private void updateBuffer(String textAdded, DynamicVariable<StringBuilder> sbVar) {
StringBuilder sb = sbVar.apply(SessionContext.this);
sb.append(textAdded);
sbVar.fireExternalModification(null, sb);
}
public boolean isRunning() {
return rootExecutionContext.getDefaultValue().isRunning();
}
}
public String joinPath(DynamicVariable<String> var, String path) {
return sys.joinPath(var(var), path);
}
public String joinPath(String... paths) {
return sys.joinPath(paths);
}
public String threadName() {
return sys.getName();
}
public CommandLine newCommandLine() {
return sys.newCommandLine();
}
public void log(String s, Object... params) {
if (!s.endsWith("%n") && !s.endsWith("\n")) {
s += "\n";
}
System.out.printf(s, params);
}
public void warn(String s, Object... params) {
logLevel(s, "WARN", params);
}
public void error(String s, Object... params) {
logLevel(s, "ERROR", params);
}
private void logLevel(String s, String level, Object[] params) {
// and here's how to get the String representation
if (!s.endsWith("%n") && !s.endsWith("\n")) {
s += "\n";
}
System.out.printf(new DateTime().toString(TIME_FORMATTER) + " " + level + " " + s, params);
}
public TaskResult<?> run(TaskDef... tasks) {
return runner.run(tasks);
}
public Task<Object, TaskResult<?>> getCurrentTask() {
return (Task) currentTask;
}
public void setCurrentTask(Task<?, ?> currentTask) {
Task.wrongThreadCheck(currentTask.$());
if (currentTask.isRootTask()) {
executionContext.rootExecutionContext.defaultTo(currentTask.getExecutionContext());
}
executionContext.currentTask.defaultTo(currentTask);
this.currentTask = (Task)currentTask;
}
public void logOutput(String textAdded) {
// System.out.print(textAdded);
executionContext.textAdded(textAdded);
}
public ExecutionContext getExecutionContext() {
return executionContext;
}
public SystemSession getSys() {
return sys;
}
public SessionRunner getRunner() {
return runner;
}
@Override
public GlobalContext getGlobal() {
return (GlobalContext) global;
}
public String getName() {
String name = sys == null ? null : sys.getName();
return name == null ? this.name : name;
}
public String concat(Object... varsAndStrings) {
return Variables.concat(this, varsAndStrings);
}
public void setGlobalRunner(GlobalTaskRunner globalRunner) {
this.globalRunner = globalRunner;
}
public <T extends Plugin> T plugin(Class<T> pluginClass) {
return getGlobal().plugin(pluginClass);
}
public <I, O extends TaskResult<?>> O runSession(Task<I, O> taskSession) {
return runner.runSession(taskSession);
}
public <I, O extends TaskResult<?>> O runSession(Task<I, O> taskSession, I input) {
return runner.runSession(taskSession.setInput(input), input);
}
public Optional<? extends TaskResult> findResult(TaskDef<Object, TaskResult<?>> def) {
return executionContext.rootExecutionContext.getDefaultValue().findResult(def);
}
// returns last available result, null if none of the tasks finished
public Optional<? extends TaskResult> lastResult() {
return executionContext.rootExecutionContext.getDefaultValue().lastResult();
}
public String getHost() {
return address.getAddress();
}
}