/** * Copyright (c) 2014-2017 by the respective copyright holders. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html */ package org.eclipse.smarthome.io.console.rfc147.internal; import java.util.Collection; import java.util.Collections; import java.util.Dictionary; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Properties; import java.util.Set; import org.eclipse.smarthome.io.console.extensions.ConsoleCommandExtension; import org.eclipse.smarthome.io.console.rfc147.internal.extension.HelpConsoleCommandExtension; import org.osgi.framework.BundleContext; import org.osgi.framework.ServiceRegistration; import org.osgi.service.component.ComponentContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Implementation of the console support using the OSGi interface to support commands proposal RFC 147. * * Read this link to get a short overview about the way to implement commands for RFC 147: * https://felix.apache.org/site/rfc-147-overview.html * * @author Markus Rathgeb - Initial contribution and API * */ public class ConsoleSupportRfc147 implements ConsoleCommandsContainer { // private static final String KEY_SCOPE = CommandProcessor.COMMAND_SCOPE; // private static final String KEY_FUNCTION = CommandProcessor.COMMAND_FUNCTION; private static final String KEY_SCOPE = "osgi.command.scope"; private static final String KEY_FUNCTION = "osgi.command.function"; private static final String SCOPE = "smarthome"; public static final OSGiConsole CONSOLE = new OSGiConsole(SCOPE); private Logger logger = LoggerFactory.getLogger(ConsoleSupportRfc147.class); private final HelpConsoleCommandExtension helpCommand = new HelpConsoleCommandExtension(); private BundleContext bc; /* * This map will contain all console command extensions. * The key consists of the reference to the console command extensions. * The value is set to null, if the console command extension is not registered, yet (e.g. the bundle context is not * known). Otherwise it stores the registered service reference, so we could unregister the command extension later. */ private final Map<ConsoleCommandExtension, ServiceRegistration<?>> commands = Collections .synchronizedMap(new HashMap<ConsoleCommandExtension, ServiceRegistration<?>>()); public ConsoleSupportRfc147() { // Add our custom help console command extensions. commands.put(helpCommand, null); } public void activate(ComponentContext ctx) { // Save bundle context to register services. this.bc = ctx.getBundleContext(); /* * The bundle context is available. * Register all console command extensions that are not registered before. */ for (Map.Entry<ConsoleCommandExtension, ServiceRegistration<?>> entry : commands.entrySet()) { if (entry.getValue() == null) { entry.setValue(registerCommand(entry.getKey())); } } // We are activated now, so the help command should be able to fetch all our commands. helpCommand.setConsoleCommandsContainer(this); } public void deactivate() { // If we get deactivated, remove from help command (so GC could do their work). helpCommand.setConsoleCommandsContainer(null); /* * De-register all previously registered command extensions. */ for (Map.Entry<ConsoleCommandExtension, ServiceRegistration<?>> entry : commands.entrySet()) { if (entry.getValue() != null) { unregisterCommand(entry.getValue()); entry.setValue(null); } } // Remove bundle context reference. this.bc = null; } public void addConsoleCommandExtension(ConsoleCommandExtension consoleCommandExtension) { final ServiceRegistration<?> old; old = commands.put(consoleCommandExtension, registerCommand(consoleCommandExtension)); if (old != null) { unregisterCommand(old); } } public void removeConsoleCommandExtension(ConsoleCommandExtension consoleCommandExtension) { final ServiceRegistration<?> old; old = commands.remove(consoleCommandExtension); if (old != null) { unregisterCommand(old); } } @SuppressWarnings({ "unchecked", "rawtypes" }) private Dictionary<String, Object> createProperties() { return (Dictionary) new Properties(); } /** * Register a console command extension. * * @param cmd the console command extension that should be registered. * @return the service registration reference on success, null if the registration was not successful. */ private ServiceRegistration<?> registerCommand(final ConsoleCommandExtension cmd) { // We could only register the service if the bundle context is known. if (this.bc == null) { return null; } Dictionary<String, Object> props = createProperties(); props.put(KEY_SCOPE, SCOPE); props.put(KEY_FUNCTION, cmd.getCommand()); try { final ServiceRegistration<?> serviceRegistration; serviceRegistration = bc.registerService(CommandWrapper.class.getName(), new CommandWrapper(cmd), props); return serviceRegistration; } catch (final IllegalStateException ex) { logger.trace("Registration failed."); return null; } } /** * Unregister a service registration. * * @param serviceRegistration the service registration for the service that should be unregistered. */ private void unregisterCommand(final ServiceRegistration<?> serviceRegistration) { try { serviceRegistration.unregister(); } catch (final IllegalStateException ex) { logger.trace("Service already unregistered."); } } @Override public Collection<ConsoleCommandExtension> getConsoleCommandExtensions() { final Set<ConsoleCommandExtension> set = new HashSet<>(); // Fill set with registered commands only. for (Map.Entry<ConsoleCommandExtension, ServiceRegistration<?>> entry : commands.entrySet()) { if (entry.getValue() != null) { set.add(entry.getKey()); } } return set; } }