Fix OSGi issues
git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1736061 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/gogo/jline/pom.xml b/gogo/jline/pom.xml
index e1e4f75..991ca55 100644
--- a/gogo/jline/pom.xml
+++ b/gogo/jline/pom.xml
@@ -37,13 +37,13 @@
<dependency>
<groupId>org.osgi</groupId>
<artifactId>org.osgi.core</artifactId>
- <version>4.2.0</version>
+ <version>4.3.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.osgi</groupId>
<artifactId>org.osgi.compendium</artifactId>
- <version>4.0.0</version>
+ <version>4.3.1</version>
<scope>provided</scope>
</dependency>
<dependency>
diff --git a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Activator.java b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Activator.java
index 82d0496..a731fcd 100644
--- a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Activator.java
+++ b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Activator.java
@@ -18,73 +18,69 @@
*/
package org.apache.felix.gogo.jline;
+import java.util.ArrayList;
import java.util.Dictionary;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
+import java.util.List;
import java.util.Set;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
import org.apache.felix.gogo.jline.Shell.Context;
+import org.apache.felix.gogo.jline.SingleServiceTracker.SingleServiceListener;
+import org.apache.felix.gogo.runtime.Token;
+import org.apache.felix.gogo.runtime.Tokenizer;
import org.apache.felix.service.command.CommandProcessor;
import org.apache.felix.service.command.CommandSession;
import org.apache.felix.service.command.Converter;
+import org.jline.terminal.Terminal;
+import org.jline.terminal.TerminalBuilder;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
-import org.osgi.framework.ServiceReference;
import org.osgi.framework.ServiceRegistration;
-import org.osgi.util.tracker.ServiceTracker;
-public class Activator implements BundleActivator {
+public class Activator implements BundleActivator, SingleServiceListener {
+ private final Set<ServiceRegistration<?>> regs = new HashSet<>();
private BundleContext context;
- private ServiceTracker commandProcessorTracker;
- private Set<ServiceRegistration> regs;
-
- private ExecutorService executor;
+ private SingleServiceTracker<CommandProcessor> commandProcessorTracker;
+ private Thread thread;
public Activator() {
- regs = new HashSet<>();
}
public void start(BundleContext context) throws Exception {
this.context = context;
- this.commandProcessorTracker = createCommandProcessorTracker();
+ this.commandProcessorTracker = new SingleServiceTracker<>(context, CommandProcessor.class, this);
this.commandProcessorTracker.open();
}
public void stop(BundleContext context) throws Exception {
- Iterator<ServiceRegistration> iterator = regs.iterator();
+ Iterator<ServiceRegistration<?>> iterator = regs.iterator();
while (iterator.hasNext()) {
ServiceRegistration reg = iterator.next();
reg.unregister();
iterator.remove();
}
-
- stopShell();
-
this.commandProcessorTracker.close();
}
- private ServiceTracker createCommandProcessorTracker() {
- return new ServiceTracker(context, CommandProcessor.class.getName(), null) {
- @Override
- public Object addingService(ServiceReference reference) {
- CommandProcessor processor = (CommandProcessor) super.addingService(reference);
- startShell(context, processor);
- return processor;
- }
-
- @Override
- public void removedService(ServiceReference reference, Object service) {
- stopShell();
- super.removedService(reference, service);
- }
- };
+ @Override
+ public void serviceFound() {
+ startShell(context, commandProcessorTracker.getService());
}
- private void startShell(final BundleContext context, CommandProcessor processor) {
+ @Override
+ public void serviceLost() {
+ stopShell();
+ }
+
+ @Override
+ public void serviceReplaced() {
+ serviceLost();
+ serviceFound();
+ }
+
+ private void startShell(BundleContext context, CommandProcessor processor) {
Dictionary<String, Object> dict = new Hashtable<>();
dict.put(CommandProcessor.COMMAND_SCOPE, "gogo");
@@ -107,57 +103,58 @@
regs.add(context.registerService(Shell.class.getName(), shell, dict));
// start shell on a separate thread...
- executor = Executors.newSingleThreadExecutor(runnable -> new Thread(runnable, "Gogo shell"));
- executor.submit(new StartShellJob(context, processor));
+ thread = new Thread(() -> doStartShell(processor, shell), "Gogo shell");
+ thread.start();
}
private void stopShell() {
- if (executor != null && !(executor.isShutdown() || executor.isTerminated())) {
- executor.shutdownNow();
-
+ if (thread != null && thread.isAlive()) {
+ thread.interrupt();
try {
- if (!executor.awaitTermination(5, TimeUnit.SECONDS)) {
+ thread.join(5000);
+ if (thread.isAlive()) {
System.err.println("!!! FAILED TO STOP EXECUTOR !!!");
}
} catch (InterruptedException e) {
// Restore administration...
Thread.currentThread().interrupt();
}
- executor = null;
+ }
+ while (!regs.isEmpty()) {
+ ServiceRegistration<?> reg = regs.iterator().next();
+ regs.remove(reg);
+ reg.unregister();
}
}
- private static class StartShellJob implements Runnable {
- private final BundleContext context;
- private final CommandProcessor processor;
-
- public StartShellJob(BundleContext context, CommandProcessor processor) {
- this.context = context;
- this.processor = processor;
- }
-
- public void run() {
- CommandSession session = processor.createSession(System.in, System.out, System.err);
+ private void doStartShell(CommandProcessor processor, Shell shell) {
+ String errorMessage = "gogo: unable to create console";
+ try (Terminal terminal = TerminalBuilder.terminal();
+ CommandSession session = processor.createSession(terminal.input(), terminal.output(), terminal.output())) {
+ session.put(Shell.VAR_TERMINAL, terminal);
try {
- // wait for gosh command to be registered
- for (int i = 0; (i < 100) && session.get("gogo:gosh") == null; ++i) {
- TimeUnit.MILLISECONDS.sleep(10);
+ List<String> args = new ArrayList<>();
+ args.add("--login");
+ String argstr = shell.getContext().getProperty("gosh.args");
+ if (argstr != null) {
+ Tokenizer tokenizer = new Tokenizer(argstr);
+ Token token;
+ while ((token = tokenizer.next()) != null) {
+ args.add(token.toString());
+ }
}
-
- String args = context.getProperty("gosh.args");
- args = (args == null) ? "" : args;
- session.execute("gosh --login " + args);
+ shell.gosh(session, args.toArray(new String[args.size()]));
} 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 {
- session.close();
+ errorMessage = loc.toString();
+ throw e;
}
+ } catch (Exception e) {
+ System.err.println(errorMessage + e.getClass().getSimpleName() + ": " + e.getMessage());
+ e.printStackTrace();
}
}
diff --git a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Converters.java b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Converters.java
index 7d67d45..439b10d 100644
--- a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Converters.java
+++ b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Converters.java
@@ -164,14 +164,14 @@
private Object convertServiceReference(Object in) throws InvalidSyntaxException {
String s = in.toString();
if (s.startsWith("(") && s.endsWith(")")) {
- ServiceReference refs[] = context.getServiceReferences(null, String.format(
+ ServiceReference refs[] = context.getServiceReferences((String) null, String.format(
"(|(service.id=%s)(service.pid=%s))", in, in));
if (refs != null && refs.length > 0) {
return refs[0];
}
}
- ServiceReference refs[] = context.getServiceReferences(null, String.format(
+ ServiceReference refs[] = context.getServiceReferences((String) null, String.format(
"(|(service.id=%s)(service.pid=%s))", in, in));
if (refs != null && refs.length > 0) {
return refs[0];
diff --git a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Shell.java b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Shell.java
index cf91985..9d29ccf 100644
--- a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Shell.java
+++ b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Shell.java
@@ -101,6 +101,10 @@
this.profile = profile != null ? profile : "gosh_profile";
}
+ public Context getContext() {
+ return context;
+ }
+
public static Terminal getTerminal(CommandSession session) {
return (Terminal) session.get(VAR_TERMINAL);
}
@@ -316,96 +320,7 @@
if (args.isEmpty()) {
if (interactive) {
- AtomicBoolean reading = new AtomicBoolean();
- newSession.setJobListener((job, previous, current) -> {
- if (previous == Status.Background || current == Status.Background
- || previous == Status.Suspended || current == Status.Suspended) {
- int w = terminal.getWidth();
- String status = current.name().toLowerCase();
- StringBuilder sb = new StringBuilder();
- for (int i = 0; i < w - 1; i++) {
- sb.append(' ');
- }
- sb.append('\r');
- sb.append("[").append(job.id()).append("] ");
- sb.append(status);
- for (int i = status.length(); i < "background".length(); i++) {
- sb.append(' ');
- }
- sb.append(" ").append(job.command()).append("\n");
- terminal.writer().write(sb.toString());
- terminal.flush();
- if (reading.get()) {
- ((LineReaderImpl) reader).redrawLine();
- ((LineReaderImpl) reader).redisplay();
- }
- }
- });
- SignalHandler intHandler = terminal.handle(Signal.INT, s -> {
- Job current = newSession.foregroundJob();
- if (current != null) {
- current.interrupt();
- }
- });
- SignalHandler suspHandler = terminal.handle(Signal.TSTP, s -> {
- Job current = newSession.foregroundJob();
- if (current != null) {
- current.suspend();
- }
- });
- try {
- while (true) {
- try {
- reading.set(true);
- try {
- reader.readLine(Shell.getPrompt(session), Shell.getRPrompt(session), null, null);
- } finally {
- reading.set(false);
- }
- ParsedLine parsedLine = reader.getParsedLine();
- if (parsedLine == null) {
- throw new EndOfFileException();
- }
- try {
- result = session.execute(((ParsedLineImpl) parsedLine).program());
- session.put(Shell.VAR_RESULT, result); // set $_ to last result
-
- if (result != null && !Boolean.FALSE.equals(session.get(".Gogo.format"))) {
- System.out.println(session.format(result, Converter.INSPECT));
- }
- } catch (Exception e) {
- session.put(Shell.VAR_EXCEPTION, e);
- }
-
- while (true) {
- Job job = session.foregroundJob();
- if (job != null) {
- //noinspection SynchronizationOnLocalVariableOrMethodParameter
- synchronized (job) {
- if (job.status() == Status.Foreground) {
- job.wait();
- }
- }
- } else {
- break;
- }
- }
-
- } catch (UserInterruptException e) {
- // continue;
- } catch (EndOfFileException e) {
- try {
- reader.getHistory().flush();
- } catch (IOException e1) {
- e.addSuppressed(e1);
- }
- break;
- }
- }
- } finally {
- terminal.handle(Signal.INT, intHandler);
- terminal.handle(Signal.TSTP, suspHandler);
- }
+ result = runShell(session, newSession, terminal, reader);
}
} else {
CharSequence program;
@@ -433,7 +348,7 @@
program = readScript(script);
}
- result = newSession.execute(program);
+ result = newSession.execute(program);
}
if (login && interactive && !opt.isSet("noshutdown")) {
@@ -444,6 +359,110 @@
return result;
}
+ private Object runShell(final CommandSession session, CommandSession newSession, Terminal terminal,
+ LineReader reader) throws InterruptedException {
+ AtomicBoolean reading = new AtomicBoolean();
+ newSession.setJobListener((job, previous, current) -> {
+ if (previous == Status.Background || current == Status.Background
+ || previous == Status.Suspended || current == Status.Suspended) {
+ int width = terminal.getWidth();
+ String status = current.name().toLowerCase();
+ terminal.writer().write(getStatusLine(job, width, status));
+ terminal.flush();
+ if (reading.get()) {
+ ((LineReaderImpl) reader).redrawLine();
+ ((LineReaderImpl) reader).redisplay();
+ }
+ }
+ });
+ SignalHandler intHandler = terminal.handle(Signal.INT, s -> {
+ Job current = newSession.foregroundJob();
+ if (current != null) {
+ current.interrupt();
+ }
+ });
+ SignalHandler suspHandler = terminal.handle(Signal.TSTP, s -> {
+ Job current = newSession.foregroundJob();
+ if (current != null) {
+ current.suspend();
+ }
+ });
+ Object result = null;
+ try {
+ while (true) {
+ try {
+ reading.set(true);
+ try {
+ reader.readLine(Shell.getPrompt(session), Shell.getRPrompt(session), null, null);
+ } finally {
+ reading.set(false);
+ }
+ ParsedLine parsedLine = reader.getParsedLine();
+ if (parsedLine == null) {
+ throw new EndOfFileException();
+ }
+ try {
+ result = session.execute(((ParsedLineImpl) parsedLine).program());
+ session.put(Shell.VAR_RESULT, result); // set $_ to last result
+
+ if (result != null && !Boolean.FALSE.equals(session.get(".Gogo.format"))) {
+ System.out.println(session.format(result, Converter.INSPECT));
+ }
+ } catch (Exception e) {
+ session.put(Shell.VAR_EXCEPTION, e);
+ }
+
+ //waitJobCompletion(session);
+
+ } catch (UserInterruptException e) {
+ // continue;
+ } catch (EndOfFileException e) {
+ try {
+ reader.getHistory().flush();
+ } catch (IOException e1) {
+ e.addSuppressed(e1);
+ }
+ break;
+ }
+ }
+ } finally {
+ terminal.handle(Signal.INT, intHandler);
+ terminal.handle(Signal.TSTP, suspHandler);
+ }
+ return result;
+ }
+
+ private void waitJobCompletion(final CommandSession session) throws InterruptedException {
+ while (true) {
+ Job job = session.foregroundJob();
+ if (job != null) {
+ //noinspection SynchronizationOnLocalVariableOrMethodParameter
+ synchronized (job) {
+ if (job.status() == Status.Foreground) {
+ job.wait();
+ }
+ }
+ } else {
+ break;
+ }
+ }
+ }
+
+ private String getStatusLine(Job job, int width, String status) {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < width - 1; i++) {
+ sb.append(' ');
+ }
+ sb.append('\r');
+ sb.append("[").append(job.id()).append("] ");
+ sb.append(status);
+ for (int i = status.length(); i < "background".length(); i++) {
+ sb.append(' ');
+ }
+ sb.append(" ").append(job.command()).append("\n");
+ return sb.toString();
+ }
+
@Descriptor("start a new shell")
public Object sh(final CommandSession session, String[] argv) throws Exception {
return gosh(session, argv);
diff --git a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/SingleServiceTracker.java b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/SingleServiceTracker.java
new file mode 100644
index 0000000..826c78a
--- /dev/null
+++ b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/SingleServiceTracker.java
@@ -0,0 +1,191 @@
+/*
+ * 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.jline;
+
+import java.util.Arrays;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.Filter;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceEvent;
+import org.osgi.framework.ServiceListener;
+import org.osgi.framework.ServiceReference;
+
+//This is from aries util
+public final class SingleServiceTracker<T> implements ServiceListener {
+
+ public static interface SingleServiceListener {
+ public void serviceFound();
+
+ public void serviceLost();
+
+ public void serviceReplaced();
+ }
+
+ private final BundleContext ctx;
+ private final String className;
+ private final AtomicReference<T> service = new AtomicReference<T>();
+ private final AtomicReference<ServiceReference> ref = new AtomicReference<ServiceReference>();
+ private final AtomicBoolean open = new AtomicBoolean(false);
+ private final SingleServiceListener serviceListener;
+ private final String filterString;
+ private final Filter filter;
+
+ public SingleServiceTracker(BundleContext context, Class<T> clazz, SingleServiceListener sl) throws InvalidSyntaxException {
+ this(context, clazz, null, sl);
+ }
+
+ public SingleServiceTracker(BundleContext context, Class<T> clazz, String filterString, SingleServiceListener sl) throws InvalidSyntaxException {
+ this(context, clazz.getName(), filterString, sl);
+ }
+
+ public SingleServiceTracker(BundleContext context, String className, String filterString, SingleServiceListener sl) throws InvalidSyntaxException {
+ this.ctx = context;
+ this.className = className;
+ this.serviceListener = sl;
+ if (filterString == null || filterString.isEmpty()) {
+ this.filterString = null;
+ this.filter = null;
+ } else {
+ this.filterString = filterString;
+ this.filter = context.createFilter(filterString);
+ }
+ }
+
+ public T getService() {
+ return service.get();
+ }
+
+ public ServiceReference getServiceReference() {
+ return ref.get();
+ }
+
+ public void open() {
+ if (open.compareAndSet(false, true)) {
+ try {
+ String filterString = '(' + Constants.OBJECTCLASS + '=' + className + ')';
+ if (filter != null) filterString = "(&" + filterString + filter + ')';
+ ctx.addServiceListener(this, filterString);
+ findMatchingReference(null);
+ } catch (InvalidSyntaxException e) {
+ // this can never happen. (famous last words :)
+ }
+ }
+ }
+
+ public void serviceChanged(ServiceEvent event) {
+ if (open.get()) {
+ if (event.getType() == ServiceEvent.UNREGISTERING) {
+ ServiceReference deadRef = event.getServiceReference();
+ if (deadRef.equals(ref.get())) {
+ findMatchingReference(deadRef);
+ }
+ } else if (event.getType() == ServiceEvent.REGISTERED && ref.get() == null) {
+ findMatchingReference(null);
+ }
+ }
+ }
+
+ private void findMatchingReference(ServiceReference original) {
+ try {
+ boolean clear = true;
+ ServiceReference[] refs = ctx.getServiceReferences(className, filterString);
+ if (refs != null && refs.length > 0) {
+ if (refs.length > 1) {
+ Arrays.sort(refs);
+ }
+ @SuppressWarnings("unchecked")
+ T service = (T) ctx.getService(refs[0]);
+ if (service != null) {
+ clear = false;
+
+ // We do the unget out of the lock so we don't exit this class while holding a lock.
+ if (!!!update(original, refs[0], service)) {
+ ctx.ungetService(refs[0]);
+ }
+ }
+ } else if (original == null) {
+ clear = false;
+ }
+
+ if (clear) {
+ update(original, null, null);
+ }
+ } catch (InvalidSyntaxException e) {
+ // this can never happen. (famous last words :)
+ }
+ }
+
+ private boolean update(ServiceReference deadRef, ServiceReference newRef, T service) {
+ boolean result = false;
+ int foundLostReplaced = -1;
+
+ // Make sure we don't try to get a lock on null
+ Object lock;
+
+ // we have to choose our lock.
+ if (newRef != null) lock = newRef;
+ else if (deadRef != null) lock = deadRef;
+ else lock = this;
+
+ // This lock is here to ensure that no two threads can set the ref and service
+ // at the same time.
+ synchronized (lock) {
+ if (open.get()) {
+ result = this.ref.compareAndSet(deadRef, newRef);
+ if (result) {
+ this.service.set(service);
+
+ if (deadRef == null && newRef != null) foundLostReplaced = 0;
+ if (deadRef != null && newRef == null) foundLostReplaced = 1;
+ if (deadRef != null && newRef != null) foundLostReplaced = 2;
+ }
+ }
+ }
+
+ if (serviceListener != null) {
+ if (foundLostReplaced == 0) serviceListener.serviceFound();
+ else if (foundLostReplaced == 1) serviceListener.serviceLost();
+ else if (foundLostReplaced == 2) serviceListener.serviceReplaced();
+ }
+
+ return result;
+ }
+
+ public void close() {
+ if (open.compareAndSet(true, false)) {
+ ctx.removeServiceListener(this);
+
+ ServiceReference deadRef;
+ synchronized (this) {
+ deadRef = ref.getAndSet(null);
+ service.set(null);
+ }
+ if (deadRef != null) {
+ serviceListener.serviceLost();
+ ctx.ungetService(deadRef);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/gogo/runtime/pom.xml b/gogo/runtime/pom.xml
index 4f06a64..eebc1b0 100644
--- a/gogo/runtime/pom.xml
+++ b/gogo/runtime/pom.xml
@@ -68,6 +68,7 @@
<Export-Package>
org.apache.felix.service.command;
org.apache.felix.service.threadio; version=${project.version}; status="provisional"; mandatory:="status",
+ org.apache.felix.gogo.runtime*; version=${project.version},
org.apache.felix.gogo.api; version=${project.version}
</Export-Package>
<Import-Package>
@@ -77,7 +78,6 @@
org.osgi.service.startlevel*; resolution:=optional,
*
</Import-Package>
- <Private-Package>org.apache.felix.gogo.runtime*</Private-Package>
<Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName>
<Bundle-Vendor>The Apache Software Foundation</Bundle-Vendor>
<Bundle-Activator>org.apache.felix.gogo.runtime.activator.Activator</Bundle-Activator>
diff --git a/gogo/runtime/src/main/java/org/apache/felix/service/command/CommandSession.java b/gogo/runtime/src/main/java/org/apache/felix/service/command/CommandSession.java
index 3cb5068..9318a62 100644
--- a/gogo/runtime/src/main/java/org/apache/felix/service/command/CommandSession.java
+++ b/gogo/runtime/src/main/java/org/apache/felix/service/command/CommandSession.java
@@ -26,7 +26,7 @@
import org.apache.felix.gogo.api.Job;
import org.apache.felix.gogo.api.JobListener;
-public interface CommandSession
+public interface CommandSession extends AutoCloseable
{
Path currentDir();