directory-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From erodrig...@apache.org
Subject svn commit: r290673 - in /directory/sandbox/trunk/osgi-spec/trunk/configuration/src/main/java/org/apache/configuration: ./ impl/
Date Wed, 21 Sep 2005 10:16:02 GMT
Author: erodriguez
Date: Wed Sep 21 03:15:53 2005
New Revision: 290673

URL: http://svn.apache.org/viewcvs?rev=290673&view=rev
Log:
Refactoring in the OSGi Config Admin service:
o  introduction of event queue for asynchronous update dispatching per the OSGi spec
o  more formal producer-consumer

Added:
    directory/sandbox/trunk/osgi-spec/trunk/configuration/src/main/java/org/apache/configuration/impl/LDAPQuery.java   (with props)
    directory/sandbox/trunk/osgi-spec/trunk/configuration/src/main/java/org/apache/configuration/impl/UpdateConsumer.java   (with props)
    directory/sandbox/trunk/osgi-spec/trunk/configuration/src/main/java/org/apache/configuration/impl/UpdateEvent.java   (with props)
Modified:
    directory/sandbox/trunk/osgi-spec/trunk/configuration/src/main/java/org/apache/configuration/impl/ConfigurationAdminFactory.java
    directory/sandbox/trunk/osgi-spec/trunk/configuration/src/main/java/org/apache/configuration/impl/ConfigurationImpl.java
    directory/sandbox/trunk/osgi-spec/trunk/configuration/src/main/java/org/apache/configuration/impl/PluginManager.java
    directory/sandbox/trunk/osgi-spec/trunk/configuration/src/main/java/org/apache/configuration/metadata.xml

Modified: directory/sandbox/trunk/osgi-spec/trunk/configuration/src/main/java/org/apache/configuration/impl/ConfigurationAdminFactory.java
URL: http://svn.apache.org/viewcvs/directory/sandbox/trunk/osgi-spec/trunk/configuration/src/main/java/org/apache/configuration/impl/ConfigurationAdminFactory.java?rev=290673&r1=290672&r2=290673&view=diff
==============================================================================
--- directory/sandbox/trunk/osgi-spec/trunk/configuration/src/main/java/org/apache/configuration/impl/ConfigurationAdminFactory.java (original)
+++ directory/sandbox/trunk/osgi-spec/trunk/configuration/src/main/java/org/apache/configuration/impl/ConfigurationAdminFactory.java Wed Sep 21 03:15:53 2005
@@ -17,29 +17,21 @@
 
 package org.apache.configuration.impl;
 
-import java.io.IOException;
-import java.util.ArrayList;
 import java.util.Dictionary;
-import java.util.Hashtable;
-import java.util.List;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
 
 import javax.naming.spi.InitialContextFactory;
 
 import org.apache.configuration.ConfigurationListener;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
-import org.osgi.framework.BundleEvent;
-import org.osgi.framework.BundleListener;
 import org.osgi.framework.Constants;
 import org.osgi.framework.InvalidSyntaxException;
-import org.osgi.framework.ServiceEvent;
 import org.osgi.framework.ServiceFactory;
-import org.osgi.framework.ServiceListener;
 import org.osgi.framework.ServiceReference;
 import org.osgi.framework.ServiceRegistration;
-import org.osgi.service.cm.Configuration;
 import org.osgi.service.cm.ConfigurationAdmin;
-import org.osgi.service.cm.ConfigurationPlugin;
 import org.osgi.service.cm.ManagedService;
 import org.osgi.service.cm.ManagedServiceFactory;
 import org.slf4j.Logger;
@@ -50,26 +42,22 @@
 /**
  * ConfigurationAdmin implementation
  */
