|
5 | 5 | * ********************************************************************** |
6 | 6 | * ORGANIZATION : Pi4J |
7 | 7 | * PROJECT : Pi4J :: LIBRARY :: Java Library (CORE) |
8 | | - * FILENAME : Runtime.java |
| 8 | + * FILENAME : DefaultRuntime.java |
9 | 9 | * |
10 | 10 | * This file is part of the Pi4J project. More information about |
11 | 11 | * this project can be found here: https://pi4j.com/ |
|
26 | 26 | */ |
27 | 27 |
|
28 | 28 | import com.pi4j.context.Context; |
29 | | -import com.pi4j.event.InitializedEventProducer; |
30 | | -import com.pi4j.event.ShutdownEventProducer; |
| 29 | +import com.pi4j.context.ContextConfig; |
| 30 | +import com.pi4j.event.*; |
31 | 31 | import com.pi4j.exception.InitializeException; |
32 | 32 | import com.pi4j.exception.ShutdownException; |
| 33 | +import com.pi4j.extension.Plugin; |
| 34 | +import com.pi4j.extension.impl.DefaultPluginService; |
| 35 | +import com.pi4j.extension.impl.PluginStore; |
33 | 36 | import com.pi4j.io.IO; |
| 37 | +import com.pi4j.io.IOType; |
| 38 | +import com.pi4j.provider.Provider; |
| 39 | +import com.pi4j.provider.impl.DefaultRuntimeProviders; |
34 | 40 | import com.pi4j.provider.impl.RuntimeProviders; |
35 | 41 | import com.pi4j.registry.Registry; |
| 42 | +import com.pi4j.util.ExecutorPool; |
| 43 | +import org.slf4j.Logger; |
| 44 | +import org.slf4j.LoggerFactory; |
36 | 45 |
|
| 46 | +import java.util.*; |
| 47 | +import java.util.concurrent.CompletableFuture; |
| 48 | +import java.util.concurrent.ExecutorService; |
37 | 49 | import java.util.concurrent.Future; |
38 | 50 |
|
39 | 51 | /** |
40 | | - * <p>Runtime interface.</p> |
41 | | - * |
42 | | - * The runtime manages the IO registry and the providers on behalf of the context. It's the only entity with |
43 | | - * write access to the registry. |
| 52 | + * <p>DefaultRuntime class.</p> |
44 | 53 | * |
45 | 54 | * @author Robert Savage (<a href="http://www.savagehomeautomation.com">http://www.savagehomeautomation.com</a>) |
46 | 55 | * @version $Id: $Id |
47 | 56 | */ |
48 | | -public interface Runtime extends InitializedEventProducer<Runtime>, ShutdownEventProducer<Runtime> { |
49 | | - /** |
50 | | - * <p>registry.</p> |
51 | | - * |
52 | | - * @return a {@link com.pi4j.registry.Registry} object. |
53 | | - */ |
54 | | - Registry registry(); |
| 57 | +public class Runtime { |
55 | 58 |
|
56 | | - /** |
57 | | - * <p>providers.</p> |
58 | | - * |
59 | | - * @return a {@link com.pi4j.provider.impl.RuntimeProviders} object. |
60 | | - */ |
61 | | - RuntimeProviders providers(); |
| 59 | + private final Logger logger = LoggerFactory.getLogger(this.getClass()); |
| 60 | + private final Context context; |
| 61 | + private final RuntimeProviders providers; |
| 62 | + private final List<Plugin> plugins; |
| 63 | + private boolean isShutdown = false; |
| 64 | + private final EventManager<Runtime, ShutdownListener, ShutdownEvent> shutdownEventManager; |
| 65 | + private final EventManager<Runtime, InitializedListener, InitializedEvent> initializedEventManager; |
| 66 | + private final ExecutorPool executorPool; |
| 67 | + private final ExecutorService runtimeExecutor; |
| 68 | + private final MutableRegistry registry; |
62 | 69 |
|
63 | 70 | /** |
64 | | - * <p>context.</p> |
| 71 | + * <p>newInstance.</p> |
65 | 72 | * |
66 | | - * @return a {@link com.pi4j.context.Context} object. |
67 | | - */ |
68 | | - Context context(); |
69 | | - |
70 | | - Future<?> submitTask(Runnable task); |
71 | | - |
72 | | - /** |
73 | | - * <p>shutdown.</p> |
| 73 | + * @param context a {@link com.pi4j.context.Context} object. |
74 | 74 | * |
75 | 75 | * @return a {@link com.pi4j.runtime.Runtime} object. |
76 | | - * |
77 | | - * @throws com.pi4j.exception.ShutdownException if any. |
78 | 76 | */ |
79 | | - Runtime shutdown() throws ShutdownException; |
| 77 | + public static Runtime newInstance(Context context) { |
| 78 | + return new Runtime(context); |
| 79 | + } |
80 | 80 |
|
81 | | - Future<Context> asyncShutdown(); |
| 81 | + // private constructor |
| 82 | + private Runtime(Context context) { |
82 | 83 |
|
83 | | - /** |
84 | | - * @return Flag indicating if the runtime has been shutdown |
85 | | - */ |
86 | | - boolean isShutdown(); |
| 84 | + // set local references |
| 85 | + this.context = context; |
| 86 | + plugins = new ArrayList<>(); |
| 87 | + this.registry = new MutableRegistry(context); |
| 88 | + this.providers = DefaultRuntimeProviders.newInstance(this); |
| 89 | + |
| 90 | + this.shutdownEventManager = new EventManager(this, |
| 91 | + (EventDelegate<ShutdownListener, ShutdownEvent>) (listener, event) -> listener.onShutdown(event)); |
| 92 | + this.initializedEventManager = new EventManager(this, |
| 93 | + (EventDelegate<InitializedListener, InitializedEvent>) (listener, event) -> listener.onInitialized(event)); |
| 94 | + |
| 95 | + // initialize executor pool and runtime executor |
| 96 | + this.executorPool = new ExecutorPool(); |
| 97 | + this.runtimeExecutor = this.executorPool.getExecutor("Pi4J.RUNTIME"); |
| 98 | + |
| 99 | + logger.debug("Pi4J runtime context successfully created & initialized.'"); |
| 100 | + |
| 101 | + // listen for shutdown to properly clean up |
| 102 | + // TODO :: ADD PI4J INTERNAL SHUTDOWN CALLBACKS/EVENTS |
| 103 | + if (this.context.config().enableShutdownHook()) { |
| 104 | + java.lang.Runtime.getRuntime().addShutdownHook(new Thread(() -> { |
| 105 | + try { |
| 106 | + // shutdown Pi4J |
| 107 | + if (!isShutdown) |
| 108 | + shutdown(); |
| 109 | + } catch (Exception e) { |
| 110 | + logger.error("Failed to shutdown Pi4J runtime", e); |
| 111 | + } |
| 112 | + }, "pi4j-shutdown")); |
| 113 | + } |
| 114 | + } |
| 115 | + |
| 116 | + public Context context() { |
| 117 | + return this.context; |
| 118 | + } |
| 119 | + |
| 120 | + public Registry registry() { |
| 121 | + return registry; |
| 122 | + } |
| 123 | + |
| 124 | + public RuntimeProviders providers() { |
| 125 | + return this.providers; |
| 126 | + } |
| 127 | + |
| 128 | + public Future<?> submitTask(Runnable task) { |
| 129 | + return this.runtimeExecutor.submit(task); |
| 130 | + } |
| 131 | + |
| 132 | + public Runtime shutdown() throws ShutdownException { |
| 133 | + if (isShutdown) { |
| 134 | + logger.warn("Pi4J context/runtime is already shutdown.'"); |
| 135 | + return this; |
| 136 | + } |
| 137 | + |
| 138 | + isShutdown = true; |
| 139 | + logger.info("Shutting down Pi4J context/runtime..."); |
| 140 | + |
| 141 | + // notify before shutdown event listeners (requires custom delegate to invoke appropriate listener method) |
| 142 | + shutdownEventManager.dispatch(new ShutdownEvent(this.context), ShutdownListener::beforeShutdown); |
| 143 | + |
| 144 | + try { |
| 145 | + |
| 146 | + // remove shutdown monitoring thread |
| 147 | + //java.lang.Runtime.getRuntime().removeShutdownHook(this.shutdownThread); |
| 148 | + |
| 149 | + // remove all I/O instances |
| 150 | + this.registry.shutdown(); |
| 151 | + |
| 152 | + // shutdown all providers |
| 153 | + this.providers.shutdown(); |
| 154 | + |
| 155 | + // shutdown all plugins |
| 156 | + for (Plugin plugin : this.plugins) { |
| 157 | + try { |
| 158 | + plugin.shutdown(this.context); |
| 159 | + } catch (Exception e) { |
| 160 | + logger.error(e.getMessage(), e); |
| 161 | + } |
| 162 | + } |
| 163 | + |
| 164 | + // shutdown executor pool |
| 165 | + this.executorPool.destroy(); |
| 166 | + |
| 167 | + } catch (Exception e) { |
| 168 | + logger.error("failed to 'shutdown(); '", e); |
| 169 | + throw new ShutdownException(e); |
| 170 | + } |
| 171 | + |
| 172 | + logger.info("Pi4J context/runtime successfully shutdown. Dispatching shutdown event."); |
| 173 | + |
| 174 | + // notify shutdown event listeners |
| 175 | + shutdownEventManager.dispatch(new ShutdownEvent(this.context)); |
| 176 | + |
| 177 | + // remove all shutdown event listeners |
| 178 | + this.shutdownEventManager.clear(); |
| 179 | + |
| 180 | + return this; |
| 181 | + } |
| 182 | + |
| 183 | + public Future<Context> asyncShutdown() { |
| 184 | + return CompletableFuture.supplyAsync(() -> { |
| 185 | + try { |
| 186 | + shutdown(); |
| 187 | + } catch (Exception e) { |
| 188 | + logger.error(e.getMessage(), e); |
| 189 | + } |
| 190 | + return context; |
| 191 | + }); |
| 192 | + } |
| 193 | + |
| 194 | + public boolean isShutdown() { |
| 195 | + return isShutdown; |
| 196 | + } |
| 197 | + |
| 198 | + public Runtime initialize() throws InitializeException { |
| 199 | + logger.info("Initializing Pi4J context/runtime..."); |
| 200 | + try { |
| 201 | + // clear plugins container |
| 202 | + plugins.clear(); |
| 203 | + |
| 204 | + // container sets for providers to load |
| 205 | + Map<IOType, Provider> providers = new HashMap<>(); |
| 206 | + |
| 207 | + // only attempt to load platforms and providers from the classpath if an auto detect option is enabled |
| 208 | + ContextConfig config = context.config(); |
| 209 | + if (config.autoDetectPlatforms() || config.autoDetectProviders()) { |
| 210 | + |
| 211 | + // detect available Pi4J Plugins by scanning the classpath looking for plugin instances |
| 212 | + ServiceLoader<Plugin> plugins = ServiceLoader.load(Plugin.class); |
| 213 | + for (Plugin plugin : plugins) { |
| 214 | + if (plugin == null) |
| 215 | + continue; |
| 216 | + |
| 217 | + if (!config.autoDetectMockPlugins() && plugin.isMock()) { |
| 218 | + logger.trace("Ignoring mock plugin: [{}] in classpath", plugin.getClass().getName()); |
| 219 | + continue; |
| 220 | + } |
| 221 | + |
| 222 | + logger.trace("detected plugin: [{}] in classpath; calling 'initialize()'", |
| 223 | + plugin.getClass().getName()); |
| 224 | + try { |
| 225 | + // add plugin to internal cache |
| 226 | + this.plugins.add(plugin); |
| 227 | + |
| 228 | + PluginStore store = new PluginStore(); |
| 229 | + plugin.initialize(DefaultPluginService.newInstance(this.context(), store)); |
| 230 | + |
| 231 | + // if auto-detect providers is enabled, |
| 232 | + // OR |
| 233 | + // Detecting Mocks is enabled and this is a mock plugin |
| 234 | + // then add any detected providers to the collection to load |
| 235 | + if (config.autoDetectProviders() || (config.autoDetectMockPlugins() && plugin.isMock())) { |
| 236 | + store.providers.forEach(provider -> addProvider(provider, providers)); |
| 237 | + } |
| 238 | + |
| 239 | + } catch (Exception ex) { |
| 240 | + // unable to initialize this provider instance |
| 241 | + logger.error("unable to 'initialize()' plugin: [{}]; {}", plugin.getClass().getName(), |
| 242 | + ex.getMessage(), ex); |
| 243 | + } |
| 244 | + } |
| 245 | + } |
| 246 | + |
| 247 | + context().config().getProviders().forEach(provider -> { |
| 248 | + Provider replaced = providers.put(provider.getType(), provider); |
| 249 | + if (replaced != null) { |
| 250 | + logger.info("Replacing auto detected provider {} {} with provider {} from context config", |
| 251 | + replaced.getType(), replaced.getName(), provider.getName()); |
| 252 | + } |
| 253 | + }); |
| 254 | + |
| 255 | + // initialize all providers |
| 256 | + this.providers.initialize(providers.values()); |
| 257 | + |
| 258 | + } catch (Exception e) { |
| 259 | + logger.error("failed to 'initialize(); '", e); |
| 260 | + throw new InitializeException(e); |
| 261 | + } |
| 262 | + |
| 263 | + logger.info("Pi4J context/runtime successfully initialized."); |
| 264 | + |
| 265 | + // notify initialized event listeners |
| 266 | + notifyInitListeners(); |
| 267 | + |
| 268 | + return this; |
| 269 | + } |
| 270 | + |
| 271 | + public <T extends IO> T shutdown(T instance) { |
| 272 | + return registry.shutdown(instance); |
| 273 | + } |
| 274 | + |
| 275 | + public void register(IO instance) { |
| 276 | + registry.register(instance); |
| 277 | + } |
87 | 278 |
|
88 | 279 | /** |
89 | | - * <p>initialize.</p> |
90 | | - * |
91 | | - * @return a {@link com.pi4j.runtime.Runtime} object. |
| 280 | + * <p>Adds providers to the given collection, to later be used in the runtime after initialization.</p> |
| 281 | + * <p>This method validates the priority of a {@link Provider}, and guarantees, that we don't have multiple |
| 282 | + * providers for the same {@link IOType}</p> |
92 | 283 | * |
93 | | - * @throws com.pi4j.exception.InitializeException if any. |
| 284 | + * @param provider |
| 285 | + * @param providers |
94 | 286 | */ |
95 | | - Runtime initialize() throws InitializeException; |
| 287 | + private void addProvider(Provider provider, Map<IOType, Provider> providers) { |
| 288 | + if (!providers.containsKey(provider.getType())) { |
| 289 | + providers.put(provider.getType(), provider); |
| 290 | + } else { |
| 291 | + Provider existingProvider = providers.get(provider.getType()); |
| 292 | + if (provider.getPriority() <= existingProvider.getPriority()) { |
| 293 | + if (existingProvider.getName().equals(provider.getName())) |
| 294 | + throw new InitializeException( |
| 295 | + provider.getType() + " with name " + provider.getName() + " (" + provider.getId() + ") is already registered."); |
| 296 | + logger.info("Ignoring provider {} {} ({}) with priority {} as lower priority than {} which has priority {}", |
| 297 | + provider.getType(), provider.getName(), provider.getId(), provider.getPriority(), |
| 298 | + existingProvider.getName(), existingProvider.getPriority()); |
| 299 | + } else { |
| 300 | + logger.info("Replacing provider {} {} ({}) with priority {} with provider {} ({}) with higher priority {}", |
| 301 | + existingProvider.getType(), existingProvider.getName(), existingProvider.getId(), existingProvider.getPriority(), |
| 302 | + provider.getName(), provider.getId(), provider.getPriority()); |
| 303 | + providers.put(provider.getType(), provider); |
| 304 | + } |
| 305 | + } |
| 306 | + } |
96 | 307 |
|
97 | | - /** |
98 | | - * Removes an IO instance from the IO registry and shuts it down. This is called indirectly when calling close() |
99 | | - * on the IO instance. |
100 | | - */ |
101 | | - <T extends IO> T shutdown(T instance); |
| 308 | + private void notifyInitListeners() { |
| 309 | + initializedEventManager.dispatch(new InitializedEvent(this.context)); |
| 310 | + } |
| 311 | + |
| 312 | + public Runtime addListener(ShutdownListener... listener) { |
| 313 | + return shutdownEventManager.add(listener); |
| 314 | + } |
| 315 | + |
| 316 | + public Runtime removeListener(ShutdownListener... listener) { |
| 317 | + return shutdownEventManager.remove(listener); |
| 318 | + } |
| 319 | + |
| 320 | + public Runtime removeAllShutdownListeners() { |
| 321 | + return shutdownEventManager.clear(); |
| 322 | + } |
| 323 | + |
| 324 | + public Runtime removeAllInitializedListeners() { |
| 325 | + return initializedEventManager.clear(); |
| 326 | + } |
| 327 | + |
| 328 | + public Runtime addListener(InitializedListener... listener) { |
| 329 | + return initializedEventManager.add(listener); |
| 330 | + } |
102 | 331 |
|
103 | | - /** Add an IO instance to the IO registry. */ |
104 | | - void register(IO instance); |
| 332 | + public Runtime removeListener(InitializedListener... listener) { |
| 333 | + return initializedEventManager.remove(listener); |
| 334 | + } |
105 | 335 | } |
0 commit comments