Index: appserver/osgi-platforms/felix-cli-remote/src/main/java/org/glassfish/osgi/felix/shell/remote/RemoteCommandSession.java =================================================================== --- appserver/osgi-platforms/felix-cli-remote/src/main/java/org/glassfish/osgi/felix/shell/remote/RemoteCommandSession.java (revision 0) +++ appserver/osgi-platforms/felix-cli-remote/src/main/java/org/glassfish/osgi/felix/shell/remote/RemoteCommandSession.java (revision 0) @@ -0,0 +1,140 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright (c) 1997-2012 Oracle and/or its affiliates. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common Development + * and Distribution License("CDDL") (collectively, the "License"). You + * may not use this file except in compliance with the License. You can + * obtain a copy of the License at + * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html + * or packager/legal/LICENSE.txt. See the License for the specific + * language governing permissions and limitations under the License. + * + * When distributing the software, include this License Header Notice in each + * file and include the License file at packager/legal/LICENSE.txt. + * + * GPL Classpath Exception: + * Oracle designates this particular file as subject to the "Classpath" + * exception as provided by Oracle in the GPL Version 2 section of the License + * file that accompanied this code. + * + * Modifications: + * If applicable, add the following below the License Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyright [year] [name of copyright owner]" + * + * Contributor(s): + * If you wish your version of this file to be governed by only the CDDL or + * only the GPL Version 2, indicate your decision by adding "[Contributor] + * elects to include this software in this distribution under the [CDDL or GPL + * Version 2] license." If you don't indicate a single choice of license, a + * recipient has the option to distribute your version of this file under + * either the CDDL, the GPL Version 2 or to extend the choice of license to + * its licensees as provided above. However, if you add GPL Version 2 code + * and therefore, elected the GPL Version 2 license, then the option applies + * only if the new code is made subject to such option by the copyright + * holder. + */ +package org.glassfish.osgi.felix.shell.remote; + +import java.io.InputStream; +import java.io.PrintStream; +import java.lang.reflect.Field; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.UUID; +import org.apache.felix.service.command.CommandSession; + +/** + * This delegating class is used to overcome some limitations of the + * {@link CommandSession} interface when it comes to session management. + * + *

+ * Once implementations are mature enough to not assume environmental behavior + * this class will become obsolete. + *