-public class ConfigurationAdminFactory implements ServiceFactory, ServiceListener, BundleListener, Lifecycle
+public class ConfigurationAdminFactory implements ServiceFactory, Lifecycle
 {
     /** the log for this class */
     private static final Logger log = LoggerFactory.getLogger( ConfigurationAdminFactory.class );
 
-    private static final String ANY_LOCATION = "*";
-    private static final String FILTER = "(|(objectClass=" + ManagedServiceFactory.class.getName() + ")"
-            + "(objectClass=" + ManagedService.class.getName() + ")" + "(objectClass="
-            + ConfigurationPlugin.class.getName() + "))";
-
     public static final String DYNAMIC_BUNDLE_LOCATION = "dynamic.service.bundleLocation";
 
-    private Hashtable locationToPids = new Hashtable();
-    private Hashtable existingBundleLocations = new Hashtable();
-    private JndiConfigurationStore store;
+    private BlockingQueue<UpdateEvent> queue = new LinkedBlockingQueue<UpdateEvent>();
     private PluginManager pluginManager;
-    private ConfigurationDispatcher configurationDispatcher;
+    private UpdateConsumer updateConsumer;
+    private Thread updateThread;
+
     private InitialContextFactory factory;
+    private JndiConfigurationStore store;
 
-    public static BundleContext bc;
+    public BundleContext bc;
 
     public ConfigurationAdminFactory( ServiceBinderContext sbc )
     {
@@ -86,23 +74,12 @@
         store = new JndiConfigurationStore( factory );
         setConfigurationListener();
 
-        pluginManager = new PluginManager();
-
-        configurationDispatcher = new ConfigurationDispatcher( pluginManager );
-
-        lookForExistingBundleLocations();
-
-        try
-        {
-            bc.addServiceListener( this, FILTER );
-            bc.addBundleListener( this );
-        }
-        catch ( InvalidSyntaxException ise )
-        {
-            log.error( ise.getMessage(), ise );
-        }
+        pluginManager = new PluginManager( bc );
 
-        lookForAlreadyRegisteredServices();
+        updateConsumer = new UpdateConsumer( bc, store, pluginManager, queue );
+        updateThread = new Thread( updateConsumer, "Update Consumer" );
+        updateThread.setDaemon( true );
+        updateThread.start();
     }
 
     /**
@@ -111,273 +88,98 @@
      */
     public void deactivate()
     {
+        updateThread.interrupt();
         store.deactivate();
     }
 
-    public String getName()
-    {
-        return "Apache Configuration Admin Service Factory";
-    }
-
     public void setInitialContextFactory( InitialContextFactory factory )
     {
         this.factory = factory;
-        log.debug( getName() + " has bound to " + factory );
+        log.debug( "Binding to " + factory );
     }
 
     public void unsetInitialContextFactory( InitialContextFactory factory )
     {
         this.factory = null;
-        log.debug( getName() + " has unbound from " + factory );
-    }
-
-    public Object getService( Bundle bundle, ServiceRegistration registration )
-    {
-        log.debug( getName() + " has received a ServiceFactory.getService() call" );
-        return new ConfigurationAdminImpl( store, bundle );
-    }
-
-    public void ungetService( Bundle bundle, ServiceRegistration registration, Object service )
-    {
-        log.debug( getName() + " has received a ServiceFactory.ungetService() call" );
-        // For now we do nothing here
+        log.debug( "Unbinding from " + factory );
     }
 
-    public void bundleChanged( BundleEvent event )
+    public void setManagedServiceFactory( ServiceReference sr, ManagedServiceFactory factory )
     {
-        if ( event.getType() == BundleEvent.UNINSTALLED )
-        {
-            String uninstalledBundleLocation = event.getBundle().getLocation();
-            existingBundleLocations.remove( uninstalledBundleLocation );
-            findAndUnbindConfigurationsDynamicallyBoundTo( uninstalledBundleLocation );
-        }
-        else
-            if ( event.getType() == BundleEvent.INSTALLED )
-            {
-                String installedBundleLocation = event.getBundle().getLocation();
-                existingBundleLocations.put( installedBundleLocation, installedBundleLocation );
-            }
-    }
+        String location = sr.getBundle().getLocation();
+        String servicePid = (String) sr.getProperty( Constants.SERVICE_PID );
 
-    public void serviceChanged( ServiceEvent event )
-    {
-        ServiceReference sr = event.getServiceReference();
-        int eventType = event.getType();
-        String[] objectClasses = (String[]) sr.getProperty( "objectClass" );
+        log.debug( "Binding to factory " + factory + " (" + servicePid + ") from " + location );
 
-        for ( int ii = 0; ii < objectClasses.length; ++ii )
+        if ( servicePid == null )
         {
-            serviceChanged( sr, eventType, objectClasses[ ii ] );
+            log.error( "ManagedServiceFactory without valid service.pid registered by " + location );
+            return;
         }
-    }
 
-    private void setConfigurationListener()
-    {
-        store.setConfigurationListener( new ConfigurationListener()
-        {
-            public void configurationChanged( Dictionary configuration )
-            {
-                try
-                {
-                    updateTargetServicesMatching( new ConfigurationDictionary( configuration ) );
-                }
-                catch ( IOException ioe )
-                {
-                    log.error( ioe.getMessage(), ioe );
-                }
-            }
-        } );
+        dispatchUpdateEvent( sr, UpdateEvent.FACTORY_REGISTERING, servicePid );
     }
 
-    private void lookForAlreadyRegisteredServices()
+    public void unsetManagedServiceFactory( ServiceReference sr, ManagedServiceFactory factory )
     {
-        notifyRegisteredServices( ConfigurationPlugin.class.getName() );
-        notifyRegisteredServices( ManagedServiceFactory.class.getName() );
-        notifyRegisteredServices( ManagedService.class.getName() );
-    }
-
-    private void notifyRegisteredServices( String className )
-    {
-        try
-        {
-            ServiceReference[] srs = bc.getServiceReferences( className, null );
-
-            for ( int ii = 0; srs != null && ii < srs.length; ++ii )
-            {
-                serviceChanged( srs[ ii ], ServiceEvent.REGISTERED, className );
-            }
-        }
-        catch ( InvalidSyntaxException ignored )
-        {
-        }
-    }
-
-    private void lookForExistingBundleLocations()
-    {
-        Bundle[] bs = bc.getBundles();
+        String location = sr.getBundle().getLocation();
+        String servicePid = (String) sr.getProperty( Constants.SERVICE_PID );
 
-        for ( int ii = 0; bs != null && ii < bs.length; ++ii )
-        {
-            existingBundleLocations.put( bs[ ii ].getLocation(), bs[ ii ].getLocation() );
-        }
-    }
+        log.debug( "Unbinding from factory " + factory + " (" + servicePid + ") from " + location );
 
-    private boolean isNonExistingBundleLocation( String bundleLocation )
-    {
-        return bundleLocation != null && existingBundleLocations.get( bundleLocation ) == null;
+        dispatchUpdateEvent( sr, UpdateEvent.FACTORY_UNREGISTERING, servicePid );
     }
 
-    private ConfigurationDictionary bindLocationIfNecessary( ServiceReference[] srs, ConfigurationDictionary config )
-            throws IOException
+    public void setManagedService( ServiceReference sr, ManagedService service )
     {
-        if ( srs == null || srs.length == 0 || config == null )
-        {
-            return config;
-        }
-
-        String configLocation = (String) config.get( ConfigurationAdmin.SERVICE_BUNDLELOCATION );
+        String location = sr.getBundle().getLocation();
+        String servicePid = (String) sr.getProperty( Constants.SERVICE_PID );
 
-        if ( isNonExistingBundleLocation( configLocation ) )
-        {
-            Boolean dynamicLocation = (Boolean) config.get( DYNAMIC_BUNDLE_LOCATION );
+        log.debug( "Binding to service " + service + " (" + servicePid + ") from " + location );
 
-            if ( dynamicLocation != null && dynamicLocation.booleanValue() )
-            {
-                configLocation = null;
-            }
-        }
-
-        if ( configLocation == null )
+        if ( servicePid == null )
         {
-            String factoryPid = (String) config.get( ConfigurationAdmin.SERVICE_FACTORYPID );
-            String servicePid = (String) config.get( Constants.SERVICE_PID );
-            String serviceLocation = srs[ 0 ].getBundle().getLocation();
-
-            ConfigurationDictionary copy = config.createCopy();
-            copy.put( ConfigurationAdmin.SERVICE_BUNDLELOCATION, serviceLocation );
-            copy.put( DYNAMIC_BUNDLE_LOCATION, Boolean.TRUE );
-
-            store.store( servicePid, factoryPid, copy );
-            return copy;
+            log.error( "ManagedService without valid service.pid registered by " + location );
+            return;
         }
 
-        return config;
-    }
-
-    private void findAndUnbindConfigurationsDynamicallyBoundTo( String bundleLocation )
-    {
-        String filter = "(&(" + ConfigurationAdmin.SERVICE_BUNDLELOCATION + "=" + bundleLocation + ")" + "("
-                + DYNAMIC_BUNDLE_LOCATION + "=" + Boolean.TRUE + "))";
-        try
-        {
-            Configuration[] configurations = store.listConfigurations( filter );
-            for ( int ii = 0; configurations != null && ii < configurations.length; ++ii )
-            {
-                configurations[ ii ].setBundleLocation( null );
-            }
-        }
-        catch ( IOException ioe )
-        {
-            log.error( "Error loading configurations while unbinding configurations bound to " + bundleLocation, ioe );
-        }
-        catch ( InvalidSyntaxException ise )
-        {
-            log.error( "Error with filter while unbinding configurations bound to " + bundleLocation, ise );
-        }
+        dispatchUpdateEvent( sr, UpdateEvent.SERVICE_REGISTERING, servicePid );
     }
 
-    private ServiceReference[] filterOnMatchingLocations( ServiceReference[] srs, String configLocation )
+    public void unsetManagedService( ServiceReference sr, ManagedService service )
     {
-        List results = new ArrayList();
+        String location = sr.getBundle().getLocation();
+        String servicePid = (String) sr.getProperty( Constants.SERVICE_PID );
 
-        for ( int ii = 0; ii < srs.length; ++ii )
-        {
-            String serviceLocation = srs[ ii ].getBundle().getLocation();
-            if ( locationsMatch( serviceLocation, configLocation ) )
-            {
-                results.add( srs[ ii ] );
-            }
-            else
-            {
-                log.error( "The bundle " + serviceLocation
-                        + " has registered a ManagedService(Factory) for a pid bound to " + configLocation );
-            }
-        }
+        log.debug( "Unbinding from service " + service + " (" + servicePid + ") from " + location );
 
-        return (ServiceReference[]) results.toArray( new ServiceReference[ results.size() ] );
+        dispatchUpdateEvent( sr, UpdateEvent.SERVICE_UNREGISTERING, servicePid );
     }
 
-    private boolean locationsMatch( String serviceLocation, String configLocation )
+    public Object getService( Bundle bundle, ServiceRegistration registration )
     {
-        if ( configLocation == null )
-        {
-            return false;
-        }
-
-        if ( configLocation.equals( ANY_LOCATION ) || configLocation.equals( serviceLocation ) )
-        {
-            return true;
-        }
-
-        return false;
+        log.debug( "Received a ServiceFactory.getService() call" );
+        return new ConfigurationAdminImpl( store, bundle );
     }
 
-    private void addToLocationToPidsAndCheck( ServiceReference sr )
+    public void ungetService( Bundle bundle, ServiceRegistration registration, Object service )
     {
-        if ( sr == null )
-        {
-            return;
-        }
-
-        String bundleLocation = sr.getBundle().getLocation();
-
-        String pid = (String) sr.getProperty( Constants.SERVICE_PID );
-
-        if ( pid == null )
-        {
-            return;
-        }
-
-        Hashtable pidsForLocation = (Hashtable) locationToPids.get( bundleLocation );
-
-        if ( pidsForLocation == null )
-        {
-            pidsForLocation = new Hashtable();
-            locationToPids.put( bundleLocation, pidsForLocation );
-        }
-
-        if ( pidsForLocation.contains( pid ) )
-        {
-            log.error( "Multiple ManagedServices registered from bundle " + bundleLocation + " for " + pid );
-        }
-
-        pidsForLocation.put( sr, pid );
+        log.debug( "Received a ServiceFactory.ungetService() call" );
+        // For now we do nothing here
     }
 
-    private void removeFromLocationToPids( ServiceReference sr )
+    private void setConfigurationListener()
     {
-        if ( sr == null )
-        {
-            return;
-        }
-
-        String bundleLocation = sr.getBundle().getLocation();
-        Hashtable pidsForLocation = (Hashtable) locationToPids.get( bundleLocation );
-
-        if ( pidsForLocation == null )
-        {
-            return;
-        }
-
-        pidsForLocation.remove( sr );
-
-        if ( pidsForLocation.isEmpty() )
+        store.setConfigurationListener( new ConfigurationListener()
         {
-            locationToPids.remove( bundleLocation );
-        }
+            public void configurationChanged( Dictionary configuration )
+            {
+                updateTargetServiceMatching( new ConfigurationDictionary( configuration ) );
+            }
+        } );
     }
 
-    private void updateTargetServicesMatching( ConfigurationDictionary cd ) throws IOException
+    private void updateTargetServiceMatching( ConfigurationDictionary cd )
     {
         String servicePid = (String) cd.get( Constants.SERVICE_PID );
 
@@ -387,236 +189,52 @@
         }
 
         String factoryPid = (String) cd.get( ConfigurationAdmin.SERVICE_FACTORYPID );
