/*
* 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.annotations.Configuration;
import bear.annotations.Method;
import bear.annotations.Project;
import bear.annotations.Variable;
import bear.plugins.DeploymentPlugin;
import bear.plugins.Plugin;
import bear.plugins.PluginShellMode;
import bear.plugins.ServerToolPlugin;
import bear.plugins.misc.ReleasesPlugin;
import bear.plugins.sh.GenericUnixRemoteEnvironmentPlugin;
import bear.session.Address;
import bear.session.DynamicVariable;
import bear.session.Result;
import bear.task.*;
import chaschev.lang.OpenBean;
import chaschev.lang.reflect.Annotations;
import chaschev.lang.reflect.MethodDesc;
import chaschev.util.Exceptions;
import com.google.common.base.*;
import com.google.common.collect.Lists;
import org.apache.commons.io.IOUtils;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.logging.log4j.core.helpers.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.net.URI;
import java.util.*;
import static com.google.common.base.Objects.firstNonNull;
import static com.google.common.base.Optional.fromNullable;
import static java.util.Collections.singletonList;
/**
* @author Andrey Chaschev chaschev@gmail.com
*/
public abstract class BearProject<SELF extends BearProject> {
private static final Logger logger = LoggerFactory.getLogger(BearProject.class);
/**
* Typically you would want to turn it off when you do several calls one of which redefines configuration.
*/
protected boolean useAnnotations = true;
protected GlobalContextFactory factory;
protected GlobalContext global;
private boolean configured;
private Map<Object, Object> variables;
private BearMain bearMain;
protected Bear bear;
protected boolean shutdownAfterRun = true;
protected DeploymentPlugin.Builder defaultDeployment;
protected ReleasesPlugin releases;
private boolean unblockUninstall;
private Object input;
private boolean async;
protected BearProject() {
this(GlobalContextFactory.INSTANCE);
global = factory.getGlobal();
bear = global.bear;
}
protected BearProject(GlobalContextFactory factory) {
this.factory = factory;
global = factory.getGlobal();
bear = global.bear;
}
public BearProject(GlobalContextFactory factory, @Nullable String propsResourceString) {
this(factory);
if (propsResourceString != null) {
try {
loadProperties(getClass().getResourceAsStream(propsResourceString));
} catch (Exception e) {
throw Exceptions.runtime(e);
}
}
}
public BearProject withMap(Map<Object, Object> variables) {
this.variables = Collections.unmodifiableMap(variables);
return this;
}
public BearProject(GlobalContextFactory factory, @Nullable File file) {
this(factory);
if (file != null) {
loadProperties(file);
}
}
public final SELF configure() {
try {
return configure(factory);
} catch (Exception e) {
throw Exceptions.runtime(e);
}
}
public final SELF configure(GlobalContextFactory factory) throws Exception {
main().configure();
Preconditions.checkArgument(!configured, "already configured");
for (Field field : OpenBean.fieldsOfType(this.getClass(), Plugin.class)) {
Class<? extends Plugin<TaskDef>> aClass = (Class<? extends Plugin<TaskDef>>) field.getType();
factory.requirePlugins(aClass);
}
factory.initPluginsAndWire(this);
Iterable<Field> fields = OpenBean.fieldsOfType(this, GridBuilder.class);
for (Field field : fields) {
((GridBuilder) field.get(this)).init(this);
}
// List<MethodDesc<? extends GridBuilder>> list = OpenBean.methodsReturning(this.getClass(), GridBuilder.class);
// iter
configureMe(factory);
configured = true;
return self();
}
protected abstract GlobalContext configureMe(GlobalContextFactory factory) throws Exception;
public BearProject loadProperties(InputStream is) throws Exception {
global.loadProperties(is);
return this;
}
public BearProject loadProperties(Properties props) {
global.loadProperties(props);
return this;
}
public BearProject loadProperties(File file) {
try {
final FileInputStream fis = new FileInputStream(file);
loadProperties(fis);
return this;
} catch (Exception e) {
throw Exceptions.runtime(e);
}
}
public GlobalContextFactory getFactory() {
return factory;
}
public GlobalContext getGlobal() {
return global;
}
public boolean isConfigured() {
return configured;
}
// public void run() {
// BearMain.run(this, variables, true);
// }
public <T> SELF set(DynamicVariable<? extends T> var, T value) {
if (!global.isSet(var)) {
global.putConst(var, value);
}
return self();
}
@SuppressWarnings("unchecked")
protected SELF self() {
return (SELF) this;
}
public SELF injectMain(BearMain bearMain) {
this.bearMain = bearMain;
return self();
}
public synchronized BearMain main() {
if (bearMain == null) {
bearMain = new BearMain(global, BearMain.getCompilerManager());
}
return bearMain;
}
public GridBuilder newGrid() {
GridBuilder gb = new GridBuilder();
gb.project = this;
gb.bearMain = main();
return gb;
}
public TaskDef<Object, TaskResult<?>> newDeployTask() {
checkDeployment();
return defaultDeployment.build();
}
protected List<TaskDef<Object, TaskResult<?>>> startServiceTaskDefs() {
checkDeployment();
return defaultDeployment.getStartService().createTasksToList(new ArrayList<TaskDef<Object, TaskResult<?>>>());
}
protected List<TaskDef<Object, TaskResult<?>>> stopServiceTaskDefs() {
checkDeployment();
return defaultDeployment.getStopService().createTasksToList(new ArrayList<TaskDef<Object, TaskResult<?>>>());
}
@Method
public GlobalTaskRunner start() {
return runTasksWithAnnotations(new Supplier<List<TaskDef<Object, TaskResult<?>>>>() {
@Override
public List<TaskDef<Object, TaskResult<?>>> get() {
return startServiceTaskDefs();
}
}, useAnnotations);
}
@Method
public GlobalTaskRunner stop() {
return runTasksWithAnnotations(new Supplier<List<TaskDef<Object, TaskResult<?>>>>() {
@Override
public List<TaskDef<Object, TaskResult<?>>> get() {
return stopServiceTaskDefs();
}
}, useAnnotations);
}
@Method
public GlobalTaskRunner deploy() {
return runTasksWithAnnotations(new Supplier<List<TaskDef<Object, TaskResult<?>>>>() {
@Override
public List<TaskDef<Object, TaskResult<?>>> get() {
return singletonList(defaultDeployment.build());
}
}, useAnnotations);
}
@Method
public GlobalTaskRunner setup() {
return setup(true);
}
public GlobalTaskRunner setup(boolean autoInstall) {
if (autoInstall) {
set(bear.verifyPlugins, true);
set(bear.autoInstallPlugins, true);
set(bear.checkDependencies, true);
}
input = this;
return runTasksWithAnnotations(new Supplier<List<InstallationTaskDef<InstallationTask>>>() {
@Override
public List<InstallationTaskDef<InstallationTask>> get() {
return singletonList(global.tasks.setup);
}
});
}
public void rollbackTo(final String ref) {
runTasksWithAnnotations(new Supplier<List<TaskDef<Object, TaskResult<?>>>>() {
@Override
public List<TaskDef<Object, TaskResult<?>>> get() {
return singletonList(rollbackToTask(ref));
}
});
}
public void invoke() {
String method = getClass().getAnnotation(Project.class).method();
Preconditions.checkArgument(!method.isEmpty(), "method() must not be empty for @Project when not providing a invoking a project");
invoke(method);
}
public void invoke(String method, Object... params) {
Preconditions.checkNotNull(method, "method must not be null");
setProjectVars();
MethodDesc methodDesc = OpenBean.getClassDesc(getClass()).getMethodDesc(method, false, params);
Configuration projectAnnotation = projectConf();
Configuration methodAnnotation = methodDesc.getMethod().getAnnotation(Configuration.class);
configureWithAnnotations(methodAnnotation, projectAnnotation);
global.put(bear.useUI, useUI(firstNonNull(methodAnnotation, projectAnnotation)));
Object result = methodDesc.invoke(this, params);
System.out.println("returned result: " + result);
}
public GlobalTaskRunner run(final List<? extends NamedCallable> callables) {
return run(callables, useAnnotations);
}
public GlobalTaskRunner run(final List<? extends NamedCallable> callables, boolean useAnnotations) {
return runTasksWithAnnotations(new Supplier<List<? extends TaskDef>>() {
@Override
public List<? extends TaskDef> get() {
return Lists.newArrayList(Lists.transform(callables, new Function<TaskCallable, TaskDef>() {
@Override
public TaskDef apply(TaskCallable input) {
return new TaskDef(input);
}
}));
}
}, useAnnotations);
}
protected Function<GridBuilder, Void> preRunHook;
public GlobalTaskRunner runTasksWithAnnotations(Supplier<? extends List<? extends TaskDef>> taskList) {
return runTasksWithAnnotations(taskList, useAnnotations);
}
public GlobalTaskRunner runTasksWithAnnotations(Supplier<? extends List<? extends TaskDef>> taskList, boolean useAnnotations) {
global.putConst(bear.activeProject, this);
Configuration projectConf = configureWithAnnotations(useAnnotations);
GridBuilder grid = newGrid()
.setShutdownAfterRun(shutdownAfterRun);
if(input != null){
grid.setInput(input);
input = null;
}
if (preRunHook != null) {
preRunHook.apply(grid);
}
grid.addAll(taskList.get());
grid.setAsync(async);
if (useUI(useAnnotations ? projectConf : null)) {
return grid.runUi();
} else {
return grid.runCli();
}
}
public Configuration configureWithAnnotations(boolean useAnnotations) {
setProjectVars();
Configuration projectConf = projectConf();
if (useAnnotations) {
configureWithAnnotations(projectConf, null);
}
if (!configured) {
configure();
}
return projectConf;
}
private Configuration projectConf() {
return getClass().getAnnotation(Configuration.class);
}
private boolean useUI(Configuration annotation) {
if (global.isSet(bear.useUI)) return global.var(bear.useUI);
if(annotation == null){
return Annotations.defaultBoolean(annotation, "useUI");
}
boolean r = annotation.useUI();
global.put(bear.useUI, r);
return r;
}
private Configuration configureWithAnnotations(
@Nullable Configuration annotation,
@Nullable Configuration fallbackTo) {
if (annotation == null && fallbackTo == null) return null;
String properties = (String) chooseValue("properties", annotation, fallbackTo);
if (properties != null) {
File file = new File(properties);
if (!file.exists() && !file.getName().endsWith(".properties")) {
file = new File(properties + ".properties");
}
Preconditions.checkArgument(file.exists(), "properties file does not exist: %s", file.getAbsolutePath());
set(main().propertiesFile, file);
}
setAnnotation(bear.repositoryURI, chooseValue("vcs", annotation, fallbackTo));
setAnnotation(bear.vcsBranchName, chooseValue("branch", annotation, fallbackTo));
setAnnotation(bear.vcsTag, chooseValue("tag", annotation, fallbackTo));
setAnnotation(bear.stage, chooseValue("stage", annotation, fallbackTo));
setAnnotation(bear.sshUsername, chooseValue("user", annotation, fallbackTo));
setAnnotation(bear.sshPassword, chooseValue("password", annotation, fallbackTo));
Variable[] vars = (Variable[]) chooseValue("variables", annotation, fallbackTo);
if (vars != null) {
for (Variable variable : vars) {
String name = variable.name();
Preconditions.checkArgument(global.variableRegistry.contains(name), "variable %s was not found in the registry", name);
global.putConst(name, variable.value());
}
}
return annotation;
}
private Object chooseValue(String property, Configuration annotation, Configuration fallbackTo) {
Object value = null;
if(annotation != null) {
Object defaultValue = Annotations.defaultValue(annotation, property);
value = OpenBean.invoke(annotation, property);
if(defaultValue.equals(value)) value = null;
}
if(value == null && fallbackTo != null) {
Object defaultValue = Annotations.defaultValue(fallbackTo, property);
value = OpenBean.invoke(fallbackTo, property);
if(defaultValue.equals(value)) value = null;
}
return value;
}
private void setAnnotation(DynamicVariable<String> var, Object value) {
if (value != null) {
set(var, (String) value);
}
}
protected TaskDef<Object, TaskResult<?>> rollbackToTask(final String labelOrPath) {
Preconditions.checkArgument(Strings.isNotEmpty(labelOrPath), "release reference string is empty");
return defaultDeployment.build()
.getRollback()
.addBeforeTask(releases.findReleaseToRollbackTo(labelOrPath));
}
protected SELF checkDeployment() {
Preconditions.checkNotNull(defaultDeployment, "deployment is not present, you need to set BearProject.defaultDeployment field");
return self();
}
private void setProjectVars() {
Project projectAnn = getClass().getAnnotation(Project.class);
setAnnotation(bear.fullName, projectAnn.name());
setAnnotation(bear.name, projectAnn.shortName());
}
public SELF setShutdownAfterRun(boolean p) {
this.shutdownAfterRun = p;
return self();
}
public DeploymentPlugin.Builder getDefaultDeployment() {
return defaultDeployment;
}
public List<Plugin<TaskDef>> getAllOrderedPlugins() {
try {
Set<Plugin<TaskDef>> plugins = new HashSet<Plugin<TaskDef>>();
for (Field field : OpenBean.fieldsOfType(this, Plugin.class)) {
Plugin plugin = (Plugin) field.get(this);
Set<Plugin<TaskDef>> set = plugin.getAllPluginDependencies();
plugins.add(plugin);
plugins.addAll(set);
}
return global.plugins.orderPlugins(plugins);
} catch (IllegalAccessException e) {
throw Exceptions.runtime(e);
}
}
public Optional<? extends Plugin<TaskDef>> findShell(String shell){
Plugin<TaskDef> shellPlugin = null;
for (Plugin<TaskDef> plugin : getAllShellPlugins(global, this.getClass())) {
if(shell.equals(plugin.cmdAnnotation())){
shellPlugin = plugin;
break;
}
}
return fromNullable(shellPlugin);
}
public List<Plugin<TaskDef>> getAllShellPlugins(GlobalContext global, Class<? extends BearProject> aClass) {
Set<Plugin<TaskDef>> plugins = new LinkedHashSet<Plugin<TaskDef>>();
plugins.add((Plugin) global.plugin(GenericUnixRemoteEnvironmentPlugin.class));
for (Field field : OpenBean.fieldsOfType(aClass, Plugin.class)) {
Plugin plugin = global.getPlugin((Class<? extends Plugin>)field.getType()).get();
addIfHasShell(plugins, plugin);
for (Plugin<TaskDef> pl : (Set<Plugin<TaskDef>>) plugin.getAllPluginDependencies()) {
addIfHasShell(plugins, pl);
}
}
return global.plugins.orderPlugins(plugins);
}
private static void addIfHasShell(Set<Plugin<TaskDef>> plugins, Plugin plugin) {
PluginShellMode shell = plugin.getShell();
if(shell != null){
plugins.add(plugin);
}
}
public SELF setInteractiveMode(){
async = true;
shutdownAfterRun = false;
return self();
}
public void setAsync(boolean async) {
this.async = async;
}
public boolean isAsync() {
return async;
}
public static class PulseResult extends TaskResult<PulseResult> {
public PulseResult(Result result) {
super(result);
}
public PulseResult(Throwable e) {
super(e);
}
}
public DependencyResult pulse() {
DependencyResult result = new DependencyResult(Result.OK);
for (Field field : OpenBean.fieldsOfType(this, ServerToolPlugin.class)) {
try {
ServerToolPlugin plugin = (ServerToolPlugin) field.get(this);
result.join(pulse(plugin, Predicates.<String>alwaysTrue()));
} catch (IllegalAccessException e) {
throw Exceptions.runtime(e);
}
}
return result;
}
protected DependencyResult pulse(ServerToolPlugin serverTool, Predicate<String> bodyPredicate) {
DependencyResult result = new DependencyResult("pulse from " + serverTool.getClass().getSimpleName());
HttpClient httpClient = new DefaultHttpClient();
for (Address address : global.var(global.bear.getStage).addresses) {
List<String> ports = global.var(serverTool.portsSplit);
for (String port : ports) {
URI uri = null;
try {
uri = new URIBuilder()
.setScheme("http")
.setHost(address.getAddress())
.setPort(Integer.parseInt(port))
.build();
logger.info("sending pulse to {}", uri);
HttpGet httpget = new HttpGet(uri);
HttpResponse response = httpClient.execute(httpget);
int code = response.getStatusLine().getStatusCode();
if (code != 200) {
result.add("code " + code + " for " + uri);
continue;
}
String body = IOUtils.toString(response.getEntity().getContent());
if (!bodyPredicate.apply(body)) {
result.add("predicate doesn't match for " + uri);
// throw new RuntimeException("predicate doesn't match for " + uri);
}
} catch (Exception e) {
result.add(String.valueOf(uri) + ": " + e.toString());
}
}
}
return result;
}
public SELF setInput(Object input) {
this.input = input;
return self();
}
}