blob: 8dcd9a59080bb656fe96cb033f3bd49c075db000 [file] [log] [blame]
Marcel Offermans613a6202009-03-11 22:31:44 +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.das;
20
21
22import java.util.ArrayList;
23import java.util.Collections;
24import java.util.Dictionary;
25import java.util.HashMap;
26import java.util.List;
27import java.util.Map;
28import java.util.Properties;
29import java.util.concurrent.Callable;
30import java.util.concurrent.CountDownLatch;
31import java.util.concurrent.ExecutorService;
32import java.util.concurrent.Executors;
33import java.util.concurrent.ScheduledExecutorService;
34import java.util.concurrent.TimeUnit;
35
36import org.apache.felix.das.util.DriverLoader;
37import org.apache.felix.das.util.DriverMatcher;
38import org.apache.felix.das.util.Util;
39import org.osgi.framework.Bundle;
40import org.osgi.framework.BundleContext;
41import org.osgi.framework.Filter;
42import org.osgi.framework.FrameworkEvent;
43import org.osgi.framework.FrameworkListener;
44import org.osgi.framework.InvalidSyntaxException;
45import org.osgi.framework.ServiceReference;
46import org.osgi.service.device.Constants;
47import org.osgi.service.device.Device;
48import org.osgi.service.device.Driver;
49import org.osgi.service.device.DriverLocator;
50import org.osgi.service.device.DriverSelector;
51import org.osgi.service.device.Match;
52import org.osgi.service.log.LogService;
53
54
55/**
56 * TODO: add javadoc
57 *
58 * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
59 */
60public class DeviceManager implements Log
61{
62
63 private final long DEFAULT_TIMEOUT_SEC = 1;
64
65 // the logger
66 private LogService m_log;
67
68 // the bundle context
69 private final BundleContext m_context;
70
71 // the driver selector
72 private DriverSelector m_selector;
73
74 // the driver locators
75 private List<DriverLocator> m_locators;
76
77 // the devices
78 private Map<ServiceReference, Object> m_devices;
79
80 // the drivers
81 private Map<ServiceReference, DriverAttributes> m_drivers;
82
83 // performs all the background actions
84 private ExecutorService m_worker;
85
86 // used to add delayed actions
87 private ScheduledExecutorService m_delayed;
88
89 private Filter m_deviceImplFilter;
90
91 private Filter m_driverImplFilter;
92
93
94 public DeviceManager( BundleContext context )
95 {
96 m_context = context;
97 }
98
99
100 public void debug( String message )
101 {
102 m_log.log( LogService.LOG_DEBUG, message );
103 }
104
105
106 public void info( String message )
107 {
108 m_log.log( LogService.LOG_INFO, message );
109 }
110
111
112 public void warning( String message )
113 {
114 m_log.log( LogService.LOG_WARNING, message );
115 }
116
117
118 public void error( String message, Throwable e )
119 {
120 System.err.println( message );
121 if ( e != null )
122 {
123 e.printStackTrace();
124 }
125 m_log.log( LogService.LOG_ERROR, message, e );
126 }
127
128
129 // dependency manager methods
130 @SuppressWarnings("unused")
131 private void init() throws InvalidSyntaxException
132 {
133 m_locators = Collections.synchronizedList( new ArrayList<DriverLocator>() );
134 m_worker = Executors.newSingleThreadExecutor( new NamedThreadFactory( "Apache Felix Device Manager" ) );
135 m_delayed = Executors.newScheduledThreadPool( 1, new NamedThreadFactory(
136 "Apache Felix Device Manager - delayed" ) );
137 m_deviceImplFilter = Util.createFilter( "(%s=%s)", new Object[]
138 { org.osgi.framework.Constants.OBJECTCLASS, Device.class.getName() } );
139 m_driverImplFilter = Util.createFilter( "(%s=%s)", new Object[]
140 { org.osgi.framework.Constants.OBJECTCLASS, Driver.class.getName() } );
141 }
142
143
144 @SuppressWarnings("unused")
145 private void start()
146 {
147 m_drivers = new HashMap<ServiceReference, DriverAttributes>();
148 m_devices = new HashMap<ServiceReference, Object>();
149 submit( new WaitForStartFramework() );
150 }
151
152
153 public void stop()
154 {
155 // nothing to do ?
156 }
157
158
159 public void destroy()
160 {
161 m_worker.shutdownNow();
162 m_delayed.shutdownNow();
163 }
164
165
166 // callback methods
167
168 public void locatorAdded( DriverLocator locator )
169 {
170 m_locators.add( locator );
171 debug( "driver locator appeared" );
172 }
173
174
175 public void locatorRemoved( DriverLocator locator )
176 {
177 m_locators.remove( locator );
178 debug( "driver locator lost" );
179 }
180
181
182 public void driverAdded( ServiceReference ref, Object obj )
183 {
184 final Driver driver = Driver.class.cast( obj );
185 m_drivers.put( ref, new DriverAttributes( ref, driver ) );
186
187 debug( "driver appeared: " + Util.showDriver( ref ) );
188 }
189
190
191 public void driverRemoved( ServiceReference ref )
192 {
193 String driverId = String.class.cast( ref.getProperty( Constants.DRIVER_ID ) );
194 debug( "driver lost: " + Util.showDriver( ref ) );
195 m_drivers.remove( driverId );
196
197 // check if devices have become idle
198 // after some time
199 schedule( new CheckForIdleDevices() );
200
201 }
202
203
204 public void deviceAdded( ServiceReference ref, Object device )
205 {
206 m_devices.put( ref, device );
207 debug( "device appeared: " + Util.showDevice( ref ) );
208 submit( new DriverAttachAlgorithm( ref, device ) );
209 }
210
211
212 public void deviceModified( ServiceReference ref, Object device )
213 {
214 debug( "device modified: " + Util.showDevice( ref ) );
215 // nothing further to do ?
216 // DeviceAttributes da = m_devices.get(ref);
217 // submit(new DriverAttachAlgorithm(da));
218 }
219
220
221 public void deviceRemoved( ServiceReference ref )
222 {
223 debug( "device removed: " + Util.showDevice( ref ) );
224 m_devices.remove( ref );
225 // nothing further to do ?
226 // the services that use this
227 // device should track it.
228 }
229
230
231 /**
232 * perform this task as soon as possible.
233 *
234 * @param task
235 * the task
236 */
237 private void submit( Callable<Object> task )
238 {
239 m_worker.submit( new LoggedCall( task ) );
240 }
241
242
243 /**
244 * perform this task after the default delay.
245 *
246 * @param task
247 * the task
248 */
249 private void schedule( Callable<Object> task )
250 {
251 m_delayed.schedule( new DelayedCall( task ), DEFAULT_TIMEOUT_SEC, TimeUnit.SECONDS );
252 }
253
254 // worker callables
255
256 /**
257 * Callable used to start the DeviceManager. It either waits (blocking the
258 * worker thread) for the framework to start, or if it has already started,
259 * returns immediately, freeing up the worker thread.
260 *
Richard S. Hall3bc8afd2009-08-12 19:03:19 +0000261 * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
Marcel Offermans613a6202009-03-11 22:31:44 +0000262 */
263 private class WaitForStartFramework implements Callable<Object>, FrameworkListener
264 {
265
266 private final CountDownLatch m_latch = new CountDownLatch( 1 );
267
268
269 public Object call() throws Exception
270 {
271 boolean addedAsListener = false;
272 if ( m_context.getBundle( 0 ).getState() == Bundle.ACTIVE )
273 {
274 m_latch.countDown();
275 debug( "Starting Device Manager immediately" );
276 }
277 else
278 {
279 m_context.addFrameworkListener( this );
280 addedAsListener = true;
281 debug( "Waiting for framework to start" );
282 }
283
284 m_latch.await();
285 for ( Map.Entry<ServiceReference, Object> entry : m_devices.entrySet() )
286 {
287 submit( new DriverAttachAlgorithm( entry.getKey(), entry.getValue() ) );
288 }
289 // cleanup
290 if ( addedAsListener )
291 {
292 m_context.removeFrameworkListener( this );
293 }
294 return null;
295 }
296
297
298 // FrameworkListener method
299 public void frameworkEvent( FrameworkEvent event )
300 {
301 switch ( event.getType() )
302 {
303 case FrameworkEvent.STARTED:
304 debug( "Framework has started" );
305 m_latch.countDown();
306 break;
307 }
308 }
309
310
311 @Override
312 public String toString()
313 {
314 return getClass().getSimpleName();
315 }
316 }
317
318 private class LoggedCall implements Callable<Object>
319 {
320
321 private final Callable<Object> m_call;
322
323
324 public LoggedCall( Callable<Object> call )
325 {
326 m_call = call;
327 }
328
329
330 private String getName()
331 {
332 return m_call.getClass().getSimpleName();
333 }
334
335
336 public Object call() throws Exception
337 {
338
339 try
340 {
341 return m_call.call();
342 }
343 catch ( Exception e )
344 {
345 error( "call failed: " + getName(), e );
346 throw e;
347 }
348 catch ( Throwable e )
349 {
350 error( "call failed: " + getName(), e );
351 throw new RuntimeException( e );
352 }
353 }
354
355 }
356
357 private class DelayedCall implements Callable<Object>
358 {
359
360 private final Callable<Object> m_call;
361
362
363 public DelayedCall( Callable<Object> call )
364 {
365 m_call = call;
366 }
367
368
369 private String getName()
370 {
371 return m_call.getClass().getSimpleName();
372 }
373
374
375 public Object call() throws Exception
376 {
377 info( "Delayed call: " + getName() );
378 return m_worker.submit( m_call );
379 }
380 }
381
382 /**
383 * Checks for Idle devices, and attaches them
384 *
Richard S. Hall3bc8afd2009-08-12 19:03:19 +0000385 * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
Marcel Offermans613a6202009-03-11 22:31:44 +0000386 */
387 private class CheckForIdleDevices implements Callable<Object>
388 {
389
390 public Object call() throws Exception
391 {
392 debug( "START - check for idle devices" );
393 for ( ServiceReference ref : getIdleDevices() )
394 {
395 info( "IDLE: " + ref.getBundle().getSymbolicName() );
396 submit( new DriverAttachAlgorithm( ref, m_devices.get( ref ) ) );
397 }
398
399 submit( new IdleDriverUninstallAlgorithm() );
400 debug( "STOP - check for idle devices" );
401 return null;
402 }
403
404
405 /**
406 * get a list of all idle devices.
407 *
408 * @return
409 */
410 private List<ServiceReference> getIdleDevices()
411 {
412 List<ServiceReference> list = new ArrayList<ServiceReference>();
413
414 for ( ServiceReference ref : m_devices.keySet() )
415 {
416 info( "checking if idle: " + ref.getBundle().getSymbolicName() );
417
418 final Bundle[] usingBundles = ref.getUsingBundles();
419 for ( Bundle bundle : usingBundles )
420 {
421 if ( isDriverBundle( bundle ) )
422 {
423 info( "used by driver: " + bundle.getSymbolicName() );
424 debug( "not idle: " + ref.getBundle().getSymbolicName() );
Marcel Offermans613a6202009-03-11 22:31:44 +0000425 break;
426 }
Stuart McCulloch0a0508c2009-04-16 09:41:41 +0000427
428 list.add( ref );
429
Marcel Offermans613a6202009-03-11 22:31:44 +0000430 }
431 }
432 return list;
433 }
434 }
435
436
437 private boolean isDriverBundle( Bundle bundle )
438 {
439 ServiceReference[] refs = bundle.getRegisteredServices();
440 for ( ServiceReference ref : refs )
441 {
442 if ( m_driverImplFilter.match( ref ) )
443 {
444 return true;
445 }
446 }
447 return false;
448 }
449
450 /**
451 *
452 * Used to uninstall unused drivers
453 *
Richard S. Hall3bc8afd2009-08-12 19:03:19 +0000454 * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
Marcel Offermans613a6202009-03-11 22:31:44 +0000455 */
456 private class IdleDriverUninstallAlgorithm implements Callable<Object>
457 {
458
459 public Object call() throws Exception
460 {
461
462 info( "cleaning driver cache" );
463 for ( DriverAttributes da : m_drivers.values() )
464 {
465 // just call the tryUninstall; the da itself
466 // will know if it should really uninstall the driver.
467 da.tryUninstall();
468 }
469
470 return null;
471 }
472 }
473
474 private class DriverAttachAlgorithm implements Callable<Object>
475 {
476
477 private final ServiceReference m_ref;
478
479 private final Device m_device;
480
481 private List<DriverAttributes> m_included;
482
483 private List<DriverAttributes> m_excluded;
484
485 private final DriverLoader m_driverLoader;
486
487 private DriverAttributes m_finalDriver;
488
489
490 public DriverAttachAlgorithm( ServiceReference ref, Object obj )
491 {
492 m_ref = ref;
493 if ( m_deviceImplFilter.match( ref ) )
494 {
495 m_device = Device.class.cast( obj );
496 }
497 else
498 {
499 m_device = null;
500 }
501
502 m_driverLoader = new DriverLoader( DeviceManager.this, m_context );
503 }
504
505
506 @SuppressWarnings("all")
507 private Dictionary createDictionary( ServiceReference ref )
508 {
509 final Properties p = new Properties();
510
511 for ( String key : ref.getPropertyKeys() )
512 {
513 p.put( key, ref.getProperty( key ) );
514 }
515 return p;
516 }
517
518
519 @SuppressWarnings("all")
520 public Object call() throws Exception
521 {
522 info( "finding suitable driver for: " + Util.showDevice( m_ref ) );
523
524 final Dictionary dict = createDictionary( m_ref );
525
526 // first create a copy of all the drivers that are already there.
527 // during the process, drivers will be added, but also excluded.
528 m_included = new ArrayList<DriverAttributes>( m_drivers.values() );
529 m_excluded = new ArrayList<DriverAttributes>();
530
531 // first find matching driver bundles
532 // if there are no driver locators
533 // we'll have to do with the drivers that where
534 // added 'manually'
535 List<String> driverIds = m_driverLoader.findDrivers( m_locators, dict );
536
537 // remove the driverIds that are already available
538 for ( DriverAttributes da : m_drivers.values() )
539 {
540 driverIds.remove( da.getDriverId() );
541 }
542 driverIds.removeAll( m_drivers.keySet() );
543 try
544 {
545 return driverAttachment( dict, driverIds.toArray( new String[0] ) );
546 }
547 finally
548 {
549 // unload loaded drivers
550 // that were unnecessarily loaded
551 m_driverLoader.unload( m_finalDriver );
552 }
553 }
554
555
556 @SuppressWarnings("all")
557 private Object driverAttachment( Dictionary dict, String[] driverIds ) throws Exception
558 {
559 m_finalDriver = null;
560
561 // remove the excluded drivers
562 m_included.removeAll( m_excluded );
563
564 // now load the drivers
565 List<ServiceReference> driverRefs = m_driverLoader.loadDrivers( m_locators, driverIds );
566 // these are the possible driver references that have been added
567 // add the to the list of included drivers
568 for ( ServiceReference serviceReference : driverRefs )
569 {
570 DriverAttributes da = m_drivers.get( serviceReference );
571 if ( da != null )
572 {
573 m_included.add( da );
574 }
575 }
576
577 // now start matching all drivers
578 final DriverMatcher mi = new DriverMatcher( DeviceManager.this );
579
580 for ( DriverAttributes driver : m_included )
581 {
582 try
583 {
584 int match = driver.match( m_ref );
585 if ( match <= Device.MATCH_NONE )
586 continue;
587 mi.add( match, driver );
588 }
589 catch ( Throwable t )
590 {
591 error( "match threw an exception", new Exception( t ) );
592 }
593 }
594
595 // get the best match
596 Match bestMatch;
597
598 // local copy
599 final DriverSelector selector = m_selector;
600 if ( selector != null )
601 {
602 bestMatch = mi.selectBestMatch( m_ref, selector );
603 }
604 else
605 {
606 bestMatch = mi.getBestMatch();
607 }
608
609 if ( bestMatch == null )
610 {
611 noDriverFound();
612 // really return
613 return null;
614 }
615
616 String driverId = String.class.cast( bestMatch.getDriver().getProperty( Constants.DRIVER_ID ) );
617
618 debug( "best match: " + driverId );
619 m_finalDriver = m_drivers.get( bestMatch.getDriver() );
620
621 if ( m_finalDriver == null )
622 {
623 error( "we found a driverId, but not the corresponding driver: " + driverId, null );
624 noDriverFound();
625 return null;
626 }
627
628 // here we get serious...
629 try
630 {
631 debug( "attaching to: " + driverId );
632 String newDriverId = m_finalDriver.attach( m_ref );
633 if ( newDriverId == null )
634 {
635 // successful attach
636 return null;
637 }
638 // its a referral
639 info( "attach led to a referral to: " + newDriverId );
640 m_excluded.add( m_finalDriver );
641 return driverAttachment( dict, new String[]
642 { newDriverId } );
643 }
644 catch ( Throwable t )
645 {
646 error( "attach failed due to an exception", t );
647 }
648 m_excluded.add( m_finalDriver );
649 return driverAttachment( dict, driverIds );
650 }
651
652
653 private void noDriverFound()
654 {
655 debug( "no suitable driver found for: " + Util.showDevice( m_ref ) );
656 if ( m_device != null )
657 {
658 m_device.noDriverFound();
659 }
660 }
661
662
663 @Override
664 public String toString()
665 {
666 return getClass().getSimpleName();// + ": " +
667 // Util.showDevice(m_ref);
668 }
669
670 }
671}