-        String bundleLocation = (String) cd.get( ConfigurationAdmin.SERVICE_BUNDLELOCATION );
 
         if ( factoryPid == null )
         {
-            updateManagedServicesMatching( servicePid, bundleLocation );
-        }
-        else
-        {
-            updateManagedServiceFactoriesMatching( servicePid, factoryPid, bundleLocation );
-        }
-    }
-
-    private void updateManagedServiceFactoriesMatching( String servicePid, String factoryPid, String bundleLocation )
-            throws IOException
-    {
-        ServiceReference[] srs = getTargetServiceReferences( ManagedServiceFactory.class, factoryPid );
-        ConfigurationDictionary cd = store.load( servicePid );
-
-        if ( cd == null )
-        {
-            updateManagedServiceFactories( srs, servicePid, factoryPid, bundleLocation );
-        }
-        else
-        {
-            updateManagedServiceFactories( srs, servicePid, factoryPid, cd );
-        }
-    }
-
-    private void updateManagedServiceFactories( ServiceReference[] srs, String servicePid, String factoryPid,
-            ConfigurationDictionary cd ) throws IOException
-    {
-        ConfigurationDictionary bound = bindLocationIfNecessary( srs, cd );
-        String boundLocation = (String) bound.get( ConfigurationAdmin.SERVICE_BUNDLELOCATION );
-        ServiceReference[] filtered = filterOnMatchingLocations( srs, boundLocation );
-
-        for ( int ii = 0; ii < filtered.length; ++ii )
-        {
-            configurationDispatcher.dispatchUpdateFor( filtered[ ii ], servicePid, factoryPid, bound );
-        }
-    }
-
-    private void updateManagedServiceFactories( ServiceReference[] srs, String servicePid, String factoryPid,
-            String boundLocation )
-    {
-        ServiceReference[] filtered = filterOnMatchingLocations( srs, boundLocation );
-
-        for ( int ii = 0; ii < filtered.length; ++ii )
-        {
-            configurationDispatcher.dispatchUpdateFor( filtered[ ii ], servicePid, factoryPid, null );
-        }
-    }
-
-    private void updateManagedServiceFactory( ServiceReference reference ) throws IOException
-    {
-        String factoryPid = (String) reference.getProperty( Constants.SERVICE_PID );
-
-        ConfigurationDictionary[] cds = store.loadAll( factoryPid );
-
-        if ( cds != null && cds.length > 0 )
-        {
-            ServiceReference[] srs = new ServiceReference[] { reference };
-
-            for ( int ii = 0; ii < cds.length; ++ii )
-            {
-                String servicePid = (String) cds[ ii ].get( Constants.SERVICE_PID );
-                updateManagedServiceFactories( srs, servicePid, factoryPid, cds[ ii ] );
-            }
-        }
-    }
-
-    private void updateManagedServicesMatching( String servicePid, String bundleLocation ) throws IOException
-    {
-        ServiceReference[] srs = getTargetServiceReferences( ManagedService.class, servicePid );
-        ConfigurationDictionary cd = store.load( servicePid );
-
-        if ( cd == null )
-        {
-            updateManagedServices( srs, servicePid, bundleLocation, null );
+            ServiceReference sr = getTargetServiceReference( ManagedService.class, servicePid );
+            dispatchUpdateEvent( sr, UpdateEvent.SERVICE_UPDATING, servicePid );
         }
         else
         {
-            ConfigurationDictionary bound = bindLocationIfNecessary( srs, cd );
-            String boundLocation = (String) bound.get( ConfigurationAdmin.SERVICE_BUNDLELOCATION );
-
-            updateManagedServices( srs, servicePid, boundLocation, bound );
+            ServiceReference sr = getTargetServiceReference( ManagedServiceFactory.class, factoryPid );
+            dispatchUpdateEvent( sr, UpdateEvent.FACTORY_UPDATING, servicePid );
         }
     }
 
-    private void updateManagedServices( ServiceReference[] srs, String servicePid, String boundLocation,
-            ConfigurationDictionary bound )
+    private ServiceReference getTargetServiceReference( Class clazz, String pid )
     {
-        ServiceReference[] filtered = filterOnMatchingLocations( srs, boundLocation );
-
-        for ( int ii = 0; ii < filtered.length; ++ii )
-        {
-            configurationDispatcher.dispatchUpdateFor( filtered[ ii ], servicePid, null, bound );
-        }
-    }
-
-    private void updateManagedService( ServiceReference sr ) throws IOException
-    {
-        final String servicePid = (String) sr.getProperty( Constants.SERVICE_PID );
-        ServiceReference[] srs = new ServiceReference[] { sr };
-        ConfigurationDictionary cd = store.load( servicePid );
+        String filter = "(" + Constants.SERVICE_PID + "=" + pid + ")";
 
-        if ( cd == null )
-        {
-            for ( int ii = 0; ii < srs.length; ++ii )
-            {
-                configurationDispatcher.dispatchUpdateFor( srs[ ii ], servicePid, null, null );
-            }
-        }
-        else
+        try
         {
-            cd = bindLocationIfNecessary( srs, cd );
-            String boundLocation = (String) cd.get( ConfigurationAdmin.SERVICE_BUNDLELOCATION );
-            srs = filterOnMatchingLocations( srs, boundLocation );
+            ServiceReference[] srs = bc.getServiceReferences( clazz.getName(), filter );
 
-            for ( int ii = 0; ii < srs.length; ++ii )
+            if ( srs.length > 0 )
             {
-                configurationDispatcher.dispatchUpdateFor( srs[ ii ], servicePid, null, cd );
+                return srs[ 0 ];
             }
-        }
-    }
-
-    private ServiceReference[] getTargetServiceReferences( Class clazz, String pid )
-    {
-        String filter = "(" + Constants.SERVICE_PID + "=" + pid + ")";
 
-        try
-        {
-            ServiceReference[] srs = bc.getServiceReferences( clazz.getName(), filter );
-            return srs == null ? new ServiceReference[ 0 ] : srs;
+            return null;
         }
         catch ( InvalidSyntaxException e )
         {
-            log.error( "Invalid syntax in ldap filter " + filter, e );
-            return new ServiceReference[ 0 ];
+            log.error( "Invalid syntax in LDAP filter " + filter, e );
+            return null;
         }
     }
 
-    private void serviceChanged( ServiceReference sr, int eventType, String objectClass )
+    private void dispatchUpdateEvent( ServiceReference sr, int type, String servicePid )
     {
-        if ( ManagedServiceFactory.class.getName().equals( objectClass ) )
-        {
-            managedServiceFactoryChanged( sr, eventType );
-        }
-        else
-            if ( ManagedService.class.getName().equals( objectClass ) )
-            {
-                managedServiceChanged( sr, eventType );
-            }
-            else
-                if ( ConfigurationPlugin.class.getName().equals( objectClass ) )
-                {
-                    pluginManager.configurationPluginChanged( sr, eventType );
-                }
-    }
+        UpdateEvent update = new UpdateEvent( sr, type, servicePid );
 
-    private void managedServiceFactoryChanged( ServiceReference sr, int eventType )
-    {
-        String servicePid = (String) sr.getProperty( Constants.SERVICE_PID );
-
-        switch ( eventType )
+        try
         {
-            case ServiceEvent.REGISTERED:
-                configurationDispatcher.addQueueFor( sr );
-
-                if ( servicePid == null )
-                {
-                    String bundleLocation = sr.getBundle().getLocation();
-                    log.error( "ManagedServiceFactory without valid service.pid registered by " + bundleLocation );
-                    return;
-                }
-
-                addToLocationToPidsAndCheck( sr );
-
-                try
-                {
-                    updateManagedServiceFactory( sr );
-                }
-                catch ( IOException e )
-                {
-                    log.error( "Error while notifying services.", e );
-                }
-
-                break;
-            case ServiceEvent.MODIFIED:
-                break;
-            case ServiceEvent.UNREGISTERING:
-                removeFromLocationToPids( sr );
-                configurationDispatcher.removeQueueFor( sr );
-                break;
+            queue.put( update );
         }
-    }
-
-    private void managedServiceChanged( ServiceReference sr, int eventType )
-    {
-        final String servicePid = (String) sr.getProperty( Constants.SERVICE_PID );
-
-        switch ( eventType )
+        catch ( InterruptedException ie )
         {
-            case ServiceEvent.REGISTERED:
-                configurationDispatcher.addQueueFor( sr );
-
-                if ( servicePid == null )
-                {
-                    String bundleLocation = sr.getBundle().getLocation();
-                    log.error( "ManagedService without valid service.pid registered by " + bundleLocation );
-                    return;
-                }
-
-                addToLocationToPidsAndCheck( sr );
-
-                try
-                {
-                    updateManagedService( sr );
-                }
-                catch ( IOException e )
-                {
-                    log.error( "Error while notifying services.", e );
-                }
-
-                break;
-            case ServiceEvent.MODIFIED:
-                break;
-            case ServiceEvent.UNREGISTERING:
-                removeFromLocationToPids( sr );
-                configurationDispatcher.removeQueueFor( sr );
-                break;
+            log.error( ie.getMessage(), ie );
         }
     }
 }

