blob: d129e423b7d14a43fba158a06112f0f974c7d9a8 [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;
22import java.util.Dictionary;
23import java.util.Properties;
Pierre De Ropc723eb32015-11-22 21:49:00 +000024import java.util.concurrent.Callable;
25import java.util.concurrent.ExecutionException;
26import java.util.concurrent.FutureTask;
27import java.util.concurrent.TimeUnit;
28import java.util.concurrent.TimeoutException;
Pierre De Rop3a00a212015-03-01 09:27:46 +000029import java.util.concurrent.atomic.AtomicBoolean;
30
Pierre De Ropc40d93f2015-05-04 20:25:57 +000031import org.apache.felix.dm.Component;
Pierre De Rop3a00a212015-03-01 09:27:46 +000032import org.apache.felix.dm.ConfigurationDependency;
33import org.apache.felix.dm.Logger;
34import org.apache.felix.dm.PropertyMetaData;
35import org.apache.felix.dm.context.AbstractDependency;
36import org.apache.felix.dm.context.DependencyContext;
37import org.apache.felix.dm.context.Event;
38import org.apache.felix.dm.context.EventType;
39import org.apache.felix.dm.impl.metatype.MetaTypeProviderImpl;
40import org.osgi.framework.BundleContext;
41import org.osgi.framework.Constants;
42import org.osgi.framework.ServiceRegistration;
43import org.osgi.service.cm.ConfigurationException;
44import org.osgi.service.cm.ManagedService;
45
46/**
47 * Implementation for a configuration dependency.
48 *
49 * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
50 */
51public class ConfigurationDependencyImpl extends AbstractDependency<ConfigurationDependency> implements ConfigurationDependency, ManagedService {
52 private Dictionary<String, Object> m_settings;
53 private String m_pid;
54 private ServiceRegistration m_registration;
Pierre De Rop24d9d9d2016-02-05 08:46:34 +000055 private volatile Class<?> m_configType;
Pierre De Rop3a00a212015-03-01 09:27:46 +000056 private MetaTypeProviderImpl m_metaType;
57 private final AtomicBoolean m_updateInvokedCache = new AtomicBoolean();
58 private final Logger m_logger;
59 private final BundleContext m_context;
Pierre De Rop43ca21b2015-11-16 20:55:32 +000060 private boolean m_needsInstance = true;
Pierre De Ropc723eb32015-11-22 21:49:00 +000061 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 +000062
63 public ConfigurationDependencyImpl() {
64 this(null, null);
65 }
66
67 public ConfigurationDependencyImpl(BundleContext context, Logger logger) {
68 m_context = context;
69 m_logger = logger;
70 setRequired(true);
71 setCallback("updated");
72 }
73
74 public ConfigurationDependencyImpl(ConfigurationDependencyImpl prototype) {
75 super(prototype);
76 m_context = prototype.m_context;
77 m_pid = prototype.m_pid;
78 m_logger = prototype.m_logger;
79 m_metaType = prototype.m_metaType != null ? new MetaTypeProviderImpl(prototype.m_metaType, this, null) : null;
Pierre De Rop43ca21b2015-11-16 20:55:32 +000080 m_needsInstance = prototype.needsInstance();
Pierre De Ropc90bfa32016-02-04 22:55:19 +000081 m_configType = prototype.m_configType;
Pierre De Rop3a00a212015-03-01 09:27:46 +000082 }
83
84 @Override
85 public Class<?> getAutoConfigType() {
86 return null; // we don't support auto config mode.
87 }
88
89 @Override
90 public DependencyContext createCopy() {
91 return new ConfigurationDependencyImpl(this);
92 }
93
Pierre De Ropc90bfa32016-02-04 22:55:19 +000094 /**
95 * Sets a callback method invoked on the instantiated component.
96 */
Pierre De Rop3a00a212015-03-01 09:27:46 +000097 public ConfigurationDependencyImpl setCallback(String callback) {
98 super.setCallbacks(callback, null);
99 return this;
100 }
101
Pierre De Ropc90bfa32016-02-04 22:55:19 +0000102 /**
103 * Sets a callback method on an external callback instance object.
104 * The component is not yet instantiated at the time the callback is invoked.
105 * We check if callback instance is null, in this case, the callback will be invoked on the instantiated component.
106 */
Pierre De Rop3a00a212015-03-01 09:27:46 +0000107 public ConfigurationDependencyImpl setCallback(Object instance, String callback) {
Pierre De Ropc90bfa32016-02-04 22:55:19 +0000108 boolean needsInstantiatedComponent = (instance == null);
109 return setCallback(instance, callback, needsInstantiatedComponent);
Pierre De Rop43ca21b2015-11-16 20:55:32 +0000110 }
111
Pierre De Ropc90bfa32016-02-04 22:55:19 +0000112 /**
113 * Sets a callback method on an external callback instance object.
114 * If needsInstance == true, the component is instantiated at the time the callback is invoked.
115 * We check if callback instance is null, in this case, the callback will be invoked on the instantiated component.
116 */
Pierre De Rop43ca21b2015-11-16 20:55:32 +0000117 public ConfigurationDependencyImpl setCallback(Object instance, String callback, boolean needsInstance) {
Pierre De Rop3a00a212015-03-01 09:27:46 +0000118 super.setCallbacks(instance, callback, null);
Pierre De Rop43ca21b2015-11-16 20:55:32 +0000119 m_needsInstance = needsInstance;
Pierre De Rop3a00a212015-03-01 09:27:46 +0000120 return this;
121 }
Pierre De Ropc90bfa32016-02-04 22:55:19 +0000122
123 /**
124 * Sets a type-safe callback method invoked on the instantiated component.
125 */
126 public ConfigurationDependency setCallback(String callback, Class<?> configType) {
127 setCallback(callback);
128 m_configType = configType;
129 return this;
130 }
Pierre De Rop3a00a212015-03-01 09:27:46 +0000131
Pierre De Ropc90bfa32016-02-04 22:55:19 +0000132 /**
133 * Sets a type-safe callback method on an external callback instance object.
134 * The component is not yet instantiated at the time the callback is invoked.
135 */
136 public ConfigurationDependency setCallback(Object instance, String callback, Class<?> configType) {
137 setCallback(instance, callback);
138 m_configType = configType;
139 return this;
140 }
141
142 /**
143 * Sets a type-safe callback method on an external callback instance object.
144 * If needsInstance == true, the component is instantiated at the time the callback is invoked.
145 */
146 public ConfigurationDependencyImpl setCallback(Object instance, String callback, Class<?> configType, boolean needsInstance) {
147 setCallback(instance, callback, needsInstance);
148 m_configType = configType;
149 return this;
150 }
151
Pierre De Rop4973a4b2016-02-05 06:41:03 +0000152 /**
153 * This method indicates to ComponentImpl if the component must be instantiated when this Dependency is started.
154 * If the callback has to be invoked on the component instance, then the component
155 * instance must be instantiated at the time the Dependency is started because when "CM" calls ConfigurationDependencyImpl.updated()
156 * callback, then at this point we have to synchronously delegate the callback to the component instance, and re-throw to CM
157 * any exceptions (if any) thrown by the component instance updated callback.
158 */
Pierre De Rop3a00a212015-03-01 09:27:46 +0000159 @Override
160 public boolean needsInstance() {
Pierre De Rop43ca21b2015-11-16 20:55:32 +0000161 return m_needsInstance;
Pierre De Rop3a00a212015-03-01 09:27:46 +0000162 }
163
164 @Override
165 public void start() {
166 BundleContext context = m_component.getBundleContext();
167 if (context != null) { // If null, we are in a test environment
168 Properties props = new Properties();
169 props.put(Constants.SERVICE_PID, m_pid);
170 ManagedService ms = this;
171 if (m_metaType != null) {
172 ms = m_metaType;
173 }
174 m_registration = context.registerService(ManagedService.class.getName(), ms, props);
175 }
176 super.start();
177 }
178
179 @Override
180 public void stop() {
181 if (m_registration != null) {
182 try {
183 m_registration.unregister();
184 } catch (IllegalStateException e) {}
185 m_registration = null;
186 }
187 super.stop();
188 }
189
190 public ConfigurationDependency setPid(String pid) {
191 ensureNotActive();
192 m_pid = pid;
193 return this;
194 }
195
196 @Override
197 public String getSimpleName() {
198 return m_pid;
199 }
200
201 @Override
202 public String getFilter() {
203 return null;
204 }
205
206 public String getType() {
207 return "configuration";
208 }
209
210 public ConfigurationDependency add(PropertyMetaData properties)
211 {
212 createMetaTypeImpl();
213 m_metaType.add(properties);
214 return this;
215 }
216
217 public ConfigurationDependency setDescription(String description)
218 {
219 createMetaTypeImpl();
220 m_metaType.setDescription(description);
221 return this;
222 }
223
224 public ConfigurationDependency setHeading(String heading)
225 {
226 createMetaTypeImpl();
227 m_metaType.setName(heading);
228 return this;
229 }
230
231 public ConfigurationDependency setLocalization(String path)
232 {
233 createMetaTypeImpl();
234 m_metaType.setLocalization(path);
235 return this;
236 }
237
238 @SuppressWarnings("unchecked")
239 @Override
240 public Dictionary<String, Object> getProperties() {
241 if (m_settings == null) {
242 throw new IllegalStateException("cannot find configuration");
243 }
244 return m_settings;
245 }
246
247 @SuppressWarnings({"unchecked", "rawtypes"})
248 @Override
Pierre De Ropc723eb32015-11-22 21:49:00 +0000249 public void updated(final Dictionary settings) throws ConfigurationException {
Pierre De Rop3a00a212015-03-01 09:27:46 +0000250 m_updateInvokedCache.set(false);
251 Dictionary<String, Object> oldSettings = null;
252 synchronized (this) {
253 oldSettings = m_settings;
254 }
255
256 if (oldSettings == null && settings == null) {
257 // CM has started but our configuration is not still present in the CM database: ignore
258 return;
259 }
260
261 // If this is initial settings, or a configuration update, we handle it synchronously.
262 // We'll conclude that the dependency is available only if invoking updated did not cause
263 // any ConfigurationException.
Pierre De Ropc723eb32015-11-22 21:49:00 +0000264 // However, we still want to schedule the event in the component executor, to make sure that the
265 // callback is invoked safely. So, we use a Callable and a FutureTask that allows to handle the
266 // configuration update through the component executor. We still wait for the result because
267 // in case of any configuration error, we have to return it from the current thread.
268
269 Callable<ConfigurationException> result = new Callable<ConfigurationException>() {
270 @Override
271 public ConfigurationException call() throws Exception {
272 try {
273 invokeUpdated(settings); // either the callback instance or the component instances, if available.
274 } catch (ConfigurationException e) {
275 return e;
276 }
277 return null;
278 }
279 };
280
281 // Schedule the configuration update in the component executor. In Normal case, the task is immediately executed.
282 // But in a highly concurrent system, and if the component is being reconfigured, the component may be currently busy
283 // (handling a service dependency event for example), so the task will be enqueued in the component executor, and
284 // we'll wait for the task execution by using a FutureTask:
285
286 FutureTask<ConfigurationException> ft = new FutureTask<>(result);
287 m_component.getExecutor().execute(ft);
288
Pierre De Rop58018a12015-11-22 18:40:55 +0000289 try {
Pierre De Ropc723eb32015-11-22 21:49:00 +0000290 ConfigurationException confError = ft.get(UPDATE_MAXWAIT, TimeUnit.MILLISECONDS);
291 if (confError != null) {
292 throw confError; // will be logged by the Configuration Admin service;
293 }
294 }
295
296 catch (ExecutionException error) {
297 throw new ConfigurationException(null, "Configuration update error, unexpected exception.", error);
298 } catch (InterruptedException error) {
299 // will be logged by the Configuration Admin service;
300 throw new ConfigurationException(null, "Configuration update interrupted.", error);
301 } catch (TimeoutException error) {
302 // will be logged by the Configuration Admin service;
303 throw new ConfigurationException(null, "Component did not handle configuration update timely.", error);
Pierre De Rop3a00a212015-03-01 09:27:46 +0000304 }
305
306 // At this point, we have accepted the configuration.
307 synchronized (this) {
308 m_settings = settings;
309 }
310
311 if ((oldSettings == null) && (settings != null)) {
312 // Notify the component that our dependency is available.
313 m_component.handleEvent(this, EventType.ADDED, new ConfigurationEventImpl(m_pid, settings));
314 }
315 else if ((oldSettings != null) && (settings != null)) {
316 // Notify the component that our dependency has changed.
317 m_component.handleEvent(this, EventType.CHANGED, new ConfigurationEventImpl(m_pid, settings));
318 }
319 else if ((oldSettings != null) && (settings == null)) {
320 // Notify the component that our dependency has been removed.
321 // Notice that the component will be stopped, and then all required dependencies will be unbound
322 // (including our configuration dependency).
323 m_component.handleEvent(this, EventType.REMOVED, new ConfigurationEventImpl(m_pid, oldSettings));
324 }
325 }
326
327 @Override
328 public void invokeCallback(EventType type, Event ... event) {
329 switch (type) {
330 case ADDED:
331 try {
332 invokeUpdated(m_settings);
333 } catch (ConfigurationException e) {
334 logConfigurationException(e);
335 }
336 break;
337 case CHANGED:
338 // We already did that synchronously, from our updated method
339 break;
340 case REMOVED:
341 // The state machine is stopping us. We have to invoke updated(null).
Pierre De Rop58018a12015-11-22 18:40:55 +0000342 // Reset for the next time the state machine calls invokeCallback(ADDED)
Pierre De Ropc8295c22015-06-04 10:15:35 +0000343 m_updateInvokedCache.set(false);
Pierre De Rop3a00a212015-03-01 09:27:46 +0000344 break;
345 default:
346 break;
347 }
348 }
349
Pierre De Rop17274a72016-02-09 23:43:14 +0000350 /**
351 * Creates the various signatures and arguments combinations used for the configuration-type style callbacks.
352 *
353 * @param service the service for which the callback should be applied;
354 * @param configType the configuration type to use (can be <code>null</code>);
355 * @param settings the actual configuration settings.
356 */
357 static CallbackTypeDef createCallbackType(Logger logger, Component service, Class<?> configType, Dictionary<?, ?> settings) {
358 Class<?>[][] sigs = new Class[][] { { Dictionary.class }, { Component.class, Dictionary.class }, {} };
359 Object[][] args = new Object[][] { { settings }, { service, settings }, {} };
360
361 if (configType != null) {
362 try {
363 // if the configuration is null, it means we are losing it, and since we pass a null dictionary for other callback
364 // (that accepts a Dictionary), then we should have the same behavior and also pass a null conf proxy object when
365 // the configuration is lost.
366 Object configurable = settings != null ? Configurable.create(configType, settings) : null;
367
368 logger.debug("Using configuration-type injecting using %s as possible configType.", configType.getSimpleName());
369
370 sigs = new Class[][] { { Dictionary.class }, { Component.class, Dictionary.class }, { Component.class, configType }, { configType }, {} };
371 args = new Object[][] { { settings }, { service, settings }, { service, configurable }, { configurable }, {} };
372 }
373 catch (Exception e) {
374 // This is not something we can recover from, use the defaults above...
375 logger.warn("Failed to create configurable for configuration type %s!", e, configType);
376 }
377 }
378
379 return new CallbackTypeDef(sigs, args);
380 }
381
Pierre De Ropc90bfa32016-02-04 22:55:19 +0000382 private void invokeUpdated(Dictionary<?, ?> settings) throws ConfigurationException {
383 if (m_updateInvokedCache.compareAndSet(false, true)) {
384 Object[] instances = super.getInstances(); // either the callback instance or the component instances
385 if (instances == null) {
386 return;
387 }
Pierre De Rop3a00a212015-03-01 09:27:46 +0000388
Pierre De Rop17274a72016-02-09 23:43:14 +0000389 CallbackTypeDef callbackInfo = createCallbackType(m_logger, m_component, m_configType, settings);
Pierre De Ropc90bfa32016-02-04 22:55:19 +0000390
391 for (int i = 0; i < instances.length; i++) {
392 try {
Pierre De Rop17274a72016-02-09 23:43:14 +0000393 InvocationUtil.invokeCallbackMethod(instances[i], m_add, callbackInfo.m_sigs, callbackInfo.m_args);
Pierre De Ropc90bfa32016-02-04 22:55:19 +0000394 }
395 catch (InvocationTargetException e) {
396 // The component has thrown an exception during it's callback invocation.
397 if (e.getTargetException() instanceof ConfigurationException) {
398 // the callback threw an OSGi ConfigurationException: just re-throw it.
399 throw (ConfigurationException) e.getTargetException();
400 }
401 else {
402 // wrap the callback exception into a ConfigurationException.
403 throw new ConfigurationException(null, "Configuration update failed", e.getTargetException());
404 }
405 }
406 catch (NoSuchMethodException e) {
407 // if the method does not exist, ignore it
408 }
409 catch (Throwable t) {
410 // wrap any other exception as a ConfigurationException.
411 throw new ConfigurationException(null, "Configuration update failed", t);
412 }
413 }
414 }
Pierre De Rop3a00a212015-03-01 09:27:46 +0000415 }
416
417 private synchronized void createMetaTypeImpl() {
418 if (m_metaType == null) {
419 m_metaType = new MetaTypeProviderImpl(m_pid, m_context, m_logger, this, null);
420 }
421 }
422
423 private void logConfigurationException(ConfigurationException e) {
424 if (m_logger != null) {
425 m_logger.log(Logger.LOG_ERROR, "Got exception while handling configuration update for pid " + m_pid, e);
426 }
427 }
428}