blob: 8f5f70bc53121f6bf397e8843bbcaa143e8b65bf [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;
24import java.util.concurrent.atomic.AtomicBoolean;
25
Pierre De Ropc40d93f2015-05-04 20:25:57 +000026import org.apache.felix.dm.Component;
Pierre De Rop3a00a212015-03-01 09:27:46 +000027import org.apache.felix.dm.ConfigurationDependency;
28import org.apache.felix.dm.Logger;
29import org.apache.felix.dm.PropertyMetaData;
30import org.apache.felix.dm.context.AbstractDependency;
31import org.apache.felix.dm.context.DependencyContext;
32import org.apache.felix.dm.context.Event;
33import org.apache.felix.dm.context.EventType;
34import org.apache.felix.dm.impl.metatype.MetaTypeProviderImpl;
35import org.osgi.framework.BundleContext;
36import org.osgi.framework.Constants;
37import org.osgi.framework.ServiceRegistration;
38import org.osgi.service.cm.ConfigurationException;
39import org.osgi.service.cm.ManagedService;
40
41/**
42 * Implementation for a configuration dependency.
43 *
44 * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
45 */
46public class ConfigurationDependencyImpl extends AbstractDependency<ConfigurationDependency> implements ConfigurationDependency, ManagedService {
47 private Dictionary<String, Object> m_settings;
48 private String m_pid;
49 private ServiceRegistration m_registration;
50 private MetaTypeProviderImpl m_metaType;
51 private final AtomicBoolean m_updateInvokedCache = new AtomicBoolean();
52 private final Logger m_logger;
53 private final BundleContext m_context;
54
55 public ConfigurationDependencyImpl() {
56 this(null, null);
57 }
58
59 public ConfigurationDependencyImpl(BundleContext context, Logger logger) {
60 m_context = context;
61 m_logger = logger;
62 setRequired(true);
63 setCallback("updated");
64 }
65
66 public ConfigurationDependencyImpl(ConfigurationDependencyImpl prototype) {
67 super(prototype);
68 m_context = prototype.m_context;
69 m_pid = prototype.m_pid;
70 m_logger = prototype.m_logger;
71 m_metaType = prototype.m_metaType != null ? new MetaTypeProviderImpl(prototype.m_metaType, this, null) : null;
72 }
73
74 @Override
75 public Class<?> getAutoConfigType() {
76 return null; // we don't support auto config mode.
77 }
78
79 @Override
80 public DependencyContext createCopy() {
81 return new ConfigurationDependencyImpl(this);
82 }
83
84 public ConfigurationDependencyImpl setCallback(String callback) {
85 super.setCallbacks(callback, null);
86 return this;
87 }
88
89 public ConfigurationDependencyImpl setCallback(Object instance, String callback) {
90 super.setCallbacks(instance, callback, null);
91 return this;
92 }
93
94 @Override
95 public boolean needsInstance() {
Pierre De Ropc40d93f2015-05-04 20:25:57 +000096 // we need the component instances even if there is a callback instance, which could need to access to
97 // component instances while being invoked in the updated callback. So we return true here, even if there
98 // is a configured callback instance.
99 return true;
Pierre De Rop3a00a212015-03-01 09:27:46 +0000100 }
101
102 @Override
103 public void start() {
104 BundleContext context = m_component.getBundleContext();
105 if (context != null) { // If null, we are in a test environment
106 Properties props = new Properties();
107 props.put(Constants.SERVICE_PID, m_pid);
108 ManagedService ms = this;
109 if (m_metaType != null) {
110 ms = m_metaType;
111 }
112 m_registration = context.registerService(ManagedService.class.getName(), ms, props);
113 }
114 super.start();
115 }
116
117 @Override
118 public void stop() {
119 if (m_registration != null) {
120 try {
121 m_registration.unregister();
122 } catch (IllegalStateException e) {}
123 m_registration = null;
124 }
125 super.stop();
126 }
127
128 public ConfigurationDependency setPid(String pid) {
129 ensureNotActive();
130 m_pid = pid;
131 return this;
132 }
133
134 @Override
135 public String getSimpleName() {
136 return m_pid;
137 }
138
139 @Override
140 public String getFilter() {
141 return null;
142 }
143
144 public String getType() {
145 return "configuration";
146 }
147
148 public ConfigurationDependency add(PropertyMetaData properties)
149 {
150 createMetaTypeImpl();
151 m_metaType.add(properties);
152 return this;
153 }
154
155 public ConfigurationDependency setDescription(String description)
156 {
157 createMetaTypeImpl();
158 m_metaType.setDescription(description);
159 return this;
160 }
161
162 public ConfigurationDependency setHeading(String heading)
163 {
164 createMetaTypeImpl();
165 m_metaType.setName(heading);
166 return this;
167 }
168
169 public ConfigurationDependency setLocalization(String path)
170 {
171 createMetaTypeImpl();
172 m_metaType.setLocalization(path);
173 return this;
174 }
175
176 @SuppressWarnings("unchecked")
177 @Override
178 public Dictionary<String, Object> getProperties() {
179 if (m_settings == null) {
180 throw new IllegalStateException("cannot find configuration");
181 }
182 return m_settings;
183 }
184
185 @SuppressWarnings({"unchecked", "rawtypes"})
186 @Override
187 public void updated(Dictionary settings) throws ConfigurationException {
188 m_updateInvokedCache.set(false);
189 Dictionary<String, Object> oldSettings = null;
190 synchronized (this) {
191 oldSettings = m_settings;
192 }
193
194 if (oldSettings == null && settings == null) {
195 // CM has started but our configuration is not still present in the CM database: ignore
196 return;
197 }
198
199 // If this is initial settings, or a configuration update, we handle it synchronously.
200 // We'll conclude that the dependency is available only if invoking updated did not cause
201 // any ConfigurationException.
202 if (settings != null) {
203 Object[] instances = m_component.getInstances();
204 if (instances != null) {
205 try {
206 invokeUpdated(settings);
207 } catch (ConfigurationException e) {
208 logConfigurationException(e);
209 throw e;
210 }
211 }
212 }
213
214 // At this point, we have accepted the configuration.
215 synchronized (this) {
216 m_settings = settings;
217 }
218
219 if ((oldSettings == null) && (settings != null)) {
220 // Notify the component that our dependency is available.
221 m_component.handleEvent(this, EventType.ADDED, new ConfigurationEventImpl(m_pid, settings));
222 }
223 else if ((oldSettings != null) && (settings != null)) {
224 // Notify the component that our dependency has changed.
225 m_component.handleEvent(this, EventType.CHANGED, new ConfigurationEventImpl(m_pid, settings));
226 }
227 else if ((oldSettings != null) && (settings == null)) {
228 // Notify the component that our dependency has been removed.
229 // Notice that the component will be stopped, and then all required dependencies will be unbound
230 // (including our configuration dependency).
231 m_component.handleEvent(this, EventType.REMOVED, new ConfigurationEventImpl(m_pid, oldSettings));
232 }
233 }
234
235 @Override
236 public void invokeCallback(EventType type, Event ... event) {
237 switch (type) {
238 case ADDED:
239 try {
240 invokeUpdated(m_settings);
241 } catch (ConfigurationException e) {
242 logConfigurationException(e);
243 }
244 break;
245 case CHANGED:
246 // We already did that synchronously, from our updated method
247 break;
248 case REMOVED:
249 // The state machine is stopping us. We have to invoke updated(null).
250 try {
251 m_updateInvokedCache.set(false);
252 invokeUpdated(null);
253 } catch (ConfigurationException e) {
254 logConfigurationException(e);
255 } finally {
256 // Reset for the next time the state machine calls invokeAdd
257 m_updateInvokedCache.set(false);
258 }
259 break;
260 default:
261 break;
262 }
263 }
264
265 private void invokeUpdated(Dictionary<?,?> settings) throws ConfigurationException {
266 if (m_updateInvokedCache.compareAndSet(false, true)) {
267 Object[] instances = super.getInstances(); // either the callback instance or the component instances
268 if (instances != null) {
269 for (int i = 0; i < instances.length; i++) {
270 try {
271 InvocationUtil.invokeCallbackMethod(instances[i],
272 m_add, new Class[][] {
Pierre De Ropc40d93f2015-05-04 20:25:57 +0000273 { Dictionary.class },
274 { Component.class, Dictionary.class },
275 {} },
276 new Object[][] {
277 { settings },
278 { m_component, settings },
279 {} });
Pierre De Rop3a00a212015-03-01 09:27:46 +0000280 }
281
282 catch (InvocationTargetException e) {
283 // The component has thrown an exception during it's
284 // callback invocation.
285 if (e.getTargetException() instanceof ConfigurationException) {
286 // the callback threw an OSGi
287 // ConfigurationException: just re-throw it.
288 throw (ConfigurationException) e
289 .getTargetException();
290 } else {
291 // wrap the callback exception into a
292 // ConfigurationException.
293 throw new ConfigurationException(null,
294 "Configuration update failed",
295 e.getTargetException());
296 }
297 } catch (NoSuchMethodException e) {
298 // if the method does not exist, ignore it
299 } catch (Throwable t) {
300 // wrap any other exception as a ConfigurationException.
301 throw new ConfigurationException(null,
302 "Configuration update failed", t);
303 }
304 }
305 }
306 }
307 }
308
309 private synchronized void createMetaTypeImpl() {
310 if (m_metaType == null) {
311 m_metaType = new MetaTypeProviderImpl(m_pid, m_context, m_logger, this, null);
312 }
313 }
314
315 private void logConfigurationException(ConfigurationException e) {
316 if (m_logger != null) {
317 m_logger.log(Logger.LOG_ERROR, "Got exception while handling configuration update for pid " + m_pid, e);
318 }
319 }
320}