Index: src/org/jboss/tools/openshift/express/internal/ui/console/TailServerLogAction.java =================================================================== --- src/org/jboss/tools/openshift/express/internal/ui/console/TailServerLogAction.java (revision 36982) +++ src/org/jboss/tools/openshift/express/internal/ui/console/TailServerLogAction.java (working copy) @@ -16,7 +16,9 @@ import org.eclipse.jgit.transport.URIish; import org.eclipse.jgit.util.FS; import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.console.IConsole; import org.eclipse.ui.console.IConsoleConstants; +import org.eclipse.ui.console.IConsoleListener; import org.eclipse.ui.console.MessageConsole; import org.eclipse.ui.navigator.CommonViewer; import org.eclipse.ui.views.IViewDescriptor; @@ -25,20 +27,29 @@ import org.eclipse.wst.server.ui.IServerModule; import org.jboss.tools.openshift.express.internal.client.utils.Base64Encoder; import org.jboss.tools.openshift.express.internal.core.behaviour.ExpressServerUtils; -import org.jboss.tools.openshift.express.internal.ui.console.TailServerLogWorker.MyLogger; +import org.jboss.tools.openshift.express.internal.ui.console.TailServerLogWorker.JschToEclipseLogger; import org.jboss.tools.openshift.express.internal.ui.messages.OpenShiftExpressUIMessages; import org.jboss.tools.openshift.express.internal.utils.Logger; import com.jcraft.jsch.JSch; import com.jcraft.jsch.JSchException; -public class TailServerLogAction extends Action implements ISelectionChangedListener { +/** + * The action associated with the "Show In>Remote Console" menu item. + * + * @author Xavier Coulon + * + */ +public class TailServerLogAction extends Action implements ISelectionChangedListener, IConsoleListener { /** The current selection in the view. */ private ISelection selection = null; - /** The threads that provide the 'log tail' in the console view. */ - private Map tailRunners = new HashMap(); + /** + * The message consoles associated with the 'tail' workers that write the + * output. + */ + private Map consoleWorkers = new HashMap(); /** * Constructor @@ -48,26 +59,26 @@ IViewRegistry reg = PlatformUI.getWorkbench().getViewRegistry(); IViewDescriptor desc = reg.find(IConsoleConstants.ID_CONSOLE_VIEW); setImageDescriptor(desc.getImageDescriptor()); + ConsoleUtils.registerConsoleListener(this); } /** + * Operation called when the user clicks on 'Show In>Remote Console'. If no + * Console/Worker existed, a new one is created, otherwise, it is displayed. * {@inheritDoc} */ @Override public void run() { final IServer server = getServer(); if (ExpressServerUtils.isOpenShiftRuntime(server)) { - // start a new thread to which we delegate the remote shell - // connection + tail command - final String serverId = server.getId(); MessageConsole console = ConsoleUtils.findMessageConsole(server.getId()); - if (!tailRunners.containsKey(serverId)) { - TailServerLogWorker tailServerLogRunner; + String consoleName = console.getName(); + if (!this.consoleWorkers.containsKey(consoleName)) { try { - final Process process = startTailProcess(server); - tailServerLogRunner = new TailServerLogWorker(server, console, process); - tailRunners.put(serverId, tailServerLogRunner); - Thread thread = new Thread(tailServerLogRunner); + final TailServerLogWorker tailServerLogWorker = startTailProcess(server, console); + consoleWorkers.put(consoleName, tailServerLogWorker); + consoleWorkers.put(console.getName(), tailServerLogWorker); + Thread thread = new Thread(tailServerLogWorker); thread.start(); } catch (Exception e) { Logger.error("Failed to retrieve remote server logs", e); @@ -77,27 +88,51 @@ } } - private Process startTailProcess(IServer server) throws JSchException, IOException { + /** + * Starting the tail process on the remote OpenShift Platform. This method + * relies on the JGit SSH support (including JSch) to open a connection AND + * execute a command in a single invocation. The connection establishement + * requires an SSH key, and the passphrase is prompted to the user if + * necessary. + * + * @param server the server adapter on which the action is perforemd + * @param console the console into which the tail should be writtent + * @return the Worker that encapsulate the established RemoteSession, the tail Process and the output console + * @throws JSchException in case of underlying exception + * @throws IOException in case of underlying exception + */ + private TailServerLogWorker startTailProcess(final IServer server, final MessageConsole console) + throws JSchException, IOException { final String host = server.getHost(); final String appId = ExpressServerUtils.getExpressApplicationId(server); final String appName = ExpressServerUtils.getExpressApplicationName(server); - final String logFilePath = appName + "/logs/server.log"; - final String options = new String(Base64Encoder.encode("-f -n 100".getBytes("UTF-8")), "UTF-8"); + final String logFilePath = appName + "/logs/*.log"; + final String options = "-f -n 100"; - - JSch.setLogger(new MyLogger()); + JSch.setLogger(new JschToEclipseLogger()); final SshSessionFactory sshSessionFactory = SshSessionFactory.getInstance(); final URIish uri = new URIish().setHost(host).setUser(appId); - final RemoteSession remoteSession = sshSessionFactory.getSession(uri, CredentialsProvider.getDefault(), FS.DETECTED, 100000); - + RemoteSession remoteSession = sshSessionFactory.getSession(uri, CredentialsProvider.getDefault(), FS.DETECTED, + 0); + // the rhc-tail-files command template // ssh_cmd = // "ssh -t #{app_uuid}@#{app}-#{namespace}.#{rhc_domain} 'tail#{opt['opts'] ? ' --opts ' + Base64::encode64(opt['opts']).chomp : ''} #{file_glob}'" final String command = buildCommand(logFilePath, options); Process process = remoteSession.exec(command, 0); - return process; + return new TailServerLogWorker(console, process, remoteSession); + } + /** + * Builds the 'ssh tail' command that should be executed on the remote + * OpenShift platform. + * + * @param filePath + * @param options + * @return + * @throws UnsupportedEncodingException + */ private String buildCommand(final String filePath, final String options) throws UnsupportedEncodingException { StringBuilder commandBuilder = new StringBuilder("tail "); if (options != null && !options.isEmpty()) { @@ -106,7 +141,7 @@ } commandBuilder.append(filePath); final String command = commandBuilder.toString(); - System.out.println("cmd= '" + command + "'"); + Logger.debug("ssh command to execute: " + command); return command; } @@ -136,10 +171,31 @@ return null; } - public IServerModule getServerModule() { - if (selection instanceof IServerModule) - return ((IServerModule) selection); - return null; + @Override + public void consolesAdded(IConsole[] consoles) { + // don't do anything special + } + + /** + * Operation to perform when the console is removed (through the + * CloseConsoleAction that was brung by the + * TailConsolePageParticipant). In the current case, the + * associated worker is stopped and the console/worker are removed from the + * map, so that further 'Show In>Remote Console' invocation will trigger a + * new worker process. + */ + @Override + public void consolesRemoved(IConsole[] consoles) { + // if the console is associated with a 'tail' process, stop that process + for (IConsole console : consoles) { + final String consoleName = console.getName(); + if (consoleWorkers.containsKey(consoleName)) { + final TailServerLogWorker worker = consoleWorkers.get(consoleName); + worker.stop(); + consoleWorkers.remove(consoleName); + } + } + } } Index: src/org/jboss/tools/openshift/express/internal/ui/console/TailConsolePageParticipant.java =================================================================== --- src/org/jboss/tools/openshift/express/internal/ui/console/TailConsolePageParticipant.java (revision 0) +++ src/org/jboss/tools/openshift/express/internal/ui/console/TailConsolePageParticipant.java (revision 0) @@ -0,0 +1,51 @@ +/** + * + */ +package org.jboss.tools.openshift.express.internal.ui.console; + +import org.eclipse.ui.IActionBars; +import org.eclipse.ui.console.IConsole; +import org.eclipse.ui.console.IConsoleConstants; +import org.eclipse.ui.console.IConsolePageParticipant; +import org.eclipse.ui.console.actions.CloseConsoleAction; +import org.eclipse.ui.part.IPageBookViewPage; + +/** + * Console helper that allows contributing actions to the console view when the + * Tail console is visible. Added to the console via an extension point from + * org.eclipse.ui.console. + * + * @author Xavier Coulon + * + */ +public class TailConsolePageParticipant implements IConsolePageParticipant { + + /** The standard Eclipse UI CloseConsoleAction.*/ + private CloseConsoleAction closeConsoleAction; + + public void init(IPageBookViewPage page, IConsole console) { + this.closeConsoleAction = new CloseConsoleAction(console); + IActionBars bars = page.getSite().getActionBars(); + bars.getToolBarManager().appendToGroup(IConsoleConstants.LAUNCH_GROUP, closeConsoleAction); + } + + public void dispose() { + this.closeConsoleAction = null; + } + + public void activated() { + } + + public void deactivated() { + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.core.runtime.IAdaptable#getAdapter(java.lang.Class) + */ + public Object getAdapter(Class adapter) { + return null; + } + +} Index: src/org/jboss/tools/openshift/express/internal/ui/console/ConsoleTypePropertyTester.java =================================================================== --- src/org/jboss/tools/openshift/express/internal/ui/console/ConsoleTypePropertyTester.java (revision 0) +++ src/org/jboss/tools/openshift/express/internal/ui/console/ConsoleTypePropertyTester.java (revision 0) @@ -0,0 +1,33 @@ +/** + * + */ +package org.jboss.tools.openshift.express.internal.ui.console; + +import org.eclipse.core.expressions.PropertyTester; +import org.eclipse.ui.console.MessageConsole; +import static org.jboss.tools.openshift.express.internal.ui.console.ConsoleUtils.*; + +/** + * Property tester used to verify that the given instance of + * org.eclipse.ui.console.MessageConsole is an OpenShift Message + * Console (that is, it should contain a specific attribute set a its creation). + * + * @author Xavier Coulon + * + */ +public class ConsoleTypePropertyTester extends PropertyTester { + + /** + * Verifies that the given receiver, a MessageConsole contains + * an attribute name ConsoleUtils.CONSOLE_TYPE_KEY with a value + * set to ConsoleUtils.CONSOLE_TYPE_VALUE. Using the console + * attributes avoids the need to create a subtype of + * MessageCode. + */ + @Override + public boolean test(Object receiver, String property, Object[] args, Object expectedValue) { + MessageConsole console = (MessageConsole) receiver; + return (CONSOLE_TYPE_VALUE.equals(console.getAttribute(CONSOLE_TYPE_KEY))); + } + +} Index: src/org/jboss/tools/openshift/express/internal/ui/console/TailServerLogWorker.java =================================================================== --- src/org/jboss/tools/openshift/express/internal/ui/console/TailServerLogWorker.java (revision 36982) +++ src/org/jboss/tools/openshift/express/internal/ui/console/TailServerLogWorker.java (working copy) @@ -3,27 +3,43 @@ import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; -import java.io.UnsupportedEncodingException; +import org.eclipse.jgit.transport.RemoteSession; import org.eclipse.ui.console.MessageConsole; -import org.eclipse.wst.server.core.IServer; -import com.jcraft.jsch.JSchException; import com.jcraft.jsch.Logger; +/** + * The underlying 'Tail' worker, that uses an established RemoteSession (with + * the help of JGit), runs in a dedicated process and displays the outputstream + * into a specific console. This worker is a java.lang.Runnable in + * order to run in a separate thread + * + * @author Xavier Coulon + * + */ public class TailServerLogWorker implements Runnable { - private final IServer server; - + /** the remote 'tail' process. */ private final Process process; + /** the output message console. */ private final MessageConsole console; - public TailServerLogWorker(final IServer server, final MessageConsole console, final Process process) throws UnsupportedEncodingException, - JSchException { - this.server = server; + /** the SSH session. */ + private final RemoteSession remoteSession; + + /** + * Constructor. + * + * @param console + * @param process + * @param remoteSession + */ + public TailServerLogWorker(final MessageConsole console, final Process process, final RemoteSession remoteSession) { this.console = console; this.process = process; + this.remoteSession = remoteSession; } @Override @@ -41,14 +57,28 @@ org.jboss.tools.openshift.express.internal.utils.Logger.error( "Error while receiving the remote server log", e); } finally { - } } - + /** + * Method called when the overall 'tail' process should be stopped: the + * underlying ssh remote session must be disconnected and the running + * process must be destroyed. + */ + public void stop() { + this.remoteSession.disconnect(); + this.process.destroy(); + } - static class MyLogger implements Logger { + /** + * Bridge between the JSch logger and the Eclipse logger (to ouput results + * in the .log files and/or into the 'Error log' view. + * + * @author Xavier Coulon + * + */ + static class JschToEclipseLogger implements Logger { static java.util.Hashtable name = new java.util.Hashtable(); static { @@ -59,13 +89,26 @@ name.put(new Integer(FATAL), "FATAL: "); } + @Override public boolean isEnabled(int level) { return true; } + @Override public void log(int level, String message) { - System.err.print(name.get(new Integer(level))); - System.err.println(message); + switch (level) { + case DEBUG: + case INFO: + org.jboss.tools.openshift.express.internal.utils.Logger.debug(message); + break; + case WARN: + org.jboss.tools.openshift.express.internal.utils.Logger.warn(message); + break; + case ERROR: + case FATAL: + org.jboss.tools.openshift.express.internal.utils.Logger.error(message); + break; + } } } Index: src/org/jboss/tools/openshift/express/internal/ui/console/ConsoleRemoveAction.java =================================================================== --- src/org/jboss/tools/openshift/express/internal/ui/console/ConsoleRemoveAction.java (revision 0) +++ src/org/jboss/tools/openshift/express/internal/ui/console/ConsoleRemoveAction.java (revision 0) @@ -0,0 +1,15 @@ +package org.jboss.tools.openshift.express.internal.ui.console; + +import java.security.Policy; + +import org.eclipse.jface.action.Action; + +public class ConsoleRemoveAction extends Action { + + ConsoleRemoveAction() { + } + + @Override + public void run() { + } +} \ No newline at end of file Index: src/org/jboss/tools/openshift/express/internal/ui/console/ConsoleUtils.java =================================================================== --- src/org/jboss/tools/openshift/express/internal/ui/console/ConsoleUtils.java (revision 36982) +++ src/org/jboss/tools/openshift/express/internal/ui/console/ConsoleUtils.java (working copy) @@ -18,14 +18,54 @@ import org.eclipse.ui.console.ConsolePlugin; import org.eclipse.ui.console.IConsole; import org.eclipse.ui.console.IConsoleConstants; +import org.eclipse.ui.console.IConsoleListener; import org.eclipse.ui.console.IConsoleManager; import org.eclipse.ui.console.IConsoleView; import org.eclipse.ui.console.MessageConsole; import org.jboss.tools.openshift.express.internal.utils.Logger; +/** + * A utility class to manager the message consoles creations and retrivals + * + * @author Xavier Coulon + * + */ public class ConsoleUtils { - + /** + * Constant key set into the created message console attributes to mark the + * given console as an 'openshift' one. + */ + public static final String CONSOLE_TYPE_KEY = "ConsoleType"; + + /** + * Constant value set into the created message console attributes to mark + * the given console as an 'openshift' one. + */ + public static final String CONSOLE_TYPE_VALUE = "OpenShiftTailConsole"; + + /** + * Registers the given listener as a console listener. + * + * @param consoleListener + */ + public static void registerConsoleListener(IConsoleListener consoleListener) { + ConsolePlugin plugin = ConsolePlugin.getDefault(); + IConsoleManager consoleManager = plugin.getConsoleManager(); + consoleManager.addConsoleListener(consoleListener); + } + + /** + * Retrieve the message console given its name. If no console exists yet, a + * new one is created with a specifi attribute to mark it as an 'openshift' + * console. This attribute (or marker) is use later on by the + * ConsoleTypePropertyTester to add a 'remove' button on the console in the + * consoles view. + * + * @param name + * the name of the console to find + * @return the message console (found or created) + */ public static MessageConsole findMessageConsole(String name) { ConsolePlugin plugin = ConsolePlugin.getDefault(); IConsoleManager consoleManager = plugin.getConsoleManager(); @@ -37,10 +77,17 @@ } // no console found, so create a new one MessageConsole console = new MessageConsole(name, null); + console.setAttribute(CONSOLE_TYPE_KEY, CONSOLE_TYPE_VALUE); consoleManager.addConsoles(new IConsole[] { console }); return console; } - + + /** + * Displays the given console in the consoles view which becomes visible if + * it was not the case before. + * + * @param console the console to display + */ public static void displayConsoleView(IConsole console) { IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow(); if (window != null) { Index: plugin.xml =================================================================== --- plugin.xml (revision 36982) +++ plugin.xml (working copy) @@ -146,21 +146,20 @@ -