blob: df09ddc2b001663cb686dcd85a75db960fbbff8a [file] [log] [blame]
Marcel Offermanse14b3422009-11-25 23:04:32 +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 Rope8c339f2009-12-04 22:51:51 +000019package org.apache.felix.dm.impl.dependencies;
Marcel Offermanse14b3422009-11-25 23:04:32 +000020
Marcel Offermans26081d32010-07-12 12:43:42 +000021import java.lang.reflect.InvocationTargetException;
Marcel Offermansa8c213f2010-07-02 17:27:20 +000022import java.net.URL;
Marcel Offermans203bdad2009-12-04 09:23:04 +000023import java.util.ArrayList;
Marcel Offermans117aa2f2009-12-10 09:48:17 +000024import java.util.Dictionary;
Marcel Offermans203bdad2009-12-04 09:23:04 +000025import java.util.List;
Marcel Offermanse14b3422009-11-25 23:04:32 +000026import java.util.Properties;
27
Marcel Offermans8b93efa2010-07-02 18:27:21 +000028import org.apache.felix.dm.Dependency;
29import org.apache.felix.dm.ResourceDependency;
30import org.apache.felix.dm.ResourceHandler;
31import org.apache.felix.dm.Service;
32import org.apache.felix.dm.ServiceComponentDependency;
Marcel Offermans26081d32010-07-12 12:43:42 +000033import org.apache.felix.dm.impl.InvocationUtil;
Pierre De Rope8c339f2009-12-04 22:51:51 +000034import org.apache.felix.dm.impl.Logger;
Marcel Offermanse14b3422009-11-25 23:04:32 +000035import org.osgi.framework.BundleContext;
36import org.osgi.framework.ServiceRegistration;
Marcel Offermans26081d32010-07-12 12:43:42 +000037import org.osgi.service.log.LogService;
Marcel Offermanse14b3422009-11-25 23:04:32 +000038
Marcel Offermansb1959f42010-07-01 12:23:51 +000039public class ResourceDependencyImpl extends DependencyBase implements ResourceDependency, ResourceHandler, DependencyActivation, ServiceComponentDependency {
Marcel Offermanse14b3422009-11-25 23:04:32 +000040 private volatile BundleContext m_context;
41 private volatile ServiceRegistration m_registration;
Marcel Offermansb99e7e42009-12-10 09:11:32 +000042// private long m_resourceCounter;
Marcel Offermanse14b3422009-11-25 23:04:32 +000043
44 private Object m_callbackInstance;
45 private String m_callbackAdded;
46 private String m_callbackChanged;
47 private String m_callbackRemoved;
48 private boolean m_autoConfig;
Marcel Offermanse14b3422009-11-25 23:04:32 +000049 private String m_autoConfigInstance;
Marcel Offermans203bdad2009-12-04 09:23:04 +000050 protected List m_services = new ArrayList();
Marcel Offermanse14b3422009-11-25 23:04:32 +000051 private String m_resourceFilter;
Marcel Offermansa8c213f2010-07-02 17:27:20 +000052 private URL m_trackedResource;
Marcel Offermans203bdad2009-12-04 09:23:04 +000053 private boolean m_isStarted;
Marcel Offermansb99e7e42009-12-10 09:11:32 +000054 private List m_resources = new ArrayList();
Marcel Offermansa8c213f2010-07-02 17:27:20 +000055 private URL m_resourceInstance;
Marcel Offermans117aa2f2009-12-10 09:48:17 +000056 private boolean m_propagate;
Marcel Offermans26081d32010-07-12 12:43:42 +000057 private Object m_propagateCallbackInstance;
58 private String m_propagateCallbackMethod;
Marcel Offermanse14b3422009-11-25 23:04:32 +000059
Pierre De Rope8c339f2009-12-04 22:51:51 +000060 public ResourceDependencyImpl(BundleContext context, Logger logger) {
Marcel Offermans61a81142010-04-02 15:16:50 +000061 super(logger);
Marcel Offermanse14b3422009-11-25 23:04:32 +000062 m_context = context;
Marcel Offermanse14b3422009-11-25 23:04:32 +000063 m_autoConfig = true;
64 }
65
Marcel Offermansb1959f42010-07-01 12:23:51 +000066 public ResourceDependencyImpl(ResourceDependencyImpl prototype) {
67 super(prototype);
68 m_context = prototype.m_context;
69 m_autoConfig = prototype.m_autoConfig;
70 m_callbackInstance = prototype.m_callbackInstance;
71 m_callbackAdded = prototype.m_callbackAdded;
72 m_callbackChanged = prototype.m_callbackChanged;
73 m_callbackRemoved = prototype.m_callbackRemoved;
74 m_autoConfigInstance = prototype.m_autoConfigInstance;
75 m_resourceFilter = prototype.m_resourceFilter;
76 m_trackedResource = prototype.m_trackedResource;
77 m_propagate = prototype.m_propagate;
78 }
79
80 public Dependency createCopy() {
81 return new ResourceDependencyImpl(this);
82 }
83
Marcel Offermanse14b3422009-11-25 23:04:32 +000084 public synchronized boolean isAvailable() {
Marcel Offermansb99e7e42009-12-10 09:11:32 +000085 return m_resources.size() > 0;
Marcel Offermanse14b3422009-11-25 23:04:32 +000086 }
87
Marcel Offermanse14b3422009-11-25 23:04:32 +000088 public void start(DependencyService service) {
Marcel Offermans203bdad2009-12-04 09:23:04 +000089 boolean needsStarting = false;
90 synchronized (this) {
91 m_services.add(service);
92 if (!m_isStarted) {
93 m_isStarted = true;
94 needsStarting = true;
95 }
96 }
97 if (needsStarting) {
Marcel Offermans266eee52009-12-08 10:56:09 +000098 Properties props = null;
99 if (m_resourceFilter != null) {
100 props = new Properties();
Marcel Offermansa8c213f2010-07-02 17:27:20 +0000101 props.setProperty(ResourceHandler.FILTER, m_resourceFilter);
Marcel Offermans266eee52009-12-08 10:56:09 +0000102 }
Marcel Offermans203bdad2009-12-04 09:23:04 +0000103 m_registration = m_context.registerService(ResourceHandler.class.getName(), this, props);
104 }
Marcel Offermanse14b3422009-11-25 23:04:32 +0000105 }
106
107 public void stop(DependencyService service) {
Marcel Offermans203bdad2009-12-04 09:23:04 +0000108 boolean needsStopping = false;
109 synchronized (this) {
110 if (m_services.size() == 1 && m_services.contains(service)) {
111 m_isStarted = false;
112 needsStopping = true;
113 m_services.remove(service);
114 }
115 }
116 if (needsStopping) {
117 m_registration.unregister();
118 m_registration = null;
119 }
Marcel Offermanse14b3422009-11-25 23:04:32 +0000120 }
121
Marcel Offermansa8c213f2010-07-02 17:27:20 +0000122 public void added(URL resource) {
Marcel Offermansb99e7e42009-12-10 09:11:32 +0000123 if (m_trackedResource == null || m_trackedResource.equals(resource)) {
124 long counter;
125 Object[] services;
126 synchronized (this) {
127 m_resources.add(resource);
128 counter = m_resources.size();
129 services = m_services.toArray();
130 }
131 for (int i = 0; i < services.length; i++) {
132 DependencyService ds = (DependencyService) services[i];
133 if (counter == 1) {
134 ds.dependencyAvailable(this);
135 if (!isRequired()) {
136 invokeAdded(ds, resource);
137 }
138 }
139 else {
140 ds.dependencyChanged(this);
Marcel Offermans203bdad2009-12-04 09:23:04 +0000141 invokeAdded(ds, resource);
142 }
143 }
Marcel Offermansb99e7e42009-12-10 09:11:32 +0000144 }
Marcel Offermanse14b3422009-11-25 23:04:32 +0000145 }
146
Marcel Offermansa8c213f2010-07-02 17:27:20 +0000147 public void changed(URL resource) {
Marcel Offermansb99e7e42009-12-10 09:11:32 +0000148 if (m_trackedResource == null || m_trackedResource.equals(resource)) {
149 Object[] services;
150 synchronized (this) {
151 services = m_services.toArray();
152 }
153 for (int i = 0; i < services.length; i++) {
154 DependencyService ds = (DependencyService) services[i];
155 invokeChanged(ds, resource);
156 }
Marcel Offermans203bdad2009-12-04 09:23:04 +0000157 }
Marcel Offermanse14b3422009-11-25 23:04:32 +0000158 }
159
Marcel Offermansa8c213f2010-07-02 17:27:20 +0000160 public void removed(URL resource) {
Marcel Offermansb99e7e42009-12-10 09:11:32 +0000161 if (m_trackedResource == null || m_trackedResource.equals(resource)) {
162 long counter;
163 Object[] services;
164 synchronized (this) {
165 m_resources.remove(resource);
166 counter = m_resources.size();
167 services = m_services.toArray();
168 }
169 for (int i = 0; i < services.length; i++) {
170 DependencyService ds = (DependencyService) services[i];
171 if (counter == 0) {
172 ds.dependencyUnavailable(this);
173 if (!isRequired()) {
174 invokeRemoved(ds, resource);
175 }
176 }
177 else {
178 ds.dependencyChanged(this);
Marcel Offermans203bdad2009-12-04 09:23:04 +0000179 invokeRemoved(ds, resource);
180 }
181 }
Marcel Offermanse14b3422009-11-25 23:04:32 +0000182 }
183 }
184
Marcel Offermansa8c213f2010-07-02 17:27:20 +0000185 public void invokeAdded(DependencyService ds, URL serviceInstance) {
Marcel Offermansea89b862010-06-24 13:14:43 +0000186 invoke(ds, serviceInstance, m_callbackAdded);
Marcel Offermanse14b3422009-11-25 23:04:32 +0000187 }
188
Marcel Offermansa8c213f2010-07-02 17:27:20 +0000189 public void invokeChanged(DependencyService ds, URL serviceInstance) {
Marcel Offermansea89b862010-06-24 13:14:43 +0000190 invoke(ds, serviceInstance, m_callbackChanged);
Marcel Offermanse14b3422009-11-25 23:04:32 +0000191 }
192
Marcel Offermansa8c213f2010-07-02 17:27:20 +0000193 public void invokeRemoved(DependencyService ds, URL serviceInstance) {
Marcel Offermansea89b862010-06-24 13:14:43 +0000194 invoke(ds, serviceInstance, m_callbackRemoved);
195 }
196
Marcel Offermansa8c213f2010-07-02 17:27:20 +0000197 private void invoke(DependencyService ds, URL serviceInstance, String name) {
Marcel Offermansea89b862010-06-24 13:14:43 +0000198 if (name != null) {
199 ds.invokeCallbackMethod(getCallbackInstances(ds), name,
Marcel Offermansa8c213f2010-07-02 17:27:20 +0000200 new Class[][] {{ Service.class, URL.class }, { Service.class, Object.class }, { Service.class }, { URL.class }, { Object.class }, {}},
Marcel Offermansea89b862010-06-24 13:14:43 +0000201 new Object[][] {{ ds.getServiceInterface(), serviceInstance }, { ds.getServiceInterface(), serviceInstance }, { ds.getServiceInterface() }, { serviceInstance }, { serviceInstance }, {}}
Marcel Offermans61a81142010-04-02 15:16:50 +0000202 );
Marcel Offermanse14b3422009-11-25 23:04:32 +0000203 }
204 }
Marcel Offermansea89b862010-06-24 13:14:43 +0000205
Marcel Offermanse14b3422009-11-25 23:04:32 +0000206 /**
207 * Sets the callbacks for this service. These callbacks can be used as hooks whenever a
208 * dependency is added or removed. When you specify callbacks, the auto configuration
209 * feature is automatically turned off, because we're assuming you don't need it in this
210 * case.
211 *
212 * @param added the method to call when a service was added
213 * @param removed the method to call when a service was removed
214 * @return this service dependency
215 */
216 public synchronized ResourceDependency setCallbacks(String added, String removed) {
217 return setCallbacks(null, added, null, removed);
218 }
219
220 /**
221 * Sets the callbacks for this service. These callbacks can be used as hooks whenever a
222 * dependency is added, changed or removed. When you specify callbacks, the auto
223 * configuration feature is automatically turned off, because we're assuming you don't
224 * need it in this case.
225 *
226 * @param added the method to call when a service was added
227 * @param changed the method to call when a service was changed
228 * @param removed the method to call when a service was removed
229 * @return this service dependency
230 */
231 public synchronized ResourceDependency setCallbacks(String added, String changed, String removed) {
232 return setCallbacks(null, added, changed, removed);
233 }
234
235 /**
236 * Sets the callbacks for this service. These callbacks can be used as hooks whenever a
237 * dependency is added or removed. They are called on the instance you provide. When you
238 * specify callbacks, the auto configuration feature is automatically turned off, because
239 * we're assuming you don't need it in this case.
240 *
241 * @param instance the instance to call the callbacks on
242 * @param added the method to call when a service was added
243 * @param removed the method to call when a service was removed
244 * @return this service dependency
245 */
246 public synchronized ResourceDependency setCallbacks(Object instance, String added, String removed) {
247 return setCallbacks(instance, added, null, removed);
248 }
249
250 /**
251 * Sets the callbacks for this service. These callbacks can be used as hooks whenever a
252 * dependency is added, changed or removed. They are called on the instance you provide. When you
253 * specify callbacks, the auto configuration feature is automatically turned off, because
254 * we're assuming you don't need it in this case.
255 *
256 * @param instance the instance to call the callbacks on
257 * @param added the method to call when a service was added
258 * @param changed the method to call when a service was changed
259 * @param removed the method to call when a service was removed
260 * @return this service dependency
261 */
262 public synchronized ResourceDependency setCallbacks(Object instance, String added, String changed, String removed) {
263 ensureNotActive();
264 // if at least one valid callback is specified, we turn off auto configuration
265 if (added != null || removed != null || changed != null) {
266 setAutoConfig(false);
267 }
268 m_callbackInstance = instance;
269 m_callbackAdded = added;
270 m_callbackChanged = changed;
271 m_callbackRemoved = removed;
272 return this;
273 }
274
275 private void ensureNotActive() {
276 if (m_registration != null) {
277 throw new IllegalStateException("Cannot modify state while active.");
278 }
279 }
280
281 /**
282 * Sets auto configuration for this service. Auto configuration allows the
283 * dependency to fill in any attributes in the service implementation that
284 * are of the same type as this dependency. Default is on.
285 *
286 * @param autoConfig the value of auto config
287 * @return this service dependency
288 */
289 public synchronized ResourceDependency setAutoConfig(boolean autoConfig) {
290 ensureNotActive();
291 m_autoConfig = autoConfig;
292 return this;
293 }
294
295 /**
296 * Sets auto configuration for this service. Auto configuration allows the
297 * dependency to fill in the attribute in the service implementation that
298 * has the same type and instance name.
299 *
300 * @param instanceName the name of attribute to auto config
301 * @return this service dependency
302 */
303 public synchronized ResourceDependency setAutoConfig(String instanceName) {
304 ensureNotActive();
305 m_autoConfig = (instanceName != null);
306 m_autoConfigInstance = instanceName;
307 return this;
308 }
309
Marcel Offermans61a81142010-04-02 15:16:50 +0000310// private void invokeCallbackMethod(Object[] instances, String methodName, Object service) {
311// for (int i = 0; i < instances.length; i++) {
312// try {
313// invokeCallbackMethod(instances[i], methodName, service);
314// }
315// catch (NoSuchMethodException e) {
316// m_logger.log(Logger.LOG_DEBUG, "Method '" + methodName + "' does not exist on " + instances[i] + ". Callback skipped.");
317// }
318// }
319// }
320//
321// private void invokeCallbackMethod(Object instance, String methodName, Object service) throws NoSuchMethodException {
322// Class currentClazz = instance.getClass();
323// boolean done = false;
324// while (!done && currentClazz != null) {
325// done = invokeMethod(instance, currentClazz, methodName,
326// new Class[][] {{Resource.class}, {Object.class}, {}},
327// new Object[][] {{service}, {service}, {}},
328// false);
329// if (!done) {
330// currentClazz = currentClazz.getSuperclass();
331// }
332// }
333// if (!done && currentClazz == null) {
334// throw new NoSuchMethodException(methodName);
335// }
336// }
337//
338// private boolean invokeMethod(Object object, Class clazz, String name, Class[][] signatures, Object[][] parameters, boolean isSuper) {
339// Method m = null;
340// for (int i = 0; i < signatures.length; i++) {
341// Class[] signature = signatures[i];
342// try {
343// m = clazz.getDeclaredMethod(name, signature);
344// if (!(isSuper && Modifier.isPrivate(m.getModifiers()))) {
345// m.setAccessible(true);
346// try {
347// m.invoke(object, parameters[i]);
348// }
349// catch (InvocationTargetException e) {
350// m_logger.log(Logger.LOG_ERROR, "Exception while invoking method " + m + ".", e);
351// }
352// // we did find and invoke the method, so we return true
353// return true;
354// }
355// }
356// catch (NoSuchMethodException e) {
357// // ignore this and keep looking
358// }
359// catch (Exception e) {
360// // could not even try to invoke the method
361// m_logger.log(Logger.LOG_ERROR, "Exception while trying to invoke method " + m + ".", e);
362// }
363// }
364// return false;
365// }
Marcel Offermans203bdad2009-12-04 09:23:04 +0000366
367 private synchronized Object[] getCallbackInstances(DependencyService ds) {
Marcel Offermanse14b3422009-11-25 23:04:32 +0000368 if (m_callbackInstance == null) {
Marcel Offermans203bdad2009-12-04 09:23:04 +0000369 return ds.getCompositionInstances();
Marcel Offermanse14b3422009-11-25 23:04:32 +0000370 }
Marcel Offermans203bdad2009-12-04 09:23:04 +0000371 else {
372 return new Object[] { m_callbackInstance };
373 }
Marcel Offermanse14b3422009-11-25 23:04:32 +0000374 }
375
Marcel Offermansa8c213f2010-07-02 17:27:20 +0000376 public ResourceDependency setResource(URL resource) {
Marcel Offermanse14b3422009-11-25 23:04:32 +0000377 m_trackedResource = resource;
378 return this;
379 }
380
381 public synchronized ResourceDependency setRequired(boolean required) {
382 ensureNotActive();
Marcel Offermans61a81142010-04-02 15:16:50 +0000383 setIsRequired(required);
Marcel Offermanse14b3422009-11-25 23:04:32 +0000384 return this;
385 }
386
387 public ResourceDependency setFilter(String resourceFilter) {
Marcel Offermans001db052009-12-08 08:58:40 +0000388 ensureNotActive();
Marcel Offermanse14b3422009-11-25 23:04:32 +0000389 m_resourceFilter = resourceFilter;
390 return this;
391 }
392 public synchronized boolean isAutoConfig() {
393 return m_autoConfig;
394 }
Marcel Offermansb99e7e42009-12-10 09:11:32 +0000395
Marcel Offermansa8c213f2010-07-02 17:27:20 +0000396 public URL getResource() {
Marcel Offermansb99e7e42009-12-10 09:11:32 +0000397 return lookupResource();
Marcel Offermanse14b3422009-11-25 23:04:32 +0000398 }
Marcel Offermans001db052009-12-08 08:58:40 +0000399
Marcel Offermansa8c213f2010-07-02 17:27:20 +0000400 private URL lookupResource() {
Marcel Offermansb99e7e42009-12-10 09:11:32 +0000401 try {
Marcel Offermansa8c213f2010-07-02 17:27:20 +0000402 return (URL) m_resources.get(0);
Marcel Offermansb99e7e42009-12-10 09:11:32 +0000403 }
404 catch (IndexOutOfBoundsException e) {
405 return null;
406 }
407 }
408
Marcel Offermans001db052009-12-08 08:58:40 +0000409 public Object getAutoConfigInstance() {
Marcel Offermansb99e7e42009-12-10 09:11:32 +0000410 return lookupResource();
Marcel Offermans001db052009-12-08 08:58:40 +0000411 }
412
413 public String getAutoConfigName() {
Marcel Offermansb99e7e42009-12-10 09:11:32 +0000414 return m_autoConfigInstance;
Marcel Offermans001db052009-12-08 08:58:40 +0000415 }
416
417 public Class getAutoConfigType() {
Marcel Offermansa8c213f2010-07-02 17:27:20 +0000418 return URL.class;
Marcel Offermans001db052009-12-08 08:58:40 +0000419 }
420
421 public void invokeAdded(DependencyService service) {
Marcel Offermansb99e7e42009-12-10 09:11:32 +0000422 // we remember these for future reference, needed for required callbacks
423 m_resourceInstance = lookupResource();
424 invokeAdded(service, m_resourceInstance);
Marcel Offermans001db052009-12-08 08:58:40 +0000425 }
426
427 public void invokeRemoved(DependencyService service) {
Marcel Offermansb99e7e42009-12-10 09:11:32 +0000428 invokeRemoved(service, m_resourceInstance);
429 m_resourceInstance = null;
Marcel Offermans001db052009-12-08 08:58:40 +0000430 }
Marcel Offermans117aa2f2009-12-10 09:48:17 +0000431
432 public ResourceDependency setPropagate(boolean propagate) {
433 ensureNotActive();
434 m_propagate = propagate;
435 return this;
436 }
437
Marcel Offermans26081d32010-07-12 12:43:42 +0000438 public ResourceDependency setPropagate(Object instance, String method) {
439 setPropagate(instance != null && method != null);
440 m_propagateCallbackInstance = instance;
441 m_propagateCallbackMethod = method;
442 return this;
443 }
444
Marcel Offermans117aa2f2009-12-10 09:48:17 +0000445 public Dictionary getProperties() {
Marcel Offermansa8c213f2010-07-02 17:27:20 +0000446 URL resource = lookupResource();
Marcel Offermans117aa2f2009-12-10 09:48:17 +0000447 if (resource != null) {
Marcel Offermans26081d32010-07-12 12:43:42 +0000448 if (m_propagateCallbackInstance != null && m_propagateCallbackMethod != null) {
449 try {
450 return (Dictionary) InvocationUtil.invokeCallbackMethod(m_propagateCallbackInstance, m_propagateCallbackMethod, new Class[][] {{ URL.class }}, new Object[][] {{ resource }});
451 }
452 catch (InvocationTargetException e) {
453 m_logger.log(LogService.LOG_WARNING, "Exception while invoking callback method", e.getCause());
454 }
455 catch (Exception e) {
456 m_logger.log(LogService.LOG_WARNING, "Exception while trying to invoke callback method", e);
457 }
458 throw new IllegalStateException("Could not invoke callback");
459 }
460 else {
461 Properties props = new Properties();
462 props.setProperty(ResourceHandler.HOST, resource.getHost());
463 props.setProperty(ResourceHandler.PATH, resource.getPath());
464 props.setProperty(ResourceHandler.PROTOCOL, resource.getProtocol());
465 props.setProperty(ResourceHandler.PORT, Integer.toString(resource.getPort()));
466 return props;
467 }
Marcel Offermans117aa2f2009-12-10 09:48:17 +0000468 }
469 else {
470 throw new IllegalStateException("cannot find resource");
471 }
472 }
473
474 public boolean isPropagated() {
475 return m_propagate;
476 }
Marcel Offermans61a81142010-04-02 15:16:50 +0000477
478 public ResourceDependency setInstanceBound(boolean isInstanceBound) {
479 setIsInstanceBound(isInstanceBound);
480 return this;
481 }
Marcel Offermansb1959f42010-07-01 12:23:51 +0000482
483 public String getName() {
484 StringBuilder sb = new StringBuilder();
485 if (m_resourceFilter != null) {
486 sb.append(m_resourceFilter);
487 }
488 if (m_trackedResource != null) {
Marcel Offermansa8c213f2010-07-02 17:27:20 +0000489 sb.append(m_trackedResource.toString());
Marcel Offermansb1959f42010-07-01 12:23:51 +0000490 }
491 return sb.toString();
492 }
493
494 public int getState() {
495 return (isAvailable() ? 1 : 0) + (isRequired() ? 2 : 0);
496 }
497
498 public String getType() {
499 return "resource";
500 }
Marcel Offermanse14b3422009-11-25 23:04:32 +0000501}