blob: f7ac01b142e9b3aac76b50e8e75abd1f6d328ca9 [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 */
19package org.apache.felix.dependencymanager;
20import java.lang.reflect.InvocationTargetException;
21import java.lang.reflect.Method;
22import java.util.Dictionary;
23import java.util.Properties;
24
25import org.osgi.framework.BundleContext;
26import org.osgi.framework.Constants;
27import org.osgi.framework.ServiceRegistration;
28import org.osgi.service.cm.ConfigurationException;
29import org.osgi.service.cm.ManagedService;
30
31/**
32 * Configuration dependency that can track the availability of a (valid) configuration.
33 * To use it, specify a PID for the configuration. The dependency is always required,
34 * because if it is not, it does not make sense to use the dependency manager. In that
35 * scenario, simply register your service as a <code>ManagedService(Factory)</code> and
36 * handle everything yourself. Also, only managed services are supported, not factories.
37 * There are a couple of things you need to be aware of when implementing the
38 * <code>updated(Dictionary)</code> method:
39 * <ul>
40 * <li>Make sure it throws a <code>ConfigurationException</code> when you get a
41 * configuration that is invalid. In this case, the dependency will not change:
42 * if it was not available, it will still not be. If it was available, it will
43 * remain available and implicitly assume you keep working with your old
44 * configuration.</li>
45 * <li>This method will be called before all required dependencies are available.
46 * Make sure you do not depend on these to parse your settings.</li>
47 * </ul>
48 *
49 * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
50 */
51public class ConfigurationDependency implements Dependency, ManagedService, ServiceComponentDependency {
52 private BundleContext m_context;
53 private String m_pid;
54 private ServiceRegistration m_registration;
55 private volatile Service m_service;
56 private Dictionary m_settings;
57 private boolean m_propagate;
58 private final Logger m_logger;
59 private String m_callback;
60
61 public ConfigurationDependency(BundleContext context, Logger logger) {
62 m_context = context;
63 m_logger = logger;
64 }
65
66 public synchronized boolean isAvailable() {
67 return m_settings != null;
68 }
69
70 /**
71 * Will always return <code>true</code> as optional configuration dependencies
72 * do not make sense. You might as well just implement <code>ManagedService</code>
73 * yourself in those cases.
74 */
75 public boolean isRequired() {
76 return true;
77 }
78
79 /**
80 * Returns <code>true</code> when configuration properties should be propagated
81 * as service properties.
82 */
83 public boolean isPropagated() {
84 return m_propagate;
85 }
86
87 public Dictionary getConfiguration() {
88 return m_settings;
89 }
90
91 public void start(Service service) {
92 m_service = service;
93 Properties props = new Properties();
94 props.put(Constants.SERVICE_PID, m_pid);
95 m_registration = m_context.registerService(ManagedService.class.getName(), this, props);
96 }
97
98 public void stop(Service service) {
99 m_registration.unregister();
100 m_service = null;
101 }
102
103 public Dependency setCallback(String callback) {
104 m_callback = callback;
105 return this;
106 }
107
108 public void updated(Dictionary settings) throws ConfigurationException {
109 // if non-null settings come in, we have to instantiate the service and
110 // apply these settings
111 ((ServiceImpl) m_service).initService();
112 Object service = m_service.getService();
113
114 Dictionary oldSettings = null;
115 synchronized (this) {
116 oldSettings = m_settings;
117 }
118
119 if (oldSettings == null && settings == null) {
120 // CM has started but our configuration is not still present in the CM database: ignore
121 return;
122 }
123
124 if (service != null) {
125 String callback = (m_callback == null) ? "updated" : m_callback;
126 Method m;
127 try {
128 m = service.getClass().getDeclaredMethod(callback, new Class[] { Dictionary.class });
129 m.setAccessible(true);
130 // if exception is thrown here, what does that mean for the
131 // state of this dependency? how smart do we want to be??
132 // it's okay like this, if the new settings contain errors, we
133 // remain in the state we were, assuming that any error causes
134 // the "old" configuration to stay in effect.
135 // CM will log any thrown exceptions.
136 m.invoke(service, new Object[] { settings });
137 }
138 catch (InvocationTargetException e) {
139 // The component has thrown an exception during it's callback invocation.
140 if (e.getTargetException() instanceof ConfigurationException) {
141 // the callback threw an OSGi ConfigurationException: just re-throw it.
142 throw (ConfigurationException) e.getTargetException();
143 }
144 else {
145 // wrap the callback exception into a ConfigurationException.
146 throw new ConfigurationException(null, "Service " + m_service + " with " + this.toString() + " could not be updated", e.getTargetException());
147 }
148 }
149 catch (Throwable t) {
150 // wrap any other exception as a ConfigurationException.
151 throw new ConfigurationException(null, "Service " + m_service + " with " + this.toString() + " could not be updated", t);
152 }
153 }
154 else {
155 m_logger.log(Logger.LOG_ERROR, "Service " + m_service + " with configuration dependency " + this + " could not be instantiated.");
156 return;
157 }
158
159 // If these settings did not cause a configuration exception, we determine if they have
160 // caused the dependency state to change
161 synchronized (this) {
162 m_settings = settings;
163 }
164
165 if ((oldSettings == null) && (settings != null)) {
166 m_service.dependencyAvailable(this);
167 }
168 if ((oldSettings != null) && (settings == null)) {
169 m_service.dependencyUnavailable(this);
170 }
171 if ((oldSettings != null) && (settings != null)) {
172 m_service.dependencyChanged(this);
173 }
174 }
175
176 /**
177 * Sets the <code>service.pid</code> of the configuration you
178 * are depending on.
179 */
180 public ConfigurationDependency setPid(String pid) {
181 ensureNotActive();
182 m_pid = pid;
183 return this;
184 }
185
186 /**
187 * Sets propagation of the configuration properties to the service
188 * properties. Any additional service properties specified directly
189 * are merged with these.
190 */
191 public ConfigurationDependency setPropagate(boolean propagate) {
192 ensureNotActive();
193 m_propagate = propagate;
194 return this;
195 }
196
197 private void ensureNotActive() {
198 if (m_service != null) {
199 throw new IllegalStateException("Cannot modify state while active.");
200 }
201 }
202
203 public String toString() {
204 return "ConfigurationDependency[" + m_pid + "]";
205 }
206
207 public String getName() {
208 return m_pid;
209 }
210
211 public int getState() {
212 return (isAvailable() ? 1 : 0) + (isRequired() ? 2 : 0);
213 }
214
215 public String getType() {
216 return "configuration";
217 }
218}