blob: a0791f3246014d96ac4bb286998c1cf20f180270 [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
Pierre De Ropbd642a62009-12-04 22:50:31 +000029import org.apache.felix.dm.dependencies.ConfigurationDependency;
Marcel Offermansb1959f42010-07-01 12:23:51 +000030import org.apache.felix.dm.dependencies.Dependency;
Pierre De Ropa0204f52010-03-06 22:23:57 +000031import org.apache.felix.dm.dependencies.PropertyMetaData;
Marcel Offermans157e7112010-06-18 10:40:08 +000032import org.apache.felix.dm.impl.InvocationUtil;
Pierre De Ropbd642a62009-12-04 22:50:31 +000033import org.apache.felix.dm.impl.Logger;
Pierre De Ropa0204f52010-03-06 22:23:57 +000034import org.apache.felix.dm.impl.metatype.MetaTypeProviderImpl;
Pierre De Ropbd642a62009-12-04 22:50:31 +000035import org.apache.felix.dm.management.ServiceComponentDependency;
Marcel Offermansa962bc92009-11-21 17:59:33 +000036import org.osgi.framework.BundleContext;
37import org.osgi.framework.Constants;
38import org.osgi.framework.ServiceRegistration;
39import org.osgi.service.cm.ConfigurationException;
40import org.osgi.service.cm.ManagedService;
41
42/**
43 * Configuration dependency that can track the availability of a (valid) configuration.
44 * To use it, specify a PID for the configuration. The dependency is always required,
45 * because if it is not, it does not make sense to use the dependency manager. In that
46 * scenario, simply register your service as a <code>ManagedService(Factory)</code> and
47 * handle everything yourself. Also, only managed services are supported, not factories.
48 * There are a couple of things you need to be aware of when implementing the
49 * <code>updated(Dictionary)</code> method:
50 * <ul>
51 * <li>Make sure it throws a <code>ConfigurationException</code> when you get a
52 * configuration that is invalid. In this case, the dependency will not change:
53 * if it was not available, it will still not be. If it was available, it will
54 * remain available and implicitly assume you keep working with your old
55 * configuration.</li>
56 * <li>This method will be called before all required dependencies are available.
57 * Make sure you do not depend on these to parse your settings.</li>
58 * </ul>
59 *
60 * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
61 */
Marcel Offermans61a81142010-04-02 15:16:50 +000062public class ConfigurationDependencyImpl extends DependencyBase implements ConfigurationDependency, ManagedService, ServiceComponentDependency, DependencyActivation {
Marcel Offermansa962bc92009-11-21 17:59:33 +000063 private BundleContext m_context;
64 private String m_pid;
65 private ServiceRegistration m_registration;
Marcel Offermanscae61362009-12-01 08:37:10 +000066 protected List m_services = new ArrayList();
Marcel Offermansa962bc92009-11-21 17:59:33 +000067 private Dictionary m_settings;
68 private boolean m_propagate;
Marcel Offermansa962bc92009-11-21 17:59:33 +000069 private String m_callback;
Marcel Offermanscae61362009-12-01 08:37:10 +000070 private boolean m_isStarted;
71 private final Set m_updateInvokedCache = new HashSet();
Pierre De Ropa0204f52010-03-06 22:23:57 +000072 private MetaTypeProviderImpl m_metaType;
Marcel Offermansa962bc92009-11-21 17:59:33 +000073
Pierre De Ropbd642a62009-12-04 22:50:31 +000074 public ConfigurationDependencyImpl(BundleContext context, Logger logger) {
Marcel Offermans61a81142010-04-02 15:16:50 +000075 super(logger);
Marcel Offermansa962bc92009-11-21 17:59:33 +000076 m_context = context;
Marcel Offermansa962bc92009-11-21 17:59:33 +000077 }
78
Marcel Offermansb1959f42010-07-01 12:23:51 +000079 public ConfigurationDependencyImpl(ConfigurationDependencyImpl prototype) {
80 super(prototype);
81 m_context = prototype.m_context;
82 m_pid = prototype.m_pid;
83 m_propagate = prototype.m_propagate;
84 m_callback = prototype.m_callback;
85 }
86
87 public Dependency createCopy() {
88 return new ConfigurationDependencyImpl(this);
89 }
90
Marcel Offermansa962bc92009-11-21 17:59:33 +000091 public synchronized boolean isAvailable() {
92 return m_settings != null;
93 }
94
95 /**
96 * Will always return <code>true</code> as optional configuration dependencies
97 * do not make sense. You might as well just implement <code>ManagedService</code>
98 * yourself in those cases.
99 */
100 public boolean isRequired() {
101 return true;
102 }
103
104 /**
105 * Returns <code>true</code> when configuration properties should be propagated
106 * as service properties.
107 */
108 public boolean isPropagated() {
109 return m_propagate;
110 }
111
Marcel Offermans61a81142010-04-02 15:16:50 +0000112 public ConfigurationDependency setInstanceBound(boolean isInstanceBound) {
113 setIsInstanceBound(isInstanceBound);
114 return this;
115 }
116
117
Marcel Offermansa962bc92009-11-21 17:59:33 +0000118 public Dictionary getConfiguration() {
119 return m_settings;
120 }
121
Marcel Offermanse14b3422009-11-25 23:04:32 +0000122 public void start(DependencyService service) {
Marcel Offermanscae61362009-12-01 08:37:10 +0000123 boolean needsStarting = false;
124 synchronized (this) {
125 m_services.add(service);
126 if (!m_isStarted) {
127 m_isStarted = true;
128 needsStarting = true;
129 }
130 }
131 if (needsStarting) {
132 Properties props = new Properties();
133 props.put(Constants.SERVICE_PID, m_pid);
Pierre De Ropa0204f52010-03-06 22:23:57 +0000134 ManagedService ms = this;
135 if (m_metaType != null) {
136 ms = m_metaType;
137 }
138 m_registration = m_context.registerService(ManagedService.class.getName(), ms, props);
Marcel Offermanscae61362009-12-01 08:37:10 +0000139 }
Marcel Offermansa962bc92009-11-21 17:59:33 +0000140 }
141
Marcel Offermanse14b3422009-11-25 23:04:32 +0000142 public void stop(DependencyService service) {
Marcel Offermanscae61362009-12-01 08:37:10 +0000143 boolean needsStopping = false;
144 synchronized (this) {
145 if (m_services.size() == 1 && m_services.contains(service)) {
146 m_isStarted = false;
147 needsStopping = true;
148 }
149 }
150 if (needsStopping) {
151 m_registration.unregister();
152 m_registration = null;
153 m_services.remove(service);
154 }
Marcel Offermansa962bc92009-11-21 17:59:33 +0000155 }
156
Pierre De Ropbd642a62009-12-04 22:50:31 +0000157 public ConfigurationDependency setCallback(String callback) {
Marcel Offermansa962bc92009-11-21 17:59:33 +0000158 m_callback = callback;
159 return this;
160 }
161
162 public void updated(Dictionary settings) throws ConfigurationException {
Marcel Offermanscac806f2009-12-22 22:16:43 +0000163 synchronized (m_updateInvokedCache) {
164 m_updateInvokedCache.clear();
165 }
Marcel Offermanscae61362009-12-01 08:37:10 +0000166 Dictionary oldSettings = null;
167 synchronized (this) {
168 oldSettings = m_settings;
169 }
170
171 if (oldSettings == null && settings == null) {
172 // CM has started but our configuration is not still present in the CM database: ignore
173 return;
174 }
175
176 Object[] services = m_services.toArray();
177 for (int i = 0; i < services.length; i++) {
178 DependencyService ds = (DependencyService) services[i];
179 // if non-null settings come in, we have to instantiate the service and
180 // apply these settings
181 ds.initService();
182 Object service = ds.getService();
183
184 if (service != null) {
185 invokeUpdate(ds, service, settings);
186 }
187 else {
188 m_logger.log(Logger.LOG_ERROR, "Service " + ds + " with configuration dependency " + this + " could not be instantiated.");
189 return;
190 }
191 }
192
Marcel Offermansa962bc92009-11-21 17:59:33 +0000193 synchronized (this) {
Marcel Offermanscae61362009-12-01 08:37:10 +0000194 m_settings = settings;
Marcel Offermansa962bc92009-11-21 17:59:33 +0000195 }
196
Marcel Offermanscae61362009-12-01 08:37:10 +0000197 for (int i = 0; i < services.length; i++) {
198 DependencyService ds = (DependencyService) services[i];
199 // If these settings did not cause a configuration exception, we determine if they have
200 // caused the dependency state to change
201 if ((oldSettings == null) && (settings != null)) {
202 ds.dependencyAvailable(this);
203 }
204 if ((oldSettings != null) && (settings == null)) {
205 ds.dependencyUnavailable(this);
206 }
207 if ((oldSettings != null) && (settings != null)) {
208 ds.dependencyChanged(this);
209 }
210 }
211 }
212
213 public void invokeUpdate(DependencyService ds, Object service, Dictionary settings) throws ConfigurationException {
Marcel Offermanscac806f2009-12-22 22:16:43 +0000214 boolean wasAdded;
215 synchronized (m_updateInvokedCache) {
216 wasAdded = m_updateInvokedCache.add(ds);
217 }
218 if (wasAdded) {
Marcel Offermanscae61362009-12-01 08:37:10 +0000219 String callback = (m_callback == null) ? "updated" : m_callback;
Marcel Offermanscae61362009-12-01 08:37:10 +0000220 try {
Marcel Offermanscae61362009-12-01 08:37:10 +0000221 // if exception is thrown here, what does that mean for the
222 // state of this dependency? how smart do we want to be??
223 // it's okay like this, if the new settings contain errors, we
224 // remain in the state we were, assuming that any error causes
225 // the "old" configuration to stay in effect.
226 // CM will log any thrown exceptions.
Marcel Offermans157e7112010-06-18 10:40:08 +0000227 InvocationUtil.invokeMethod(service, service.getClass(), callback, new Class[][] {{ Dictionary.class }}, new Object[][] {{ settings }}, false);
Marcel Offermanscae61362009-12-01 08:37:10 +0000228 }
229 catch (InvocationTargetException e) {
Marcel Offermansa962bc92009-11-21 17:59:33 +0000230 // The component has thrown an exception during it's callback invocation.
231 if (e.getTargetException() instanceof ConfigurationException) {
232 // the callback threw an OSGi ConfigurationException: just re-throw it.
233 throw (ConfigurationException) e.getTargetException();
234 }
235 else {
236 // wrap the callback exception into a ConfigurationException.
Marcel Offermanscae61362009-12-01 08:37:10 +0000237 throw new ConfigurationException(null, "Service " + ds + " with " + this.toString() + " could not be updated", e.getTargetException());
Marcel Offermansa962bc92009-11-21 17:59:33 +0000238 }
239 }
Pierre De Rop1f064782010-05-24 20:02:37 +0000240 catch (NoSuchMethodException e) {
241 // if the method does not exist, ignore it
242 }
Marcel Offermansa962bc92009-11-21 17:59:33 +0000243 catch (Throwable t) {
244 // wrap any other exception as a ConfigurationException.
Marcel Offermanscae61362009-12-01 08:37:10 +0000245 throw new ConfigurationException(null, "Service " + ds + " with " + this.toString() + " could not be updated", t);
Marcel Offermansa962bc92009-11-21 17:59:33 +0000246 }
247 }
Marcel Offermanscae61362009-12-01 08:37:10 +0000248 }
Marcel Offermansa962bc92009-11-21 17:59:33 +0000249
250 /**
251 * Sets the <code>service.pid</code> of the configuration you
252 * are depending on.
253 */
254 public ConfigurationDependency setPid(String pid) {
255 ensureNotActive();
256 m_pid = pid;
257 return this;
258 }
259
260 /**
261 * Sets propagation of the configuration properties to the service
262 * properties. Any additional service properties specified directly
263 * are merged with these.
264 */
265 public ConfigurationDependency setPropagate(boolean propagate) {
266 ensureNotActive();
267 m_propagate = propagate;
268 return this;
269 }
270
271 private void ensureNotActive() {
Marcel Offermanscae61362009-12-01 08:37:10 +0000272 if (m_services != null && m_services.size() > 0) {
Marcel Offermansa962bc92009-11-21 17:59:33 +0000273 throw new IllegalStateException("Cannot modify state while active.");
274 }
275 }
276
277 public String toString() {
278 return "ConfigurationDependency[" + m_pid + "]";
279 }
280
281 public String getName() {
282 return m_pid;
283 }
284
285 public int getState() {
286 return (isAvailable() ? 1 : 0) + (isRequired() ? 2 : 0);
287 }
288
289 public String getType() {
290 return "configuration";
291 }
Marcel Offermans001db052009-12-08 08:58:40 +0000292
293 public Object getAutoConfigInstance() {
294 return getConfiguration();
295 }
296
297 public String getAutoConfigName() {
298 // TODO Auto-generated method stub
299 return null;
300 }
301
302 public Class getAutoConfigType() {
303 return Dictionary.class;
304 }
305
306 public void invokeAdded(DependencyService service) {
307 try {
308 invokeUpdate(service, service.getService(), getConfiguration());
309 }
310 catch (ConfigurationException e) {
311 // if this happens, it's definitely an inconsistency, since we
312 // asked the instance the same question before (if this is a
313 // valid configuration) and then it was
314 e.printStackTrace();
315 }
316 }
317
318 public void invokeRemoved(DependencyService service) {
319 // TODO Auto-generated method stub
320 }
321
322 public boolean isAutoConfig() {
323 // TODO Auto-generated method stub
324 return false;
325 }
Marcel Offermans117aa2f2009-12-10 09:48:17 +0000326
327 public Dictionary getProperties() {
328 return getConfiguration();
329 }
Pierre De Ropa0204f52010-03-06 22:23:57 +0000330
331 public BundleContext getBundleContext() {
332 return m_context;
333 }
334
335 public Logger getLogger() {
336 return m_logger;
337 }
338
339 public ConfigurationDependency add(PropertyMetaData properties)
340 {
341 createMetaTypeImpl();
342 m_metaType.add(properties);
343 return this;
344 }
345
346 public ConfigurationDependency setDescription(String description)
347 {
348 createMetaTypeImpl();
349 m_metaType.setDescription(description);
350 return this;
351 }
352
353 public ConfigurationDependency setHeading(String heading)
354 {
355 createMetaTypeImpl();
356 m_metaType.setName(heading);
357 return this;
358 }
359
360 public ConfigurationDependency setLocalization(String path)
361 {
362 createMetaTypeImpl();
363 m_metaType.setLocalization(path);
364 return this;
365 }
366
367 private synchronized void createMetaTypeImpl() {
368 if (m_metaType == null) {
Pierre De Rop51b33372010-04-25 22:24:24 +0000369 m_metaType = new MetaTypeProviderImpl(getName(), getBundleContext(), getLogger(), this, null);
Pierre De Ropa0204f52010-03-06 22:23:57 +0000370 }
371 }
Marcel Offermansa962bc92009-11-21 17:59:33 +0000372}