+ * + * @author ancoron + */ +public class RemoteCommandSession { + + private final CommandSession delegate; + private final String id; + + public RemoteCommandSession(CommandSession delegate) + { + this.delegate = delegate; + this.id = UUID.randomUUID().toString(); + } + + /** + * Get the identifier for this session, which is a UUID of type 4. + * + * @return + */ + public String getId() { + return id; + } + + /** + * Attached the specified streams to the delegate of this instance and + * returns the modified delegate. + * + * @param in The "stdin" stream for the session + * @param out The "stdout" stream for the session + * @param err The "stderr" stream for the session + * + * @return The modified {@link CommandSession} delegate + * + * @see #detach() + */ + public CommandSession attach(InputStream in, PrintStream out, PrintStream err) { + set(this.delegate, "in", in); + set(this.delegate, "out", out); + set(this.delegate, "err", err); + return this.delegate; + } + + /** + * Detaches all previously attached streams and hence, ensures that there + * are no stale references left. + * + * @see #attach(java.io.InputStream, java.io.PrintStream, java.io.PrintStream) + */ + public void detach() { + set(this.delegate, "in", null); + set(this.delegate, "out", null); + set(this.delegate, "err", null); + } + + private void set(final Object obj, final String field, final Object value) { + try { + final Field f = obj.getClass().getDeclaredField(field); + final boolean accessible = f.isAccessible(); + if(!accessible) { + AccessController.doPrivileged(new PrivilegedAction() { + + @Override + public Void run() { + f.setAccessible(true); + try { + f.set(obj, value); + } catch(Exception x) { + throw new RuntimeException(x); + } + + // reset to previous state... + f.setAccessible(accessible); + return null; + } + }); + } else { + f.set(obj, value); + } + } catch(Exception x) { + throw new RuntimeException(x); + } + } +} Index: appserver/osgi-platforms/felix-cli-remote/src/main/java/org/glassfish/osgi/felix/shell/remote/FelixShellCommand.java =================================================================== --- appserver/osgi-platforms/felix-cli-remote/src/main/java/org/glassfish/osgi/felix/shell/remote/FelixShellCommand.java (revision 0) +++ appserver/osgi-platforms/felix-cli-remote/src/main/java/org/glassfish/osgi/felix/shell/remote/FelixShellCommand.java (revision 0) @@ -0,0 +1,312 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright (c) 1997-2012 Oracle and/or its affiliates. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common Development + * and Distribution License("CDDL") (collectively, the "License"). You + * may not use this file except in compliance with the License. You can + * obtain a copy of the License at + * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html + * or packager/legal/LICENSE.txt. See the License for the specific + * language governing permissions and limitations under the License. + * + * When distributing the software, include this License Header Notice in each + * file and include the License file at packager/legal/LICENSE.txt. + * + * GPL Classpath Exception: + * Oracle designates this particular file as subject to the "Classpath" + * exception as provided by Oracle in the GPL Version 2 section of the License + * file that accompanied this code. + * + * Modifications: + * If applicable, add the following below the License Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyright [year] [name of copyright owner]" + * + * Contributor(s): + * If you wish your version of this file to be governed by only the CDDL or + * only the GPL Version 2, indicate your decision by adding "[Contributor] + * elects to include this software in this distribution under the [CDDL or GPL + * Version 2] license." If you don't indicate a single choice of license, a + * recipient has the option to distribute your version of this file under + * either the CDDL, the GPL Version 2 or to extend the choice of license to + * its licensees as provided above. However, if you add GPL Version 2 code + * and therefore, elected the GPL Version 2 license, then the option applies + * only if the new code is made subject to such option by the copyright + * holder. + */ +package org.glassfish.osgi.felix.shell.remote; + +import com.sun.enterprise.admin.remote.ServerRemoteAdminCommand; +import com.sun.enterprise.config.serverbeans.Domain; +import com.sun.enterprise.config.serverbeans.Server; +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.inject.Inject; +import org.apache.felix.service.command.CommandProcessor; +import org.apache.felix.service.command.CommandSession; +import org.apache.felix.shell.ShellService; +import org.glassfish.api.ActionReport; +import org.glassfish.api.I18n; +import org.glassfish.api.Param; +import org.glassfish.api.admin.AdminCommand; +import org.glassfish.api.admin.AdminCommandContext; +import org.glassfish.api.admin.CommandException; +import org.glassfish.api.admin.CommandLock; +import org.glassfish.api.admin.ParameterMap; +import org.glassfish.api.admin.RestEndpoint; +import org.glassfish.api.admin.RestEndpoints; +import org.glassfish.config.support.CommandTarget; +import org.glassfish.config.support.TargetType; +import org.glassfish.hk2.api.PerLookup; +import org.glassfish.hk2.api.PostConstruct; +import org.glassfish.hk2.api.ServiceLocator; +import org.jvnet.hk2.annotations.Service; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.framework.BundleReference; +import org.osgi.framework.ServiceReference; + +/** + * A simple AdminCommand that bridges to the Felix Shell Service. + * + * @author ancoron + */ +@Service(name = "felix") +@CommandLock(CommandLock.LockType.SHARED) +@I18n("felix") +@PerLookup +@TargetType({CommandTarget.CLUSTERED_INSTANCE, CommandTarget.STANDALONE_INSTANCE}) +@RestEndpoints({ + @RestEndpoint(configBean=Domain.class, + opType=RestEndpoint.OpType.POST, + path="felix", + description="Remote Felix Shell Access") +}) +public class FelixShellCommand implements AdminCommand, PostConstruct { + + private static final Logger log = Logger.getLogger(FelixShellCommand.class.getPackage().getName()); + + private static final Map sessions = + new ConcurrentHashMap(); + + @Param(name = "command-line", primary = true, optional = true, multiple = true, defaultValue = "help") + private Object commandLine; + + @Param(name = "session", optional = true) + private String sessionOp; + + @Param(name = "session-id", optional = true) + private String sessionId; + + @Param(name = "instance", optional = true) + private String instance; + + protected BundleContext ctx; + + @Inject + ServiceLocator locator; + + @Inject + Domain domain; + + @Override + public void execute(AdminCommandContext context) { + ActionReport report = context.getActionReport(); + + if(instance != null) { + Server svr = domain.getServerNamed(instance); + if(svr == null) { + report.setMessage("No server target found for " + + instance); + report.setActionExitCode(ActionReport.ExitCode.FAILURE); + return; + } + String host = svr.getAdminHost(); + int port = svr.getAdminPort(); + + try { + ServerRemoteAdminCommand remote = + new ServerRemoteAdminCommand( + locator, + "felix", + host, + port, + false, + "admin", + "", + log); + + ParameterMap params = new ParameterMap(); + + if(commandLine == null) { + params.set("DEFAULT".toLowerCase(), "asadmin-felix-shell"); + } else if(commandLine instanceof String) { + params.set("DEFAULT".toLowerCase(), (String) commandLine); + } else if(commandLine instanceof List) { + params.set("DEFAULT".toLowerCase(), (List) commandLine); + } + + if(sessionOp != null) { + params.set("session", sessionOp); + } + + if(sessionId != null) { + params.set("session-id", sessionId); + } + + report.setMessage(remote.executeCommand(params)); + report.setActionExitCode(ActionReport.ExitCode.SUCCESS); + return; + } catch(CommandException x) { + report.setMessage("Remote execution failed: " + + x.getMessage()); + report.setFailureCause(x); + report.setActionExitCode(ActionReport.ExitCode.FAILURE); + return; + } + } + + String cmdName = ""; + String cmd = ""; + if(commandLine == null) { + cmd = "asadmin-felix-shell"; + cmdName = cmd; + } else if(commandLine instanceof String) { + cmd = (String) commandLine; + cmdName = cmd; + } else if(commandLine instanceof List) { + for(Object arg : (List) commandLine) { + if(cmd.length() == 0) { + // first arg + cmd = (String) arg; + cmdName = cmd; + } else { + cmd += " " + (String) arg; + } + } + } else if(commandLine instanceof String[]) { + for(Object arg : (String[]) commandLine) { + if(cmd.length() == 0) { + // first arg + cmd = (String) arg; + cmdName = cmd; + } else { + cmd += " " + (String) arg; + } + } + } else { + // shouldn't happen... + report.setMessage("Unable to deal with argument list of type " + + commandLine.getClass().getName()); + report.setActionExitCode(ActionReport.ExitCode.WARNING); + return; + } + + // standard output... + ByteArrayOutputStream bOut = new ByteArrayOutputStream(512); + PrintStream out = new PrintStream(bOut); + + // error output... + ByteArrayOutputStream bErr = new ByteArrayOutputStream(512); + PrintStream err = new PrintStream(bErr); + + try { + Object shell = null; + + ServiceReference sref = ctx.getServiceReference( + "org.apache.felix.service.command.CommandProcessor"); + if(sref != null) { + shell = ctx.getService(sref); + } + + if(shell == null) { + // try with felix... + sref = ctx.getServiceReference("org.apache.felix.shell.ShellService"); + if(sref != null) { + shell = ctx.getService(sref); + } + + if(shell == null) { + report.setMessage("No Shell Service available"); + report.setActionExitCode(ActionReport.ExitCode.WARNING); + return; + } else if("asadmin-felix-shell".equals(cmdName)) { + out.println("felix"); + } else { + ShellService s = (ShellService) shell; + s.executeCommand(cmd, out, err); + } + } else { + // try with gogo... + CommandProcessor cp = (CommandProcessor) shell; + if(sessionOp == null) { + if("asadmin-felix-shell".equals(cmdName)) { + out.println("gogo"); + } else { + CommandSession session = cp.createSession(null, out, err); + session.execute(cmd); + session.close(); + } + } else if("new".equals(sessionOp)) { + CommandSession session = cp.createSession(null, null, null); + RemoteCommandSession remote = new RemoteCommandSession(session); + + log.log(Level.FINE, "Remote session established: {0}", + remote.getId()); + + sessions.put(remote.getId(), remote); + out.println(remote.getId()); + } else if("list".equals(sessionOp)) { + for(String id : sessions.keySet()) { + out.println(id); + } + } else if("execute".equals(sessionOp)) { + RemoteCommandSession remote = sessions.get(sessionId); + CommandSession session = remote.attach(null, out, err); + session.execute(cmd); + remote.detach(); + } else if("stop".equals(sessionOp)) { + RemoteCommandSession remote = sessions.remove(sessionId); + CommandSession session = remote.attach(null, out, err); + session.close(); + + log.log(Level.FINE, "Remote session closed: {0}", + remote.getId()); + } + } + + out.flush(); + err.flush(); + + String output = bOut.toString("UTF-8"); + String errors = bErr.toString("UTF-8"); + report.setMessage(output); + + if(errors.length() > 0) { + report.setMessage(errors); + report.setActionExitCode(ActionReport.ExitCode.WARNING); + } else { + report.setActionExitCode(ActionReport.ExitCode.SUCCESS); + } + } catch (Exception ex) { + report.setMessage(ex.getMessage()); + report.setActionExitCode(ActionReport.ExitCode.WARNING); + } + } + + @Override + public void postConstruct() { + if(ctx == null) { + Bundle me = BundleReference.class.cast(getClass().getClassLoader()).getBundle(); + ctx = me.getBundleContext(); + } + } +} Index: appserver/osgi-platforms/felix-cli-remote/src/main/resources/org/glassfish/osgi/felix/shell/remote/LocalStrings.properties =================================================================== --- appserver/osgi-platforms/felix-cli-remote/src/main/resources/org/glassfish/osgi/felix/shell/remote/LocalStrings.properties (revision 0) +++ appserver/osgi-platforms/felix-cli-remote/src/main/resources/org/glassfish/osgi/felix/shell/remote/LocalStrings.properties (revision 0) @@ -0,0 +1,45 @@ +# +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. +# +# Copyright (c) 2012 Oracle and/or its affiliates. All rights reserved. +# +# The contents of this file are subject to the terms of either the GNU +# General Public License Version 2 only ("GPL") or the Common Development +# and Distribution License("CDDL") (collectively, the "License"). You +# may not use this file except in compliance with the License. You can +# obtain a copy of the License at +# https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html +# or packager/legal/LICENSE.txt. See the License for the specific +# language governing permissions and limitations under the License. +# +# When distributing the software, include this License Header Notice in each +# file and include the License file at packager/legal/LICENSE.txt. +# +# GPL Classpath Exception: +# Oracle designates this particular file as subject to the "Classpath" +# exception as provided by Oracle in the GPL Version 2 section of the License +# file that accompanied this code. +# +# Modifications: +# If applicable, add the following below the License Header, with the fields +# enclosed by brackets [] replaced by your own identifying information: +# "Portions Copyright [year] [name of copyright owner]" +# +# Contributor(s): +# If you wish your version of this file to be governed by only the CDDL or +# only the GPL Version 2, indicate your decision by adding "[Contributor] +# elects to include this software in this distribution under the [CDDL or GPL +# Version 2] license." If you don't indicate a single choice of license, a +# recipient has the option to distribute your version of this file under +# either the CDDL, the GPL Version 2 or to extend the choice of license to +# its licensees as provided above. However, if you add GPL Version 2 code +# and therefore, elected the GPL Version 2 license, then the option applies +# only if the new code is made subject to such option by the copyright +# holder. +# + +felix=Delegates a command line to the Felix Shell Service +felix.command-line=The full command-line as given inside the Felix Shell +felix.session=The session operation (one of: new, stop, execute, list) +felix.session-id=The session id to be specified for the session operations "execute" and "stop" +felix.instance=The server instance to delegate the command to (the default is the DAS and the DAS must be running to execute some command on another instance) Index: appserver/osgi-platforms/felix-cli-remote/osgi.bundle =================================================================== --- appserver/osgi-platforms/felix-cli-remote/osgi.bundle (revision 0) +++ appserver/osgi-platforms/felix-cli-remote/osgi.bundle (revision 0) @@ -0,0 +1,53 @@ +# +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. +# +# Copyright (c) 2012 Oracle and/or its affiliates. All rights reserved. +# +# The contents of this file are subject to the terms of either the GNU +# General Public License Version 2 only ("GPL") or the Common Development +# and Distribution License("CDDL") (collectively, the "License"). You +# may not use this file except in compliance with the License. You can +# obtain a copy of the License at +# https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html +# or packager/legal/LICENSE.txt. See the License for the specific +# language governing permissions and limitations under the License. +# +# When distributing the software, include this License Header Notice in each +# file and include the License file at packager/legal/LICENSE.txt. +# +# GPL Classpath Exception: +# Oracle designates this particular file as subject to the "Classpath" +# exception as provided by Oracle in the GPL Version 2 section of the License +# file that accompanied this code. +# +# Modifications: +# If applicable, add the following below the License Header, with the fields +# enclosed by brackets [] replaced by your own identifying information: +# "Portions Copyright [year] [name of copyright owner]" +# +# Contributor(s): +# If you wish your version of this file to be governed by only the CDDL or +# only the GPL Version 2, indicate your decision by adding "[Contributor] +# elects to include this software in this distribution under the [CDDL or GPL +# Version 2] license." If you don't indicate a single choice of license, a +# recipient has the option to distribute your version of this file under +# either the CDDL, the GPL Version 2 or to extend the choice of license to +# its licensees as provided above. However, if you add GPL Version 2 code +# and therefore, elected the GPL Version 2 license, then the option applies +# only if the new code is made subject to such option by the copyright +# holder. +# + +-exportcontents: \ + org.glassfish.osgi.felix.shell.remote; version=${project.osgi.version} + +Import-Package: \ + * + +Bundle-SymbolicName: \ + ${project.groupId}.${project.artifactId} + +# shell packages resolved at runtime, whatever there is... +DynamicImport-Package: \ + org.apache.felix.shell, \ + org.apache.felix.service.command; status="provisional" Index: appserver/osgi-platforms/felix-cli-remote/pom.xml =================================================================== --- appserver/osgi-platforms/felix-cli-remote/pom.xml (revision 0) +++ appserver/osgi-platforms/felix-cli-remote/pom.xml (revision 0) @@ -0,0 +1,85 @@ + + + + 4.0.0 + + org.glassfish.main.osgi-platforms + osgi-console-extensions + 4.0-SNAPSHOT + + + org.glassfish.main.osgi + felix-cli-remote + + Admin CLI bridge command to the Felix Shell + + + + org.osgi + org.osgi.core + + + org.glassfish.main.common + glassfish-api + ${project.version} + + + org.glassfish.main.admin + admin-util + ${project.version} + + + org.apache.felix + org.apache.felix.shell + 1.4.1 + provided + + + org.apache.felix + org.apache.felix.gogo.runtime + 0.8.0 + provided + + + Index: appserver/osgi-platforms/felix-cli-interactive/osgi.bundle =================================================================== --- appserver/osgi-platforms/felix-cli-interactive/osgi.bundle (revision 0) +++ appserver/osgi-platforms/felix-cli-interactive/osgi.bundle (revision 0) @@ -0,0 +1,48 @@ +# +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. +# +# Copyright (c) 2012 Oracle and/or its affiliates. All rights reserved. +# +# The contents of this file are subject to the terms of either the GNU +# General Public License Version 2 only ("GPL") or the Common Development +# and Distribution License("CDDL") (collectively, the "License"). You +# may not use this file except in compliance with the License. You can +# obtain a copy of the License at +# https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html +# or packager/legal/LICENSE.txt. See the License for the specific +# language governing permissions and limitations under the License. +# +# When distributing the software, include this License Header Notice in each +# file and include the License file at packager/legal/LICENSE.txt. +# +# GPL Classpath Exception: +# Oracle designates this particular file as subject to the "Classpath" +# exception as provided by Oracle in the GPL Version 2 section of the License +# file that accompanied this code. +# +# Modifications: +# If applicable, add the following below the License Header, with the fields +# enclosed by brackets [] replaced by your own identifying information: +# "Portions Copyright [year] [name of copyright owner]" +# +# Contributor(s): +# If you wish your version of this file to be governed by only the CDDL or +# only the GPL Version 2, indicate your decision by adding "[Contributor] +# elects to include this software in this distribution under the [CDDL or GPL +# Version 2] license." If you don't indicate a single choice of license, a +# recipient has the option to distribute your version of this file under +# either the CDDL, the GPL Version 2 or to extend the choice of license to +# its licensees as provided above. However, if you add GPL Version 2 code +# and therefore, elected the GPL Version 2 license, then the option applies +# only if the new code is made subject to such option by the copyright +# holder. +# + +-exportcontents: \ + org.glassfish.osgi.felix.shell.interactive; version=${project.osgi.version} + +Import-Package: \ + * + +Bundle-SymbolicName: \ + ${project.groupId}.${project.artifactId} Index: appserver/osgi-platforms/felix-cli-interactive/pom.xml =================================================================== --- appserver/osgi-platforms/felix-cli-interactive/pom.xml (revision 0) +++ appserver/osgi-platforms/felix-cli-interactive/pom.xml (revision 0) @@ -0,0 +1,106 @@ + + + + 4.0.0 + + org.glassfish.main.osgi-platforms + osgi-console-extensions + 4.0-SNAPSHOT + + + org.glassfish.main.osgi + felix-cli-interactive + + Admin CLI interactive shell command to the Felix Shell + An interactive shell inside asadmin to access a remote OSGi shell + + + + CDDL + GPL 1.1 + http://glassfish.java.net/public/CDDL+GPL_1_1.html + + + BSD + + + + + + org.glassfish.main.admin + admin-cli + ${project.version} + + + jline + jline + 2.9 + provided + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + 2.5.1 + + + copy-libs + + unpack-dependencies + + prepare-package + + jline + ${project.build.outputDirectory} + true + + + + + + + Index: appserver/osgi-platforms/felix-cli-interactive/src/main/java/org/glassfish/osgi/felix/shell/interactive/LocalFelixShellCommand.java =================================================================== --- appserver/osgi-platforms/felix-cli-interactive/src/main/java/org/glassfish/osgi/felix/shell/interactive/LocalFelixShellCommand.java (revision 0) +++ appserver/osgi-platforms/felix-cli-interactive/src/main/java/org/glassfish/osgi/felix/shell/interactive/LocalFelixShellCommand.java (revision 0) @@ -0,0 +1,517 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright (c) 1997-2010 Oracle and/or its affiliates. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common Development + * and Distribution License("CDDL") (collectively, the "License"). You + * may not use this file except in compliance with the License. You can + * obtain a copy of the License at + * http://glassfish.java.net/public/CDDL+GPL_1_1.html + * or packager/legal/LICENSE.txt. See the License for the specific + * language governing permissions and limitations under the License. + * + * When distributing the software, include this License Header Notice in each + * file and include the License file at packager/legal/LICENSE.txt. + * + * GPL Classpath Exception: + * Oracle designates this particular file as subject to the "Classpath" + * exception as provided by Oracle in the GPL Version 2 section of the License + * file that accompanied this code. + * + * Modifications: + * If applicable, add the following below the License Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyright [year] [name of copyright owner]" + * + * Contributor(s): + * If you wish your version of this file to be governed by only the CDDL or + * only the GPL Version 2, indicate your decision by adding "[Contributor] + * elects to include this software in this distribution under the [CDDL or GPL + * Version 2] license." If you don't indicate a single choice of license, a + * recipient has the option to distribute your version of this file under + * either the CDDL, the GPL Version 2 or to extend the choice of license to + * its licensees as provided above. However, if you add GPL Version 2 code + * and therefore, elected the GPL Version 2 license, then the option applies + * only if the new code is made subject to such option by the copyright + * holder. + * + * This class is forked from com.sun.enterprise.admin.cli.MultimodeCommand + * + * Original code authors: + * केदार(km@dev.java.net) + * Bill Shannon + */ +package org.glassfish.osgi.felix.shell.interactive; + +import com.sun.enterprise.admin.cli.ArgumentTokenizer; +import com.sun.enterprise.admin.cli.CLICommand; +import com.sun.enterprise.admin.cli.CLIUtil; +import com.sun.enterprise.admin.cli.Environment; +import com.sun.enterprise.admin.cli.MultimodeCommand; +import com.sun.enterprise.admin.cli.ProgramOptions; +import com.sun.enterprise.admin.cli.remote.RemoteCLICommand; +import com.sun.enterprise.admin.util.CommandModelData; +import com.sun.enterprise.universal.i18n.LocalStringsImpl; +import java.io.File; +import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import javax.inject.Inject; +import jline.console.ConsoleReader; +import jline.console.completer.Completer; +import jline.console.completer.NullCompleter; +import jline.console.completer.StringsCompleter; +import org.glassfish.api.I18n; +import org.glassfish.api.Param; +import org.glassfish.api.admin.CommandException; +import org.glassfish.api.admin.CommandModel.ParamModel; +import org.glassfish.api.admin.CommandValidationException; +import org.glassfish.api.admin.InvalidCommandException; +import org.glassfish.hk2.api.ActiveDescriptor; +import org.glassfish.hk2.api.DynamicConfiguration; +import org.glassfish.hk2.api.DynamicConfigurationService; +import org.glassfish.hk2.api.PerLookup; +import org.glassfish.hk2.api.ServiceLocator; +import org.glassfish.hk2.utilities.BuilderHelper; +import org.jvnet.hk2.annotations.Service; + +/** + * A simple local asadmin sub-command to establish an interactive felix shell. + * + * @author ancoron + */ +@Service(name = "felix-shell") +@I18n("felix-shell") +@PerLookup +public class LocalFelixShellCommand extends CLICommand { + + protected static final String REMOTE_COMMAND = "felix"; + protected static final String SESSIONID_OPTION = "--session-id"; + protected static final String SESSION_OPTION = "--session"; + protected static final String SESSION_OPTION_EXECUTE = "execute"; + protected static final String SESSION_OPTION_START = "new"; + protected static final String SESSION_OPTION_STOP = "stop"; + + @Inject + private ServiceLocator locator; + + @Param(name = "instance", optional = true) + private String instance; + + @Param(optional = true, shortName = "f") + private File file; + + @Param(name = "printprompt", optional = true) + private Boolean printPromptOpt; + + private boolean printPrompt; + + @Param(optional = true) + private String encoding; + + private boolean echo; // saved echo flag + private RemoteCLICommand cmd; // the remote sub-command + private String shellType; + + // re-using existing strings... + private static final LocalStringsImpl strings = + new LocalStringsImpl(MultimodeCommand.class); + + protected String[] enhanceForTarget(String[] args) { + if(instance != null) { + String[] targetArgs = new String[args.length + 2]; + targetArgs[1] = "--instance"; + targetArgs[2] = instance; + System.arraycopy(args, 0, targetArgs, 0, 1); + System.arraycopy(args, 1, targetArgs, 3, args.length - 1); + args = targetArgs; + } + return args; + } + + protected String[] prepareArguments(String sessionId, String[] args) { + if(sessionId != null) { + // attach command to remote session... + String[] sessionArgs = new String[args.length + 5]; + sessionArgs[0] = REMOTE_COMMAND; + sessionArgs[1] = SESSION_OPTION; + sessionArgs[2] = SESSION_OPTION_EXECUTE; + sessionArgs[3] = SESSIONID_OPTION; + sessionArgs[4] = sessionId; + System.arraycopy(args, 0, sessionArgs, 5, args.length); + args = sessionArgs; + } else { + String[] felixArgs = new String[args.length + 1]; + felixArgs[0] = REMOTE_COMMAND; + System.arraycopy(args, 0, felixArgs, 1, args.length); + args = felixArgs; + } + return args; + } + + protected String startSession() throws CommandException { + String sessionId = null; + if("gogo".equals(shellType)) { + // start a remote session... + String[] args = {REMOTE_COMMAND, SESSION_OPTION, + SESSION_OPTION_START}; + args = enhanceForTarget(args); + sessionId = cmd.executeAndReturnOutput(args).trim(); + } + return sessionId; + } + + protected int stopSession(String sessionId) throws CommandException { + int rc = 0; + if(sessionId != null) { + // stop the remote session... + String[] args = {REMOTE_COMMAND, SESSION_OPTION, + SESSION_OPTION_STOP, SESSIONID_OPTION, sessionId}; + args = enhanceForTarget(args); + rc = cmd.execute(args); + } + return rc; + } + + /** + * The validate method validates that the type and quantity of + * parameters and operands matches the requirements for this + * command. The validate method supplies missing options from + * the environment. + */ + @Override + protected void validate() + throws CommandException, CommandValidationException { + if (printPromptOpt != null) { + printPrompt = printPromptOpt.booleanValue(); + } else { + printPrompt = programOpts.isInteractive(); + } + /* + * Save value of --echo because CLICommand will reset it + * before calling our executeCommand method but we want it + * to also apply to all commands in multimode. + */ + echo = programOpts.isEcho(); + } + + /** + * In the usage message modify the --printprompt option to have a + * default based on the --interactive option. + */ + @Override + protected Collection usageOptions() { + Collection opts = commandModel.getParameters(); + Set uopts = new LinkedHashSet(); + ParamModel p = new CommandModelData.ParamModelData("printprompt", + boolean.class, true, + Boolean.toString(programOpts.isInteractive())); + for (ParamModel pm : opts) { + if (pm.getName().equals("printprompt")) { + uopts.add(p); + } else { + uopts.add(pm); + } + } + return uopts; + } + + @Override + protected int executeCommand() + throws CommandException, CommandValidationException { + ConsoleReader reader = null; + + if(cmd == null) { + throw new CommandException("Remote command 'felix' is not available."); + } + + programOpts.setEcho(echo); // restore echo flag, saved in validate + try { + if (encoding != null) { + // see Configuration.getEncoding()... + System.setProperty("input.encoding", encoding); + } + + String[] args = new String[] {REMOTE_COMMAND, + "asadmin-felix-shell"}; + args = enhanceForTarget(args); + shellType = cmd.executeAndReturnOutput(args).trim(); + + if (file == null) { + System.out.println(strings.get("multimodeIntro")); + reader = new ConsoleReader(REMOTE_COMMAND, + new FileInputStream(FileDescriptor.in), System.out, + null); + } else { + printPrompt = false; + if (!file.canRead()) { + throw new CommandException("File: " + file + + " can not be read"); + } + + OutputStream out = new OutputStream() { + + @Override + public void write(int b) throws IOException { + return; + } + + @Override + public void write(byte[] b) throws IOException { + return; + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + return; + } + }; + + reader = new ConsoleReader(REMOTE_COMMAND, + new FileInputStream(file), out, + null); + } + + reader.setBellEnabled(false); + reader.addCompleter(getCommandCompleter()); + + return executeCommands(reader); + } catch (IOException e) { + throw new CommandException(e); + } + } + + /** + * Get the command completion. + * + * TODO: make this non-static! + * TODO: ask remote for dynamically added commands + * + * @return The command completer + */ + private Completer getCommandCompleter() { + if("gogo".equals(shellType)) { + return new StringsCompleter( + "bundlelevel", + "cd", + "frameworklevel", + "headers", + "help", + "inspect", + "install", + "lb", + "log", + "ls", + "refresh", + "resolve", + "start", + "stop", + "uninstall", + "update", + "which", + "cat", + "each", + "echo", + "format", + "getopt", + "gosh", + "grep", + "not", + "set", + "sh", + "source", + "tac", + "telnetd", + "type", + "until", + "deploy", + "info", + "javadoc", + "list", + "repos", + "source" + ); + } else if("felix".equals(shellType)) { + return new StringsCompleter( + "exit", + "quit", + "help", + "bundlelevel", + "cd", + "find", + "headers", + "inspect", + "install", + "log", + "ps", + "refresh", + "resolve", + "scr", + "shutdown", + "start", + "startlevel", + "stop", + "sysprop", + "uninstall", + "update", + "version" + ); + } + + return new NullCompleter(); + } + + + /** + * Read commands from the specified BufferedReader + * and execute them. If printPrompt is set, prompt first. + * + * @return the exit code of the last command executed + */ + private int executeCommands(ConsoleReader reader) + throws CommandException, CommandValidationException, IOException { + String line = null; + int rc = 0; + + /* + * Any program options we start with are copied to the environment + * to serve as defaults for commands we run, and then we give each + * command an empty program options. + */ + programOpts.toEnvironment(env); + + String sessionId = startSession(); + + try { + for (;;) { + if (printPrompt) { + line = reader.readLine(shellType + "$ "); + } else { + line = reader.readLine(); + } + + if (line == null) { + if (printPrompt) { + System.out.println(); + } + break; + } + + if (line.trim().startsWith("#")) // ignore comment lines + { + continue; + } + + String[] args = null; + try { + args = getArgs(line); + } catch (ArgumentTokenizer.ArgumentException ex) { + logger.info(ex.getMessage()); + continue; + } + + if (args.length == 0) { + continue; + } + + String command = args[0]; + if (command.trim().length() == 0) { + continue; + } + + // handle built-in exit and quit commands + // XXX - care about their arguments? + if (command.equals("exit") || command.equals("quit")) { + break; + } + + ProgramOptions po = null; + try { + /* + * Every command gets its own copy of program options + * so that any program options specified in its + * command line options don't effect other commands. + * But all commands share the same environment. + */ + po = new ProgramOptions(env); + // copy over AsadminMain info + po.setClassPath(programOpts.getClassPath()); + po.setClassName(programOpts.getClassName()); + // remove the old one and replace it + atomicReplace(locator, po); + + args = prepareArguments(sessionId, args); + + args = enhanceForTarget(args); + + String output = cmd.executeAndReturnOutput(args).trim(); + if(output != null && output.length() > 0) { + logger.info(output); + } + } catch (CommandValidationException cve) { + logger.severe(cve.getMessage()); + logger.severe(cmd.getUsage()); + rc = ERROR; + } catch (InvalidCommandException ice) { + // find closest match with local or remote commands + logger.severe(ice.getMessage()); + } catch (CommandException ce) { + logger.severe(ce.getMessage()); + rc = ERROR; + } finally { + // restore the original program options + // XXX - is this necessary? + atomicReplace(locator, programOpts); + } + + CLIUtil.writeCommandToDebugLog(name, env, args, rc); + } + } finally { + // what if something breaks on the wire? + rc = stopSession(sessionId); + } + return rc; + } + + private static void atomicReplace(ServiceLocator locator, ProgramOptions options) { + DynamicConfigurationService dcs = locator.getService(DynamicConfigurationService.class); + DynamicConfiguration config = dcs.createDynamicConfiguration(); + + config.addUnbindFilter(BuilderHelper.createContractFilter(ProgramOptions.class.getName())); + ActiveDescriptor desc = BuilderHelper.createConstantDescriptor( + options, null, ProgramOptions.class); + config.addActiveDescriptor(desc); + + config.commit(); + } + + private String[] getArgs(String line) + throws ArgumentTokenizer.ArgumentException { + List args = new ArrayList(); + ArgumentTokenizer t = new ArgumentTokenizer(line); + while (t.hasMoreTokens()) { + args.add(t.nextToken()); + } + return args.toArray(new String[args.size()]); + } + + @Override + public void postConstruct() { + super.postConstruct(); + try { + cmd = new RemoteCLICommand(REMOTE_COMMAND, + locator.getService(ProgramOptions.class), + locator.getService(Environment.class)); + } catch (CommandException ex) { + // ignore - will be handled by execute() + } + } +} Index: appserver/osgi-platforms/felix-cli-interactive/src/main/resources/org/glassfish/osgi/felix/shell/interactive/LocalStrings.properties =================================================================== --- appserver/osgi-platforms/felix-cli-interactive/src/main/resources/org/glassfish/osgi/felix/shell/interactive/LocalStrings.properties (revision 0) +++ appserver/osgi-platforms/felix-cli-interactive/src/main/resources/org/glassfish/osgi/felix/shell/interactive/LocalStrings.properties (revision 0) @@ -0,0 +1,46 @@ +# +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. +# +# Copyright (c) 2012 Oracle and/or its affiliates. All rights reserved. +# +# The contents of this file are subject to the terms of either the GNU +# General Public License Version 2 only ("GPL") or the Common Development +# and Distribution License("CDDL") (collectively, the "License"). You +# may not use this file except in compliance with the License. You can +# obtain a copy of the License at +# http://glassfish.java.net/public/CDDL+GPL_1_1.html +# or packager/legal/LICENSE.txt. See the License for the specific +# language governing permissions and limitations under the License. +# +# When distributing the software, include this License Header Notice in each +# file and include the License file at packager/legal/LICENSE.txt. +# +# GPL Classpath Exception: +# Oracle designates this particular file as subject to the "Classpath" +# exception as provided by Oracle in the GPL Version 2 section of the License +# file that accompanied this code. +# +# Modifications: +# If applicable, add the following below the License Header, with the fields +# enclosed by brackets [] replaced by your own identifying information: +# "Portions Copyright [year] [name of copyright owner]" +# +# Contributor(s): +# If you wish your version of this file to be governed by only the CDDL or +# only the GPL Version 2, indicate your decision by adding "[Contributor] +# elects to include this software in this distribution under the [CDDL or GPL +# Version 2] license." If you don't indicate a single choice of license, a +# recipient has the option to distribute your version of this file under +# either the CDDL, the GPL Version 2 or to extend the choice of license to +# its licensees as provided above. However, if you add GPL Version 2 code +# and therefore, elected the GPL Version 2 license, then the option applies +# only if the new code is made subject to such option by the copyright +# holder. +# +# Original author: Ancoron Luciferis +# + +felix-shell=An interactive Shell to access a remote Felix Shell Service +felix-shell.instance=The server instance to delegate the commands to (the default is the DAS and the DAS must be running to execute some command on another instance) +felix-shell.file=A file containing commands to be executed (the shell is stopped after processing all commands in that file) +felix-shell.printprompt=Print the shell prompt or not (enabled by default) Index: appserver/osgi-platforms/pom.xml =================================================================== --- appserver/osgi-platforms/pom.xml (revision 56188) +++ appserver/osgi-platforms/pom.xml (working copy) @@ -58,6 +58,8 @@ felix-webconsole-extension glassfish-osgi-console-plugin glassfish-osgi-console-plugin-l10n + felix-cli-remote + felix-cli-interactive Index: appserver/packager/glassfish-osgi/pom.xml =================================================================== --- appserver/packager/glassfish-osgi/pom.xml (revision 56188) +++ appserver/packager/glassfish-osgi/pom.xml (working copy) @@ -90,7 +90,11 @@ compile runtime ${packager.artifact.excludes} - org.glassfish.fighterfish,org.apache.felix + + org.glassfish.fighterfish, + org.apache.felix, + org.glassfish.main.osgi + true @@ -172,6 +176,16 @@ org.apache.felix org.apache.felix.eventadmin + + org.glassfish.main.osgi + felix-cli-remote + ${project.version} + + + org.glassfish.main.osgi + felix-cli-interactive + ${project.version} + Index: appserver/packager/glassfish-osgi/build.xml =================================================================== --- appserver/packager/glassfish-osgi/build.xml (revision 56188) +++ appserver/packager/glassfish-osgi/build.xml (working copy) @@ -71,6 +71,12 @@ + + + + Index: nucleus/packager/nucleus-osgi/pom.xml =================================================================== --- nucleus/packager/nucleus-osgi/pom.xml (revision 56188) +++ nucleus/packager/nucleus-osgi/pom.xml (working copy) @@ -85,13 +85,6 @@ org.apache.felix - org.apache.felix.shell.remote - 1.1.2 - jar - ${temp.stagedir} - - - org.apache.felix org.apache.felix.gogo.runtime 0.8.0 jar Index: nucleus/osgi-platforms/felix/src/main/resources/glassfish/config/osgi.properties =================================================================== --- nucleus/osgi-platforms/felix/src/main/resources/glassfish/config/osgi.properties (revision 56188) +++ nucleus/osgi-platforms/felix/src/main/resources/glassfish/config/osgi.properties (working copy) @@ -200,7 +200,7 @@ ${com.sun.aas.installRootURI}modules/org.apache.felix.gogo.runtime.jar \ ${com.sun.aas.installRootURI}modules/org.apache.felix.gogo.command.jar \ ${com.sun.aas.installRootURI}modules/org.apache.felix.gogo.shell.jar \ - ${com.sun.aas.installRootURI}modules/org.apache.felix.shell.remote.jar + ${com.sun.aas.installRootURI}modules/felix-cli-remote.jar # This property is used to configure a list of bundles to be started by our autoprocessor. # Eventual activation of the bundles depend on bundle's start level and activation policy. @@ -223,7 +223,7 @@ # to set the start level of the OSGi framework once server is up and running so that # optional services can start. The initial start level of framework is controlled using # the standard framework property called org.osgi.framework.startlevel.beginning -glassfish.osgi.start.level.final=2 +glassfish.osgi.start.level.final=3 # What should be the initial start level of framework. # For performance reason, initially we set the start level to 1 so that no optional