| /* |
| * 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(); |
| } |
| } |
| } |
| } |