blob: 572516115ea95376dccc16bd39658fa3a52c24c9 [file] [log] [blame]
Pierre De Rop3a00a212015-03-01 09:27:46 +00001/*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19package org.apache.felix.dm.impl;
20
21import java.lang.reflect.InvocationTargetException;
Pierre De Rope4f43092016-02-14 12:49:02 +000022import java.util.Arrays;
Pierre De Rop3a00a212015-03-01 09:27:46 +000023import java.util.Dictionary;
Pierre De Rop9e5cdba2016-02-17 20:35:16 +000024import java.util.Objects;
Pierre De Rop3a00a212015-03-01 09:27:46 +000025import java.util.Properties;
Pierre De Ropc723eb32015-11-22 21:49:00 +000026import java.util.concurrent.Callable;
27import java.util.concurrent.ExecutionException;
28import java.util.concurrent.FutureTask;
29import java.util.concurrent.TimeUnit;
30import java.util.concurrent.TimeoutException;
Pierre De Rop3a00a212015-03-01 09:27:46 +000031import java.util.concurrent.atomic.AtomicBoolean;
Pierre De Rope4f43092016-02-14 12:49:02 +000032import java.util.stream.Stream;
Pierre De Rop3a00a212015-03-01 09:27:46 +000033
Pierre De Ropc40d93f2015-05-04 20:25:57 +000034import org.apache.felix.dm.Component;
Pierre De Rop3a00a212015-03-01 09:27:46 +000035import org.apache.felix.dm.ConfigurationDependency;
36import org.apache.felix.dm.Logger;
37import org.apache.felix.dm.PropertyMetaData;
38import org.apache.felix.dm.context.AbstractDependency;
39import org.apache.felix.dm.context.DependencyContext;
40import org.apache.felix.dm.context.Event;
41import org.apache.felix.dm.context.EventType;
42import org.apache.felix.dm.impl.metatype.MetaTypeProviderImpl;
43import org.osgi.framework.BundleContext;
44import org.osgi.framework.Constants;
45import org.osgi.framework.ServiceRegistration;
46import org.osgi.service.cm.ConfigurationException;
47import org.osgi.service.cm.ManagedService;
48
49/**
50 * Implementation for a configuration dependency.
51 *
52 * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
53 */
54public class ConfigurationDependencyImpl extends AbstractDependency<ConfigurationDependency> implements ConfigurationDependency, ManagedService {
55 private Dictionary<String, Object> m_settings;
56 private String m_pid;
57 private ServiceRegistration m_registration;
Pierre De Rop24d9d9d2016-02-05 08:46:34 +000058 private volatile Class<?> m_configType;
Pierre De Rop3a00a212015-03-01 09:27:46 +000059 private MetaTypeProviderImpl m_metaType;
60 private final AtomicBoolean m_updateInvokedCache = new AtomicBoolean();
61 private final Logger m_logger;
62 private final BundleContext m_context;
Pierre De Rop43ca21b2015-11-16 20:55:32 +000063 private boolean m_needsInstance = true;
Pierre De Ropc723eb32015-11-22 21:49:00 +000064 private final static int UPDATE_MAXWAIT = 30000; // max time to wait until a component has handled a configuration change event.
Pierre De Rop3a00a212015-03-01 09:27:46 +000065
66 public ConfigurationDependencyImpl() {
67 this(null, null);
68 }
69
70 public ConfigurationDependencyImpl(BundleContext context, Logger logger) {
71 m_context = context;
72 m_logger = logger;
73 setRequired(true);
74 setCallback("updated");
75 }
76
77 public ConfigurationDependencyImpl(ConfigurationDependencyImpl prototype) {
78 super(prototype);
79 m_context = prototype.m_context;
80 m_pid = prototype.m_pid;
81 m_logger = prototype.m_logger;
82 m_metaType = prototype.m_metaType != null ? new MetaTypeProviderImpl(prototype.m_metaType, this, null) : null;
Pierre De Rop43ca21b2015-11-16 20:55:32 +000083 m_needsInstance = prototype.needsInstance();
Pierre De Ropc90bfa32016-02-04 22:55:19 +000084 m_configType = prototype.m_configType;
Pierre De Rop3a00a212015-03-01 09:27:46 +000085 }
86
87 @Override
88 public Class<?> getAutoConfigType() {
89 return null; // we don't support auto config mode.
90 }
91
92 @Override
93 public DependencyContext createCopy() {
94 return new ConfigurationDependencyImpl(this);
95 }
96
Pierre De Ropc90bfa32016-02-04 22:55:19 +000097 /**
98 * Sets a callback method invoked on the instantiated component.
99 */
Pierre De Rop3a00a212015-03-01 09:27:46 +0000100 public ConfigurationDependencyImpl setCallback(String callback) {
101 super.setCallbacks(callback, null);
102 return this;
103 }
104
Pierre De Ropc90bfa32016-02-04 22:55:19 +0000105 /**
106 * Sets a callback method on an external callback instance object.
107 * The component is not yet instantiated at the time the callback is invoked.
108 * We check if callback instance is null, in this case, the callback will be invoked on the instantiated component.
109 */
Pierre De Rop3a00a212015-03-01 09:27:46 +0000110 public ConfigurationDependencyImpl setCallback(Object instance, String callback) {
Pierre De Ropc90bfa32016-02-04 22:55:19 +0000111 boolean needsInstantiatedComponent = (instance == null);
112 return setCallback(instance, callback, needsInstantiatedComponent);
Pierre De Rop43ca21b2015-11-16 20:55:32 +0000113 }
114
Pierre De Ropc90bfa32016-02-04 22:55:19 +0000115 /**
116 * Sets a callback method on an external callback instance object.
117 * If needsInstance == true, the component is instantiated at the time the callback is invoked.
118 * We check if callback instance is null, in this case, the callback will be invoked on the instantiated component.
119 */
Pierre De Rop43ca21b2015-11-16 20:55:32 +0000120 public ConfigurationDependencyImpl setCallback(Object instance, String callback, boolean needsInstance) {
Pierre De Rop3a00a212015-03-01 09:27:46 +0000121 super.setCallbacks(instance, callback, null);
Pierre De Rop43ca21b2015-11-16 20:55:32 +0000122 m_needsInstance = needsInstance;
Pierre De Rop3a00a212015-03-01 09:27:46 +0000123 return this;
124 }
Pierre De Ropc90bfa32016-02-04 22:55:19 +0000125
126 /**
127 * Sets a type-safe callback method invoked on the instantiated component.
128 */
129 public ConfigurationDependency setCallback(String callback, Class<?> configType) {
Pierre De Rop9e5cdba2016-02-17 20:35:16 +0000130 Objects.nonNull(configType);
Pierre De Ropc90bfa32016-02-04 22:55:19 +0000131 setCallback(callback);
132 m_configType = configType;
Pierre De Rop9e5cdba2016-02-17 20:35:16 +0000133 m_pid = (m_pid == null) ? configType.getName() : m_pid;
Pierre De Ropc90bfa32016-02-04 22:55:19 +0000134 return this;
135 }
Pierre De Rop3a00a212015-03-01 09:27:46 +0000136
Pierre De Ropc90bfa32016-02-04 22:55:19 +0000137 /**
138 * Sets a type-safe callback method on an external callback instance object.
139 * The component is not yet instantiated at the time the callback is invoked.
140 */
141 public ConfigurationDependency setCallback(Object instance, String callback, Class<?> configType) {
Pierre De Rop9e5cdba2016-02-17 20:35:16 +0000142 Objects.nonNull(configType);
Pierre De Ropc90bfa32016-02-04 22:55:19 +0000143 setCallback(instance, callback);
144 m_configType = configType;
Pierre De Rop9e5cdba2016-02-17 20:35:16 +0000145 m_pid = (m_pid == null) ? configType.getName() : m_pid;
Pierre De Ropc90bfa32016-02-04 22:55:19 +0000146 return this;
147 }
148
149 /**
150 * Sets a type-safe callback method on an external callback instance object.
151 * If needsInstance == true, the component is instantiated at the time the callback is invoked.
152 */
153 public ConfigurationDependencyImpl setCallback(Object instance, String callback, Class<?> configType, boolean needsInstance) {
154 setCallback(instance, callback, needsInstance);
155 m_configType = configType;
156 return this;
157 }
158
Pierre De Rop4973a4b2016-02-05 06:41:03 +0000159 /**
160 * This method indicates to ComponentImpl if the component must be instantiated when this Dependency is started.
161 * If the callback has to be invoked on the component instance, then the component
162 * instance must be instantiated at the time the Dependency is started because when "CM" calls ConfigurationDependencyImpl.updated()
163 * callback, then at this point we have to synchronously delegate the callback to the component instance, and re-throw to CM
164 * any exceptions (if any) thrown by the component instance updated callback.
165 */
Pierre De Rop3a00a212015-03-01 09:27:46 +0000166 @Override
167 public boolean needsInstance() {
Pierre De Rop43ca21b2015-11-16 20:55:32 +0000168 return m_needsInstance;
Pierre De Rop3a00a212015-03-01 09:27:46 +0000169 }
170
171 @Override
172 public void start() {
173 BundleContext context = m_component.getBundleContext();
174 if (context != null) { // If null, we are in a test environment
175 Properties props = new Properties();
176 props.put(Constants.SERVICE_PID, m_pid);
177 ManagedService ms = this;
178 if (m_metaType != null) {
179 ms = m_metaType;
180 }
181 m_registration = context.registerService(ManagedService.class.getName(), ms, props);
182 }
183 super.start();
184 }
185
186 @Override
187 public void stop() {
188 if (m_registration != null) {
189 try {
190 m_registration.unregister();
191 } catch (IllegalStateException e) {}
192 m_registration = null;
193 }
194 super.stop();
195 }
196
197 public ConfigurationDependency setPid(String pid) {
198 ensureNotActive();
199 m_pid = pid;
200 return this;
201 }
202
203 @Override
204 public String getSimpleName() {
205 return m_pid;
206 }
207
208 @Override
209 public String getFilter() {
210 return null;
211 }
212
213 public String getType() {
214 return "configuration";
215 }
216
217 public ConfigurationDependency add(PropertyMetaData properties)
218 {
219 createMetaTypeImpl();
220 m_metaType.add(properties);
221 return this;
222 }
223
224 public ConfigurationDependency setDescription(String description)
225 {
226 createMetaTypeImpl();
227 m_metaType.setDescription(description);
228 return this;
229 }
230
231 public ConfigurationDependency setHeading(String heading)
232 {
233 createMetaTypeImpl();
234 m_metaType.setName(heading);
235 return this;
236 }
237
238 public ConfigurationDependency setLocalization(String path)
239 {
240 createMetaTypeImpl();
241 m_metaType.setLocalization(path);
242 return this;
243 }
244
245 @SuppressWarnings("unchecked")
246 @Override
247 public Dictionary<String, Object> getProperties() {
248 if (m_settings == null) {
249 throw new IllegalStateException("cannot find configuration");
250 }
251 return m_settings;
252 }
253
254 @SuppressWarnings({"unchecked", "rawtypes"})
255 @Override
Pierre De Ropc723eb32015-11-22 21:49:00 +0000256 public void updated(final Dictionary settings) throws ConfigurationException {
Pierre De Rop3a00a212015-03-01 09:27:46 +0000257 m_updateInvokedCache.set(false);
258 Dictionary<String, Object> oldSettings = null;
259 synchronized (this) {
260 oldSettings = m_settings;
261 }
262
263 if (oldSettings == null && settings == null) {
264 // CM has started but our configuration is not still present in the CM database: ignore
265 return;
266 }
267
268 // If this is initial settings, or a configuration update, we handle it synchronously.
269 // We'll conclude that the dependency is available only if invoking updated did not cause
270 // any ConfigurationException.
Pierre De Ropc723eb32015-11-22 21:49:00 +0000271 // However, we still want to schedule the event in the component executor, to make sure that the
272 // callback is invoked safely. So, we use a Callable and a FutureTask that allows to handle the
273 // configuration update through the component executor. We still wait for the result because
274 // in case of any configuration error, we have to return it from the current thread.
275
276 Callable<ConfigurationException> result = new Callable<ConfigurationException>() {
277 @Override
278 public ConfigurationException call() throws Exception {
279 try {
280 invokeUpdated(settings); // either the callback instance or the component instances, if available.
281 } catch (ConfigurationException e) {
282 return e;
283 }
284 return null;
285 }
286 };
287
288 // Schedule the configuration update in the component executor. In Normal case, the task is immediately executed.
289 // But in a highly concurrent system, and if the component is being reconfigured, the component may be currently busy
290 // (handling a service dependency event for example), so the task will be enqueued in the component executor, and
291 // we'll wait for the task execution by using a FutureTask:
292
293 FutureTask<ConfigurationException> ft = new FutureTask<>(result);
294 m_component.getExecutor().execute(ft);
295
Pierre De Rop58018a12015-11-22 18:40:55 +0000296 try {
Pierre De Ropc723eb32015-11-22 21:49:00 +0000297 ConfigurationException confError = ft.get(UPDATE_MAXWAIT, TimeUnit.MILLISECONDS);
298 if (confError != null) {
299 throw confError; // will be logged by the Configuration Admin service;
300 }
301 }
302
303 catch (ExecutionException error) {
304 throw new ConfigurationException(null, "Configuration update error, unexpected exception.", error);
305 } catch (InterruptedException error) {
306 // will be logged by the Configuration Admin service;
307 throw new ConfigurationException(null, "Configuration update interrupted.", error);
308 } catch (TimeoutException error) {
309 // will be logged by the Configuration Admin service;
310 throw new ConfigurationException(null, "Component did not handle configuration update timely.", error);
Pierre De Rop3a00a212015-03-01 09:27:46 +0000311 }
312
313 // At this point, we have accepted the configuration.
314 synchronized (this) {
315 m_settings = settings;
316 }
317
318 if ((oldSettings == null) && (settings != null)) {
319 // Notify the component that our dependency is available.
320 m_component.handleEvent(this, EventType.ADDED, new ConfigurationEventImpl(m_pid, settings));
321 }
322 else if ((oldSettings != null) && (settings != null)) {
323 // Notify the component that our dependency has changed.
324 m_component.handleEvent(this, EventType.CHANGED, new ConfigurationEventImpl(m_pid, settings));
325 }
326 else if ((oldSettings != null) && (settings == null)) {
327 // Notify the component that our dependency has been removed.
328 // Notice that the component will be stopped, and then all required dependencies will be unbound
329 // (including our configuration dependency).
330 m_component.handleEvent(this, EventType.REMOVED, new ConfigurationEventImpl(m_pid, oldSettings));
331 }
332 }
333
334 @Override
335 public void invokeCallback(EventType type, Event ... event) {
336 switch (type) {
337 case ADDED:
338 try {
339 invokeUpdated(m_settings);
340 } catch (ConfigurationException e) {
341 logConfigurationException(e);
342 }
343 break;
344 case CHANGED:
345 // We already did that synchronously, from our updated method
346 break;
347 case REMOVED:
348 // The state machine is stopping us. We have to invoke updated(null).
Pierre De Rop58018a12015-11-22 18:40:55 +0000349 // Reset for the next time the state machine calls invokeCallback(ADDED)
Pierre De Ropc8295c22015-06-04 10:15:35 +0000350 m_updateInvokedCache.set(false);
Pierre De Rop3a00a212015-03-01 09:27:46 +0000351 break;
352 default:
353 break;
354 }
355 }
356
Pierre De Rop17274a72016-02-09 23:43:14 +0000357 /**
358 * Creates the various signatures and arguments combinations used for the configuration-type style callbacks.
359 *
360 * @param service the service for which the callback should be applied;
361 * @param configType the configuration type to use (can be <code>null</code>);
362 * @param settings the actual configuration settings.
363 */
364 static CallbackTypeDef createCallbackType(Logger logger, Component service, Class<?> configType, Dictionary<?, ?> settings) {
365 Class<?>[][] sigs = new Class[][] { { Dictionary.class }, { Component.class, Dictionary.class }, {} };
366 Object[][] args = new Object[][] { { settings }, { service, settings }, {} };
367
368 if (configType != null) {
369 try {
370 // if the configuration is null, it means we are losing it, and since we pass a null dictionary for other callback
371 // (that accepts a Dictionary), then we should have the same behavior and also pass a null conf proxy object when
372 // the configuration is lost.
373 Object configurable = settings != null ? Configurable.create(configType, settings) : null;
374
375 logger.debug("Using configuration-type injecting using %s as possible configType.", configType.getSimpleName());
376
377 sigs = new Class[][] { { Dictionary.class }, { Component.class, Dictionary.class }, { Component.class, configType }, { configType }, {} };
378 args = new Object[][] { { settings }, { service, settings }, { service, configurable }, { configurable }, {} };
379 }
380 catch (Exception e) {
381 // This is not something we can recover from, use the defaults above...
382 logger.warn("Failed to create configurable for configuration type %s!", e, configType);
383 }
384 }
385
386 return new CallbackTypeDef(sigs, args);
387 }
388
Pierre De Ropc90bfa32016-02-04 22:55:19 +0000389 private void invokeUpdated(Dictionary<?, ?> settings) throws ConfigurationException {
390 if (m_updateInvokedCache.compareAndSet(false, true)) {
Pierre De Rop85a643a2016-02-14 11:02:59 +0000391
392 // FELIX-5155: if component impl is an internal DM adapter, we must not invoke the callback on it
393 // because in case there is an external callback instance specified for the configuration callback,
394 // then we don't want to invoke it now. The external callback instance will be invoked
395 // on the other actual configuration dependency copied into the actual component instance created by the
396 // adapter.
397
Pierre De Rope4f43092016-02-14 12:49:02 +0000398 Object mainComponentInstance = m_component.getInstance();
Pierre De Rop85a643a2016-02-14 11:02:59 +0000399 if (mainComponentInstance instanceof AbstractDecorator) {
400 return;
401 }
402
Pierre De Ropc90bfa32016-02-04 22:55:19 +0000403 Object[] instances = super.getInstances(); // either the callback instance or the component instances
404 if (instances == null) {
405 return;
406 }
Pierre De Rop3a00a212015-03-01 09:27:46 +0000407
Pierre De Rop17274a72016-02-09 23:43:14 +0000408 CallbackTypeDef callbackInfo = createCallbackType(m_logger, m_component, m_configType, settings);
Pierre De Rope4f43092016-02-14 12:49:02 +0000409 boolean callbackFound = false;
Pierre De Ropc90bfa32016-02-04 22:55:19 +0000410 for (int i = 0; i < instances.length; i++) {
411 try {
Pierre De Rop17274a72016-02-09 23:43:14 +0000412 InvocationUtil.invokeCallbackMethod(instances[i], m_add, callbackInfo.m_sigs, callbackInfo.m_args);
Pierre De Rope4f43092016-02-14 12:49:02 +0000413 callbackFound |= true;
Pierre De Ropc90bfa32016-02-04 22:55:19 +0000414 }
415 catch (InvocationTargetException e) {
416 // The component has thrown an exception during it's callback invocation.
417 if (e.getTargetException() instanceof ConfigurationException) {
418 // the callback threw an OSGi ConfigurationException: just re-throw it.
419 throw (ConfigurationException) e.getTargetException();
420 }
421 else {
422 // wrap the callback exception into a ConfigurationException.
423 throw new ConfigurationException(null, "Configuration update failed", e.getTargetException());
424 }
425 }
426 catch (NoSuchMethodException e) {
427 // if the method does not exist, ignore it
428 }
429 catch (Throwable t) {
430 // wrap any other exception as a ConfigurationException.
431 throw new ConfigurationException(null, "Configuration update failed", t);
432 }
433 }
Pierre De Rope4f43092016-02-14 12:49:02 +0000434
435 if (! callbackFound) {
436 String[] instanceClasses = Stream.of(instances).map(c -> c.getClass().getName()).toArray(String[]::new);
Pierre De Rop9cb1c042016-02-14 13:17:24 +0000437 m_logger.log(Logger.LOG_ERROR, "\"" + m_add + "\" configuration callback not found in any of the component classes: " + Arrays.toString(instanceClasses));
Pierre De Rope4f43092016-02-14 12:49:02 +0000438 }
Pierre De Ropc90bfa32016-02-04 22:55:19 +0000439 }
Pierre De Rop3a00a212015-03-01 09:27:46 +0000440 }
441
442 private synchronized void createMetaTypeImpl() {
443 if (m_metaType == null) {
444 m_metaType = new MetaTypeProviderImpl(m_pid, m_context, m_logger, this, null);
445 }
446 }
Pierre De Rop9cb1c042016-02-14 13:17:24 +0000447
Pierre De Rop3a00a212015-03-01 09:27:46 +0000448 private void logConfigurationException(ConfigurationException e) {
Pierre De Rop9cb1c042016-02-14 13:17:24 +0000449 m_logger.log(Logger.LOG_ERROR, "Got exception while handling configuration update for pid " + m_pid, e);
Pierre De Rop3a00a212015-03-01 09:27:46 +0000450 }
451}