Modified: directory/sandbox/trunk/osgi-spec/trunk/configuration/src/main/java/org/apache/configuration/impl/ConfigurationImpl.java
URL: http://svn.apache.org/viewcvs/directory/sandbox/trunk/osgi-spec/trunk/configuration/src/main/java/org/apache/configuration/impl/ConfigurationImpl.java?rev=290673&r1=290672&r2=290673&view=diff
==============================================================================
--- directory/sandbox/trunk/osgi-spec/trunk/configuration/src/main/java/org/apache/configuration/impl/ConfigurationImpl.java (original)
+++ directory/sandbox/trunk/osgi-spec/trunk/configuration/src/main/java/org/apache/configuration/impl/ConfigurationImpl.java Wed Sep 21 03:15:53 2005
@@ -114,7 +114,7 @@
         return properties.createCopyAndRemoveLocation();
     }
 
-    public ConfigurationDictionary getConfigurationDictionary()
+    private ConfigurationDictionary getConfigurationDictionary()
     {
         return properties;
     }

Added: directory/sandbox/trunk/osgi-spec/trunk/configuration/src/main/java/org/apache/configuration/impl/LDAPQuery.java
URL: http://svn.apache.org/viewcvs/directory/sandbox/trunk/osgi-spec/trunk/configuration/src/main/java/org/apache/configuration/impl/LDAPQuery.java?rev=290673&view=auto
==============================================================================
--- directory/sandbox/trunk/osgi-spec/trunk/configuration/src/main/java/org/apache/configuration/impl/LDAPQuery.java (added)
+++ directory/sandbox/trunk/osgi-spec/trunk/configuration/src/main/java/org/apache/configuration/impl/LDAPQuery.java Wed Sep 21 03:15:53 2005
@@ -0,0 +1,374 @@
+/*
+ * Copyright (c) 2003, KNOPFLERFISH project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials
+ *   provided with the distribution.
+ *
+ * - Neither the name of the KNOPFLERFISH project nor the names of its
+ *   contributors may be used to endorse or promote products derived
+ *   from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.apache.configuration.impl;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.math.BigInteger;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.Vector;
+
+import org.osgi.framework.InvalidSyntaxException;
+
+/**
+ * LDAP filter functions.
+ *
+ * @author Tommy Bohlin
+ * @author Jan Stein
+ * @version $Revision: 1.1.1.1 $
+ */
+class LDAPQuery {
+  private static final char WILDCARD=65535;
+  private static final String WILDCARD_STRING= new String(new char [] { WILDCARD });
+
+  private static final String NULL     ="Null query";
+  private static final String GARBAGE  ="Trailing garbage";
+  private static final String MALFORMED="Malformed query";
+  private static final String EMPTY    ="Empty list";
+  private static final String SUBEXPR  ="No subexpression";
+  private static final String OPERATOR ="Undefined operator";
+  private static final String TRUNCATED="Truncated expression";
+  private static final String EQUALITY ="Only equality supported";
+
+  private static final int EQ     = 0;
+  private static final int LE     = 1;
+  private static final int GE     = 2;
+  private static final int APPROX = 3;
+
+  private static final Class[]  classArg=new Class[] { String.class };
+  private static final Class[] objectArg=new Class[] { Object.class };
+
+  private static Class classBigDecimal;
+  private static Constructor consBigDecimal;
+  private static Method compBigDecimal;
+
+  static {
+    try {
+      classBigDecimal = Class.forName("java.math.BigDecimal");
+      consBigDecimal = classBigDecimal.getConstructor(new Class [] { String.class });
+      compBigDecimal = classBigDecimal.getMethod("compareTo", new Class [] { classBigDecimal });
+    } catch (Exception ignore) {
+      classBigDecimal = null;
+    }
+  }
+
+  boolean val;
+  String tail;
+  Dictionary prop;
+
+  public static void check(String q) throws InvalidSyntaxException {
+    query(q, null);
+  }
+
+  public static boolean query(String q, Dictionary p)
+    throws InvalidSyntaxException {
+    LDAPQuery lq=new LDAPQuery(q,p);
+    lq.doQuery();
+    if(lq.tail.length()>0) lq.error(GARBAGE);
+    return lq.val;
+  }
+
+  LDAPQuery(String q, Dictionary p) throws InvalidSyntaxException {
+    if(q==null || q.length()==0) error(NULL);
+    tail=q;
+    prop=p;
+  }
+
+  void doQuery() throws InvalidSyntaxException {
+    if(tail.length()<3 || !prefix("(")) error(MALFORMED);
+
+    switch(tail.charAt(0)) {
+    case '&': doAnd(); break;
+    case '|': doOr(); break;
+    case '!': doNot(); break;
+    default: doSimple(); break;
+    }
+
+    if(!prefix(")")) error(MALFORMED);
+  }
+
+  private void doAnd() throws InvalidSyntaxException {
+    tail=tail.substring(1);
+    boolean val1=true;
+    if(!tail.startsWith("(")) error(EMPTY);
+    do {
+      doQuery();
+      if(!val) val1=false;
+    } while(tail.startsWith("("));
+    val=val1;
+  }
+
+  private void doOr() throws InvalidSyntaxException {
+    tail=tail.substring(1);
+    boolean val1=false;
+    if(!tail.startsWith("(")) error(EMPTY);
+    do {
+      doQuery();
+      if(val) val1=true;
+    } while(tail.startsWith("("));
+    val=val1;
+  }
+
+  private void doNot() throws InvalidSyntaxException {
+    tail=tail.substring(1);
+    if(!tail.startsWith("(")) error(SUBEXPR);
+    doQuery();
+    val=!val;
+  }
+
+  private void doSimple() throws InvalidSyntaxException {
+    int op=0;
+    Object attr=getAttr();
+
+    if(prefix("=" )) op=EQ;
+    else if(prefix("<=")) op=LE;
+    else if(prefix(">=")) op=GE;
+    else if(prefix("~=")) op=APPROX;
+    else error(OPERATOR);
+
+    val=compare(attr,op,getValue());
+  }
+
+  private boolean prefix(String pre) {
+    if(!tail.startsWith(pre)) return false;
+    tail=tail.substring(pre.length());
+    return true;
+  }
+
+  private Object getAttr() {
+    int len=tail.length();
+    int ix=0;
+    label:for(;ix<len;ix++) {
+      switch(tail.charAt(ix)) {
+      case '(':
+      case ')':
+      case '<':
+      case '>':
+      case '=':
+      case '~':
+      case '*':
+      case '\\':
+	break label;
+      }
+    }
+    String attr=tail.substring(0,ix);
+    tail=tail.substring(ix);
+    return prop != null ? prop.get(attr) : null;
+  }
+
+  private String getValue() {
+    StringBuffer sb=new StringBuffer();
+    int len=tail.length();
+    int ix=0;
+    label:for(;ix<len;ix++) {
+      char c=tail.charAt(ix);
+      switch(c) {
+      case '(':
+      case ')':
+	break label;
+      case '*':
+	sb.append(WILDCARD);
+	break;
+      case '\\':
+	if(ix==len-1) break label;
+	sb.append(tail.charAt(++ix));
+	break;
+      default:
+	sb.append(c);
+	break;
+      }
+    }
+    tail=tail.substring(ix);
+    return sb.toString();
+  }
+
+  private void error(String m) throws InvalidSyntaxException {
+    throw new InvalidSyntaxException(m,tail);
+  }
+
+  private boolean compare(Object obj, int op, String s) {
+    if(obj==null) return false;
+    if(op==EQ && s.equals(WILDCARD_STRING)) return true;
+    try {
+      if(obj instanceof String) {
+	return compareString((String)obj,op,s);
+      } else if(obj instanceof Character) {
+	return compareString(obj.toString(),op,s);
+      } else if(obj instanceof Boolean) {
+	if (op==LE || op==GE)
+	  return false;
+	return ((Boolean)obj).equals(new Boolean(s));
+      } else if (obj instanceof Number) {
+	if (obj instanceof Byte) {
+	  switch(op) {
+	  case LE:
+	    return ((Byte)obj).byteValue() <= Byte.parseByte(s);
+	  case GE:
+	    return ((Byte)obj).byteValue() >= Byte.parseByte(s);
+	  default: /*APPROX and EQ*/
+	    return (new Byte(s)).equals(obj);
+	  }
+	} else if (obj instanceof Integer) {
+	  switch(op) {
+	  case LE:
+	    return ((Integer)obj).intValue() <= Integer.parseInt(s);
+	  case GE:
+	    return ((Integer)obj).intValue() >= Integer.parseInt(s);
+	  default: /*APPROX and EQ*/
+	    return (new Integer(s)).equals(obj);
+	  }
+	} else if (obj instanceof Short) {
+	  switch(op) {
+	  case LE:
+	    return ((Short)obj).shortValue() <= Short.parseShort(s);
+	  case GE:
+	    return ((Short)obj).shortValue() >= Short.parseShort(s);
+	  default: /*APPROX and EQ*/
+	    return (new Short(s)).equals(obj);
+	  }
+	} else if (obj instanceof Long) {
+	  switch(op) {
+	  case LE:
+	    return ((Long)obj).longValue() <= Long.parseLong(s);
+	  case GE:
+	    return ((Long)obj).longValue() >= Long.parseLong(s);
+	  default: /*APPROX and EQ*/
+	    return (new Long(s)).equals(obj);
+	  }
+	} else if (obj instanceof Float) {
+	  switch(op) {
+	  case LE:
+	    return ((Float)obj).floatValue() <= (new Float(s)).floatValue();
+	  case GE:
+	    return ((Float)obj).floatValue() >= (new Float(s)).floatValue();
+	  default: /*APPROX and EQ*/
+	    return (new Float(s)).equals(obj);
+	  }
+	} else if (obj instanceof Double) {
+	  switch(op) {
+	  case LE:
+	    return ((Double)obj).doubleValue() <= (new Double(s)).doubleValue();
+	  case GE:
+	    return ((Double)obj).doubleValue() >= (new Double(s)).doubleValue();
+	  default: /*APPROX and EQ*/
+	    return (new Double(s)).equals(obj);
+	  }
+	} else if (obj instanceof BigInteger) {
+	  int c = ((BigInteger)obj).compareTo(new BigInteger(s));
+	  switch(op) {
+	  case LE:
+	    return c <= 0;
+	  case GE:
+	    return c >= 0;
+	  default: /*APPROX and EQ*/
+	    return c == 0;
+	  }
+	} else if (classBigDecimal != null && classBigDecimal.isInstance(obj)) {
+	  Object n = consBigDecimal.newInstance(new Object [] { s });
+	  int c = ((Integer)compBigDecimal.invoke(obj, new Object [] { n })).intValue();
+	  switch(op) {
+	  case LE:
+	    return c <= 0;
+	  case GE:
+	    return c >= 0;
+	  default: /*APPROX and EQ*/
+	    return c == 0;
+	  }
+	} 
+      } else if(obj instanceof Vector) {
+	for(Enumeration e=((Vector)obj).elements();e.hasMoreElements();)
+	  if(compare(e.nextElement(),op,s)) return true;
+      } else if(obj.getClass().isArray()) {
+	int len=Array.getLength(obj);
+	for(int i=0;i<len;i++)
+	  if(compare(Array.get(obj,i),op,s)) return true;
+      }
+    } catch(Exception e) { }
+    return false;
+  }
+
+  static boolean compareString(String s1, int op, String s2) {
+    switch(op) {
+    case LE:
+      return s1.compareTo(s2) <= 0;
+    case GE:
+      return s1.compareTo(s2) >= 0;
+    case EQ:
+      return patSubstr(s1,s2);
+    case APPROX:
+      return fixupString(s2).equals(fixupString(s1));
+    default:
+      return false;
+    }
+  }
+
+  static String fixupString(String s) {
+    StringBuffer sb=new StringBuffer();
+    int len=s.length();
+    boolean isStart=true;
+    boolean isWhite=false;
+    for(int i=0;i<len;i++) {
+      char c=s.charAt(i);
+      if(Character.isWhitespace(c)) {
+	isWhite=true;
+      } else {
+	if(!isStart && isWhite) sb.append(' ');
+	if(Character.isUpperCase(c)) c=Character.toLowerCase(c);
+	sb.append(c);
+	isStart=false;
+	isWhite=false;
+      }
+    }
+    return sb.toString();
+  }
+
+  static boolean patSubstr(String s, String pat) {
+    if(s==null) return false;
+    if(pat.length()==0) return s.length()==0;
+    if(pat.charAt(0)==WILDCARD) {
+      pat=pat.substring(1);
+      for(;;) {
+	if(patSubstr(s,pat)) return true;
+	if(s.length()==0) return false;
+	s=s.substring(1);
+      }
+    } else {
+      if(s.length()==0 || s.charAt(0)!=pat.charAt(0)) return false;
+      return patSubstr(s.substring(1),pat.substring(1));
+    }
+  }
+}
+

