blob: f1125e5aab34ca2127929d218c2e61744482aa5f [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.felix.gogo.shell;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.Arrays;
import java.util.Dictionary;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import org.apache.felix.service.command.CommandProcessor;
import org.apache.felix.service.command.CommandSession;
import org.apache.felix.service.command.Converter;
import org.osgi.annotation.bundle.Header;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.ServiceRegistration;
import org.osgi.util.tracker.ServiceTracker;
@Header(name = Constants.BUNDLE_ACTIVATOR, value = "${@class}")
public class Activator implements BundleActivator
{
private BundleContext context;
private ServiceTracker<?,?> commandProcessorTracker;
private Set<ServiceRegistration<?>> regs;
private volatile ExecutorService executor;
private volatile StartShellJob shellJob;
public Activator()
{
regs = new HashSet<>();
}
public void start(BundleContext context) {
this.context = context;
this.commandProcessorTracker = createCommandProcessorTracker();
this.commandProcessorTracker.open();
}
public void stop(BundleContext context) {
Set<ServiceRegistration<?>> currentRegs;
synchronized (regs)
{
currentRegs = new HashSet<>(regs);
regs.clear();
}
for (ServiceRegistration<?> reg : currentRegs)
{
reg.unregister();
}
this.commandProcessorTracker.close();
stopShell();
}
private ServiceTracker<CommandProcessor, CommandProcessor> createCommandProcessorTracker()
{
return new ServiceTracker<CommandProcessor, CommandProcessor>(context, CommandProcessor.class, null)
{
@Override
public CommandProcessor addingService(ServiceReference<CommandProcessor> reference)
{
CommandProcessor processor = super.addingService(reference);
startShell(context, processor);
return processor;
}
@Override
public void removedService(ServiceReference<CommandProcessor> reference, CommandProcessor service)
{
stopShell();
super.removedService(reference, service);
}
};
}
private void startShell(BundleContext context, CommandProcessor processor)
{
Dictionary<String, Object> dict = new Hashtable<>();
dict.put(CommandProcessor.COMMAND_SCOPE, "gogo");
Set<ServiceRegistration<?>> currentRegs = new HashSet<>();
// register converters
currentRegs.add(context.registerService(Converter.class.getName(), new Converters(context.getBundle(0).getBundleContext()), null));
// register commands
dict.put(CommandProcessor.COMMAND_FUNCTION, Builtin.functions);
currentRegs.add(context.registerService(Builtin.class.getName(), new Builtin(), dict));
dict.put(CommandProcessor.COMMAND_FUNCTION, Procedural.functions);
currentRegs.add(context.registerService(Procedural.class.getName(), new Procedural(), dict));
dict.put(CommandProcessor.COMMAND_FUNCTION, Posix.functions);
currentRegs.add(context.registerService(Posix.class.getName(), new Posix(), dict));
dict.put(CommandProcessor.COMMAND_FUNCTION, Telnet.functions);
currentRegs.add(context.registerService(Telnet.class.getName(), new Telnet(processor), dict));
Shell shell = new Shell(context, processor);
dict.put(CommandProcessor.COMMAND_FUNCTION, Shell.functions);
currentRegs.add(context.registerService(Shell.class.getName(), shell, dict));
synchronized (regs)
{
regs.addAll(currentRegs);
currentRegs.clear();
}
// start shell on a separate thread...
executor = Executors.newSingleThreadExecutor(new ThreadFactory() {
@Override
public Thread newThread(Runnable runnable) {
return new Thread(runnable, "Gogo shell");
}
});
shellJob = new StartShellJob(context, processor);
executor.submit(shellJob);
}
private void stopShell()
{
if (executor != null && !(executor.isShutdown() || executor.isTerminated()))
{
if (shellJob != null)
{
shellJob.terminate();
}
executor.shutdown();
try
{
if (!executor.awaitTermination(5, TimeUnit.SECONDS))
{
System.err.println("!!! FAILED TO STOP EXECUTOR !!!");
Map<Thread, StackTraceElement[]> allStackTraces = Thread.getAllStackTraces();
for (Map.Entry<Thread, StackTraceElement[]> entry : allStackTraces.entrySet())
{
Thread t = entry.getKey();
System.err.printf("Thread: %s (%s): %s\n", t.getName(), t.getState(), Arrays.toString(entry.getValue()));
}
}
}
catch (InterruptedException e)
{
// Restore administration...
Thread.currentThread().interrupt();
}
executor = null;
}
}
private static class StartShellJob implements Runnable
{
private final BundleContext context;
private final CommandProcessor processor;
private volatile CommandSession session;
private volatile Thread shellThread;
public StartShellJob(BundleContext context, CommandProcessor processor)
{
this.context = context;
this.processor = processor;
}
public void run()
{
shellThread = Thread.currentThread();
session = processor.createSession(new FileInputStream(FileDescriptor.in),
new FileOutputStream(FileDescriptor.out),
new FileOutputStream(FileDescriptor.err));
try
{
// wait for gosh command to be registered
for (int i = 0; (i < 100) && session.get("gogo:gosh") == null; ++i)
{
TimeUnit.MILLISECONDS.sleep(10);
}
String args = context.getProperty("gosh.args");
args = (args == null) ? "" : args;
session.execute("gosh --login " + args);
}
catch (InterruptedException e)
{
// Ok, back off...
Thread.currentThread().interrupt();
}
catch (Exception e)
{
Object loc = session.get(".location");
if (null == loc || !loc.toString().contains(":"))
{
loc = "gogo";
}
System.err.println(loc + ": " + e.getClass().getSimpleName() + ": " + e.getMessage());
e.printStackTrace();
}
finally
{
terminate();
}
}
public void terminate()
{
if (session != null)
{
session.close();
session = null;
}
if (shellThread != null) {
shellThread.interrupt();
}
}
}
}