blob: 3ffb0d459dcfc5494ca65e7ba6b439db0969cd96 [file] [log] [blame]
Marcel Offermansa962bc92009-11-21 17:59:33 +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 */
Pierre De Ropbd642a62009-12-04 22:50:31 +000019package org.apache.felix.dm.impl.dependencies;
Marcel Offermanse14b3422009-11-25 23:04:32 +000020
Marcel Offermansa962bc92009-11-21 17:59:33 +000021import java.lang.reflect.InvocationTargetException;
Marcel Offermanscae61362009-12-01 08:37:10 +000022import java.util.ArrayList;
Marcel Offermansa962bc92009-11-21 17:59:33 +000023import java.util.Dictionary;
Marcel Offermanscae61362009-12-01 08:37:10 +000024import java.util.HashSet;
25import java.util.List;
Marcel Offermansa962bc92009-11-21 17:59:33 +000026import java.util.Properties;
Marcel Offermanscae61362009-12-01 08:37:10 +000027import java.util.Set;
Marcel Offermansa962bc92009-11-21 17:59:33 +000028
Marcel Offermans8b93efa2010-07-02 18:27:21 +000029import org.apache.felix.dm.ConfigurationDependency;
30import org.apache.felix.dm.Dependency;
Marcel Offermans3d921212010-08-09 13:37:02 +000031import org.apache.felix.dm.DependencyActivation;
32import org.apache.felix.dm.DependencyService;
Marcel Offermans8b93efa2010-07-02 18:27:21 +000033import org.apache.felix.dm.PropertyMetaData;
34import org.apache.felix.dm.ServiceComponentDependency;
Marcel Offermans157e7112010-06-18 10:40:08 +000035import org.apache.felix.dm.impl.InvocationUtil;
Pierre De Ropbd642a62009-12-04 22:50:31 +000036import org.apache.felix.dm.impl.Logger;
Pierre De Ropa0204f52010-03-06 22:23:57 +000037import org.apache.felix.dm.impl.metatype.MetaTypeProviderImpl;
Marcel Offermansa962bc92009-11-21 17:59:33 +000038import org.osgi.framework.BundleContext;
39import org.osgi.framework.Constants;
40import org.osgi.framework.ServiceRegistration;
41import org.osgi.service.cm.ConfigurationException;
42import org.osgi.service.cm.ManagedService;
Marcel Offermans26081d32010-07-12 12:43:42 +000043import org.osgi.service.log.LogService;
Marcel Offermansa962bc92009-11-21 17:59:33 +000044
45/**
46 * Configuration dependency that can track the availability of a (valid) configuration.
47 * To use it, specify a PID for the configuration. The dependency is always required,
48 * because if it is not, it does not make sense to use the dependency manager. In that
49 * scenario, simply register your service as a <code>ManagedService(Factory)</code> and
50 * handle everything yourself. Also, only managed services are supported, not factories.
51 * There are a couple of things you need to be aware of when implementing the
52 * <code>updated(Dictionary)</code> method:
53 * <ul>
54 * <li>Make sure it throws a <code>ConfigurationException</code> when you get a
55 * configuration that is invalid. In this case, the dependency will not change:
56 * if it was not available, it will still not be. If it was available, it will
57 * remain available and implicitly assume you keep working with your old
58 * configuration.</li>
59 * <li>This method will be called before all required dependencies are available.
60 * Make sure you do not depend on these to parse your settings.</li>
61 * </ul>
62 *
63 * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
64 */
Marcel Offermans61a81142010-04-02 15:16:50 +000065public class ConfigurationDependencyImpl extends DependencyBase implements ConfigurationDependency, ManagedService, ServiceComponentDependency, DependencyActivation {
Marcel Offermansa962bc92009-11-21 17:59:33 +000066 private BundleContext m_context;
67 private String m_pid;
68 private ServiceRegistration m_registration;
Marcel Offermanscae61362009-12-01 08:37:10 +000069 protected List m_services = new ArrayList();
Marcel Offermansa962bc92009-11-21 17:59:33 +000070 private Dictionary m_settings;
Marcel Offermansa962bc92009-11-21 17:59:33 +000071 private String m_callback;
Marcel Offermanscae61362009-12-01 08:37:10 +000072 private boolean m_isStarted;
73 private final Set m_updateInvokedCache = new HashSet();
Pierre De Ropa0204f52010-03-06 22:23:57 +000074 private MetaTypeProviderImpl m_metaType;
Marcel Offermans26081d32010-07-12 12:43:42 +000075 private boolean m_propagate;
76 private Object m_propagateCallbackInstance;
77 private String m_propagateCallbackMethod;
Marcel Offermansa962bc92009-11-21 17:59:33 +000078
Pierre De Ropbd642a62009-12-04 22:50:31 +000079 public ConfigurationDependencyImpl(BundleContext context, Logger logger) {
Marcel Offermans61a81142010-04-02 15:16:50 +000080 super(logger);
Marcel Offermansa962bc92009-11-21 17:59:33 +000081 m_context = context;
Marcel Offermansa962bc92009-11-21 17:59:33 +000082 }
83
Marcel Offermansb1959f42010-07-01 12:23:51 +000084 public ConfigurationDependencyImpl(ConfigurationDependencyImpl prototype) {
85 super(prototype);
86 m_context = prototype.m_context;
87 m_pid = prototype.m_pid;
88 m_propagate = prototype.m_propagate;
89 m_callback = prototype.m_callback;
90 }
91
92 public Dependency createCopy() {
93 return new ConfigurationDependencyImpl(this);
94 }
95
Marcel Offermansa962bc92009-11-21 17:59:33 +000096 public synchronized boolean isAvailable() {
97 return m_settings != null;
98 }
99
100 /**
101 * Will always return <code>true</code> as optional configuration dependencies
102 * do not make sense. You might as well just implement <code>ManagedService</code>
103 * yourself in those cases.
104 */
105 public boolean isRequired() {
106 return true;
107 }
108
109 /**
110 * Returns <code>true</code> when configuration properties should be propagated
111 * as service properties.
112 */
113 public boolean isPropagated() {
114 return m_propagate;
115 }
116
Marcel Offermans61a81142010-04-02 15:16:50 +0000117 public ConfigurationDependency setInstanceBound(boolean isInstanceBound) {
118 setIsInstanceBound(isInstanceBound);
119 return this;
120 }
121
122
Marcel Offermansa962bc92009-11-21 17:59:33 +0000123 public Dictionary getConfiguration() {
124 return m_settings;
125 }
126
Marcel Offermanse14b3422009-11-25 23:04:32 +0000127 public void start(DependencyService service) {
Marcel Offermanscae61362009-12-01 08:37:10 +0000128 boolean needsStarting = false;
129 synchronized (this) {
130 m_services.add(service);
131 if (!m_isStarted) {
132 m_isStarted = true;
133 needsStarting = true;
134 }
135 }
136 if (needsStarting) {
137 Properties props = new Properties();
138 props.put(Constants.SERVICE_PID, m_pid);
Pierre De Ropa0204f52010-03-06 22:23:57 +0000139 ManagedService ms = this;
140 if (m_metaType != null) {
141 ms = m_metaType;
142 }
143 m_registration = m_context.registerService(ManagedService.class.getName(), ms, props);
Marcel Offermanscae61362009-12-01 08:37:10 +0000144 }
Marcel Offermansa962bc92009-11-21 17:59:33 +0000145 }
146
Marcel Offermanse14b3422009-11-25 23:04:32 +0000147 public void stop(DependencyService service) {
Marcel Offermanscae61362009-12-01 08:37:10 +0000148 boolean needsStopping = false;
149 synchronized (this) {
150 if (m_services.size() == 1 && m_services.contains(service)) {
151 m_isStarted = false;
152 needsStopping = true;
153 }
154 }
155 if (needsStopping) {
156 m_registration.unregister();
157 m_registration = null;
158 m_services.remove(service);
159 }
Marcel Offermansa962bc92009-11-21 17:59:33 +0000160 }
161
Pierre De Ropbd642a62009-12-04 22:50:31 +0000162 public ConfigurationDependency setCallback(String callback) {
Marcel Offermansa962bc92009-11-21 17:59:33 +0000163 m_callback = callback;
164 return this;
165 }
166
167 public void updated(Dictionary settings) throws ConfigurationException {
Marcel Offermanscac806f2009-12-22 22:16:43 +0000168 synchronized (m_updateInvokedCache) {
169 m_updateInvokedCache.clear();
170 }
Marcel Offermanscae61362009-12-01 08:37:10 +0000171 Dictionary oldSettings = null;
172 synchronized (this) {
173 oldSettings = m_settings;
174 }
175
176 if (oldSettings == null && settings == null) {
177 // CM has started but our configuration is not still present in the CM database: ignore
178 return;
179 }
180
181 Object[] services = m_services.toArray();
182 for (int i = 0; i < services.length; i++) {
183 DependencyService ds = (DependencyService) services[i];
184 // if non-null settings come in, we have to instantiate the service and
185 // apply these settings
186 ds.initService();
187 Object service = ds.getService();
188
189 if (service != null) {
190 invokeUpdate(ds, service, settings);
191 }
192 else {
193 m_logger.log(Logger.LOG_ERROR, "Service " + ds + " with configuration dependency " + this + " could not be instantiated.");
194 return;
195 }
196 }
197
Marcel Offermansa962bc92009-11-21 17:59:33 +0000198 synchronized (this) {
Marcel Offermanscae61362009-12-01 08:37:10 +0000199 m_settings = settings;
Marcel Offermansa962bc92009-11-21 17:59:33 +0000200 }
201
Marcel Offermanscae61362009-12-01 08:37:10 +0000202 for (int i = 0; i < services.length; i++) {
203 DependencyService ds = (DependencyService) services[i];
204 // If these settings did not cause a configuration exception, we determine if they have
205 // caused the dependency state to change
206 if ((oldSettings == null) && (settings != null)) {
207 ds.dependencyAvailable(this);
208 }
209 if ((oldSettings != null) && (settings == null)) {
210 ds.dependencyUnavailable(this);
211 }
212 if ((oldSettings != null) && (settings != null)) {
213 ds.dependencyChanged(this);
214 }
215 }
216 }
217
218 public void invokeUpdate(DependencyService ds, Object service, Dictionary settings) throws ConfigurationException {
Marcel Offermanscac806f2009-12-22 22:16:43 +0000219 boolean wasAdded;
220 synchronized (m_updateInvokedCache) {
221 wasAdded = m_updateInvokedCache.add(ds);
222 }
223 if (wasAdded) {
Marcel Offermanscae61362009-12-01 08:37:10 +0000224 String callback = (m_callback == null) ? "updated" : m_callback;
Marcel Offermanscae61362009-12-01 08:37:10 +0000225 try {
Marcel Offermanscae61362009-12-01 08:37:10 +0000226 // if exception is thrown here, what does that mean for the
227 // state of this dependency? how smart do we want to be??
228 // it's okay like this, if the new settings contain errors, we
229 // remain in the state we were, assuming that any error causes
230 // the "old" configuration to stay in effect.
231 // CM will log any thrown exceptions.
Marcel Offermans157e7112010-06-18 10:40:08 +0000232 InvocationUtil.invokeMethod(service, service.getClass(), callback, new Class[][] {{ Dictionary.class }}, new Object[][] {{ settings }}, false);
Marcel Offermanscae61362009-12-01 08:37:10 +0000233 }
234 catch (InvocationTargetException e) {
Marcel Offermansa962bc92009-11-21 17:59:33 +0000235 // The component has thrown an exception during it's callback invocation.
236 if (e.getTargetException() instanceof ConfigurationException) {
237 // the callback threw an OSGi ConfigurationException: just re-throw it.
238 throw (ConfigurationException) e.getTargetException();
239 }
240 else {
241 // wrap the callback exception into a ConfigurationException.
Marcel Offermanscae61362009-12-01 08:37:10 +0000242 throw new ConfigurationException(null, "Service " + ds + " with " + this.toString() + " could not be updated", e.getTargetException());
Marcel Offermansa962bc92009-11-21 17:59:33 +0000243 }
244 }
Pierre De Rop1f064782010-05-24 20:02:37 +0000245 catch (NoSuchMethodException e) {
246 // if the method does not exist, ignore it
247 }
Marcel Offermansa962bc92009-11-21 17:59:33 +0000248 catch (Throwable t) {
249 // wrap any other exception as a ConfigurationException.
Marcel Offermanscae61362009-12-01 08:37:10 +0000250 throw new ConfigurationException(null, "Service " + ds + " with " + this.toString() + " could not be updated", t);
Marcel Offermansa962bc92009-11-21 17:59:33 +0000251 }
252 }
Marcel Offermanscae61362009-12-01 08:37:10 +0000253 }
Marcel Offermansa962bc92009-11-21 17:59:33 +0000254
255 /**
256 * Sets the <code>service.pid</code> of the configuration you
257 * are depending on.
258 */
259 public ConfigurationDependency setPid(String pid) {
260 ensureNotActive();
261 m_pid = pid;
262 return this;
263 }
264
265 /**
266 * Sets propagation of the configuration properties to the service
267 * properties. Any additional service properties specified directly
268 * are merged with these.
269 */
270 public ConfigurationDependency setPropagate(boolean propagate) {
271 ensureNotActive();
272 m_propagate = propagate;
273 return this;
274 }
275
276 private void ensureNotActive() {
Marcel Offermanscae61362009-12-01 08:37:10 +0000277 if (m_services != null && m_services.size() > 0) {
Marcel Offermansa962bc92009-11-21 17:59:33 +0000278 throw new IllegalStateException("Cannot modify state while active.");
279 }
280 }
281
282 public String toString() {
283 return "ConfigurationDependency[" + m_pid + "]";
284 }
285
286 public String getName() {
287 return m_pid;
288 }
289
290 public int getState() {
291 return (isAvailable() ? 1 : 0) + (isRequired() ? 2 : 0);
292 }
293
294 public String getType() {
295 return "configuration";
296 }
Marcel Offermans001db052009-12-08 08:58:40 +0000297
298 public Object getAutoConfigInstance() {
299 return getConfiguration();
300 }
301
302 public String getAutoConfigName() {
303 // TODO Auto-generated method stub
304 return null;
305 }
306
307 public Class getAutoConfigType() {
308 return Dictionary.class;
309 }
310
311 public void invokeAdded(DependencyService service) {
312 try {
313 invokeUpdate(service, service.getService(), getConfiguration());
314 }
315 catch (ConfigurationException e) {
316 // if this happens, it's definitely an inconsistency, since we
317 // asked the instance the same question before (if this is a
318 // valid configuration) and then it was
319 e.printStackTrace();
320 }
321 }
322
323 public void invokeRemoved(DependencyService service) {
324 // TODO Auto-generated method stub
325 }
326
327 public boolean isAutoConfig() {
328 // TODO Auto-generated method stub
329 return false;
330 }
Marcel Offermans117aa2f2009-12-10 09:48:17 +0000331
Marcel Offermans26081d32010-07-12 12:43:42 +0000332 public ConfigurationDependency setPropagate(Object instance, String method) {
333 setPropagate(instance != null && method != null);
334 m_propagateCallbackInstance = instance;
335 m_propagateCallbackMethod = method;
336 return this;
337 }
338
Marcel Offermans117aa2f2009-12-10 09:48:17 +0000339 public Dictionary getProperties() {
Marcel Offermans26081d32010-07-12 12:43:42 +0000340 Dictionary config = getConfiguration();
341 if (config != null) {
342 if (m_propagateCallbackInstance != null && m_propagateCallbackMethod != null) {
343 try {
344 return (Dictionary) InvocationUtil.invokeCallbackMethod(m_propagateCallbackInstance, m_propagateCallbackMethod, new Class[][] {{ Dictionary.class }, {}}, new Object[][] {{ config }, {}});
345 }
346 catch (InvocationTargetException e) {
347 m_logger.log(LogService.LOG_WARNING, "Exception while invoking callback method", e.getCause());
348 }
349 catch (Exception e) {
350 m_logger.log(LogService.LOG_WARNING, "Exception while trying to invoke callback method", e);
351 }
352 throw new IllegalStateException("Could not invoke callback");
353 }
354 else {
355 return config;
356 }
357 }
358 else {
359 throw new IllegalStateException("cannot find configuration");
360 }
Marcel Offermans117aa2f2009-12-10 09:48:17 +0000361 }
Pierre De Ropa0204f52010-03-06 22:23:57 +0000362
363 public BundleContext getBundleContext() {
364 return m_context;
365 }
366
367 public Logger getLogger() {
368 return m_logger;
369 }
370
371 public ConfigurationDependency add(PropertyMetaData properties)
372 {
373 createMetaTypeImpl();
374 m_metaType.add(properties);
375 return this;
376 }
377
378 public ConfigurationDependency setDescription(String description)
379 {
380 createMetaTypeImpl();
381 m_metaType.setDescription(description);
382 return this;
383 }
384
385 public ConfigurationDependency setHeading(String heading)
386 {
387 createMetaTypeImpl();
388 m_metaType.setName(heading);
389 return this;
390 }
391
392 public ConfigurationDependency setLocalization(String path)
393 {
394 createMetaTypeImpl();
395 m_metaType.setLocalization(path);
396 return this;
397 }
398
399 private synchronized void createMetaTypeImpl() {
400 if (m_metaType == null) {
Pierre De Rop51b33372010-04-25 22:24:24 +0000401 m_metaType = new MetaTypeProviderImpl(getName(), getBundleContext(), getLogger(), this, null);
Pierre De Ropa0204f52010-03-06 22:23:57 +0000402 }
403 }
Marcel Offermansa962bc92009-11-21 17:59:33 +0000404}