Propchange: directory/sandbox/trunk/osgi-spec/trunk/configuration/src/main/java/org/apache/configuration/impl/LDAPQuery.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: directory/sandbox/trunk/osgi-spec/trunk/configuration/src/main/java/org/apache/configuration/impl/LDAPQuery.java
------------------------------------------------------------------------------
    svn:executable = *

Modified: directory/sandbox/trunk/osgi-spec/trunk/configuration/src/main/java/org/apache/configuration/impl/PluginManager.java
URL: http://svn.apache.org/viewcvs/directory/sandbox/trunk/osgi-spec/trunk/configuration/src/main/java/org/apache/configuration/impl/PluginManager.java?rev=290673&r1=290672&r2=290673&view=diff
==============================================================================
--- directory/sandbox/trunk/osgi-spec/trunk/configuration/src/main/java/org/apache/configuration/impl/PluginManager.java (original)
+++ directory/sandbox/trunk/osgi-spec/trunk/configuration/src/main/java/org/apache/configuration/impl/PluginManager.java Wed Sep 21 03:15:53 2005
@@ -17,12 +17,15 @@
 
 package org.apache.configuration.impl;
 
-import java.util.Enumeration;
-import java.util.Hashtable;
-import java.util.Vector;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.TreeMap;
 
+import org.osgi.framework.BundleContext;
 import org.osgi.framework.Constants;
+import org.osgi.framework.InvalidSyntaxException;
 import org.osgi.framework.ServiceEvent;
+import org.osgi.framework.ServiceListener;
 import org.osgi.framework.ServiceReference;
 import org.osgi.service.cm.ConfigurationPlugin;
 import org.slf4j.Logger;
@@ -31,85 +34,78 @@
 /**
  * This class is responsible for managing ConfigurationPlugins.
  */
