/*******************************************************************************
* Copyright (c) 2012 - 2016 GoPivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* GoPivotal, Inc. - initial API and implementation
* DISID Corporation, S.L - Spring Roo maintainer
*******************************************************************************/
package org.springframework.roo.shell.eclipse;
import java.io.File;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IProjectDescription;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.launch.Framework;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.ide.eclipse.core.SpringCore;
import org.springframework.ide.eclipse.core.java.ClassUtils;
import org.springframework.ide.eclipse.roo.core.RooCoreActivator;
import org.springframework.roo.felix.pgp.PgpKeyId;
import org.springframework.roo.felix.pgp.PgpService;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
import org.springsource.ide.eclipse.commons.core.SpringCoreUtils;
import org.springsource.ide.eclipse.commons.frameworks.core.internal.commands.CommandFactory;
import org.springsource.ide.eclipse.commons.frameworks.core.internal.commands.ICommandListener;
import org.springsource.ide.eclipse.commons.frameworks.core.internal.commands.ICommandParameterDescriptor;
import org.springsource.ide.eclipse.commons.frameworks.core.internal.commands.JavaParameterDescriptor;
import org.springsource.ide.eclipse.commons.frameworks.core.internal.commands.ParameterFactory;
import org.springsource.ide.eclipse.commons.frameworks.core.internal.plugins.Plugin;
import org.springsource.ide.eclipse.commons.frameworks.core.internal.plugins.PluginService;
import org.springsource.ide.eclipse.commons.frameworks.core.internal.plugins.PluginService.InstallOrUpgradeStatus;
import org.springsource.ide.eclipse.commons.frameworks.core.internal.plugins.PluginVersion;
/**
* @author Christian Dupuis
* @author Steffen Pingel
* @author Juan Carlos GarcĂa
*/
public class Bootstrap {
private Object appender;
private Map<String, String> commandDescription = new HashMap<String, String>();
private Framework framework;
private int identity;
private final String projectLocation;
private Object projectRefresher;
private final String rooHome;
private Object shell;
private final String rooVersion;
private IProject project;
public Bootstrap(IProject project, String projectLocation, String rooHome, String rooVersion, Object projectRefresher) {
this.project = project;
this.projectLocation = projectLocation;
this.rooHome = rooHome;
this.rooVersion = rooVersion;
this.projectRefresher = projectRefresher;
}
public Integer complete(String command, Integer pos, List<String> completions) {
try {
return (Integer) ClassUtils.invokeMethod(shell, "complete", new Object[] { command, new Integer(pos),
completions }, new Class[] { String.class, Integer.class, List.class });
}
catch (Throwable e) {
RooCoreActivator.log(e);
}
return pos;
}
public void execute(String command) {
if (shell != null) {
try {
boolean result = (Boolean) ClassUtils.invokeMethod(shell, "executeCommand", command);
// ROO-3726: If new module has been create, import it to workspace
if(command.startsWith("module create --moduleName ") && result){
// Getting module name
String moduleName = command.replace("module create --moduleName ", "").split(" ")[0];
// Adding module to workspace
IWorkspace workspace = ResourcesPlugin.getWorkspace();
IProject moduleProject = workspace.getRoot().getProject(moduleName.concat(":").concat(project.getName()));
IProjectDescription moduleDescription = workspace.newProjectDescription(moduleName.concat(":").concat(project.getName()));
moduleDescription.setLocation(new Path(projectLocation.concat("/").concat(moduleName)));
moduleProject.create(moduleDescription, new NullProgressMonitor());
moduleProject.open(0, new NullProgressMonitor());
moduleProject.setDescription(moduleDescription, new NullProgressMonitor());
SpringCoreUtils.addProjectNature(moduleProject, SpringCore.NATURE_ID, new NullProgressMonitor());
}
// Always refresh the project after execute a command
project.refreshLocal(IResource.DEPTH_INFINITE, new NullProgressMonitor());
}
catch (Throwable e) {
RooCoreActivator.log(e);
}
}
}
public Map<String, String> getCommandDescription() {
return commandDescription;
}
public String getShellPrompt() {
try {
return (String) ClassUtils.invokeMethod(shell, "getShellPrompt");
}
catch (Throwable e) {
RooCoreActivator.log(e);
}
return "roo> ";
}
public boolean isShutdown() {
try {
Object exitShellRequest = ClassUtils.invokeMethod(shell, "getExitShellRequest", null, null);
if (exitShellRequest != null) {
int exitCode = (Integer) ClassUtils.invokeMethod(exitShellRequest, "getExitCode", null, null);
return exitCode > -1;
}
}
catch (Throwable e) {
RooCoreActivator.log(e);
}
return false;
}
public void shutdown() {
try {
// Obtain the UAA service and shutdown the transmission thread
if (framework != null) {
ServiceReference reference = framework.getBundleContext().getServiceReference("org.springframework.uaa.client.UaaService");
if (reference != null) {
Object uaaService = framework.getBundleContext().getService(reference);
ClassUtils.invokeMethod(uaaService, "stopTransmissionThread");
ungetService(reference);
}
}
// STS-1884: wait for roobot bundle to shutdown in Roo <= 1.1.4
loop: while (true) {
Map<Thread, StackTraceElement[]> stacks = Thread.getAllStackTraces();
for (Thread thread : stacks.keySet()) {
if ("Spring Roo RooBot Add-In Index Eager Download".equals(thread.getName())) {
Thread.sleep(500);
continue loop;
}
}
break;
}
ClassUtils.invokeMethod(shell, "close", null, null);
if (framework != null) {
framework.stop(0);
}
}
catch (Throwable e) {
RooCoreActivator.log(e);
}
}
public void start(Object appender, String projectName) {
try {
this.appender = appender;
Main felixLauncher = new Main();
// cache directory to Spring Roo 2.0+ versions
String cacheDir = "/sts-cache";
// cache directory to Spring Roo 1.x versions
if(rooVersion.startsWith("1")){
cacheDir = "/sts-cache-" + projectName;
}
framework = felixLauncher.start(new File(projectLocation).getCanonicalPath(),
new File(rooHome + "/bundle").getCanonicalPath(),
new File(rooHome + cacheDir).getCanonicalPath(), new File(rooHome
+ "/conf/config.properties").toURI().toURL().toString(), rooVersion);
new Thread(new RooShellExitMonitor()).start();
// We need to wait for the Roo shell to be ready
Thread startupMonitor = new Thread(new RooShellStartupMonitor());
startupMonitor.start();
startupMonitor.join();
}
catch (Throwable e) {
// TODO propagate the error message up to the shell
RooCoreActivator.log(new Status(IStatus.ERROR, RooCoreActivator.PLUGIN_ID,
"Failed to start the Felix framework", e));
}
}
public void addCommand(ICommandListener listener) {
try {
if (framework != null) {
ServiceReference[] references = framework.getBundleContext().getAllServiceReferences(
"org.springframework.roo.shell.CommandMarker", null);
ServiceReference fieldConverterReference = framework.getBundleContext().getServiceReference(
"org.springframework.roo.shell.converters.StaticFieldConverter");
if (references != null && fieldConverterReference != null) {
Object fieldConverter = framework.getBundleContext().getService(fieldConverterReference);
for (ServiceReference commandReference : references) {
Object command = framework.getBundleContext().getService(commandReference);
extractCommand(command, listener, fieldConverter);
ungetService(commandReference);
}
ungetService(fieldConverterReference);
}
}
}
catch (Throwable e) {
RooCoreActivator.log(e);
}
}
// public List<RooAddOn> searchAddOns() {
// ServiceReference addOnServiceReference = null;
// try {
// ServiceReference[] references = framework.getBundleContext().getAllServiceReferences(
// "org.springframework.roo.shell.CommandMarker", null);
// if (references != null) {
// for (ServiceReference reference : references) {
// if ("org.springframework.roo.addon.roobot.client.AddOnCommands".equals(reference.getProperty("component.name"))) {
// addOnServiceReference = reference;
// }
// }
// }
//
// //"org.springframework.roo.addon.roobot.client.AddOnCommands"
//// ServiceReference addOnServiceReference = framework.getBundleContext().getServiceReference("106"
//// );
// if (addOnServiceReference != null) {
// Object addOnService = framework.getBundleContext().getService(addOnServiceReference);
// String searchTerms = null;
// boolean refresh = false;
// int linesPerResult = 20;
// int maxResults = 20;
// boolean trustedOnly = false;
// boolean compatibleOnly = false;
// String requiresCommand = null;
//// List<Object> bundles = (List<Object>) ClassUtils.invokeMethod(addOnService, "search", new Object[] { searchTerms, refresh, linesPerResult, maxResults, trustedOnly, compatibleOnly, requiresCommand}, new Class[] {
//// String.class, boolean.class, int.class, int.class, boolean.class, boolean.class, String.class});
// List<Object> bundles = (List<Object>) ClassUtils.invokeMethod(addOnService, "searchAddOns", new Object[] { searchTerms, refresh, trustedOnly, compatibleOnly, requiresCommand}, new Class[] {
// String.class, boolean.class, boolean.class, boolean.class, String.class});
// List<RooAddOn> addons = new ArrayList<RooAddOn>();
// for (Object bundle : bundles) {
// String symbolicName = (String) ClassUtils.invokeMethod(bundle, "getSymbolicName", null);
// RooAddOn addOn = new RooAddOn(symbolicName);
// List versions = (List) ClassUtils.invokeMethod(bundle, "getVersions", null);
// for (Object version : versions) {
// PluginVersion pluginVersion = new PluginVersion();
// pluginVersion.setName((String)ClassUtils.invokeMethod(version, "getPresentationName", null));
// pluginVersion.setVersion((String)ClassUtils.invokeMethod(version, "getVersion", null));
// addOn.addVersion(pluginVersion);
// addOn.setLatestReleasedVersion(pluginVersion);
// }
// if (addOn.getLatestReleasedVersion() != null)
// addons.add(addOn);
// }
// return addons;
//
// }
// }
// catch (Throwable e) {
// RooCoreActivator.log(e);
// } finally {
// ungetService(addOnServiceReference);
// }
// return null;
// }
public void trust(PgpKeyId keyId) {
ServiceReference reference = framework.getBundleContext().getServiceReference(PgpService.class.getName());
if (reference != null) {
try {
PgpService service = (PgpService)framework.getBundleContext().getService(reference);
service.trust(keyId);
}
catch (Throwable e) {
RooCoreActivator.log(e);
}
}
ungetService(reference);
}
public void trustAll() {
ServiceReference reference = framework.getBundleContext().getServiceReference(PgpService.class.getName());
if (reference != null) {
try {
Object service = framework.getBundleContext().getService(reference);
ClassUtils.invokeMethod(service, "setAutomaticTrust", new Object[] { true });
}
catch (Throwable e) {
RooCoreActivator.log(e);
}
}
ungetService(reference);
}
public List<Plugin> searchAddOns(String searchTerms, boolean refresh, boolean trustedOnly, boolean compatibleOnly) {
ServiceReference addOnServiceReference = null;
try {
addOnServiceReference = getAddOnService(addOnServiceReference);
if (addOnServiceReference != null) {
PluginService service = (PluginService) framework.getBundleContext().getService(addOnServiceReference);
return service.search(searchTerms, refresh, trustedOnly, compatibleOnly);
}
}
catch (Throwable e) {
RooCoreActivator.log(e);
} finally {
ungetService(addOnServiceReference);
}
return null;
}
public IStatus install(PluginVersion version) {
ServiceReference addOnServiceReference = null;
try {
addOnServiceReference = getAddOnService(addOnServiceReference);
if (addOnServiceReference != null) {
PluginService service = (PluginService) framework.getBundleContext().getService(addOnServiceReference);
InstallOrUpgradeStatus result = service.install(version);
return handleResult(result);
}
}
catch (Throwable e) {
RooCoreActivator.log(e);
} finally {
ungetService(addOnServiceReference);
}
return new Status(IStatus.ERROR, RooCoreActivator.PLUGIN_ID, "RooBot Eclipse Service is not available.");
}
public IStatus update(PluginVersion version) {
ServiceReference addOnServiceReference = null;
try {
addOnServiceReference = getAddOnService(addOnServiceReference);
if (addOnServiceReference != null) {
PluginService service = (PluginService) framework.getBundleContext().getService(addOnServiceReference);
InstallOrUpgradeStatus result = service.upgrade(version);
return handleResult(result);
}
}
catch (Throwable e) {
RooCoreActivator.log(e);
} finally {
ungetService(addOnServiceReference);
}
return new Status(IStatus.ERROR, RooCoreActivator.PLUGIN_ID, "RooBot Eclipse Service is not available.");
}
private IStatus handleResult(InstallOrUpgradeStatus result) {
if (result == InstallOrUpgradeStatus.SUCCESS) {
return Status.OK_STATUS;
}
else if (result == InstallOrUpgradeStatus.INVALID_REPOSITORY_URL) {
return new Status(IStatus.ERROR, RooCoreActivator.PLUGIN_ID,
"Operation failed: Invalid repository URL.");
}
else if (result == InstallOrUpgradeStatus.VERIFICATION_NEEDED) {
return new Status(IStatus.ERROR, RooCoreActivator.PLUGIN_ID,
"Operation failed: Verification required.");
}
else {
return new Status(IStatus.ERROR, RooCoreActivator.PLUGIN_ID, "Operation failed.");
}
}
public IStatus uninstall(PluginVersion version) {
ServiceReference addOnServiceReference = null;
try {
addOnServiceReference = getAddOnService(addOnServiceReference);
if (addOnServiceReference != null) {
PluginService service = (PluginService) framework.getBundleContext().getService(addOnServiceReference);
InstallOrUpgradeStatus result = service.remove(version);
if (result == InstallOrUpgradeStatus.SUCCESS) {
return Status.OK_STATUS;
}
else {
return new Status(IStatus.ERROR, RooCoreActivator.PLUGIN_ID, "Operation failed.");
}
}
}
catch (Throwable e) {
RooCoreActivator.log(e);
} finally {
ungetService(addOnServiceReference);
}
return new Status(IStatus.ERROR, RooCoreActivator.PLUGIN_ID, "RooBot Eclipse Service is not available.");
}
private ServiceReference getAddOnService(ServiceReference addOnServiceReference) throws InvalidSyntaxException {
if (framework != null) {
ServiceReference[] references = framework.getBundleContext().getAllServiceReferences(
"org.springframework.roo.shell.CommandMarker", null);
if (references != null) {
for (ServiceReference reference : references) {
if ("org.springframework.roo.addon.roobot.eclipse.client.AddOnEclipseCommands".equals(reference
.getProperty("component.name"))) {
addOnServiceReference = reference;
}
}
}
}
return addOnServiceReference;
}
@SuppressWarnings("unchecked")
private void extractCommand(final Object command, final ICommandListener listener, final Object fieldConverter) {
if (fieldConverter == null) {
return;
}
// Collect all unavailable commands
final List<String> unavailableCommands = new ArrayList<String>();
ReflectionUtils.doWithMethods(command.getClass(), new ReflectionUtils.MethodCallback() {
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
Annotation[] annotations = method.getAnnotations();
for (Annotation annotation : annotations) {
if (annotation.annotationType().getCanonicalName()
.equals("org.springframework.roo.shell.CliAvailabilityIndicator")) {
String[] values = (String[]) AnnotationUtils.getValue(annotation);
try {
if (!(Boolean) ReflectionUtils.invokeMethod(method, command)) {
for (String value : values) {
unavailableCommands.add(value);
}
}
}
catch (Exception e) {
// ignore here
}
}
}
}
});
// Prepare static field converter values to populate multi option
// parameters
Field fieldsField = ReflectionUtils.findField(fieldConverter.getClass(), "fields");
fieldsField.setAccessible(true);
final Map<Class<?>, Map<String, Field>> fields = (Map<Class<?>, Map<String, Field>>) ReflectionUtils.getField(
fieldsField, fieldConverter);
// Collect actual commands and parameters; filter against un-available
// commands
ReflectionUtils.doWithMethods(command.getClass(), new ReflectionUtils.MethodCallback() {
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
Annotation[] annotations = method.getAnnotations();
for (Annotation annotation : annotations) {
if (annotation.annotationType().getCanonicalName()
.equals("org.springframework.roo.shell.CliCommand")) {
// Filter commands against those unavailable
List<String> commands = new ArrayList<String>();
for (String value : (String[]) AnnotationUtils.getValue(annotation)) {
if (!unavailableCommands.contains(value)) {
commands.add(value);
}
}
if (commands.size() > 0) {
List<ICommandParameterDescriptor> parameters = new ArrayList<ICommandParameterDescriptor>();
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
for (int i = 0; i < parameterAnnotations.length; i++) {
Annotation[] parameterAnnotation = parameterAnnotations[i];
for (int j = 0; j < parameterAnnotation.length; j++) {
Annotation cliOption = parameterAnnotation[j];
if (cliOption.annotationType().getCanonicalName()
.equals("org.springframework.roo.shell.CliOption")) {
if (!Boolean.parseBoolean(AnnotationUtils.getValue(cliOption, "systemProvided")
.toString())) {
Class<?> type = method.getParameterTypes()[i];
// Search for proper key
String[] names = ((String[]) AnnotationUtils.getValue(cliOption, "key"));
String name = null;
String prefix = null;
for (String n : names) {
if (StringUtils.hasText(n)) {
name = n;
prefix = "--";
break;
}
}
String description = (String) AnnotationUtils.getValue(cliOption, "help");
Boolean mandatory = (Boolean) AnnotationUtils.getValue(cliOption,
"mandatory");
if (type.getCanonicalName()
.equals("org.springframework.roo.model.JavaType")) {
parameters.add(ParameterFactory.createJavaParameterDescriptor(name,
description, mandatory, null, true, prefix, " ",
JavaParameterDescriptor.FLAG_CLASS));
}
else if (type.getCanonicalName().equals(
"org.springframework.roo.model.JavaPackage")) {
parameters.add(ParameterFactory.createJavaParameterDescriptor(name,
description, mandatory, null, true, prefix, " ",
JavaParameterDescriptor.FLAG_PACKAGE));
}
else if (type.equals(Boolean.class) || type.equals(boolean.class)) {
parameters.add(ParameterFactory.createBooleanParameterDescriptor(name,
description, mandatory, false, true, prefix, " "));
}
else {
if (fields.containsKey(type)) {
Map<String, Field> options = fields.get(type);
parameters.add(ParameterFactory.createComboParameterDescriptor(
name, description, mandatory, null, true, prefix, " ",
options.keySet().toArray(new String[0])));
}
else {
parameters.add(ParameterFactory.createBaseParameterDescriptor(name,
description, mandatory, null, true, "--", " "));
}
}
}
}
}
}
Object description = AnnotationUtils.getValue(annotation, "help");
for (String command : commands) {
listener.addCommandDescriptor(CommandFactory.createCommandDescriptor(command,
(description != null ? description.toString() : null),
parameters.toArray(new ICommandParameterDescriptor[parameters.size()])));
}
}
}
}
}
});
}
private void extractCommandDescription(Class<?> clazz, final Map<String, String> commandDescriptions) {
ReflectionUtils.doWithMethods(clazz, new ReflectionUtils.MethodCallback() {
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
Annotation[] annotations = method.getAnnotations();
for (Annotation annotation : annotations) {
if (annotation.annotationType().getCanonicalName()
.equals("org.springframework.roo.shell.CliCommand")) {
String[] commands = (String[]) AnnotationUtils.getValue(annotation);
Object description = AnnotationUtils.getValue(annotation, "help");
if (description != null) {
for (String command : commands) {
commandDescriptions.put(command, description.toString());
}
}
}
}
}
});
}
private void initShell() {
ServiceReference reference = framework.getBundleContext().getServiceReference(
"org.springframework.roo.shell.Shell");
ServiceReference pmReference = framework.getBundleContext().getServiceReference(
"org.springframework.roo.process.manager.ProcessManager");
if (reference != null && pmReference != null) {
try {
this.identity = System.identityHashCode(framework.getBundleContext().getService(pmReference));
Object tempShell = framework.getBundleContext().getService(reference);
ClassUtils.invokeMethod(tempShell, "init", new Object[] { appender, identity, projectRefresher,
new Object(), new Object(), new Object(), rooHome, projectLocation }, new Class[] {
Object.class, int.class, Object.class, Object.class, Object.class, Object.class, String.class,
String.class });
this.shell = tempShell;
}
catch (Throwable e) {
RooCoreActivator.log(e);
}
}
ungetService(pmReference);
try {
ServiceReference[] references = framework.getBundleContext().getAllServiceReferences(
"org.springframework.roo.shell.CommandMarker", null);
if (references != null) {
for (ServiceReference commandReference : references) {
Object command = framework.getBundleContext().getService(commandReference);
extractCommandDescription(command.getClass(), commandDescription);
ungetService(commandReference);
}
}
}
catch (InvalidSyntaxException e) {
RooCoreActivator.log(e);
}
}
private void ungetService(ServiceReference reference) {
if (reference != null) {
framework.getBundleContext().ungetService(reference);
}
}
public class RooShellExitMonitor implements Runnable {
public void run() {
try {
framework.waitForStop(0);
// TODO close tab
}
catch (Exception e) {
// TODO present on the shell
}
}
}
public class RooShellStartupMonitor implements Runnable {
private static final long STARTUP_TIMEOUT = 60 * 1000;
private boolean failed = true;
public boolean isFailed() {
return failed;
}
public void run() {
long time = System.currentTimeMillis();
try {
while (shell == null && System.currentTimeMillis() - time < STARTUP_TIMEOUT) {
initShell();
if (shell != null) {
failed = false;
return;
}
Thread.sleep(200);
}
}
catch (Exception e) {
// TODO present on the shell
}
}
}
}