blob: 4d5f507704a3fa2508becf53ab8264822703fdfb [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 Ropae4a5db2009-12-04 22:08:05 +000019package org.apache.felix.dm;
Marcel Offermansa962bc92009-11-21 17:59:33 +000020
21import java.util.ArrayList;
22import java.util.Collections;
23import java.util.List;
24
Pierre De Ropae4a5db2009-12-04 22:08:05 +000025import org.apache.felix.dm.dependencies.BundleDependency;
26import org.apache.felix.dm.dependencies.ConfigurationDependency;
Pierre De Ropa0204f52010-03-06 22:23:57 +000027import org.apache.felix.dm.dependencies.PropertyMetaData;
Pierre De Ropae4a5db2009-12-04 22:08:05 +000028import org.apache.felix.dm.dependencies.ResourceDependency;
29import org.apache.felix.dm.dependencies.ServiceDependency;
30import org.apache.felix.dm.dependencies.TemporalServiceDependency;
Pierre De Rop34231582010-05-23 20:05:16 +000031import org.apache.felix.dm.impl.AdapterServiceImpl;
Pierre De Rop19476fe2010-05-23 08:13:58 +000032import org.apache.felix.dm.impl.AspectServiceImpl;
Pierre De Rop13dd63d2010-05-23 21:58:28 +000033import org.apache.felix.dm.impl.BundleAdapterServiceImpl;
Pierre De Rop3e100372010-05-24 12:43:44 +000034import org.apache.felix.dm.impl.FactoryConfigurationAdapterServiceImpl;
Pierre De Ropae4a5db2009-12-04 22:08:05 +000035import org.apache.felix.dm.impl.Logger;
Pierre De Rop445ddec2010-05-23 21:05:27 +000036import org.apache.felix.dm.impl.ResourceAdapterServiceImpl;
Pierre De Ropae4a5db2009-12-04 22:08:05 +000037import org.apache.felix.dm.impl.ServiceImpl;
38import org.apache.felix.dm.impl.dependencies.BundleDependencyImpl;
39import org.apache.felix.dm.impl.dependencies.ConfigurationDependencyImpl;
40import org.apache.felix.dm.impl.dependencies.ResourceDependencyImpl;
41import org.apache.felix.dm.impl.dependencies.ServiceDependencyImpl;
42import org.apache.felix.dm.impl.dependencies.TemporalServiceDependencyImpl;
Pierre De Ropa0204f52010-03-06 22:23:57 +000043import org.apache.felix.dm.impl.metatype.PropertyMetaDataImpl;
Marcel Offermans4fd903f2009-12-29 09:18:05 +000044import org.apache.felix.dm.resources.Resource;
Pierre De Ropae4a5db2009-12-04 22:08:05 +000045import org.apache.felix.dm.service.Service;
Marcel Offermansb8405a52009-12-22 19:01:31 +000046import org.osgi.framework.BundleContext;
Marcel Offermansa962bc92009-11-21 17:59:33 +000047
48/**
Marcel Offermans4fd903f2009-12-29 09:18:05 +000049 * The dependency manager manages all services and their dependencies. Using
50 * this API you can declare all services and their dependencies. Under normal
51 * circumstances, you get passed an instance of this class through the
52 * <code>DependencyActivatorBase</code> subclass you use as your
53 * <code>BundleActivator</code>, but it is also possible to create your
54 * own instance.
Marcel Offermansa962bc92009-11-21 17:59:33 +000055 *
56 * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
57 */
58public class DependencyManager {
Marcel Offermansad760672010-03-03 15:30:01 +000059 public static final String ASPECT = "org.apache.felix.dependencymanager.aspect";
Marcel Offermansa962bc92009-11-21 17:59:33 +000060 private final BundleContext m_context;
61 private final Logger m_logger;
62 private List m_services = Collections.synchronizedList(new ArrayList());
63
64 /**
Marcel Offermans4fd903f2009-12-29 09:18:05 +000065 * Creates a new dependency manager. You need to supply the
66 * <code>BundleContext</code> to be used by the dependency
67 * manager to register services and communicate with the
68 * framework.
Marcel Offermansa962bc92009-11-21 17:59:33 +000069 *
70 * @param context the bundle context
Marcel Offermansa962bc92009-11-21 17:59:33 +000071 */
Pierre De Ropae4a5db2009-12-04 22:08:05 +000072 public DependencyManager(BundleContext context) {
73 this(context, new Logger(context));
74 }
75
Marcel Offermans4fd903f2009-12-29 09:18:05 +000076 DependencyManager(BundleContext context, Logger logger) {
Marcel Offermansa962bc92009-11-21 17:59:33 +000077 m_context = context;
78 m_logger = logger;
79 }
Pierre De Ropae4a5db2009-12-04 22:08:05 +000080
Marcel Offermansa962bc92009-11-21 17:59:33 +000081 /**
82 * Adds a new service to the dependency manager. After the service was added
83 * it will be started immediately.
84 *
85 * @param service the service to add
86 */
87 public void add(Service service) {
88 m_services.add(service);
89 service.start();
90 }
91
92 /**
93 * Removes a service from the dependency manager. Before the service is removed
94 * it is stopped first.
95 *
96 * @param service the service to remove
97 */
98 public void remove(Service service) {
99 service.stop();
100 m_services.remove(service);
101 }
102
103 /**
104 * Creates a new service.
105 *
106 * @return the new service
107 */
108 public Service createService() {
109 return new ServiceImpl(m_context, this, m_logger);
110 }
111
112 /**
113 * Creates a new service dependency.
114 *
115 * @return the service dependency
116 */
117 public ServiceDependency createServiceDependency() {
Pierre De Ropae4a5db2009-12-04 22:08:05 +0000118 return new ServiceDependencyImpl(m_context, m_logger);
Marcel Offermansa962bc92009-11-21 17:59:33 +0000119 }
120
Marcel Offermans74363c32009-11-23 19:56:08 +0000121 /**
122 * Creates a new temporal service dependency.
123 *
Pierre De Ropc3dfbb72010-05-24 13:16:35 +0000124 * @return a new temporal service dependency
Marcel Offermans74363c32009-11-23 19:56:08 +0000125 */
126 public TemporalServiceDependency createTemporalServiceDependency() {
Pierre De Ropae4a5db2009-12-04 22:08:05 +0000127 return new TemporalServiceDependencyImpl(m_context, m_logger);
Marcel Offermans74363c32009-11-23 19:56:08 +0000128 }
Marcel Offermans2925f172009-12-02 13:03:39 +0000129
130 /**
131 * Creates a new configuration dependency.
132 *
Marcel Offermans4fd903f2009-12-29 09:18:05 +0000133 * @return the configuration dependency
Marcel Offermans2925f172009-12-02 13:03:39 +0000134 */
Marcel Offermansa962bc92009-11-21 17:59:33 +0000135 public ConfigurationDependency createConfigurationDependency() {
Pierre De Ropae4a5db2009-12-04 22:08:05 +0000136 return new ConfigurationDependencyImpl(m_context, m_logger);
Marcel Offermansa962bc92009-11-21 17:59:33 +0000137 }
138
Marcel Offermans2925f172009-12-02 13:03:39 +0000139 /**
Pierre De Ropa0204f52010-03-06 22:23:57 +0000140 * Creates a new configuration property MetaData.
141 * @return a new Configuration property MetaData.
142 */
143 public PropertyMetaData createPropertyMetaData() {
144 return new PropertyMetaDataImpl();
145 }
146
147 /**
Marcel Offermans2925f172009-12-02 13:03:39 +0000148 * Creates a new bundle dependency.
149 *
Pierre De Ropc3dfbb72010-05-24 13:16:35 +0000150 * @return a new BundleDependency instance.
Marcel Offermans2925f172009-12-02 13:03:39 +0000151 */
Marcel Offermansd66c5ce2009-11-26 09:58:44 +0000152 public BundleDependency createBundleDependency() {
Pierre De Ropae4a5db2009-12-04 22:08:05 +0000153 return new BundleDependencyImpl(m_context, m_logger);
Marcel Offermansd66c5ce2009-11-26 09:58:44 +0000154 }
Marcel Offermans2925f172009-12-02 13:03:39 +0000155
156 /**
157 * Creates a new resource dependency.
158 *
Marcel Offermans4fd903f2009-12-29 09:18:05 +0000159 * @return the resource dependency
Marcel Offermans2925f172009-12-02 13:03:39 +0000160 */
161 public ResourceDependency createResourceDependency() {
Pierre De Ropae4a5db2009-12-04 22:08:05 +0000162 return new ResourceDependencyImpl(m_context, m_logger);
Marcel Offermans2925f172009-12-02 13:03:39 +0000163 }
Marcel Offermans80eeafe2009-12-01 22:12:26 +0000164
Marcel Offermans2925f172009-12-02 13:03:39 +0000165 /**
Marcel Offermans4fd903f2009-12-29 09:18:05 +0000166 * Creates a new aspect. The aspect will be applied to any service that
167 * matches the specified interface and filter. For each matching service
168 * an aspect will be created based on the aspect implementation class.
169 * The aspect will be registered with the same interface and properties
170 * as the original service, plus any extra properties you supply here.
171 * It will also inherit all dependencies, and if you declare the original
172 * service as a member it will be injected.
Marcel Offermans2925f172009-12-02 13:03:39 +0000173 *
Pierre De Rop19476fe2010-05-23 08:13:58 +0000174 * <h3>Usage Example</h3>
175 *
Pierre De Ropc3dfbb72010-05-24 13:16:35 +0000176 * <blockquote><pre>
Pierre De Rop19476fe2010-05-23 08:13:58 +0000177 * manager.createAspectService(ExistingService.class, "(foo=bar)", 10, "m_aspect")
178 * .setImplementation(ExistingServiceAspect.class)
Pierre De Ropc3dfbb72010-05-24 13:16:35 +0000179 * .setServiceProperties(new Hashtable() {{ put("additional", "properties"); }});
180 * </pre></blockquote>
Pierre De Rop19476fe2010-05-23 08:13:58 +0000181 *
Marcel Offermans4fd903f2009-12-29 09:18:05 +0000182 * @param serviceInterface the service interface to apply the aspect to
183 * @param serviceFilter the filter condition to use with the service interface
Pierre De Rop19476fe2010-05-23 08:13:58 +0000184 * @param ranking the level used to organize the aspect chain ordering
Pierre De Ropc3dfbb72010-05-24 13:16:35 +0000185 * @param attributeName the aspect implementation field name where to inject original service.
Pierre De Rop19476fe2010-05-23 08:13:58 +0000186 * If null, any field matching the original service will be injected.
Marcel Offermans4fd903f2009-12-29 09:18:05 +0000187 * @return a service that acts as a factory for generating aspects
Marcel Offermans2925f172009-12-02 13:03:39 +0000188 */
Pierre De Rop19476fe2010-05-23 08:13:58 +0000189 public Service createAspectService(Class serviceInterface, String serviceFilter, int ranking, String attributeName) {
190 return new AspectServiceImpl(this, serviceInterface, serviceFilter, ranking, attributeName);
Marcel Offermans80eeafe2009-12-01 22:12:26 +0000191 }
Marcel Offermanse9c13d92010-07-01 14:01:02 +0000192
Marcel Offermans4fd903f2009-12-29 09:18:05 +0000193 /**
194 * Creates a new adapter. The adapter will be applied to any service that
195 * matches the specified interface and filter. For each matching service
196 * an adapter will be created based on the adapter implementation class.
197 * The adapter will be registered with the specified interface and existing properties
198 * from the original service plus any extra properties you supply here.
199 * It will also inherit all dependencies, and if you declare the original
200 * service as a member it will be injected.
201 *
Pierre De Rop34231582010-05-23 20:05:16 +0000202 * <h3>Usage Example</h3>
203 *
Pierre De Ropc3dfbb72010-05-24 13:16:35 +0000204 * <blockquote><pre>
Pierre De Rop34231582010-05-23 20:05:16 +0000205 * manager.createAdapterService(AdapteeService.class, "(foo=bar)")
Pierre De Rop445ddec2010-05-23 21:05:27 +0000206 * // The interface to use when registering adapter
Pierre De Rop34231582010-05-23 20:05:16 +0000207 * .setInterface(AdapterService.class, new Hashtable() {{ put("additional", "properties"); }})
208 * // the implementation of the adapter
209 * .setImplementation(AdapterImpl.class);
Pierre De Ropc3dfbb72010-05-24 13:16:35 +0000210 * </pre></blockquote>
Marcel Offermansd665eaf2010-02-16 15:56:35 +0000211 * @param serviceInterface the service interface to apply the adapter to
Marcel Offermans4fd903f2009-12-29 09:18:05 +0000212 * @param serviceFilter the filter condition to use with the service interface
Marcel Offermans4fd903f2009-12-29 09:18:05 +0000213 * @return a service that acts as a factory for generating adapters
214 */
Pierre De Rop34231582010-05-23 20:05:16 +0000215 public Service createAdapterService(Class serviceInterface, String serviceFilter) {
216 return new AdapterServiceImpl(this, serviceInterface, serviceFilter);
Marcel Offermans61a81142010-04-02 15:16:50 +0000217 }
Pierre De Rop34231582010-05-23 20:05:16 +0000218
Marcel Offermans4fd903f2009-12-29 09:18:05 +0000219 /**
220 * Creates a new resource adapter. The adapter will be applied to any resource that
221 * matches the specified filter condition. For each matching resource
222 * an adapter will be created based on the adapter implementation class.
223 * The adapter will be registered with the specified interface and existing properties
224 * from the original resource plus any extra properties you supply here.
225 * It will also inherit all dependencies, and if you declare the original
226 * service as a member it will be injected.
227 *
Pierre De Rop445ddec2010-05-23 21:05:27 +0000228 * <h3>Usage Example</h3>
229 *
Pierre De Ropc3dfbb72010-05-24 13:16:35 +0000230 * <blockquote><pre>
Pierre De Rop445ddec2010-05-23 21:05:27 +0000231 * manager.createResourceAdapterService("(&(path=/test)(repository=TestRepository))", true)
232 * // The interface to use when registering adapter
Pierre De Ropc3dfbb72010-05-24 13:16:35 +0000233 * .setInterface(AdapterService.class.getName(), new Hashtable() {{ put("foo", "bar"); }})
Pierre De Rop445ddec2010-05-23 21:05:27 +0000234 * // the implementation of the adapter
Pierre De Ropc3dfbb72010-05-24 13:16:35 +0000235 * .setImplementation(AdapterServiceImpl.class);
236 * </pre></blockquote>
Pierre De Rop445ddec2010-05-23 21:05:27 +0000237 *
Marcel Offermans4fd903f2009-12-29 09:18:05 +0000238 * @param resourceFilter the filter condition to use with the resource
Marcel Offermans4fd903f2009-12-29 09:18:05 +0000239 * @param propagate <code>true</code> if properties from the resource should be propagated to the service
Marcel Offermanse9c13d92010-07-01 14:01:02 +0000240 * @param callbackInstance
241 * @param callbackChanged
Marcel Offermans4fd903f2009-12-29 09:18:05 +0000242 * @return a service that acts as a factory for generating resource adapters
243 * @see Resource
244 */
Marcel Offermanse9c13d92010-07-01 14:01:02 +0000245 public Service createResourceAdapterService(String resourceFilter, boolean propagate, Object callbackInstance, String callbackChanged) {
246 return new ResourceAdapterServiceImpl(this, resourceFilter, propagate, callbackInstance, callbackChanged);
Marcel Offermans61a81142010-04-02 15:16:50 +0000247 }
Pierre De Rop445ddec2010-05-23 21:05:27 +0000248
Marcel Offermans4fd903f2009-12-29 09:18:05 +0000249 /**
250 * Creates a new bundle adapter. The adapter will be applied to any bundle that
251 * matches the specified bundle state mask and filter condition. For each matching
252 * bundle an adapter will be created based on the adapter implementation class.
253 * The adapter will be registered with the specified interface
254 *
255 * TODO and existing properties from the original resource plus any extra properties you supply here.
256 * It will also inherit all dependencies, and if you declare the original
257 * service as a member it will be injected.
258 *
Pierre De Rop13dd63d2010-05-23 21:58:28 +0000259 * <h3>Usage Example</h3>
260 *
Pierre De Ropc3dfbb72010-05-24 13:16:35 +0000261 * <blockquote><pre>
Pierre De Rop13dd63d2010-05-23 21:58:28 +0000262 * manager.createBundleAdapterService(Bundle.INSTALLED | Bundle.RESOLVED | Bundle.ACTIVE,
263 * "(Bundle-SymbolicName=org.apache.felix.dependencymanager)",
264 * true)
265 * // The interface to use when registering adapter
Pierre De Ropc3dfbb72010-05-24 13:16:35 +0000266 * .setInterface(AdapterService.class.getName(), new Hashtable() {{ put("foo", "bar"); }})
Pierre De Rop13dd63d2010-05-23 21:58:28 +0000267 * // the implementation of the adapter
Pierre De Ropc3dfbb72010-05-24 13:16:35 +0000268 * .setImplementation(AdapterServiceImpl.class);
269 * </pre></blockquote>
Pierre De Rop13dd63d2010-05-23 21:58:28 +0000270 *
Marcel Offermans4fd903f2009-12-29 09:18:05 +0000271 * @param bundleStateMask the bundle state mask to apply
272 * @param bundleFilter the filter to apply to the bundle manifest
Marcel Offermansd665eaf2010-02-16 15:56:35 +0000273 * @param propagate <code>true</code> if properties from the bundle should be propagated to the service
Marcel Offermans4fd903f2009-12-29 09:18:05 +0000274 * @return a service that acts as a factory for generating bundle adapters
275 */
Pierre De Rop13dd63d2010-05-23 21:58:28 +0000276 public Service createBundleAdapterService(int bundleStateMask, String bundleFilter, boolean propagate) {
277 return new BundleAdapterServiceImpl(this, bundleStateMask, bundleFilter, propagate);
Marcel Offermans61a81142010-04-02 15:16:50 +0000278 }
Marcel Offermans80eeafe2009-12-01 22:12:26 +0000279
Marcel Offermansa962bc92009-11-21 17:59:33 +0000280 /**
Pierre De Ropef94c882010-04-17 18:23:35 +0000281 * Creates a new Managed Service Factory Configuration Adapter. For each new Config Admin factory configuration matching
282 * the factoryPid, an adapter will be created based on the adapter implementation class.
283 * The adapter will be registered with the specified interface, and with the specified adapter service properties.
284 * Depending on the <code>propagate</code> parameter, every public factory configuration properties
285 * (which don't start with ".") will be propagated along with the adapter service properties.
286 * It will also inherit all dependencies.
287 *
Pierre De Rop3e100372010-05-24 12:43:44 +0000288 * <h3>Usage Example</h3>
289 *
Pierre De Ropc3dfbb72010-05-24 13:16:35 +0000290 * <blockquote><pre>
Pierre De Rop3e100372010-05-24 12:43:44 +0000291 * manager.createFactoryConfigurationAdapterService("MyFactoryPid", "update", true)
292 * // The interface to use when registering adapter
Pierre De Ropc3dfbb72010-05-24 13:16:35 +0000293 * .setInterface(AdapterService.class.getName(), new Hashtable() {{ put("foo", "bar"); }})
Pierre De Rop3e100372010-05-24 12:43:44 +0000294 * // the implementation of the adapter
295 * .setImplementation(AdapterServiceImpl.class);
Pierre De Ropc3dfbb72010-05-24 13:16:35 +0000296 * </pre></blockquote>
297 *
Pierre De Ropef94c882010-04-17 18:23:35 +0000298 * @param factoryPid the pid matching the factory configuration
299 * @param update the adapter method name that will be notified when the factory configuration is created/updated.
Pierre De Ropef94c882010-04-17 18:23:35 +0000300 * @param propagate true if public factory configuration should be propagated to the adapter service properties
301 * @return a service that acts as a factory for generating the managed service factory configuration adapter
302 */
Pierre De Rop3e100372010-05-24 12:43:44 +0000303 public Service createFactoryConfigurationAdapterService(String factoryPid, String update, boolean propagate) {
304 return new FactoryConfigurationAdapterServiceImpl(this, factoryPid, update, propagate);
Pierre De Ropef94c882010-04-17 18:23:35 +0000305 }
306
307 /**
Pierre De Rop3e100372010-05-24 12:43:44 +0000308 * Creates a new Managed Service Factory Configuration Adapter with meta type support. For each new Config Admin
309 * factory configuration matching the factoryPid, an adapter will be created based on the adapter implementation
310 * class. The adapter will be registered with the specified interface, and with the specified adapter service
311 * properties. Depending on the <code>propagate</code> parameter, every public factory configuration properties
Pierre De Ropef94c882010-04-17 18:23:35 +0000312 * (which don't start with ".") will be propagated along with the adapter service properties.
313 * It will also inherit all dependencies.
314 *
Pierre De Ropc3dfbb72010-05-24 13:16:35 +0000315 * <h3>Usage Example</h3>
316 *
317 * <blockquote><pre>
318 * PropertyMetaData[] propertiesMetaData = new PropertyMetaData[] {
319 * manager.createPropertyMetaData()
320 * .setCardinality(Integer.MAX_VALUE)
321 * .setType(String.class)
322 * .setHeading("English words")
323 * .setDescription("Declare here some valid english words")
324 * .setDefaults(new String[] {"hello", "world"})
325 * .setId("words")
326 * };
327 *
328 * manager.add(createFactoryConfigurationAdapterService("FactoryPid",
329 * "updated",
330 * true, // propagate CM settings
331 * "EnglishDictionary",
332 * "English dictionary configuration properties",
333 * null,
334 * propertiesMetaData)
335 * .setImplementation(Adapter.class));
336 * </pre></blockquote>
337 *
Pierre De Ropef94c882010-04-17 18:23:35 +0000338 * @param factoryPid the pid matching the factory configuration
339 * @param update the adapter method name that will be notified when the factory configuration is created/updated.
Pierre De Ropef94c882010-04-17 18:23:35 +0000340 * @param propagate true if public factory configuration should be propagated to the adapter service properties
Pierre De Rop3e100372010-05-24 12:43:44 +0000341 * @param heading The label used to display the tab name (or section) where the properties are displayed.
342 * Example: "Printer Service"
343 * @param desc A human readable description of the factory PID this configuration is associated with.
344 * Example: "Configuration for the PrinterService bundle"
Pierre De Rop9a8ebfd2010-04-25 22:25:34 +0000345 * @param localization Points to the basename of the Properties file that can localize the Meta Type informations.
346 * The default localization base name for the properties is OSGI-INF/l10n/bundle, but can
Pierre De Rop3e100372010-05-24 12:43:44 +0000347 * be overridden by the manifest Bundle-Localization header (see core specification, in section Localization
348 * on page 68). You can specify a specific localization basename file using this parameter
349 * (e.g. <code>"person"</code> will match person_du_NL.properties in the root bundle directory).
Pierre De Rop9a8ebfd2010-04-25 22:25:34 +0000350 * @param propertiesMetaData Array of MetaData regarding configuration properties
351 * @return a service that acts as a factory for generating the managed service factory configuration adapter
352 */
Pierre De Rop3e100372010-05-24 12:43:44 +0000353 public Service createFactoryConfigurationAdapterService(String factoryPid, String update, boolean propagate,
354 String heading, String desc, String localization,
355 PropertyMetaData[] propertiesMetaData)
Pierre De Rop9a8ebfd2010-04-25 22:25:34 +0000356 {
Pierre De Rop3e100372010-05-24 12:43:44 +0000357 return new FactoryConfigurationAdapterServiceImpl(this, factoryPid, update, propagate, m_context, m_logger,
358 heading, desc, localization, propertiesMetaData);
Pierre De Rop9a8ebfd2010-04-25 22:25:34 +0000359 }
360
361 /**
Marcel Offermansa962bc92009-11-21 17:59:33 +0000362 * Returns a list of services.
363 *
364 * @return a list of services
365 */
366 public List getServices() {
367 return Collections.unmodifiableList(m_services);
368 }
Marcel Offermansa962bc92009-11-21 17:59:33 +0000369}