-final class PluginManager
+final class PluginManager implements ServiceListener
 {
     /** the log for this class */
     private static final Logger log = LoggerFactory.getLogger( PluginManager.class );
 
     private final static String CM_RANKING = "service.cmRanking";
-    private final static String CM_TARGET = ConfigurationPlugin.CM_TARGET;
 
-    /**
-     * Sorted Vector of ServiceReferences to ConfigurationPlugins.
-     */
-    private Vector preModificationPlugins = new Vector();
+    private static final String FILTER = "(objectClass=" + ConfigurationPlugin.class.getName() + ")";
+
+    private BundleContext bc;
 
     /**
-     * Sorted Vector of ServiceReferences to ConfigurationPlugins.
+     * Sorted Map of ranked ServiceReferences.
      */
-    private Vector modifyingPlugins = new Vector();
+    private Map<RankingKey, ServiceReference> preModificationPlugins = new TreeMap<RankingKey, ServiceReference>();
 
     /**
-     * Sorted Vector of ServiceReferences to ConfigurationPlugins.
+     * Sorted Map of ranked ServiceReferences.
      */
-    private Vector postModificationPlugins = new Vector();
+    private Map<RankingKey, ServiceReference> modifyingPlugins = new TreeMap<RankingKey, ServiceReference>();
 
     /**
-     * Hashtable mapping a ServiceReference to its ranking (Integer).
+     * Sorted Map of ranked ServiceReferences.
      */
-    private Hashtable rankings = new Hashtable();
+    private Map<RankingKey, ServiceReference> postModificationPlugins = new TreeMap<RankingKey, ServiceReference>();
+
+    PluginManager( BundleContext bc )
+    {
+        this.bc = bc;
+
+        try
+        {
+            bc.addServiceListener( this, FILTER );
+        }
+        catch ( InvalidSyntaxException ise )
+        {
+            log.error( ise.getMessage(), ise );
+        }
+    }
 
     /**
      * Handle ConfigurationPlugin ServiceEvents.
      * 
-     * @param serviceReference ServiceReference of the plugin the event concerns
-     * @param eventType Type of the event that caused the change
+     * @param event The event that caused the change
      */
-    public synchronized void configurationPluginChanged( ServiceReference serviceReference, int eventType )
+    public synchronized void serviceChanged( ServiceEvent event )
     {
-        Object rankingProperty = serviceReference.getProperty( CM_RANKING );
+        ServiceReference sr = event.getServiceReference();
+        int eventType = event.getType();
+        Object rankingProperty = sr.getProperty( CM_RANKING );
+
+        Integer ranking;
 
-        if ( rankingProperty == null )
+        if ( rankingProperty == null || rankingProperty.getClass() != Integer.class )
         {
-            rankingProperty = new Integer( 0 );
+            ranking = new Integer( 0 );
         }
         else
-            if ( rankingProperty.getClass() != Integer.class )
-            {
-                rankingProperty = new Integer( 0 );
-            }
-
-        Long serviceId = (Long) serviceReference.getProperty( Constants.SERVICE_ID );
-
-        if ( serviceId == null )
         {
-            log.error( "Missing service id for a ConfigurationPlugin" );
-            return;
+            ranking = (Integer) rankingProperty;
         }
 
-        int ranking = ( (Integer) rankingProperty ).intValue();
+        RankingKey key = new RankingKey( ranking, sr );
 
         switch ( eventType )
         {
             case ServiceEvent.REGISTERED:
-                rankings.put( serviceId, rankingProperty );
-                insertPluginReference( serviceReference, ranking );
-                break;
             case ServiceEvent.MODIFIED:
-                int oldRanking = ( (Integer) rankings.get( serviceId ) ).intValue();
-
-                if ( ranking == oldRanking )
-                {
-                    return;
-                }
-
-                removePluginReference( serviceId, oldRanking );
-                rankings.put( serviceId, rankingProperty );
-                insertPluginReference( serviceReference, ranking );
+                insertPluginReference( ranking, key, sr );
                 break;
             case ServiceEvent.UNREGISTERING:
-                rankings.remove( serviceId );
-                removePluginReference( serviceId, ranking );
+                removePluginReference( key );
                 break;
             default:
                 break;
@@ -123,88 +119,36 @@
      * @param serviceReference The ServiceReference.
      * @param ranking The ranking the ServiceReference.
      */
-    private void insertPluginReference( ServiceReference serviceReference, int ranking )
+    private void insertPluginReference( int ranking, RankingKey key, ServiceReference serviceReference )
     {
+        removePluginReference( key );
+
         if ( ranking < 0 )
         {
-            insertPluginReference( serviceReference, ranking, preModificationPlugins );
+            preModificationPlugins.put( key, serviceReference );
         }
         else
             if ( 0 <= ranking && ranking <= 1000 )
             {
-                insertPluginReference( serviceReference, ranking, modifyingPlugins );
+                modifyingPlugins.put( key, serviceReference );
             }
             else
                 if ( ranking > 1000 )
                 {
-                    insertPluginReference( serviceReference, ranking, postModificationPlugins );
+                    postModificationPlugins.put( key, serviceReference );
                 }
     }
 
     /**
-     * Insert a ServiceReference in a Vector sorted on cm.ranking property.
+     * Remove a plugin service reference given a ranking key.
      * 
-     * @param serviceReference The ServiceReference.
-     * @param pluginsVector The vector.
+     * @param key The key to the service reference of the ConfigurationPlugin.
      */
-    private void insertPluginReference( ServiceReference serviceReference, int ranking, Vector pluginsVector )
+    private void removePluginReference( RankingKey key )
     {
-        int ii;
-
-        for ( ii = 0; ii < pluginsVector.size(); ++ii )
-        {
-            ServiceReference nextReference = (ServiceReference) pluginsVector.elementAt( ii );
-            Long serviceId = (Long) nextReference.getProperty( Constants.SERVICE_ID );
-            Integer rankingOfNextReference = (Integer) rankings.get( serviceId );
-
-            if ( ranking < rankingOfNextReference.intValue() )
-            {
-                break;
-            }
-        }
-
-        pluginsVector.insertElementAt( serviceReference, ii );
-    }
-
-    /**
-     * Remove a ServiceReference to a ConfigurationPlugin given a service.id
-     * and a ranking.
-     * 
-     * @param serviceId The service.id of the ConfigurationPlugin.
-     * @param ranking The ranking of the ConfigurationPlugin.
-     */
-    private void removePluginReference( Object serviceId, int ranking )
-    {
-        if ( ranking < 0 )
-        {
-            removePluginReference( serviceId, preModificationPlugins );
-        }
-        else
-            if ( 0 <= ranking && ranking <= 1000 )
-            {
-                removePluginReference( serviceId, modifyingPlugins );
-            }
-            else
-                if ( ranking > 1000 )
-                {
-                    removePluginReference( serviceId, postModificationPlugins );
-                }
-    }
-
-    private void removePluginReference( Object serviceId, Vector pluginsVector )
-    {
-        for ( int ii = 0; ii < pluginsVector.size(); ++ii )
-        {
-            ServiceReference serviceReference = (ServiceReference) pluginsVector.elementAt( ii );
-
-            Long currentId = (Long) serviceReference.getProperty( Constants.SERVICE_ID );
-
-            if ( currentId.equals( serviceId ) )
-            {
-                pluginsVector.removeElementAt( ii );
-                return;
-            }
-        }
+        preModificationPlugins.remove( key );
+        modifyingPlugins.remove( key );
+        postModificationPlugins.remove( key );
     }
 
     /**
@@ -228,9 +172,7 @@
         }
 
         callPlugins( targetServiceReference, dictionary, preModificationPlugins, false );
-
         dictionary = callPlugins( targetServiceReference, dictionary, modifyingPlugins, true );
-
         callPlugins( targetServiceReference, dictionary, postModificationPlugins, false );
 
         if ( dictionary != null )
@@ -242,7 +184,7 @@
     }
 
     /**
-     * Call all plugins contained in a Vector and optionally allow
+     * Call all plugins contained in collection of plugins and optionally allow
      * modifications.
      * 
      * @param targetServiceReference Reference to the target ManagedService(Factory).
@@ -253,23 +195,22 @@
      * @return The modified configuration dictionary.
      */
     private ConfigurationDictionary callPlugins( ServiceReference targetServiceReference,
-            ConfigurationDictionary dictionary, Vector plugins, boolean allowModification )
+            ConfigurationDictionary dictionary, Map plugins, boolean allowModification )
     {
         String pid = (String) targetServiceReference.getProperty( Constants.SERVICE_PID );
         ConfigurationDictionary currentDictionary = dictionary;
-        Enumeration e = plugins.elements();
+        Iterator it = plugins.values().iterator();
 
-        while ( e.hasMoreElements() )
+        while ( it.hasNext() )
         {
-            ServiceReference pluginReference = (ServiceReference) e.nextElement();
+            ServiceReference pluginReference = (ServiceReference) it.next();
 
-            // Only call the plugin if no cm.target is specified or if it
-            // matches the pid of the target service
-            String cmTarget = (String) pluginReference.getProperty( CM_TARGET );
+            // Call the plugin if no cm.target is specified or if target matches
+            String cmTarget = (String) pluginReference.getProperty( ConfigurationPlugin.CM_TARGET );
 
             if ( cmTarget == null || cmTarget.equals( pid ) )
             {
-                ConfigurationPlugin plugin = (ConfigurationPlugin) ConfigurationAdminFactory.bc.getService( pluginReference );
+                ConfigurationPlugin plugin = (ConfigurationPlugin) bc.getService( pluginReference );
 
                 if ( plugin == null )
                 {
@@ -297,5 +238,60 @@
         }
 
         return currentDictionary;
+    }
+
+    private class RankingKey implements Comparable
+    {
+        private Integer ranking;
+        private ServiceReference reference;
+
+        private RankingKey( Integer ranking, ServiceReference reference )
+        {
+            this.ranking = ranking;
+            this.reference = reference;
+        }
+
+        public int compareTo( Object o )
+        {
+            final int BEFORE = -1;
+            final int EQUAL = 0;
+            final int AFTER = 1;
+
+            if ( this == o )
+            {
+                return EQUAL;
+            }
+
+            final RankingKey that = (RankingKey) o;
+
+            if ( this.reference == that.reference )
+            {
+                return EQUAL;
+            }
+
+            if ( this.ranking < that.ranking )
+            {
+                return BEFORE;
+            }
+
+            if ( this.ranking > that.ranking )
+            {
+                return AFTER;
+            }
+
+            return EQUAL;
+        }
+
+        public boolean equals( Object o )
+        {
+            if ( this == o )
+            {
+                return true;
+            }
+
+            final RankingKey that = (RankingKey) o;
+
+            return this.reference == that.reference;
+        }
     }
 }

Added: directory/sandbox/trunk/osgi-spec/trunk/configuration/src/main/java/org/apache/configuration/impl/UpdateConsumer.java
URL: http://svn.apache.org/viewcvs/directory/sandbox/trunk/osgi-spec/trunk/configuration/src/main/java/org/apache/configuration/impl/UpdateConsumer.java?rev=290673&view=auto
==============================================================================
--- directory/sandbox/trunk/osgi-spec/trunk/configuration/src/main/java/org/apache/configuration/impl/UpdateConsumer.java (added)
+++ directory/sandbox/trunk/osgi-spec/trunk/configuration/src/main/java/org/apache/configuration/impl/UpdateConsumer.java Wed Sep 21 03:15:53 2005
@@ -0,0 +1,481 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+
+package org.apache.configuration.impl;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.BlockingQueue;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleEvent;
+import org.osgi.framework.BundleListener;
+import org.osgi.framework.Constants;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.cm.Configuration;
+import org.osgi.service.cm.ConfigurationAdmin;
+import org.osgi.service.cm.ConfigurationException;
+import org.osgi.service.cm.ManagedService;
+import org.osgi.service.cm.ManagedServiceFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class UpdateConsumer implements Runnable, BundleListener
+{
+    /** the log for this class */
+    private static final Logger log = LoggerFactory.getLogger( ConfigurationAdminFactory.class );
+
+    public static final String DYNAMIC_BUNDLE_LOCATION = "dynamic.service.bundleLocation";
+
+    private static final String ANY_LOCATION = "*";
+    private Map<String, Set<String>> locationToPids = new HashMap<String, Set<String>>();
+    private Set<String> existingBundleLocations = new HashSet<String>();
+
+    private BundleContext bc;
+    private JndiConfigurationStore store;
+    private PluginManager pluginManager;
+    private BlockingQueue<UpdateEvent> queue;
+
+    public UpdateConsumer( BundleContext bc, JndiConfigurationStore store, PluginManager pluginManager,
+            BlockingQueue<UpdateEvent> queue )
+    {
+        this.bc = bc;
+        this.store = store;
+        this.pluginManager = pluginManager;
+        this.queue = queue;
+
+        lookForExistingBundleLocations();
+    }
+
+    public void bundleChanged( BundleEvent event )
+    {
+        if ( event.getType() == BundleEvent.UNINSTALLED )
+        {
+            String uninstalledBundleLocation = event.getBundle().getLocation();
+            existingBundleLocations.remove( uninstalledBundleLocation );
+            findAndUnbindConfigurationsDynamicallyBoundTo( uninstalledBundleLocation );
+        }
+        else
+            if ( event.getType() == BundleEvent.INSTALLED )
+            {
+                String installedBundleLocation = event.getBundle().getLocation();
+                existingBundleLocations.add( installedBundleLocation );
+            }
+    }
+
+    public void run()
+    {
+        UpdateEvent update;
+
+        while ( true )
+        {
+            try
+            {
+                update = queue.take();
+                log.debug( "Dequeued update " + update.toString() );
+                handleEvent( update );
+            }
+            catch ( InterruptedException ie )
+            {
+                if ( log.isDebugEnabled() )
+                {
+                    log.debug( ie.getMessage(), ie );
+                }
+                else
+                {
+                    log.info( ie.getMessage() );
+                }
+            }
+        }
+    }
+
+    private void handleEvent( UpdateEvent update )
+    {
+        ServiceReference sr = (ServiceReference) update.getSource();
+
+        if ( sr == null )
+        {
+            return;
+        }
+
+        String servicePid = update.getServicePid();
+
+        try
+        {
+            switch ( update.getType() )
+            {
+                case UpdateEvent.SERVICE_REGISTERING:
+                    addLocationToPidsAndCheck( sr );
+                    configureManagedService( sr );
+                    break;
+                case UpdateEvent.FACTORY_REGISTERING:
+                    addLocationToPidsAndCheck( sr );
+                    configureManagedServiceFactory( sr );
+                    break;
+                case UpdateEvent.SERVICE_UPDATING:
+                    updateManagedServiceMatching( sr );
+                    break;
+                case UpdateEvent.FACTORY_UPDATING:
+                    updateManagedServiceFactoryMatching( sr, servicePid );
+                    break;
+                case UpdateEvent.SERVICE_UNREGISTERING:
+                    removeFromLocationToPids( sr );
+                    break;
+                case UpdateEvent.FACTORY_UNREGISTERING:
+                    removeFromLocationToPids( sr );
+                    break;
+                default:
+                    break;
+            }
+        }
+        catch ( IOException ioe )
+        {
+            log.debug( ioe.getMessage(), ioe );
+        }
+    }
+
+    private void update( ServiceReference sr, String pid, String factoryPid, ConfigurationDictionary cd )
+    {
+        Object targetService = getTargetService( sr );
+
+        if ( targetService == null )
+        {
+            return;
+        }
+
+        ConfigurationDictionary processedConfiguration = pluginManager.callPluginsAndCreateACopy( sr, cd );
+
+        try
+        {
+            if ( factoryPid != null )
+            {
+                update( (ManagedServiceFactory) targetService, pid, cd, processedConfiguration );
+            }
+            else
+            {
+                update( (ManagedService) targetService, processedConfiguration );
+            }
+        }
+        catch ( ConfigurationException ce )
+        {
+            log.error( ce.getMessage(), ce );
+        }
+    }
+
+    private void update( ManagedServiceFactory targetService, String pid, ConfigurationDictionary configuration,
+            ConfigurationDictionary processedConfiguration ) throws ConfigurationException
+    {
+        if ( targetService == null )
+        {
+            return;
+        }
+
+        if ( configuration == null )
+        {
+            targetService.deleted( pid );
+        }
+        else
+            if ( processedConfiguration == null )
+            {
+                targetService.updated( pid, null );
+            }
+            else
+            {
+                targetService.updated( pid, configuration );
+            }
+    }
+
+    private void update( ManagedService targetService, ConfigurationDictionary processedConfiguration )
+            throws ConfigurationException
+    {
+        if ( targetService == null )
+        {
+            return;
+        }
+
+        targetService.updated( processedConfiguration );
+    }
+
+    private Object getTargetService( ServiceReference sr )
+    {
+        if ( sr == null )
+        {
+            return null;
+        }
+
+        return bc.getService( sr );
+    }
+
+    // SERVICE_REGISTERING
+    private void configureManagedService( ServiceReference sr ) throws IOException
+    {
+        final String servicePid = (String) sr.getProperty( Constants.SERVICE_PID );
+        ConfigurationDictionary cd = store.load( servicePid );
+
+        if ( cd == null )
+        {
+            update( sr, servicePid, null, null );
+        }
+        else
+        {
+            cd = bindLocationIfNecessary( sr, cd );
+            String boundLocation = (String) cd.get( ConfigurationAdmin.SERVICE_BUNDLELOCATION );
+            sr = filterOnMatchingLocations( sr, boundLocation );
+
+            update( sr, servicePid, null, cd );
+        }
+    }
+
+    // FACTORY_REGISTERING
+    private void configureManagedServiceFactory( ServiceReference sr ) throws IOException
+    {
+        String factoryPid = (String) sr.getProperty( Constants.SERVICE_PID );
+
+        ConfigurationDictionary[] cds = store.loadAll( factoryPid );
+
+        if ( cds != null && cds.length > 0 )
+        {
+            for ( int ii = 0; ii < cds.length; ++ii )
+            {
+                String servicePid = (String) cds[ ii ].get( Constants.SERVICE_PID );
+                configureManagedServiceFactory( sr, servicePid, factoryPid, cds[ ii ] );
+            }
+        }
+    }
+
+    // FACTORY_REGISTERING
+    private void configureManagedServiceFactory( ServiceReference sr, String servicePid, String factoryPid,
+            ConfigurationDictionary cd ) throws IOException
+    {
+        ConfigurationDictionary bound = bindLocationIfNecessary( sr, cd );
+        String boundLocation = (String) bound.get( ConfigurationAdmin.SERVICE_BUNDLELOCATION );
+        ServiceReference filtered = filterOnMatchingLocations( sr, boundLocation );
+
+        update( filtered, servicePid, factoryPid, bound );
+    }
+
+    // SERVICE_UPDATING
+    private void updateManagedServiceMatching( ServiceReference sr ) throws IOException
+    {
+        final String servicePid = (String) sr.getProperty( Constants.SERVICE_PID );
+        final String bundleLocation = (String) sr.getProperty( ConfigurationAdmin.SERVICE_BUNDLELOCATION );
+
+        ConfigurationDictionary cd = store.load( servicePid );
+
+        if ( cd == null )
+        {
+            updateManagedService( sr, servicePid, bundleLocation, null );
+        }
+        else
+        {
+            ConfigurationDictionary bound = bindLocationIfNecessary( sr, cd );
+            String boundLocation = (String) bound.get( ConfigurationAdmin.SERVICE_BUNDLELOCATION );
+
+            updateManagedService( sr, servicePid, boundLocation, bound );
+        }
+    }
+
+    // SERVICE_UPDATING
+    private void updateManagedService( ServiceReference sr, String servicePid, String boundLocation,
+            ConfigurationDictionary bound )
+    {
+        ServiceReference filtered = filterOnMatchingLocations( sr, boundLocation );
+
+        update( filtered, servicePid, null, bound );
+    }
+
+    // FACTORY_UPDATING
+    private void updateManagedServiceFactoryMatching( ServiceReference sr, String servicePid ) throws IOException
+    {
+        final String bundleLocation = (String) sr.getProperty( ConfigurationAdmin.SERVICE_BUNDLELOCATION );
+
+        ConfigurationDictionary cd = store.load( servicePid );
+        String factoryPid = (String) cd.get( ConfigurationAdmin.SERVICE_FACTORYPID );
+
+        if ( cd == null )
+        {
+            ServiceReference filtered = filterOnMatchingLocations( sr, bundleLocation );
+            update( filtered, servicePid, factoryPid, null );
+        }
+        else
+        {
+            ConfigurationDictionary bound = bindLocationIfNecessary( sr, cd );
+            String boundLocation = (String) bound.get( ConfigurationAdmin.SERVICE_BUNDLELOCATION );
+            ServiceReference filtered = filterOnMatchingLocations( sr, boundLocation );
+            update( filtered, servicePid, factoryPid, bound );
+        }
+    }
+
+    private void lookForExistingBundleLocations()
+    {
+        Bundle[] bs = bc.getBundles();
+
+        for ( int ii = 0; bs != null && ii < bs.length; ++ii )
+        {
+            existingBundleLocations.add( bs[ ii ].getLocation() );
+        }
+    }
+
+    private ConfigurationDictionary bindLocationIfNecessary( ServiceReference sr, ConfigurationDictionary config )
+            throws IOException
+    {
+        if ( sr == null || config == null )
+        {
+            return config;
+        }
+
+        String configLocation = (String) config.get( ConfigurationAdmin.SERVICE_BUNDLELOCATION );
+
+        if ( configLocation != null && !existingBundleLocations.contains( configLocation ) )
+        {
+            Boolean dynamicLocation = (Boolean) config.get( DYNAMIC_BUNDLE_LOCATION );
+
+            if ( dynamicLocation != null && dynamicLocation.booleanValue() )
+            {
+                configLocation = null;
+            }
+        }
+
+        if ( configLocation == null )
+        {
+            String factoryPid = (String) config.get( ConfigurationAdmin.SERVICE_FACTORYPID );
+            String servicePid = (String) config.get( Constants.SERVICE_PID );
+            String serviceLocation = sr.getBundle().getLocation();
+
+            ConfigurationDictionary copy = config.createCopy();
+            copy.put( ConfigurationAdmin.SERVICE_BUNDLELOCATION, serviceLocation );
+            copy.put( DYNAMIC_BUNDLE_LOCATION, Boolean.TRUE );
+
+            store.store( servicePid, factoryPid, copy );
+            return copy;
+        }
+
+        return config;
+    }
+
+    private void findAndUnbindConfigurationsDynamicallyBoundTo( String bundleLocation )
+    {
+        String filter = "(&(" + ConfigurationAdmin.SERVICE_BUNDLELOCATION + "=" + bundleLocation + ")" + "("
+                + DYNAMIC_BUNDLE_LOCATION + "=" + Boolean.TRUE + "))";
+        try
+        {
+            Configuration[] configurations = store.listConfigurations( filter );
+            for ( int ii = 0; configurations != null && ii < configurations.length; ++ii )
+            {
+                configurations[ ii ].setBundleLocation( null );
+            }
+        }
+        catch ( IOException ioe )
+        {
+            log.error( "Error loading configurations while unbinding configurations bound to " + bundleLocation, ioe );
+        }
+        catch ( InvalidSyntaxException ise )
+        {
+            log.error( "Error with filter while unbinding configurations bound to " + bundleLocation, ise );
+        }
+    }
+
+    private ServiceReference filterOnMatchingLocations( ServiceReference sr, String configLocation )
+    {
+        String serviceLocation = sr.getBundle().getLocation();
+
+        if ( locationsMatch( serviceLocation, configLocation ) )
+        {
+            return sr;
+        }
+
+        log.error( "The bundle " + serviceLocation + " has registered a ManagedService(Factory) for a pid bound to "
+                + configLocation );
+
+        return null;
+    }
+
+    private boolean locationsMatch( String serviceLocation, String configLocation )
+    {
+        if ( configLocation == null )
+        {
+            return false;
+        }
+
+        if ( configLocation.equals( ANY_LOCATION ) || configLocation.equals( serviceLocation ) )
+        {
+            return true;
+        }
+
+        return false;
+    }
+
+    private void addLocationToPidsAndCheck( ServiceReference sr )
+    {
+        if ( sr == null )
+        {
+            return;
+        }
+
+        String bundleLocation = sr.getBundle().getLocation();
+
+        String pid = (String) sr.getProperty( Constants.SERVICE_PID );
+
+        if ( pid == null )
+        {
+            return;
+        }
+
+        Set<String> pidsForLocation = locationToPids.get( bundleLocation );
+
+        if ( pidsForLocation == null )
+        {
+            pidsForLocation = new HashSet<String>();
+            locationToPids.put( bundleLocation, pidsForLocation );
+        }
+
+        if ( pidsForLocation.contains( pid ) )
+        {
+            log.error( "Multiple ManagedServices registered from bundle " + bundleLocation + " for " + pid );
+        }
+
+        pidsForLocation.add( pid );
+    }
+
+    private void removeFromLocationToPids( ServiceReference sr )
+    {
+        if ( sr == null )
+        {
+            return;
+        }
+
+        String bundleLocation = sr.getBundle().getLocation();
+        Set<String> pidsForLocation = locationToPids.get( bundleLocation );
+
+        if ( pidsForLocation == null )
+        {
+            return;
+        }
+
+        pidsForLocation.remove( bundleLocation );
+
+        if ( pidsForLocation.isEmpty() )
+        {
+            locationToPids.remove( bundleLocation );
+        }
+    }
+}

Propchange: directory/sandbox/trunk/osgi-spec/trunk/configuration/src/main/java/org/apache/configuration/impl/UpdateConsumer.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: directory/sandbox/trunk/osgi-spec/trunk/configuration/src/main/java/org/apache/configuration/impl/UpdateEvent.java
URL: http://svn.apache.org/viewcvs/directory/sandbox/trunk/osgi-spec/trunk/configuration/src/main/java/org/apache/configuration/impl/UpdateEvent.java?rev=290673&view=auto
==============================================================================
--- directory/sandbox/trunk/osgi-spec/trunk/configuration/src/main/java/org/apache/configuration/impl/UpdateEvent.java (added)
+++ directory/sandbox/trunk/osgi-spec/trunk/configuration/src/main/java/org/apache/configuration/impl/UpdateEvent.java Wed Sep 21 03:15:53 2005
@@ -0,0 +1,91 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+
+package org.apache.configuration.impl;
+
+import java.util.EventObject;
+
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceReference;
+
+final class UpdateEvent extends EventObject
+{
+    public static final int SERVICE_REGISTERING = 1;
+    public static final int FACTORY_REGISTERING = 2;
+    public static final int SERVICE_UNREGISTERING = 3;
+    public static final int FACTORY_UNREGISTERING = 4;
+    public static final int SERVICE_UPDATING = 5;
+    public static final int FACTORY_UPDATING = 6;
+
+    private int type;
+    private String servicePid;
+
+    public UpdateEvent( ServiceReference sr, int type, String servicePid )
+    {
+        super( sr );
+
+        this.type = type;
+        this.servicePid = servicePid;
+    }
+
+    public int getType()
+    {
+        return type;
+    }
+
+    public String getServicePid()
+    {
+        return servicePid;
+    }
+
+    public String toString()
+    {
+        ServiceReference sr = (ServiceReference) getSource();
+        String servicePid = (String) sr.getProperty( Constants.SERVICE_PID );
+
+        StringBuffer sb = new StringBuffer();
+
+        switch ( type )
+        {
+            case SERVICE_REGISTERING:
+                sb.append( "SERVICE_REGISTERING" );
+                break;
+            case FACTORY_REGISTERING:
+                sb.append( "FACTORY_REGISTERING" );
+                break;
+            case SERVICE_UNREGISTERING:
+                sb.append( "SERVICE_UNREGISTERING" );
+                break;
+            case FACTORY_UNREGISTERING:
+                sb.append( "FACTORY_UNREGISTERING" );
+                break;
+            case SERVICE_UPDATING:
+                sb.append( "SERVICE_UPDATING" );
+                break;
+            case FACTORY_UPDATING:
+                sb.append( "FACTORY_UPDATING" );
+                break;
+            default:
+                sb.append( "NULL EVENT" );
+                break;
+        }
+
+        sb.append( " with servicePid " + servicePid );
+
+        return sb.toString();
+    }
+}

Propchange: directory/sandbox/trunk/osgi-spec/trunk/configuration/src/main/java/org/apache/configuration/impl/UpdateEvent.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: directory/sandbox/trunk/osgi-spec/trunk/configuration/src/main/java/org/apache/configuration/metadata.xml
URL: http://svn.apache.org/viewcvs/directory/sandbox/trunk/osgi-spec/trunk/configuration/src/main/java/org/apache/configuration/metadata.xml?rev=290673&r1=290672&r2=290673&view=diff
==============================================================================
--- directory/sandbox/trunk/osgi-spec/trunk/configuration/src/main/java/org/apache/configuration/metadata.xml (original)
+++ directory/sandbox/trunk/osgi-spec/trunk/configuration/src/main/java/org/apache/configuration/metadata.xml Wed Sep 21 03:15:53 2005
@@ -11,5 +11,21 @@
         bind-method="setInitialContextFactory"
         unbind-method="unsetInitialContextFactory"
     />
+    <requires
+        service="org.osgi.service.cm.ManagedServiceFactory"
+        filter=""
+        cardinality="0..n"
+        policy="dynamic"
+        bind-method="setManagedServiceFactory"
+        unbind-method="unsetManagedServiceFactory"
+    />
+    <requires
+        service="org.osgi.service.cm.ManagedService"
+        filter=""
+        cardinality="0..n"
+        policy="dynamic"
+        bind-method="setManagedService"
+        unbind-method="unsetManagedService"
+    />
   </component>
 </bundle>



Mime
View raw message