You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@karaf.apache.org by gn...@apache.org on 2014/02/18 09:28:30 UTC
[6/6] git commit: [KARAF-2763] Provide a standalone extender to
support the new annotations
[KARAF-2763] Provide a standalone extender to support the new annotations
Project: http://git-wip-us.apache.org/repos/asf/karaf/repo
Commit: http://git-wip-us.apache.org/repos/asf/karaf/commit/2bd28679
Tree: http://git-wip-us.apache.org/repos/asf/karaf/tree/2bd28679
Diff: http://git-wip-us.apache.org/repos/asf/karaf/diff/2bd28679
Branch: refs/heads/master
Commit: 2bd28679e01f3800a2d686fcc214c850f0a3c2c5
Parents: e4f7bc4
Author: Guillaume Nodet <gn...@gmail.com>
Authored: Tue Feb 18 09:24:43 2014 +0100
Committer: Guillaume Nodet <gn...@gmail.com>
Committed: Tue Feb 18 09:24:43 2014 +0100
----------------------------------------------------------------------
shell/console/pom.xml | 9 +
.../shell/inject/impl/InjectionExtender.java | 100 ++++++
.../shell/inject/impl/InjectionExtension.java | 350 +++++++++++++++++++
.../shell/inject/impl/MultiServiceTracker.java | 89 +++++
.../karaf/shell/inject/impl/Satisfiable.java | 30 ++
.../shell/inject/impl/SingleServiceTracker.java | 160 +++++++++
.../OSGI-INF/blueprint/karaf-console.xml | 7 +
7 files changed, 745 insertions(+)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/karaf/blob/2bd28679/shell/console/pom.xml
----------------------------------------------------------------------
diff --git a/shell/console/pom.xml b/shell/console/pom.xml
index 9a04677..0e39177 100644
--- a/shell/console/pom.xml
+++ b/shell/console/pom.xml
@@ -82,6 +82,11 @@
<scope>provided</scope>
</dependency>
<dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.utils</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
<groupId>org.apache.sshd</groupId>
<artifactId>sshd-core</artifactId>
</dependency>
@@ -129,6 +134,7 @@
<instructions>
<Import-Package>
!org.apache.felix.gogo.runtime.*,
+ !org.apache.karaf.shell.inject.impl,
org.osgi.service.event;resolution:=optional,
org.apache.karaf.branding;resolution:=optional,
org.apache.sshd.agent*;resolution:=optional,
@@ -159,6 +165,9 @@
<Private-Package>
org.apache.karaf.shell.console.impl*,
org.apache.karaf.shell.security.impl*,
+ org.apache.karaf.shell.inject.impl*,
+ org.apache.felix.utils.extender,
+ org.apache.felix.utils.manifest
</Private-Package>
<Main-Class>
org.apache.karaf.shell.console.impl.Main
http://git-wip-us.apache.org/repos/asf/karaf/blob/2bd28679/shell/console/src/main/java/org/apache/karaf/shell/inject/impl/InjectionExtender.java
----------------------------------------------------------------------
diff --git a/shell/console/src/main/java/org/apache/karaf/shell/inject/impl/InjectionExtender.java b/shell/console/src/main/java/org/apache/karaf/shell/inject/impl/InjectionExtender.java
new file mode 100644
index 0000000..6177390
--- /dev/null
+++ b/shell/console/src/main/java/org/apache/karaf/shell/inject/impl/InjectionExtender.java
@@ -0,0 +1,100 @@
+/*
+ * 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.karaf.shell.inject.impl;
+
+import org.apache.felix.utils.extender.AbstractExtender;
+import org.apache.felix.utils.extender.Extension;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Bundle extender scanning for command classes.
+ */
+public class InjectionExtender extends AbstractExtender {
+
+ public static final String KARAF_COMMANDS = "Karaf-Injection";
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(InjectionExtender.class);
+
+ //
+ // Adapt BundleActivator to make it blueprint friendly
+ //
+
+ private BundleContext bundleContext;
+
+ public void setBundleContext(BundleContext bundleContext) {
+ this.bundleContext = bundleContext;
+ }
+
+ public void init() throws Exception {
+ start(bundleContext);
+ }
+
+ public void destroy() throws Exception {
+ stop(bundleContext);
+ }
+
+ //
+ // Extender implementation
+ //
+
+ @Override
+ protected Extension doCreateExtension(Bundle bundle) throws Exception {
+ if (bundle.getHeaders().get(KARAF_COMMANDS) != null) {
+ return new InjectionExtension(bundle);
+ }
+ return null;
+ }
+
+ @Override
+ protected void debug(Bundle bundle, String msg) {
+ StringBuilder buf = new StringBuilder();
+ if ( bundle != null )
+ {
+ buf.append( bundle.getSymbolicName() );
+ buf.append( " (" );
+ buf.append( bundle.getBundleId() );
+ buf.append( "): " );
+ }
+ buf.append(msg);
+ LOGGER.debug(buf.toString());
+ }
+
+ @Override
+ protected void warn(Bundle bundle, String msg, Throwable t) {
+ StringBuilder buf = new StringBuilder();
+ if ( bundle != null )
+ {
+ buf.append( bundle.getSymbolicName() );
+ buf.append( " (" );
+ buf.append( bundle.getBundleId() );
+ buf.append( "): " );
+ }
+ buf.append(msg);
+ LOGGER.warn(buf.toString(), t);
+ }
+
+ @Override
+ protected void error(String msg, Throwable t) {
+ LOGGER.error(msg, t);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/karaf/blob/2bd28679/shell/console/src/main/java/org/apache/karaf/shell/inject/impl/InjectionExtension.java
----------------------------------------------------------------------
diff --git a/shell/console/src/main/java/org/apache/karaf/shell/inject/impl/InjectionExtension.java b/shell/console/src/main/java/org/apache/karaf/shell/inject/impl/InjectionExtension.java
new file mode 100644
index 0000000..fd807a3
--- /dev/null
+++ b/shell/console/src/main/java/org/apache/karaf/shell/inject/impl/InjectionExtension.java
@@ -0,0 +1,350 @@
+/*
+ * 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.karaf.shell.inject.impl;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.felix.gogo.commands.Action;
+import org.apache.felix.service.command.CommandProcessor;
+import org.apache.felix.service.command.Function;
+import org.apache.felix.utils.extender.Extension;
+import org.apache.felix.utils.manifest.Clause;
+import org.apache.felix.utils.manifest.Parser;
+import org.apache.karaf.shell.commands.Command;
+import org.apache.karaf.shell.commands.CommandWithAction;
+import org.apache.karaf.shell.inject.Destroy;
+import org.apache.karaf.shell.inject.Init;
+import org.apache.karaf.shell.inject.Reference;
+import org.apache.karaf.shell.inject.Service;
+import org.apache.karaf.shell.commands.basic.AbstractCommand;
+import org.apache.karaf.shell.console.BundleContextAware;
+import org.apache.karaf.shell.console.CompletableFunction;
+import org.apache.karaf.shell.console.Completer;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.framework.wiring.BundleWiring;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Commands extension
+ */
+public class InjectionExtension implements Extension, Satisfiable {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(InjectionExtension.class);
+
+ private final Bundle bundle;
+ private final CountDownLatch started;
+ private final MultiServiceTracker tracker;
+ private final List<Satisfiable> satisfiables = new ArrayList<Satisfiable>();
+
+
+ public InjectionExtension(Bundle bundle) {
+ this.bundle = bundle;
+ this.started = new CountDownLatch(1);
+ this.tracker = new MultiServiceTracker(bundle.getBundleContext(), this);
+ }
+
+ @Override
+ public void found() {
+ for (Satisfiable s : satisfiables) {
+ s.found();
+ }
+ }
+
+ @Override
+ public void updated() {
+ for (Satisfiable s : satisfiables) {
+ s.updated();
+ }
+ }
+
+ @Override
+ public void lost() {
+ for (Satisfiable s : satisfiables) {
+ s.lost();
+ }
+ }
+
+ public void start() throws Exception {
+ try {
+ String header = bundle.getHeaders().get(InjectionExtender.KARAF_COMMANDS);
+ Clause[] clauses = Parser.parseHeader(header);
+ BundleWiring wiring = bundle.adapt(BundleWiring.class);
+ for (Clause clause : clauses) {
+ String name = clause.getName();
+ int options = BundleWiring.LISTRESOURCES_LOCAL;
+ name = name.replace('.', '/');
+ if (name.endsWith("*")) {
+ options |= BundleWiring.LISTRESOURCES_RECURSE;
+ name = name.substring(0, name.length() - 1);
+ }
+ if (!name.startsWith("/")) {
+ name = "/" + name;
+ }
+ if (name.endsWith("/")) {
+ name = name.substring(0, name.length() - 1);
+ }
+ Collection<String> classes = wiring.listResources(name, "*.class", options);
+ for (String className : classes) {
+ className = className.replace('/', '.').replace(".class", "");
+ inspectClass(bundle.loadClass(className));
+ }
+ }
+ tracker.open();
+ } finally {
+ started.countDown();
+ }
+ }
+
+ public void destroy() {
+ try {
+ started.await(5000, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ LOGGER.warn("The wait for bundle being started before destruction has been interrupted.", e);
+ }
+ tracker.close();
+ }
+
+ private void inspectClass(final Class<?> clazz) throws Exception {
+ Service reg = clazz.getAnnotation(Service.class);
+ if (reg == null) {
+ return;
+ }
+ if (Action.class.isAssignableFrom(clazz)) {
+ final Command cmd = clazz.getAnnotation(Command.class);
+ if (cmd == null) {
+ throw new IllegalArgumentException("Command " + clazz.getName() + " is not annotated with @Command");
+ }
+ // Create trackers
+ for (Class<?> cl = clazz; cl != Object.class; cl = cl.getSuperclass()) {
+ for (Field field : cl.getDeclaredFields()) {
+ if (field.getAnnotation(Reference.class) != null) {
+ if (field.getType() != BundleContext.class) {
+ tracker.track(field.getType());
+ }
+ }
+ }
+ }
+ satisfiables.add(new AutoRegisterCommand((Class<? extends Action>) clazz));
+ }
+ if (Completer.class.isAssignableFrom(clazz)) {
+ // Create trackers
+ for (Class<?> cl = clazz; cl != Object.class; cl = cl.getSuperclass()) {
+ for (Field field : cl.getDeclaredFields()) {
+ if (field.getAnnotation(Reference.class) != null) {
+ if (field.getType() != BundleContext.class) {
+ tracker.track(field.getType());
+ }
+ }
+ }
+ }
+ satisfiables.add(new AutoRegisterService(clazz));
+ }
+ }
+
+ public class AutoRegisterService implements Satisfiable {
+
+ private final Class<?> clazz;
+ private Object service;
+ private ServiceRegistration registration;
+
+ public AutoRegisterService(Class<?> clazz) {
+ this.clazz = clazz;
+ }
+
+ @Override
+ public void found() {
+ try {
+ // Create completer
+ service = clazz.newInstance();
+ Set<String> classes = new HashSet<String>();
+ // Inject services
+ for (Class<?> cl = clazz; cl != Object.class; cl = cl.getSuperclass()) {
+ classes.add(cl.getName());
+ for (Class c : cl.getInterfaces()) {
+ classes.add(c.getName());
+ }
+ for (Field field : cl.getDeclaredFields()) {
+ if (field.getAnnotation(Reference.class) != null) {
+ Object value;
+ if (field.getType() == BundleContext.class) {
+ value = InjectionExtension.this.bundle.getBundleContext();
+ } else {
+ value = InjectionExtension.this.tracker.getService(field.getType());
+ }
+ if (value == null) {
+ throw new RuntimeException("No OSGi service matching " + field.getType().getName());
+ }
+ field.setAccessible(true);
+ field.set(service, value);
+ }
+ }
+ }
+ for (Method method : clazz.getDeclaredMethods()) {
+ Init ann = method.getAnnotation(Init.class);
+ if (ann != null && method.getParameterTypes().length == 0 && method.getReturnType() == void.class) {
+ method.setAccessible(true);
+ method.invoke(service);
+ }
+ }
+ Hashtable<String, String> props = new Hashtable<String, String>();
+ registration = bundle.getBundleContext().registerService(classes.toArray(new String[classes.size()]), service, props);
+ } catch (Exception e) {
+ throw new RuntimeException("Unable to creation service " + clazz.getName(), e);
+ }
+ }
+
+ @Override
+ public void updated() {
+ lost();
+ found();
+ }
+
+ @Override
+ public void lost() {
+ if (registration != null) {
+ for (Method method : clazz.getDeclaredMethods()) {
+ Destroy ann = method.getAnnotation(Destroy.class);
+ if (ann != null && method.getParameterTypes().length == 0 && method.getReturnType() == void.class) {
+ method.setAccessible(true);
+ try {
+ method.invoke(service);
+ } catch (Exception e) {
+ LOGGER.warn("Error destroying service", e);
+ }
+ }
+ }
+ registration.unregister();
+ registration = null;
+ }
+ }
+
+ }
+
+ public class AutoRegisterCommand extends AbstractCommand implements Satisfiable, CompletableFunction {
+
+ private final Class<? extends Action> actionClass;
+ private ServiceRegistration registration;
+
+ public AutoRegisterCommand(Class<? extends Action> actionClass) {
+ this.actionClass = actionClass;
+ }
+
+ @Override
+ public void found() {
+ // Register command
+ Command cmd = actionClass.getAnnotation(Command.class);
+ Hashtable<String, String> props = new Hashtable<String, String>();
+ props.put(CommandProcessor.COMMAND_SCOPE, cmd.scope());
+ props.put(CommandProcessor.COMMAND_FUNCTION, cmd.name());
+ String[] classes = {
+ Function.class.getName(),
+ CompletableFunction.class.getName(),
+ CommandWithAction.class.getName(),
+ AbstractCommand.class.getName()
+ };
+ registration = bundle.getBundleContext().registerService(classes, this, props);
+ }
+
+ @Override
+ public void updated() {
+
+ }
+
+ @Override
+ public void lost() {
+ if (registration != null) {
+ registration.unregister();
+ registration = null;
+ }
+ }
+
+ @Override
+ public Action createNewAction() {
+ try {
+ Action action = actionClass.newInstance();
+ // Inject services
+ for (Class<?> cl = actionClass; cl != Object.class; cl = cl.getSuperclass()) {
+ for (Field field : cl.getDeclaredFields()) {
+ if (field.getAnnotation(Reference.class) != null) {
+ Object value;
+ if (field.getType() == BundleContext.class) {
+ value = InjectionExtension.this.bundle.getBundleContext();
+ } else {
+ value = InjectionExtension.this.tracker.getService(field.getType());
+ }
+ if (value == null) {
+ throw new RuntimeException("No OSGi service matching " + field.getType().getName());
+ }
+ field.setAccessible(true);
+ field.set(action, value);
+ }
+ }
+ }
+ if (action instanceof BundleContextAware) {
+ ((BundleContextAware) action).setBundleContext(bundle.getBundleContext());
+ }
+ for (Method method : actionClass.getDeclaredMethods()) {
+ Init ann = method.getAnnotation(Init.class);
+ if (ann != null && method.getParameterTypes().length == 0 && method.getReturnType() == void.class) {
+ method.setAccessible(true);
+ method.invoke(action);
+ }
+ }
+ return action;
+ } catch (Exception e) {
+ throw new RuntimeException("Unable to creation command action " + actionClass.getName(), e);
+ }
+ }
+
+ @Override
+ public void releaseAction(Action action) throws Exception {
+ for (Method method : actionClass.getDeclaredMethods()) {
+ Destroy ann = method.getAnnotation(Destroy.class);
+ if (ann != null && method.getParameterTypes().length == 0 && method.getReturnType() == void.class) {
+ method.setAccessible(true);
+ method.invoke(action);
+ }
+ }
+ super.releaseAction(action);
+ }
+
+ @Override
+ public List<Completer> getCompleters() {
+ return null;
+ }
+
+ @Override
+ public Map<String, Completer> getOptionalCompleters() {
+ return null;
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/karaf/blob/2bd28679/shell/console/src/main/java/org/apache/karaf/shell/inject/impl/MultiServiceTracker.java
----------------------------------------------------------------------
diff --git a/shell/console/src/main/java/org/apache/karaf/shell/inject/impl/MultiServiceTracker.java b/shell/console/src/main/java/org/apache/karaf/shell/inject/impl/MultiServiceTracker.java
new file mode 100644
index 0000000..5394dca
--- /dev/null
+++ b/shell/console/src/main/java/org/apache/karaf/shell/inject/impl/MultiServiceTracker.java
@@ -0,0 +1,89 @@
+/*
+ * 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.karaf.shell.inject.impl;
+
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.osgi.framework.BundleContext;
+
+/**
+ * Track multiple services by their type
+ */
+public class MultiServiceTracker {
+
+ private final BundleContext bundleContext;
+ private final Satisfiable satisfiable;
+ private final ConcurrentMap<Class, SingleServiceTracker> trackers = new ConcurrentHashMap<Class, SingleServiceTracker>();
+ private final AtomicInteger count = new AtomicInteger();
+
+ public MultiServiceTracker(BundleContext bundleContext, Satisfiable satisfiable) {
+ this.bundleContext = bundleContext;
+ this.satisfiable = satisfiable;
+ }
+
+ @SuppressWarnings("unchecked")
+ public void track(Class service) {
+ if (trackers.get(service) == null) {
+ SingleServiceTracker tracker = new SingleServiceTracker(bundleContext, service, new Satisfiable() {
+ @Override
+ public void found() {
+ if (count.incrementAndGet() == trackers.size()) {
+ satisfiable.found();
+ }
+ }
+
+ @Override
+ public void updated() {
+ if (count.get() == trackers.size()) {
+ satisfiable.updated();
+ }
+ }
+
+ @Override
+ public void lost() {
+ if (count.getAndDecrement() == trackers.size()) {
+ satisfiable.lost();
+ }
+ }
+
+ });
+ trackers.putIfAbsent(service, tracker);
+ }
+ }
+
+ public <T> T getService(Class<T> clazz) {
+ SingleServiceTracker tracker = trackers.get(clazz);
+ return tracker != null ? clazz.cast(tracker.getService()) : null;
+ }
+
+ public void open() {
+ for (SingleServiceTracker tracker : trackers.values()) {
+ tracker.open();
+ }
+ }
+
+ public void close() {
+ for (SingleServiceTracker tracker : trackers.values()) {
+ tracker.close();
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/karaf/blob/2bd28679/shell/console/src/main/java/org/apache/karaf/shell/inject/impl/Satisfiable.java
----------------------------------------------------------------------
diff --git a/shell/console/src/main/java/org/apache/karaf/shell/inject/impl/Satisfiable.java b/shell/console/src/main/java/org/apache/karaf/shell/inject/impl/Satisfiable.java
new file mode 100644
index 0000000..ed5ceb6
--- /dev/null
+++ b/shell/console/src/main/java/org/apache/karaf/shell/inject/impl/Satisfiable.java
@@ -0,0 +1,30 @@
+/*
+ * 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.karaf.shell.inject.impl;
+
+/**
+ * Interface to be called with a boolean satisfaction status.
+ */
+public interface Satisfiable {
+
+ void found();
+ void updated();
+ void lost();
+
+}
http://git-wip-us.apache.org/repos/asf/karaf/blob/2bd28679/shell/console/src/main/java/org/apache/karaf/shell/inject/impl/SingleServiceTracker.java
----------------------------------------------------------------------
diff --git a/shell/console/src/main/java/org/apache/karaf/shell/inject/impl/SingleServiceTracker.java b/shell/console/src/main/java/org/apache/karaf/shell/inject/impl/SingleServiceTracker.java
new file mode 100644
index 0000000..9ecec4d
--- /dev/null
+++ b/shell/console/src/main/java/org/apache/karaf/shell/inject/impl/SingleServiceTracker.java
@@ -0,0 +1,160 @@
+/*
+ * 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.karaf.shell.inject.impl;
+
+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;
+
+/**
+ * Track a single service by its type.
+ *
+ * @param <T>
+ */
+public final class SingleServiceTracker<T> {
+
+ 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 Satisfiable serviceListener;
+ private Filter filter;
+
+ private final ServiceListener listener = new ServiceListener() {
+ 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);
+ }
+ }
+ }
+ };
+
+ public SingleServiceTracker(BundleContext context, Class<T> clazz, Satisfiable sl) {
+ ctx = context;
+ this.className = clazz.getName();
+ serviceListener = sl;
+ }
+
+ 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(listener, filterString);
+ findMatchingReference(null);
+ } catch (InvalidSyntaxException e) {
+ // this can never happen. (famous last words :)
+ }
+ }
+ }
+
+ private void findMatchingReference(ServiceReference original) {
+ boolean clear = true;
+ ServiceReference ref = ctx.getServiceReference(className);
+ if (ref != null && (filter == null || filter.match(ref))) {
+ @SuppressWarnings("unchecked")
+ T service = (T) ctx.getService(ref);
+ 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, ref, service)) {
+ ctx.ungetService(ref);
+ }
+ }
+ } else if (original == null) {
+ clear = false;
+ }
+
+ if (clear) {
+ update(original, null, null);
+ }
+ }
+
+ 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.found();
+ else if (foundLostReplaced == 1) serviceListener.lost();
+ else if (foundLostReplaced == 2) serviceListener.updated();
+ }
+
+ return result;
+ }
+
+ public void close() {
+ if (open.compareAndSet(true, false)) {
+ ctx.removeServiceListener(listener);
+
+ synchronized (this) {
+ ServiceReference deadRef = ref.getAndSet(null);
+ service.set(null);
+ if (deadRef != null) ctx.ungetService(deadRef);
+ }
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/karaf/blob/2bd28679/shell/console/src/main/resources/OSGI-INF/blueprint/karaf-console.xml
----------------------------------------------------------------------
diff --git a/shell/console/src/main/resources/OSGI-INF/blueprint/karaf-console.xml b/shell/console/src/main/resources/OSGI-INF/blueprint/karaf-console.xml
index bbe0c5b..1abeb1e 100644
--- a/shell/console/src/main/resources/OSGI-INF/blueprint/karaf-console.xml
+++ b/shell/console/src/main/resources/OSGI-INF/blueprint/karaf-console.xml
@@ -82,4 +82,11 @@
</bean>
<service ref="secureCommandConfigTransformer" interface="org.osgi.service.cm.ConfigurationListener"/>
+ <!-- Console commands extender -->
+ <bean id="consoleExtender"
+ class="org.apache.karaf.shell.inject.impl.InjectionExtender"
+ init-method="init" destroy-method="destroy">
+ <property name="bundleContext" ref="blueprintBundleContext"/>
+ </bean>
+
</blueprint>