diff --git a/shell/core/src/main/java/org/apache/karaf/shell/api/action/lifecycle/Config.java b/shell/core/src/main/java/org/apache/karaf/shell/api/action/lifecycle/Config.java
new file mode 100644
index 00000000000..0b4691d2dc8
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/api/action/lifecycle/Config.java
@@ -0,0 +1,51 @@
+/*
+ * 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.api.action.lifecycle;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Injects OSGi ConfigurationAdmin properties into a field.
+ *
+ *
The annotated field must be of type {@code Map}. The
+ * configuration properties for the given PID are retrieved from
+ * {@link org.osgi.service.cm.ConfigurationAdmin} and injected as a
+ * {@link java.util.LinkedHashMap}.
+ *
+ * Usage example:
+ *
+ * @Config(value = "service.pid", pid = "foo.bar")
+ * Map<String, Object> properties = new LinkedHashMap<>();
+ *
+ *
+ * If the configuration PID does not exist, an empty map is injected.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.FIELD})
+public @interface Config {
+
+ /**
+ * The configuration PID to look up in ConfigurationAdmin.
+ */
+ String pid();
+
+}
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/impl/action/command/ManagerImpl.java b/shell/core/src/main/java/org/apache/karaf/shell/impl/action/command/ManagerImpl.java
index be9af5cfcb1..0438d5113ca 100644
--- a/shell/core/src/main/java/org/apache/karaf/shell/impl/action/command/ManagerImpl.java
+++ b/shell/core/src/main/java/org/apache/karaf/shell/impl/action/command/ManagerImpl.java
@@ -21,14 +21,18 @@
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.karaf.shell.api.action.Action;
import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.lifecycle.Config;
import org.apache.karaf.shell.api.action.lifecycle.Destroy;
import org.apache.karaf.shell.api.action.lifecycle.Init;
import org.apache.karaf.shell.api.action.lifecycle.Manager;
@@ -38,9 +42,15 @@
import org.apache.karaf.shell.api.console.Parser;
import org.apache.karaf.shell.api.console.Registry;
import org.apache.karaf.shell.support.converter.GenericType;
+import org.osgi.service.cm.Configuration;
+import org.osgi.service.cm.ConfigurationAdmin;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
public class ManagerImpl implements Manager {
+ private static final Logger LOGGER = LoggerFactory.getLogger(ManagerImpl.class);
+
private final Registry dependencies;
private final Registry registrations;
private final Map, Object> instances = new HashMap<>();
@@ -96,6 +106,40 @@ public T instantiate(Class extends T> clazz, Registry registry) throws Exc
}
}
}
+ // Inject configuration properties
+ ConfigurationAdmin configAdmin = registry.getService(ConfigurationAdmin.class);
+ if (configAdmin == null && registry != this.dependencies) {
+ configAdmin = this.dependencies.getService(ConfigurationAdmin.class);
+ }
+ for (Class> cl = clazz; cl != Object.class; cl = cl.getSuperclass()) {
+ for (Field field : cl.getDeclaredFields()) {
+ Config cfg = field.getAnnotation(Config.class);
+ if (cfg != null) {
+ Map props = new LinkedHashMap<>();
+ if (configAdmin != null) {
+ try {
+ Configuration configuration = configAdmin.getConfiguration(cfg.pid(), "?");
+ if (configuration != null) {
+ Dictionary dict = configuration.getProperties();
+ if (dict != null) {
+ Enumeration keys = dict.keys();
+ while (keys.hasMoreElements()) {
+ String key = keys.nextElement();
+ props.put(key, dict.get(key));
+ }
+ }
+ }
+ } catch (Exception e) {
+ LOGGER.warn("Unable to retrieve configuration for PID {}", cfg.pid(), e);
+ }
+ } else {
+ LOGGER.debug("ConfigurationAdmin service not available, injecting empty map for PID {}", cfg.pid());
+ }
+ field.setAccessible(true);
+ field.set(instance, props);
+ }
+ }
+ }
for (Method method : clazz.getDeclaredMethods()) {
Init ann = method.getAnnotation(Init.class);
if (ann != null && method.getParameterTypes().length == 0 && method.getReturnType() == void.class) {
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/impl/action/osgi/CommandExtension.java b/shell/core/src/main/java/org/apache/karaf/shell/impl/action/osgi/CommandExtension.java
index a424c26a933..ff56b24d5b7 100644
--- a/shell/core/src/main/java/org/apache/karaf/shell/impl/action/osgi/CommandExtension.java
+++ b/shell/core/src/main/java/org/apache/karaf/shell/impl/action/osgi/CommandExtension.java
@@ -30,6 +30,7 @@
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.api.action.lifecycle.Config;
import org.apache.karaf.shell.api.action.lifecycle.Manager;
import org.apache.karaf.shell.api.action.lifecycle.Reference;
import org.apache.karaf.shell.api.action.lifecycle.Service;
@@ -43,6 +44,7 @@
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.wiring.BundleWiring;
+import org.osgi.service.cm.ConfigurationAdmin;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -176,8 +178,12 @@ private void inspectClass(final Class> clazz) throws Exception {
return;
}
// Create trackers
+ boolean needsConfigAdmin = false;
for (Class> cl = clazz; cl != Object.class; cl = cl.getSuperclass()) {
for (Field field : cl.getDeclaredFields()) {
+ if (field.getAnnotation(Config.class) != null) {
+ needsConfigAdmin = true;
+ }
Reference ref = field.getAnnotation(Reference.class);
if (ref != null) {
GenericType type = new GenericType(field.getGenericType());
@@ -194,6 +200,9 @@ private void inspectClass(final Class> clazz) throws Exception {
}
}
}
+ if (needsConfigAdmin && !registry.hasService(ConfigurationAdmin.class)) {
+ tracker.trackSingle(ConfigurationAdmin.class, false, "");
+ }
classes.add(clazz);
}
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/impl/console/osgi/LocalConsoleManager.java b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/osgi/LocalConsoleManager.java
index 440e05afea8..3cf7b175b5e 100644
--- a/shell/core/src/main/java/org/apache/karaf/shell/impl/console/osgi/LocalConsoleManager.java
+++ b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/osgi/LocalConsoleManager.java
@@ -77,7 +77,7 @@ public void start() throws Exception {
final Subject subject = createLocalKarafSubject();
this.session = JaasHelper.doAs(subject, (PrivilegedAction) () -> {
String encoding = getEncoding();
- PrintStream pout = new PrintStream(terminal.output()) {
+ PrintStream pout = new PrintStream(terminal.output(), true, Charset.forName(encoding)) {
@Override
public void close() {
// do nothing
diff --git a/shell/core/src/test/java/org/apache/karaf/shell/impl/action/command/ManagerImplConfigTest.java b/shell/core/src/test/java/org/apache/karaf/shell/impl/action/command/ManagerImplConfigTest.java
new file mode 100644
index 00000000000..4ef8d7a8400
--- /dev/null
+++ b/shell/core/src/test/java/org/apache/karaf/shell/impl/action/command/ManagerImplConfigTest.java
@@ -0,0 +1,235 @@
+/*
+ * 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.impl.action.command;
+
+import java.util.Hashtable;
+import java.util.Map;
+
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.lifecycle.Config;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.karaf.shell.api.console.Registry;
+import org.junit.Test;
+import org.osgi.service.cm.Configuration;
+import org.osgi.service.cm.ConfigurationAdmin;
+
+import static org.easymock.EasyMock.*;
+import static org.junit.Assert.*;
+
+public class ManagerImplConfigTest {
+
+ @Command(scope = "test", name = "config-single")
+ @Service
+ public static class SingleConfigCommand implements Action {
+ @Config(pid = "my.pid")
+ Map config;
+
+ @Override
+ public Object execute() throws Exception {
+ return null;
+ }
+ }
+
+ @Command(scope = "test", name = "config-multi")
+ @Service
+ public static class MultiConfigCommand implements Action {
+ @Config(pid = "pid.one")
+ Map configOne;
+
+ @Config(pid = "pid.two")
+ Map configTwo;
+
+ @Override
+ public Object execute() throws Exception {
+ return null;
+ }
+ }
+
+ @Command(scope = "test", name = "config-empty")
+ @Service
+ public static class EmptyConfigCommand implements Action {
+ @Config(pid = "nonexistent.pid")
+ Map config;
+
+ @Override
+ public Object execute() throws Exception {
+ return null;
+ }
+ }
+
+ @Test
+ public void testConfigInjection() throws Exception {
+ Hashtable props = new Hashtable<>();
+ props.put("key1", "value1");
+ props.put("key2", 42);
+
+ Configuration configuration = createMock(Configuration.class);
+ expect(configuration.getProperties()).andReturn(props);
+ replay(configuration);
+
+ ConfigurationAdmin configAdmin = createMock(ConfigurationAdmin.class);
+ expect(configAdmin.getConfiguration("my.pid", "?")).andReturn(configuration);
+ replay(configAdmin);
+
+ Registry registry = createMock(Registry.class);
+ expect(registry.getService(ConfigurationAdmin.class)).andReturn(configAdmin);
+ replay(registry);
+
+ ManagerImpl manager = new ManagerImpl(registry, registry, true);
+ SingleConfigCommand cmd = manager.instantiate(SingleConfigCommand.class, registry);
+
+ assertNotNull(cmd.config);
+ assertEquals("value1", cmd.config.get("key1"));
+ assertEquals(42, cmd.config.get("key2"));
+ assertEquals(2, cmd.config.size());
+
+ verify(configuration, configAdmin, registry);
+ }
+
+ @Test
+ public void testConfigInjectionWithNullProperties() throws Exception {
+ Configuration configuration = createMock(Configuration.class);
+ expect(configuration.getProperties()).andReturn(null);
+ replay(configuration);
+
+ ConfigurationAdmin configAdmin = createMock(ConfigurationAdmin.class);
+ expect(configAdmin.getConfiguration("nonexistent.pid", "?")).andReturn(configuration);
+ replay(configAdmin);
+
+ Registry registry = createMock(Registry.class);
+ expect(registry.getService(ConfigurationAdmin.class)).andReturn(configAdmin);
+ replay(registry);
+
+ ManagerImpl manager = new ManagerImpl(registry, registry, true);
+ EmptyConfigCommand cmd = manager.instantiate(EmptyConfigCommand.class, registry);
+
+ assertNotNull(cmd.config);
+ assertTrue(cmd.config.isEmpty());
+
+ verify(configuration, configAdmin, registry);
+ }
+
+ @Test
+ public void testConfigInjectionWithoutConfigAdmin() throws Exception {
+ Registry registry = createMock(Registry.class);
+ expect(registry.getService(ConfigurationAdmin.class)).andReturn(null);
+ replay(registry);
+
+ ManagerImpl manager = new ManagerImpl(registry, registry, true);
+ SingleConfigCommand cmd = manager.instantiate(SingleConfigCommand.class, registry);
+
+ assertNotNull(cmd.config);
+ assertTrue(cmd.config.isEmpty());
+
+ verify(registry);
+ }
+
+ @Test
+ public void testMultipleConfigInjection() throws Exception {
+ Hashtable props1 = new Hashtable<>();
+ props1.put("host", "localhost");
+ props1.put("port", 8080);
+
+ Hashtable props2 = new Hashtable<>();
+ props2.put("timeout", 30000L);
+
+ Configuration config1 = createMock(Configuration.class);
+ expect(config1.getProperties()).andReturn(props1);
+ replay(config1);
+
+ Configuration config2 = createMock(Configuration.class);
+ expect(config2.getProperties()).andReturn(props2);
+ replay(config2);
+
+ ConfigurationAdmin configAdmin = createMock(ConfigurationAdmin.class);
+ expect(configAdmin.getConfiguration("pid.one", "?")).andReturn(config1);
+ expect(configAdmin.getConfiguration("pid.two", "?")).andReturn(config2);
+ replay(configAdmin);
+
+ Registry registry = createMock(Registry.class);
+ expect(registry.getService(ConfigurationAdmin.class)).andReturn(configAdmin);
+ replay(registry);
+
+ ManagerImpl manager = new ManagerImpl(registry, registry, true);
+ MultiConfigCommand cmd = manager.instantiate(MultiConfigCommand.class, registry);
+
+ assertNotNull(cmd.configOne);
+ assertEquals("localhost", cmd.configOne.get("host"));
+ assertEquals(8080, cmd.configOne.get("port"));
+ assertEquals(2, cmd.configOne.size());
+
+ assertNotNull(cmd.configTwo);
+ assertEquals(30000L, cmd.configTwo.get("timeout"));
+ assertEquals(1, cmd.configTwo.size());
+
+ verify(config1, config2, configAdmin, registry);
+ }
+
+ @Test
+ public void testConfigInjectionWithConfigAdminException() throws Exception {
+ ConfigurationAdmin configAdmin = createMock(ConfigurationAdmin.class);
+ expect(configAdmin.getConfiguration("my.pid", "?")).andThrow(new java.io.IOException("config error"));
+ replay(configAdmin);
+
+ Registry registry = createMock(Registry.class);
+ expect(registry.getService(ConfigurationAdmin.class)).andReturn(configAdmin);
+ replay(registry);
+
+ ManagerImpl manager = new ManagerImpl(registry, registry, true);
+ SingleConfigCommand cmd = manager.instantiate(SingleConfigCommand.class, registry);
+
+ // Should get empty map when ConfigAdmin throws
+ assertNotNull(cmd.config);
+ assertTrue(cmd.config.isEmpty());
+
+ verify(configAdmin, registry);
+ }
+
+ @Test
+ public void testConfigInjectionFallsBackToDependencies() throws Exception {
+ Hashtable props = new Hashtable<>();
+ props.put("key", "value");
+
+ Configuration configuration = createMock(Configuration.class);
+ expect(configuration.getProperties()).andReturn(props);
+ replay(configuration);
+
+ ConfigurationAdmin configAdmin = createMock(ConfigurationAdmin.class);
+ expect(configAdmin.getConfiguration("my.pid", "?")).andReturn(configuration);
+ replay(configAdmin);
+
+ // Local registry returns null, dependencies registry has ConfigAdmin
+ Registry localRegistry = createMock(Registry.class);
+ expect(localRegistry.getService(ConfigurationAdmin.class)).andReturn(null);
+ replay(localRegistry);
+
+ Registry dependenciesRegistry = createMock(Registry.class);
+ expect(dependenciesRegistry.getService(ConfigurationAdmin.class)).andReturn(configAdmin);
+ replay(dependenciesRegistry);
+
+ ManagerImpl manager = new ManagerImpl(dependenciesRegistry, dependenciesRegistry, true);
+ SingleConfigCommand cmd = manager.instantiate(SingleConfigCommand.class, localRegistry);
+
+ assertNotNull(cmd.config);
+ assertEquals("value", cmd.config.get("key"));
+
+ verify(configuration, configAdmin, localRegistry, dependenciesRegistry);
+ }
+}
diff --git a/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/ShellCommand.java b/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/ShellCommand.java
index 39d0adcd19c..0023522ed4b 100644
--- a/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/ShellCommand.java
+++ b/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/ShellCommand.java
@@ -100,7 +100,7 @@ public void run() {
commandThread = Thread.currentThread();
int exitStatus = 0;
try {
- session = sessionFactory.create(in, new PrintStream(out), new PrintStream(err));
+ session = sessionFactory.create(in, new PrintStream(out, true), new PrintStream(err, true));
for (Map.Entry e : env.getEnv().entrySet()) {
session.put(e.getKey(), e.getValue());
}