FELIX-11 Implement Configuration Admin (Initial Checkin)
git-svn-id: https://svn.apache.org/repos/asf/incubator/felix/trunk@527592 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/configadmin/src/main/java/org/apache/felix/cm/impl/CaseInsensitiveDictionary.java b/configadmin/src/main/java/org/apache/felix/cm/impl/CaseInsensitiveDictionary.java
new file mode 100644
index 0000000..39b992d
--- /dev/null
+++ b/configadmin/src/main/java/org/apache/felix/cm/impl/CaseInsensitiveDictionary.java
@@ -0,0 +1,266 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.felix.cm.impl;
+
+
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Vector;
+
+
+/**
+ * The <code>CaseInsensitiveDictionary</code> is a
+ * <code>java.util.Dictionary</code> which conforms to the requirements laid
+ * out by the Configuration Admin Service Specification requiring the property
+ * names to keep case but to ignore case when accessing the properties.
+ *
+ * @author fmeschbe
+ */
+class CaseInsensitiveDictionary extends Dictionary
+{
+
+ /**
+ * The backend dictionary with lower case keys.
+ */
+ private Hashtable internalMap;
+
+ /**
+ * Mapping of lower case keys to original case keys as last used to set
+ * a property value.
+ */
+ private Hashtable originalKeys;
+
+
+ CaseInsensitiveDictionary()
+ {
+ internalMap = new Hashtable();
+ originalKeys = new Hashtable();
+ }
+
+
+ CaseInsensitiveDictionary( Dictionary props )
+ {
+ this();
+
+ Enumeration keys = props.keys();
+ while ( keys.hasMoreElements() )
+ {
+ Object key = keys.nextElement();
+ if ( !( key instanceof String ) )
+ {
+ throw new IllegalArgumentException( "Key [" + key + "] must be a String" );
+ }
+
+ // check uniqueness of key
+ String lowerCase = ( ( String ) key ).toLowerCase();
+ if ( internalMap.containsKey( lowerCase ) )
+ {
+ throw new IllegalArgumentException( "Key [" + key + "] already present in different case" );
+ }
+
+ // check the value
+ Object value = props.get( key );
+ checkValue( value );
+
+ // add the key/value pair
+ internalMap.put( lowerCase, value );
+ originalKeys.put( lowerCase, key );
+ }
+ }
+
+
+ CaseInsensitiveDictionary( CaseInsensitiveDictionary props )
+ {
+ internalMap = new Hashtable( props.internalMap );
+ originalKeys = new Hashtable( props.originalKeys );
+ }
+
+
+ /* (non-Javadoc)
+ * @see java.util.Dictionary#elements()
+ */
+ public Enumeration elements()
+ {
+ return Collections.enumeration( internalMap.values() );
+ }
+
+
+ /* (non-Javadoc)
+ * @see java.util.Dictionary#get(java.lang.Object)
+ */
+ public Object get( Object key )
+ {
+ if ( key == null )
+ {
+ throw new NullPointerException( "key" );
+ }
+
+ String stringKey = String.valueOf( key ).toLowerCase();
+ return internalMap.get( stringKey );
+ }
+
+
+ /* (non-Javadoc)
+ * @see java.util.Dictionary#isEmpty()
+ */
+ public boolean isEmpty()
+ {
+ return internalMap.isEmpty();
+ }
+
+
+ /* (non-Javadoc)
+ * @see java.util.Dictionary#keys()
+ */
+ public Enumeration keys()
+ {
+ return Collections.enumeration( originalKeys.values() );
+ }
+
+
+ /* (non-Javadoc)
+ * @see java.util.Dictionary#put(java.lang.Object, java.lang.Object)
+ */
+ public Object put( Object key, Object value )
+ {
+ if ( key == null || value == null )
+ {
+ throw new NullPointerException( "key or value" );
+ }
+
+ if ( !( key instanceof String ) )
+ {
+ throw new IllegalArgumentException( "Key [" + key + "] must be a String" );
+ }
+
+ checkValue( value );
+
+ String lowerCase = String.valueOf( key ).toLowerCase();
+ originalKeys.put( lowerCase, key );
+ return internalMap.put( lowerCase, value );
+ }
+
+
+ /* (non-Javadoc)
+ * @see java.util.Dictionary#remove(java.lang.Object)
+ */
+ public Object remove( Object key )
+ {
+ if ( key == null )
+ {
+ throw new NullPointerException( "key" );
+ }
+
+ String lowerCase = String.valueOf( key ).toLowerCase();
+ originalKeys.remove( lowerCase );
+ return internalMap.remove( lowerCase );
+ }
+
+
+ /* (non-Javadoc)
+ * @see java.util.Dictionary#size()
+ */
+ public int size()
+ {
+ return internalMap.size();
+ }
+
+
+ //---------- internal -----------------------------------------------------
+
+ static void checkValue( Object value )
+ {
+ Class type;
+ if ( value instanceof Object[] )
+ {
+ // check simple or primitive
+ type = value.getClass().getComponentType();
+
+ // check for primitive type
+ if ( type == Integer.TYPE || type == Long.TYPE || type == Float.TYPE || type == Double.TYPE
+ || type == Byte.TYPE || type == Short.TYPE || type == Character.TYPE || type == Boolean.TYPE )
+ {
+ return;
+ }
+
+ }
+ else if ( value instanceof Vector )
+ {
+ // check simple
+ Vector vector = ( Vector ) value;
+ if ( vector.isEmpty() )
+ {
+ throw new IllegalArgumentException( "Vector must not be empty" );
+ }
+
+ // ensure all elements have the same type
+ type = null;
+ for ( int i = 0; i < vector.size(); i++ )
+ {
+ Object el = vector.get( i );
+ if ( el == null )
+ {
+ throw new IllegalArgumentException( "Vector must not contain null elements" );
+ }
+ if ( type == null )
+ {
+ type = el.getClass();
+ }
+ else if ( type != el.getClass() )
+ {
+ throw new IllegalArgumentException( "Vector element types must not be mixed" );
+ }
+ }
+
+ }
+ else if ( value != null )
+ {
+ // get the type to check (must be simple)
+ type = value.getClass();
+
+ }
+ else
+ {
+ // null is illegal
+ throw new IllegalArgumentException( "Value must not be null" );
+ }
+
+ // check for simple type
+ if ( type == String.class || type == Integer.class || type == Long.class || type == Float.class
+ || type == Double.class || type == Byte.class || type == Short.class || type == Character.class
+ || type == Boolean.class )
+ {
+ return;
+ }
+
+ // not a valid type
+ throw new IllegalArgumentException( "Value [" + value + "] has unsupported (base-) type " + type );
+ }
+
+
+ //---------- Object Overwrites --------------------------------------------
+
+ public String toString()
+ {
+ return internalMap.toString();
+ }
+
+}
diff --git a/configadmin/src/main/java/org/apache/felix/cm/impl/ConfigurationAdapter.java b/configadmin/src/main/java/org/apache/felix/cm/impl/ConfigurationAdapter.java
new file mode 100644
index 0000000..7f9b22d
--- /dev/null
+++ b/configadmin/src/main/java/org/apache/felix/cm/impl/ConfigurationAdapter.java
@@ -0,0 +1,176 @@
+/*
+ * $Url: $
+ * $Id$
+ *
+ * Copyright 1997-2005 Day Management AG
+ * Barfuesserplatz 6, 4001 Basel, Switzerland
+ * All Rights Reserved.
+ *
+ * This software is the confidential and proprietary information of
+ * Day Management AG, ("Confidential Information"). You shall not
+ * disclose such Confidential Information and shall use it only in
+ * accordance with the terms of the license agreement you entered into
+ * with Day.
+ */
+package org.apache.felix.cm.impl;
+
+
+import java.io.IOException;
+import java.util.Dictionary;
+
+import org.osgi.service.cm.Configuration;
+
+
+/**
+ * The <code>ConfigurationAdapter</code> TODO
+ *
+ * @author fmeschbe
+ * @version $Rev:$, $Date:$
+ */
+public class ConfigurationAdapter implements Configuration
+{
+
+ private ConfigurationAdminImpl configurationAdmin;
+ private ConfigurationImpl delegatee;
+
+
+ ConfigurationAdapter( ConfigurationAdminImpl configurationAdmin, ConfigurationImpl delegatee )
+ {
+ this.configurationAdmin = configurationAdmin;
+ this.delegatee = delegatee;
+ }
+
+
+ /**
+ * @return
+ * @see org.apache.felix.cm.impl.ConfigurationImpl#getPid()
+ */
+ public String getPid()
+ {
+ checkDeleted();
+ return delegatee.getPid();
+ }
+
+
+ /**
+ * @return
+ * @see org.apache.felix.cm.impl.ConfigurationImpl#getFactoryPid()
+ */
+ public String getFactoryPid()
+ {
+ checkDeleted();
+ return delegatee.getFactoryPid();
+ }
+
+
+ /**
+ * @return
+ * @see org.apache.felix.cm.impl.ConfigurationImpl#getBundleLocation()
+ */
+ public String getBundleLocation()
+ {
+ configurationAdmin.checkPermission();
+ checkDeleted();
+ return delegatee.getBundleLocation();
+ }
+
+
+ /**
+ * @param bundleLocation
+ * @see org.apache.felix.cm.impl.ConfigurationImpl#setBundleLocation(java.lang.String)
+ */
+ public void setBundleLocation( String bundleLocation )
+ {
+ configurationAdmin.checkPermission();
+ checkDeleted();
+ delegatee.setBundleLocation( bundleLocation );
+ }
+
+
+ /**
+ * @throws IOException
+ * @see org.apache.felix.cm.impl.ConfigurationImpl#update()
+ */
+ public void update() throws IOException
+ {
+ checkDeleted();
+ delegatee.update();
+ }
+
+
+ /**
+ * @param properties
+ * @throws IOException
+ * @see org.apache.felix.cm.impl.ConfigurationImpl#update(java.util.Dictionary)
+ */
+ public void update( Dictionary properties ) throws IOException
+ {
+ checkDeleted();
+ delegatee.update( properties );
+ }
+
+
+ /**
+ * @return
+ * @see org.apache.felix.cm.impl.ConfigurationImpl#getProperties()
+ */
+ public Dictionary getProperties()
+ {
+ checkDeleted();
+ return delegatee.getProperties();
+ }
+
+
+ /**
+ * @throws IOException
+ * @see org.apache.felix.cm.impl.ConfigurationImpl#delete()
+ */
+ public void delete() throws IOException
+ {
+ checkDeleted();
+ delegatee.delete();
+ }
+
+
+ /**
+ * @return
+ * @see org.apache.felix.cm.impl.ConfigurationImpl#hashCode()
+ */
+ public int hashCode()
+ {
+ return delegatee.hashCode();
+ }
+
+
+ /**
+ * @param obj
+ * @return
+ * @see org.apache.felix.cm.impl.ConfigurationImpl#equals(java.lang.Object)
+ */
+ public boolean equals( Object obj )
+ {
+ return delegatee.equals( obj );
+ }
+
+
+ /**
+ * @return
+ * @see org.apache.felix.cm.impl.ConfigurationImpl#toString()
+ */
+ public String toString()
+ {
+ return delegatee.toString();
+ }
+
+ /**
+ * Checks whether this configuration object has already been deleted.
+ *
+ * @throws IllegalStateException If this configuration object has been
+ * deleted.
+ */
+ private void checkDeleted() {
+ if (delegatee.isDeleted()) {
+ throw new IllegalStateException( "Configuration " + delegatee.getPid() + " deleted" );
+ }
+ }
+}
diff --git a/configadmin/src/main/java/org/apache/felix/cm/impl/ConfigurationAdminFactory.java b/configadmin/src/main/java/org/apache/felix/cm/impl/ConfigurationAdminFactory.java
new file mode 100644
index 0000000..2719704
--- /dev/null
+++ b/configadmin/src/main/java/org/apache/felix/cm/impl/ConfigurationAdminFactory.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.felix.cm.impl;
+
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.ServiceFactory;
+import org.osgi.framework.ServiceRegistration;
+
+
+/**
+ * The <code>ConfigurationAdminFactory</code> is the <code>ServiceFactory</code>
+ * registered as the <code>ConfigurationAdmin</code> service responsible to
+ * create the real <code>ConfiguratAdmin</code> instances returend to client
+ * bundles. Each bundle gets a separate instance.
+ *
+ * @author fmeschbe
+ */
+class ConfigurationAdminFactory implements ServiceFactory
+{
+
+ // The configuration manager to which the configuration admin instances
+ // delegate most of their work
+ private ConfigurationManager configurationManager;
+
+
+ ConfigurationAdminFactory( ConfigurationManager configurationManager )
+ {
+ this.configurationManager = configurationManager;
+ }
+
+
+ /**
+ * Returns a new instance of the {@link ConfigurationAdminImpl} class for
+ * the given bundle.
+ */
+ public Object getService( Bundle bundle, ServiceRegistration registration )
+ {
+ return new ConfigurationAdminImpl( configurationManager, bundle );
+ }
+
+
+ /**
+ * Disposes off the given {@link ConfigurationAdminImpl} instance as the
+ * given bundle has no use of it any more.
+ */
+ public void ungetService( Bundle bundle, ServiceRegistration registration, Object service )
+ {
+ ( ( ConfigurationAdminImpl ) service ).dispose();
+ }
+
+}
diff --git a/configadmin/src/main/java/org/apache/felix/cm/impl/ConfigurationAdminImpl.java b/configadmin/src/main/java/org/apache/felix/cm/impl/ConfigurationAdminImpl.java
new file mode 100644
index 0000000..9a91b2b
--- /dev/null
+++ b/configadmin/src/main/java/org/apache/felix/cm/impl/ConfigurationAdminImpl.java
@@ -0,0 +1,181 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.felix.cm.impl;
+
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.service.cm.Configuration;
+import org.osgi.service.cm.ConfigurationAdmin;
+import org.osgi.service.cm.ConfigurationPermission;
+
+
+/**
+ * The <code>ConfigurationAdminImpl</code> is the per-bundle frontend to the
+ * configuration manager. Instances of this class are created on-demand for
+ * each bundle trying to get hold of the <code>ConfigurationAdmin</code>
+ * service.
+ *
+ * @author fmeschbe
+ */
+public class ConfigurationAdminImpl implements ConfigurationAdmin
+{
+
+ // The configuration manager to which most of the tasks are delegated
+ private ConfigurationManager configurationManager;
+
+ // The bundle for which this instance has been created
+ private Bundle bundle;
+
+
+ ConfigurationAdminImpl( ConfigurationManager configurationManager, Bundle bundle )
+ {
+ this.configurationManager = configurationManager;
+ this.bundle = bundle;
+ }
+
+
+ void dispose()
+ {
+ this.bundle = null;
+ this.configurationManager = null;
+ }
+
+
+ Bundle getBundle()
+ {
+ return bundle;
+ }
+
+
+ //---------- ConfigurationAdmin interface ---------------------------------
+
+ /* (non-Javadoc)
+ * @see org.osgi.service.cm.ConfigurationAdmin#createFactoryConfiguration(java.lang.String)
+ */
+ public Configuration createFactoryConfiguration( String factoryPid ) throws IOException
+ {
+ return wrap( configurationManager.createFactoryConfiguration( this, factoryPid ) );
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.osgi.service.cm.ConfigurationAdmin#createFactoryConfiguration(java.lang.String, java.lang.String)
+ */
+ public Configuration createFactoryConfiguration( String factoryPid, String location ) throws IOException
+ {
+ checkPermission();
+
+ return wrap( configurationManager.createFactoryConfiguration( factoryPid, location ) );
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.osgi.service.cm.ConfigurationAdmin#getConfiguration(java.lang.String)
+ */
+ public Configuration getConfiguration( String pid ) throws IOException
+ {
+ ConfigurationImpl config = configurationManager.getConfiguration( pid );
+
+ if ( config.getBundleLocation() == null )
+ {
+ config.setBundleLocation( getBundle().getLocation() );
+ }
+ else if ( !config.getBundleLocation().equals( getBundle().getLocation() ) )
+ {
+ checkPermission();
+ }
+
+ return wrap( config );
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.osgi.service.cm.ConfigurationAdmin#getConfiguration(java.lang.String, java.lang.String)
+ */
+ public Configuration getConfiguration( String pid, String location ) throws IOException
+ {
+ checkPermission();
+
+ return wrap( configurationManager.getConfiguration( pid, location ) );
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.osgi.service.cm.ConfigurationAdmin#listConfigurations(java.lang.String)
+ */
+ public Configuration[] listConfigurations( String filter ) throws IOException, InvalidSyntaxException
+ {
+ ConfigurationImpl ci[] = configurationManager.listConfigurations( this, filter );
+ if ( ci == null )
+ {
+ return null;
+ }
+
+ Configuration[] cfgs = new Configuration[ci.length];
+ for ( int i = 0; i < cfgs.length; i++ )
+ {
+ cfgs[i] = wrap( ci[i] );
+ }
+
+ return cfgs;
+ }
+
+
+ //---------- Security checks ----------------------------------------------
+
+ private Configuration wrap( ConfigurationImpl configuration )
+ {
+ return new ConfigurationAdapter( this, configuration );
+ }
+
+
+ /**
+ * Checks whether the bundle to which this instance has been given has the
+ * <code>CONFIGURE</code> permission and returns <code>true</code> in this
+ * case.
+ */
+ boolean hasPermission()
+ {
+ return bundle.hasPermission( new ConfigurationPermission( "*", ConfigurationPermission.CONFIGURE ) );
+ }
+
+
+ /**
+ * Checks whether the bundle to which this instance has been given has the
+ * <code>CONFIGURE</code> permission and throws a <code>SecurityException</code>
+ * if this is not the case.
+ *
+ * @throws SecurityException if the bundle to which this instance belongs
+ * does not have the <code>CONFIGURE</code> permission.
+ */
+ void checkPermission()
+ {
+ if ( !hasPermission() )
+ {
+ throw new SecurityException( "Bundle " + bundle.getSymbolicName()
+ + " not permitted for Configuration Tasks" );
+ }
+ }
+
+}
diff --git a/configadmin/src/main/java/org/apache/felix/cm/impl/ConfigurationImpl.java b/configadmin/src/main/java/org/apache/felix/cm/impl/ConfigurationImpl.java
new file mode 100644
index 0000000..b359652
--- /dev/null
+++ b/configadmin/src/main/java/org/apache/felix/cm/impl/ConfigurationImpl.java
@@ -0,0 +1,383 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.felix.cm.impl;
+
+
+import java.io.IOException;
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+import org.apache.felix.cm.PersistenceManager;
+import org.osgi.framework.Constants;
+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.log.LogService;
+
+
+/**
+ * The <code>ConfigurationImpl</code> is the implementation of the Configuration
+ * Admin Service Specification <i>Configuration object</i> (section 104.4).
+ *
+ * @author fmeschbe
+ */
+class ConfigurationImpl
+{
+
+ /**
+ * The {@link ConfigurationManager configuration manager} instance which
+ * caused this configuration object to be created.
+ */
+ private ConfigurationManager configurationManager;
+
+ /**
+ * The {@link PersistenceManager persistence manager} which loaded this
+ * configuration instance and which is used to store and delete configuration
+ * data.
+ */
+ private PersistenceManager persistenceManager;
+
+ /**
+ * The serviceReference PID of this configuration.
+ */
+ private String pid;
+
+ /**
+ * The factory serviceReference PID of this configuration or <code>null</code> if this
+ * is not a factory configuration.
+ */
+ private String factoryPID;
+
+ /**
+ * The location of the bundle to which this configuration instance is bound.
+ * This is not necessarily the same as the bundle of the
+ * {@link #configurationAdmin}. If this configuration is not bound to a
+ * bundle, this field is <code>null</code>.
+ */
+ private String bundleLocation;
+
+ /**
+ * The <code>ServiceReference</code> of the serviceReference which first asked for
+ * this configuration. This field is <code>null</code> if the configuration
+ * has not been handed to a serviceReference by way of the <code>ManagedService.update(Dictionary)</code>
+ * or <code>ManagedServiceFactory.updated(String, Dictionary)</code>
+ * method.
+ */
+ private ServiceReference serviceReference;
+
+ /**
+ * The configuration data of this configuration instance. This is a private
+ * copy of the properties of which a copy is made when the
+ * {@link #getProperties()} method is called. This field is <code>null</code> if
+ * the configuration has been created and never stored to persistence.
+ */
+ private CaseInsensitiveDictionary properties;
+
+
+ ConfigurationImpl( ConfigurationManager configurationManager, PersistenceManager persistenceManager,
+ Dictionary properties )
+ {
+ this.configurationManager = configurationManager;
+ this.persistenceManager = persistenceManager;
+
+ this.pid = ( String ) properties.remove( Constants.SERVICE_PID );
+ this.factoryPID = ( String ) properties.remove( ConfigurationAdmin.SERVICE_FACTORYPID );
+ this.bundleLocation = ( String ) properties.remove( ConfigurationAdmin.SERVICE_BUNDLELOCATION );
+
+ configure( properties );
+ }
+
+
+ ConfigurationImpl( ConfigurationManager configurationManager, PersistenceManager persistenceManager, String pid,
+ String factoryPid )
+ {
+ this.configurationManager = configurationManager;
+ this.persistenceManager = persistenceManager;
+ this.pid = pid;
+ this.factoryPID = factoryPid;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.osgi.service.cm.Configuration#delete()
+ */
+ public void delete() throws IOException
+ {
+ if ( !isDeleted() )
+ {
+ persistenceManager.delete( pid );
+ persistenceManager = null;
+
+ configurationManager.deleted( this );
+ }
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.osgi.service.cm.Configuration#getPid()
+ */
+ public String getPid()
+ {
+ return pid;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.osgi.service.cm.Configuration#getFactoryPid()
+ */
+ public String getFactoryPid()
+ {
+ return factoryPID;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.osgi.service.cm.Configuration#getBundleLocation()
+ */
+ public String getBundleLocation()
+ {
+ return bundleLocation;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.osgi.service.cm.Configuration#getProperties()
+ */
+ public Dictionary getProperties()
+ {
+ // no properties yet
+ if ( properties == null )
+ {
+ return null;
+ }
+
+ CaseInsensitiveDictionary props = new CaseInsensitiveDictionary( properties );
+
+ // fix special properties (pid, factory PID, bundle location)
+ setAutoProperties( props, false );
+
+ return props;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.osgi.service.cm.Configuration#setBundleLocation(java.lang.String)
+ */
+ public void setBundleLocation( String bundleLocation )
+ {
+ if ( !isDeleted() )
+ {
+ this.bundleLocation = bundleLocation;
+
+ // 104.15.2.8 The bundle location will be set persistently
+ try
+ {
+ store();
+ }
+ catch ( IOException ioe )
+ {
+ configurationManager.log( LogService.LOG_ERROR, "Persisting new bundle location failed", ioe );
+ }
+ }
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.osgi.service.cm.Configuration#update()
+ */
+ public void update() throws IOException
+ {
+ if ( !isDeleted() )
+ {
+ // read configuration from persistence (again)
+ Dictionary properties = persistenceManager.load( pid );
+
+ // ensure serviceReference pid
+ String servicePid = ( String ) properties.get( Constants.SERVICE_PID );
+ if ( servicePid != null && !pid.equals( servicePid ) )
+ {
+ throw new IOException( "PID of configuration file does match requested PID; expected " + pid + ", got "
+ + servicePid );
+ }
+
+ configure( properties );
+
+ configurationManager.updated( this );
+ }
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.osgi.service.cm.Configuration#update(java.util.Dictionary)
+ */
+ public void update( Dictionary properties ) throws IOException
+ {
+ if ( !isDeleted() )
+ {
+ CaseInsensitiveDictionary newProperties = new CaseInsensitiveDictionary( properties );
+
+ setAutoProperties( newProperties, true );
+
+ persistenceManager.store( pid, newProperties );
+
+ configure( newProperties );
+
+ configurationManager.updated( this );
+ }
+ }
+
+
+ //---------- Object overwrites --------------------------------------------
+
+ public boolean equals( Object obj )
+ {
+ if ( obj == this )
+ {
+ return true;
+ }
+
+ if ( obj instanceof Configuration )
+ {
+ return pid.equals( ( ( Configuration ) obj ).getPid() );
+ }
+
+ return false;
+ }
+
+
+ public int hashCode()
+ {
+ return pid.hashCode();
+ }
+
+
+ public String toString()
+ {
+ return "Configuration PID=" + pid + ", factoryPID=" + factoryPID + ", bundleLocation=" + bundleLocation;
+ }
+
+
+ //---------- private helper -----------------------------------------------
+
+ void setServiceReference( ServiceReference serviceReference )
+ {
+ this.serviceReference = serviceReference;
+ }
+
+
+ ServiceReference getServiceReference()
+ {
+ return serviceReference;
+ }
+
+
+ void store() throws IOException
+ {
+ Dictionary props = getProperties();
+
+ // if this is a new configuration, we just use an empty Dictionary
+ if ( props == null )
+ {
+ props = new Hashtable();
+
+ // add automatic properties including the bundle location (if set)
+ setAutoProperties( props, true );
+ }
+ else if ( getBundleLocation() != null )
+ {
+ props.put( ConfigurationAdmin.SERVICE_BUNDLELOCATION, getBundleLocation() );
+ }
+
+ // only store now, if this is not a new configuration
+ persistenceManager.store( pid, props );
+ }
+
+
+ boolean isDeleted()
+ {
+ if ( persistenceManager != null )
+ {
+ if ( properties == null || persistenceManager.exists( pid ) )
+ {
+ return false;
+ }
+
+ persistenceManager = null;
+ }
+
+ return true;
+ }
+
+
+ private void configure( Dictionary properties )
+ {
+ // remove predefined properties
+ clearAutoProperties( properties );
+
+ // ensure CaseInsensitiveDictionary
+ if ( properties instanceof CaseInsensitiveDictionary )
+ {
+ this.properties = ( CaseInsensitiveDictionary ) properties;
+ }
+ else
+ {
+ this.properties = new CaseInsensitiveDictionary( properties );
+ }
+ }
+
+
+ void setAutoProperties( Dictionary properties, boolean withBundleLocation )
+ {
+ // set pid and factory pid in the properties
+ replaceProperty( properties, Constants.SERVICE_PID, pid );
+ replaceProperty( properties, ConfigurationAdmin.SERVICE_FACTORYPID, factoryPID );
+
+ // bundle location is not set here
+ if ( withBundleLocation )
+ {
+ replaceProperty( properties, ConfigurationAdmin.SERVICE_BUNDLELOCATION, getBundleLocation() );
+ }
+ else
+ {
+ properties.remove( ConfigurationAdmin.SERVICE_BUNDLELOCATION );
+ }
+ }
+
+
+ void clearAutoProperties( Dictionary properties )
+ {
+ properties.remove( Constants.SERVICE_PID );
+ properties.remove( ConfigurationAdmin.SERVICE_FACTORYPID );
+ properties.remove( ConfigurationAdmin.SERVICE_BUNDLELOCATION );
+ }
+
+
+ private void replaceProperty( Dictionary properties, String key, String value )
+ {
+ if ( value == null )
+ {
+ properties.remove( key );
+ }
+ else
+ {
+ properties.put( key, value );
+ }
+ }
+}
diff --git a/configadmin/src/main/java/org/apache/felix/cm/impl/ConfigurationManager.java b/configadmin/src/main/java/org/apache/felix/cm/impl/ConfigurationManager.java
new file mode 100644
index 0000000..d6b2190
--- /dev/null
+++ b/configadmin/src/main/java/org/apache/felix/cm/impl/ConfigurationManager.java
@@ -0,0 +1,1261 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.felix.cm.impl;
+
+
+import java.awt.image.ImagingOpException;
+import java.io.IOException;
+import java.security.SecureRandom;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+import org.apache.felix.cm.PersistenceManager;
+import org.apache.felix.cm.file.FilePersistenceManager;
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleEvent;
+import org.osgi.framework.BundleListener;
+import org.osgi.framework.Constants;
+import org.osgi.framework.Filter;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.cm.ConfigurationAdmin;
+import org.osgi.service.cm.ConfigurationEvent;
+import org.osgi.service.cm.ConfigurationException;
+import org.osgi.service.cm.ConfigurationListener;
+import org.osgi.service.cm.ConfigurationPlugin;
+import org.osgi.service.cm.ManagedService;
+import org.osgi.service.cm.ManagedServiceFactory;
+import org.osgi.service.log.LogService;
+import org.osgi.util.tracker.ServiceTracker;
+
+
+/**
+ * The <code>ConfigurationManager</code> is the central class in this
+ * implementation of the Configuration Admin Service Specification. As such it
+ * has the following tasks:
+ * <ul>
+ * <li>It is a <code>BundleActivator</code> which is called when the bundle
+ * is started and stopped.
+ * <li>It is a <code>BundleListener</code> which gets informed when the
+ * states of bundles change. Mostly this is needed to unbind any bound
+ * configuration in case a bundle is uninstalled.
+ * <li>It is a <code>ServiceListener</code> which gets informed when
+ * <code>ManagedService</code> and <code>ManagedServiceFactory</code>
+ * services are registered and unregistered. This is used to provide
+ * configuration to these services. As a service listener it also listens for
+ * {@link PersistenceManager} instances being registered to support different
+ * configuration persistence layers.
+ * <li>A {@link ConfigurationAdminFactory} instance is registered as the
+ * <code>ConfigurationAdmin</code> service.
+ * <li>A {@link FilePersistenceManager} instance is registered as a default
+ * {@link PersistenceManager}.
+ * <li>Last but not least this instance manages all tasks laid out in the
+ * specification such as maintaining configuration, taking care of configuration
+ * events, etc.
+ * </ul>
+ * <p>
+ * The default {@link FilePersistenceManager} is configured with a configuration
+ * location taken from the <code>felix.cm.dir</code> framework property. If
+ * this property is not set the <code>config</code> directory in the current
+ * working directory as specified in the <code>user.dir</code> system property
+ * is used.
+ *
+ * @author fmeschbe
+ */
+public class ConfigurationManager implements BundleActivator, BundleListener
+{
+
+ /**
+ * The name of the bundle context property defining the location for the
+ * configuration files (value is "felix.cm.dir").
+ *
+ * @see #FilePersistenceManager(BundleContext)
+ */
+ public static final String CM_CONFIG_DIR = "felix.cm.dir";
+
+ // random number generator to create configuration PIDs for factory
+ // configurations
+ private static SecureRandom numberGenerator;
+
+ // comparator used to keep the ordered persistence manager map
+ private static final Comparator cmRankComp = new RankingComparator( true, ConfigurationPlugin.CM_RANKING );
+
+ // the BundleContext of the Configuration Admin Service bundle
+ private BundleContext bundleContext;
+
+ // the service reference to the ConfigurationAdmin service
+ private ServiceReference configurationAdminReference;
+
+ // the ServiceTracker to emit log services (see log(int, String, Throwable))
+ private ServiceTracker logTracker;
+
+ // the ConfigurationEvent listeners
+ private ServiceTracker configurationListenerTracker;
+
+ // service tracker for managed services
+ private ServiceTracker managedServiceTracker;
+
+ // service tracker for managed service factories
+ private ServiceTracker managedServiceFactoryTracker;
+
+ // PersistenceManager services
+ private ServiceTracker persistenceManagerTracker;
+
+ // the thread used to schedule tasks required to run asynchronously
+ private UpdateThread updateThread;
+
+ /**
+ * The actual list of {@link PersistenceManager persistence managers} to use
+ * when looking for configuration data. This list is built from the
+ * {@link #persistenceManagerMap}, which is ordered according to the
+ * {@link RankingComparator}.
+ */
+ private PersistenceManager[] persistenceManagers;
+
+ // the persistenceManagerTracker.getTrackingCount when the
+ // persistenceManagers were last got
+ private int pmtCount;
+
+ // the cache of Factory instances mapped by their factory PID
+ private Map factories;
+
+ // the cache of Configuration instances mapped by their PID
+ private Map configurations;
+
+
+ public void start( BundleContext bundleContext )
+ {
+ // track the log service using a ServiceTracker
+ logTracker = new ServiceTracker( bundleContext, LogService.class.getName(), null );
+ logTracker.open();
+
+ // set up some fields
+ this.bundleContext = bundleContext;
+ this.factories = new HashMap();
+ this.configurations = new HashMap();
+
+ // configurationlistener support
+ configurationListenerTracker = new ServiceTracker( bundleContext, ConfigurationListener.class.getName(), null );
+ configurationListenerTracker.open();
+
+ // initialize the asynchonous updater thread
+ this.updateThread = new UpdateThread( this );
+ this.updateThread.start();
+
+ // set up the location (might throw IllegalArgumentException)
+ try
+ {
+ FilePersistenceManager fpm = new FilePersistenceManager( bundleContext.getProperty( CM_CONFIG_DIR ) );
+ Hashtable props = new Hashtable();
+ props.put( Constants.SERVICE_PID, fpm.getClass().getName() );
+ props.put( Constants.SERVICE_DESCRIPTION, "Platform Filesystem Persistence Manager" );
+ props.put( Constants.SERVICE_VENDOR, "Apache Software Foundation" );
+ props.put( Constants.SERVICE_RANKING, new Integer( Integer.MIN_VALUE ) );
+ bundleContext.registerService( PersistenceManager.class.getName(), fpm, props );
+ }
+ catch ( IllegalArgumentException iae )
+ {
+ log( LogService.LOG_ERROR, "Cannot create the FilePersistenceManager", iae );
+ }
+
+ // register as bundle and service listener
+ bundleContext.addBundleListener( this );
+
+ // get all persistence managers to begin with
+ pmtCount = 1; // make sure to get the persistence managers at least
+ // once
+ persistenceManagerTracker = new ServiceTracker( bundleContext, PersistenceManager.class.getName(), null );
+ persistenceManagerTracker.open();
+
+ // create and register configuration admin - start after PM tracker ...
+ ConfigurationAdminFactory caf = new ConfigurationAdminFactory( this );
+ Hashtable props = new Hashtable();
+ props.put( Constants.SERVICE_PID, "org.apache.felix.cm.ConfigurationAdmin" );
+ props.put( Constants.SERVICE_DESCRIPTION, "Configuration Admin Service Specification 1.2 Implementation" );
+ props.put( Constants.SERVICE_VENDOR, "Apache Software Foundation" );
+ bundleContext.registerService( ConfigurationAdmin.class.getName(), caf, props );
+
+ // start handling ManagedService[Factory] services
+ managedServiceTracker = new ManagedServiceTracker();
+ managedServiceFactoryTracker = new ManagedServiceFactoryTracker();
+ }
+
+
+ public void stop( BundleContext bundleContext )
+ {
+ // stop handling ManagedService[Factory] services
+ managedServiceFactoryTracker.close();
+ managedServiceTracker.close();
+
+ // don't care for PersistenceManagers any more
+ persistenceManagerTracker.close();
+
+ // stop listening for events
+ bundleContext.removeBundleListener( this );
+
+ if ( configurationListenerTracker != null )
+ {
+ configurationListenerTracker.close();
+ }
+
+ if ( updateThread != null )
+ {
+ // terminate asynchrounous updates
+ updateThread.terminate();
+
+ // wait for all updates to terminate
+ try
+ {
+ updateThread.join();
+ }
+ catch ( InterruptedException ie )
+ {
+ // don't really care
+ }
+ }
+
+ if ( logTracker != null )
+ {
+ logTracker.close();
+ }
+
+ this.bundleContext = null;
+ this.configurations = null;
+ }
+
+
+ // ---------- Configuration caching support --------------------------------
+
+ ConfigurationImpl getCachedConfiguration( String pid )
+ {
+ synchronized ( configurations )
+ {
+ return ( ConfigurationImpl ) configurations.get( pid );
+ }
+ }
+
+
+ Iterator getCachedConfigurations()
+ {
+ synchronized ( configurations )
+ {
+ return configurations.values().iterator();
+ }
+ }
+
+
+ void cacheConfiguration( ConfigurationImpl configuration )
+ {
+ synchronized ( configurations )
+ {
+ configurations.put( configuration.getPid(), configuration );
+ }
+ }
+
+
+ void removeConfiguration( ConfigurationImpl configuration )
+ {
+ synchronized ( configurations )
+ {
+ configurations.remove( configuration.getPid() );
+ }
+ }
+
+
+ // ---------- ConfigurationAdminImpl support -------------------------------
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.osgi.service.cm.ConfigurationAdmin#createFactoryConfiguration(java.lang.String)
+ */
+ ConfigurationImpl createFactoryConfiguration( ConfigurationAdminImpl configurationAdmin, String factoryPid )
+ throws IOException
+ {
+ Factory factory = getFactory( factoryPid );
+
+ // check Persmission if factory is bound to another bundle
+ if ( factory.getBundleLocation() != null
+ && !factory.getBundleLocation().equals( configurationAdmin.getBundle().getLocation() ) )
+ {
+ configurationAdmin.checkPermission();
+ }
+
+ // create the configuration
+ String pid = createPid( factoryPid );
+ ConfigurationImpl config = createConfiguration( pid, factoryPid );
+ config.setBundleLocation( configurationAdmin.getBundle().getLocation() );
+
+ // add the configuration to the factory
+ factory.addPID( pid );
+ factory.store();
+
+ return config;
+ }
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.osgi.service.cm.ConfigurationAdmin#createFactoryConfiguration(java.lang.String,
+ * java.lang.String)
+ */
+ ConfigurationImpl createFactoryConfiguration( String factoryPid, String location ) throws IOException
+ {
+ // create the configuration
+ String pid = createPid( factoryPid );
+ ConfigurationImpl config = createConfiguration( pid, factoryPid );
+ if ( location != null )
+ {
+ config.setBundleLocation( location );
+ }
+
+ // add the configuration to the factory
+ Factory factory = getFactory( factoryPid );
+ factory.addPID( pid );
+ factory.store();
+
+ return config;
+ }
+
+
+ ConfigurationImpl getConfiguration( String pid ) throws IOException
+ {
+ return getConfiguration( pid, true );
+ }
+
+
+ ConfigurationImpl getConfiguration( String pid, String bundleLocation ) throws IOException
+ {
+ ConfigurationImpl config = getConfiguration( pid, false );
+
+ if ( config == null )
+ {
+ config = createConfiguration( pid, null );
+ if ( bundleLocation != null )
+ {
+ config.setBundleLocation( bundleLocation );
+ }
+ }
+
+ return config;
+ }
+
+
+ ConfigurationImpl[] listConfigurations( ConfigurationAdminImpl configurationAdmin, String filterString )
+ throws IOException, InvalidSyntaxException
+ {
+ Filter filter = null;
+ if ( filterString != null )
+ {
+ filter = bundleContext.createFilter( filterString );
+ }
+
+ boolean unprivileged = configurationAdmin != null && !configurationAdmin.hasPermission();
+ String location = unprivileged ? configurationAdmin.getBundle().getLocation() : null;
+
+ List configList = new ArrayList();
+
+ PersistenceManager[] pmList = getPersistenceManagers();
+ for ( int i = 0; i < pmList.length; i++ )
+ {
+ Enumeration configs = pmList[i].getDictionaries();
+ while ( configs.hasMoreElements() )
+ {
+ Dictionary config = ( Dictionary ) configs.nextElement();
+
+ // ignore non-Configuration dictionaries
+ if ( config.get( Constants.SERVICE_PID ) == null )
+ {
+ continue;
+ }
+
+ // ignore this config if not privileged and not bound to bundle
+ if ( unprivileged )
+ {
+ Object boundLocation = config.get( ConfigurationAdmin.SERVICE_BUNDLELOCATION );
+ if ( !location.equals( boundLocation ) )
+ {
+ continue;
+ }
+ }
+
+ // check filter
+ if ( filter == null || filter.match( config ) )
+ {
+ configList.add( new ConfigurationImpl( this, pmList[i], config ) );
+ }
+ }
+ }
+
+ return ( org.apache.felix.cm.impl.ConfigurationImpl[] ) configList.toArray( new ConfigurationImpl[configList
+ .size()] );
+ }
+
+
+ void deleted( ConfigurationImpl config )
+ {
+ // remove the configuration from the cache
+ removeConfiguration( config );
+ updateThread.schedule( new DeleteConfiguration( config ) );
+ }
+
+
+ void updated( ConfigurationImpl config )
+ {
+ updateThread.schedule( new UpdateConfiguration( config ) );
+ }
+
+
+ void fireConfigurationEvent( int type, ConfigurationImpl config )
+ {
+
+ updateThread.schedule( new FireConfigurationEvent( type, config ) );
+ }
+
+
+ // ---------- BundleListener -----------------------------------------------
+
+ public void bundleChanged( BundleEvent event )
+ {
+ if ( event.getType() == BundleEvent.UNINSTALLED )
+ {
+ String location = event.getBundle().getLocation();
+ String filter = "(service.bundleLocation=" + location + ")";
+
+ try
+ {
+ ConfigurationImpl[] configs = listConfigurations( null, filter );
+ if ( configs != null && configs.length > 0 )
+ {
+ for ( int i = 0; i < configs.length; i++ )
+ {
+ configs[i].setBundleLocation( null );
+ try
+ {
+ configs[i].store();
+ }
+ catch ( IOException ioe )
+ {
+ log( LogService.LOG_WARNING,
+ "Problem storing unbound configuration " + configs[i].getPid(), ioe );
+ }
+ }
+ }
+ }
+ catch ( Exception e )
+ {
+ log( LogService.LOG_WARNING, "Problem unbinding configurations for bundle " + location, e );
+ }
+
+ // unbind cached configurations
+ Iterator configurations = getCachedConfigurations();
+ while ( configurations.hasNext() )
+ {
+ ConfigurationImpl cfg = ( ConfigurationImpl ) configurations.next();
+ if ( location.equals( cfg.getBundleLocation() ) )
+ {
+ cfg.setBundleLocation( null );
+ }
+ }
+
+ // TODO: find factories to unbind !!
+ }
+ }
+
+
+ // ---------- internal -----------------------------------------------------
+
+ private PersistenceManager[] getPersistenceManagers()
+ {
+ if ( persistenceManagers == null || persistenceManagerTracker.getTrackingCount() > pmtCount )
+ {
+
+ ServiceReference[] refs = persistenceManagerTracker.getServiceReferences();
+ if ( refs == null || refs.length == 0 )
+ {
+ return new PersistenceManager[0];
+ }
+
+ SortedSet pms = new TreeSet( cmRankComp );
+ for ( int i = 0; i < refs.length; i++ )
+ {
+ pms.add( persistenceManagerTracker.getService( refs[i] ) );
+ }
+
+ persistenceManagers = ( PersistenceManager[] ) pms.toArray( new PersistenceManager[pms.size()] );
+ }
+
+ return persistenceManagers;
+ }
+
+
+ private void configure( ServiceReference sr, ManagedService service )
+ {
+ String pid = ( String ) sr.getProperty( Constants.SERVICE_PID );
+ if ( pid != null )
+ {
+ ManagedServiceUpdate update = new ManagedServiceUpdate( pid, sr, service );
+ updateThread.schedule( update );
+ }
+
+ }
+
+
+ private void configure( ServiceReference sr, ManagedServiceFactory service )
+ {
+ String pid = ( String ) sr.getProperty( Constants.SERVICE_PID );
+ if ( pid != null )
+ {
+ ManagedServiceFactoryUpdate update = new ManagedServiceFactoryUpdate( pid, sr, service );
+ updateThread.schedule( update );
+ }
+
+ }
+
+
+ ConfigurationImpl getConfiguration( String pid, boolean create ) throws IOException
+ {
+ ConfigurationImpl config = getCachedConfiguration( pid );
+ if ( config != null )
+ {
+ return config;
+ }
+
+ PersistenceManager[] pmList = getPersistenceManagers();
+ for ( int i = 0; i < pmList.length; i++ )
+ {
+ if ( pmList[i].exists( pid ) )
+ {
+ Dictionary props = pmList[i].load( pid );
+ config = new ConfigurationImpl( this, pmList[i], props );
+ cacheConfiguration( config );
+ return config;
+ }
+ }
+
+ // if getting here, there is no configuration yet, optionally create new
+ return ( create ) ? createConfiguration( pid, null ) : null;
+ }
+
+
+ ConfigurationImpl createConfiguration( String pid, String factoryPid ) throws IOException
+ {
+ ConfigurationImpl config = new ConfigurationImpl( this, getPersistenceManagers()[0], pid, factoryPid );
+
+ // immediately store the configuration, yet getProperties() must still
+ // return null
+ config.store();
+
+ cacheConfiguration( config );
+
+ return config;
+ }
+
+
+ Factory getFactory( String factoryPid ) throws IOException
+ {
+ Factory factory = ( Factory ) factories.get( factoryPid );
+ if ( factory != null )
+ {
+ return factory;
+ }
+
+ PersistenceManager[] pmList = getPersistenceManagers();
+ for ( int i = 0; i < pmList.length; i++ )
+ {
+ if ( Factory.exists( pmList[i], factoryPid ) )
+ {
+ factory = Factory.load( pmList[i], factoryPid );
+ factories.put( factoryPid, factory );
+ return factory;
+ }
+ }
+
+ // if getting here, there is no configuration yet, optionally create new
+ return createFactory( factoryPid );
+ }
+
+
+ Factory createFactory( String factoryPid )
+ {
+ Factory factory = new Factory( getPersistenceManagers()[0], factoryPid );
+ factories.put( factoryPid, factory );
+ return factory;
+ }
+
+
+ private Dictionary callPlugins( ServiceReference sr, ConfigurationImpl cfg )
+ {
+ Dictionary props = cfg.getProperties();
+
+ ServiceReference[] plugins = null;
+ try
+ {
+ String pid = ( String ) sr.getProperty( Constants.SERVICE_PID );
+ String filter = "(|(!(cm.target=*))(cm.target=" + pid + "))";
+ plugins = bundleContext.getServiceReferences( ConfigurationPlugin.class.getName(), filter );
+
+ }
+ catch ( InvalidSyntaxException ise )
+ {
+ // no filter, no exception ...
+ }
+
+ // abort early if there are no plugins
+ if ( plugins == null || plugins.length == 0 )
+ {
+ return props;
+ }
+
+ // sort the plugins by their service.cmRanking
+ SortedSet pluginSet = new TreeSet( cmRankComp );
+ for ( int i = 0; plugins != null && i < plugins.length; i++ )
+ {
+ pluginSet.add( plugins[i] );
+ }
+
+ // call the plugins in order
+ for ( Iterator pi = pluginSet.iterator(); pi.hasNext(); )
+ {
+ ServiceReference pluginRef = ( ServiceReference ) pi.next();
+ ConfigurationPlugin plugin = ( ConfigurationPlugin ) bundleContext.getService( pluginRef );
+ try
+ {
+ plugin.modifyConfiguration( sr, props );
+ }
+ catch ( Throwable t )
+ {
+ log( LogService.LOG_ERROR, "Unexpected problem calling" + " configuration plugin", t );
+ }
+ finally
+ {
+ // ensure ungetting the plugin
+ bundleContext.ungetService( pluginRef );
+ }
+ cfg.setAutoProperties( props, false );
+ }
+
+ return props;
+ }
+
+
+ /**
+ * Creates a PID for the given factoryPid
+ *
+ * @param factoryPid
+ * @return
+ */
+ private static String createPid( String factoryPid )
+ {
+ SecureRandom ng = numberGenerator;
+ if ( ng == null )
+ {
+ numberGenerator = ng = new SecureRandom();
+ }
+
+ byte[] randomBytes = new byte[16];
+ ng.nextBytes( randomBytes );
+ randomBytes[6] &= 0x0f; /* clear version */
+ randomBytes[6] |= 0x40; /* set to version 4 */
+ randomBytes[8] &= 0x3f; /* clear variant */
+ randomBytes[8] |= 0x80; /* set to IETF variant */
+
+ StringBuffer buf = new StringBuffer( factoryPid.length() + 1 + 36 );
+
+ // prefix the new pid with the factory pid
+ buf.append( factoryPid ).append( "." );
+
+ // serialize the UUID into the buffer
+ for ( int i = 0; i < randomBytes.length; i++ )
+ {
+
+ if ( i == 4 || i == 6 || i == 8 || i == 10 )
+ {
+ buf.append( '-' );
+ }
+
+ int val = randomBytes[i] & 0xff;
+ buf.append( Integer.toHexString( val >> 4 ) );
+ buf.append( Integer.toHexString( val & 0xf ) );
+ }
+
+ return buf.toString();
+ }
+
+
+ void log( int level, String message, Throwable t )
+ {
+ LogService log = ( LogService ) logTracker.getService();
+ if ( log != null )
+ {
+ log.log( configurationAdminReference, level, message, t );
+ return;
+ }
+
+ String code;
+ switch ( level )
+ {
+ case LogService.LOG_INFO:
+ code = "*INFO *";
+ break;
+
+ case LogService.LOG_WARNING:
+ code = "*WARN *";
+ break;
+
+ case LogService.LOG_ERROR:
+ code = "*ERROR*";
+ break;
+
+ case LogService.LOG_DEBUG:
+ default:
+ code = "*DEBUG*";
+ }
+
+ System.err.println( code + " " + message );
+ if ( t != null )
+ {
+ t.printStackTrace( System.err );
+ }
+ }
+
+ // ---------- inner classes ------------------------------------------------
+
+ private class ManagedServiceUpdate implements Runnable
+ {
+ private String pid;
+
+ private ServiceReference sr;
+
+ private ManagedService service;
+
+
+ ManagedServiceUpdate( String pid, ServiceReference sr, ManagedService service )
+ {
+ this.pid = pid;
+ this.sr = sr;
+ this.service = service;
+ }
+
+
+ public void run()
+ {
+ ConfigurationImpl cfg;
+ try
+ {
+ cfg = getConfiguration( pid, sr.getBundle().getLocation() );
+ }
+ catch ( IOException ioe )
+ {
+ log( LogService.LOG_ERROR, "Error loading configuration for " + pid, ioe );
+ return;
+ }
+
+ // 104.3 Ignore duplicate PIDs from other bundles and report them to
+ // the log
+ // 104.4.1 No update call back for PID already bound to another
+ // bundle location
+ String bundleLocation = sr.getBundle().getLocation();
+ if ( cfg.getBundleLocation() != null && !bundleLocation.equals( cfg.getBundleLocation() ) )
+ {
+ log( LogService.LOG_ERROR, "Cannot use configuration for " + pid + " requested by bundle "
+ + sr.getBundle().getLocation() + " but belongs to " + cfg.getBundleLocation(), null );
+ return;
+ }
+
+ // 104.3 Report an error in the log if more than one service with
+ // the same PID asks for the configuration
+ if ( cfg.getServiceReference() != null && !sr.equals( cfg.getServiceReference() ) )
+ {
+ log( LogService.LOG_ERROR, "Configuration for " + pid + " has already been used for service "
+ + cfg.getServiceReference() + " and will now also be given to " + sr, null );
+ }
+ else
+ {
+ // assign the configuration to the service
+ cfg.setServiceReference( sr );
+ }
+
+ // prepare the configuration for the service (call plugins)
+ Dictionary dictionary = callPlugins( sr, cfg );
+
+ // update the service with the configuration
+ try
+ {
+ service.updated( dictionary );
+ }
+ catch ( ConfigurationException ce )
+ {
+ if ( ce.getProperty() != null )
+ {
+ log( LogService.LOG_ERROR, sr + ": Updating configuration property " + ce.getProperty()
+ + " caused a problem: " + ce.getReason(), ce );
+ }
+ else
+ {
+ log( LogService.LOG_ERROR, sr + ": Updating configuration caused a problem: " + ce.getReason(), ce );
+
+ }
+ }
+ catch ( Throwable t )
+ {
+ log( LogService.LOG_ERROR, sr + ": Unexpected problem updating configuration", t );
+ }
+
+ // 104.5.3 CM_UPDATED is sent asynchronously to all
+ // ConfigurationListeners
+ fireConfigurationEvent( ConfigurationEvent.CM_UPDATED, cfg );
+ }
+ }
+
+ private class ManagedServiceFactoryUpdate implements Runnable
+ {
+ private String factoryPid;
+
+ private ServiceReference sr;
+
+ private ManagedServiceFactory service;
+
+
+ ManagedServiceFactoryUpdate( String factoryPid, ServiceReference sr, ManagedServiceFactory service )
+ {
+ this.factoryPid = factoryPid;
+ this.sr = sr;
+ this.service = service;
+ }
+
+
+ public void run()
+ {
+ Factory factory;
+ try
+ {
+ factory = getFactory( factoryPid );
+ }
+ catch ( IOException ioe )
+ {
+ log( LogService.LOG_ERROR, "Cannot get factory mapping for factory PID " + factoryPid, ioe );
+ return;
+ }
+
+ String bundleLocation = sr.getBundle().getLocation();
+ if ( factory.getBundleLocation() == null )
+ {
+ // bind to the location of the service if unbound
+ factory.setBundleLocation( bundleLocation );
+ }
+ else if ( !bundleLocation.equals( factory.getBundleLocation() ) )
+ {
+ // factory PID is bound to another bundle
+ log( LogService.LOG_ERROR, "Cannot use Factory configuration for " + factoryPid
+ + " requested by bundle " + sr.getBundle().getLocation() + " but belongs to "
+ + factory.getBundleLocation(), null );
+ return;
+ }
+
+ Set pids = factory.getPIDs();
+
+ for ( Iterator pi = pids.iterator(); pi.hasNext(); )
+ {
+ String pid = ( String ) pi.next();
+ ConfigurationImpl cfg;
+ try
+ {
+ cfg = getConfiguration( pid, bundleLocation );
+ }
+ catch ( IOException ioe )
+ {
+ log( LogService.LOG_ERROR, "Error loading configuration for " + pid, ioe );
+ continue;
+ }
+
+ // sanity check on the configuration
+ if ( cfg == null )
+ {
+ log( LogService.LOG_ERROR, "Configuration " + pid + " referred to by factory " + factoryPid
+ + " does not exist", null );
+ factory.removePID( pid );
+ factory.storeSilently();
+ continue;
+ }
+ else if ( !factoryPid.equals( cfg.getFactoryPid() ) )
+ {
+ log( LogService.LOG_ERROR, "Configuration " + pid + " referred to by factory " + factoryPid
+ + " does not exist seems to belong to factory " + cfg.getFactoryPid(), null );
+ factory.removePID( pid );
+ factory.storeSilently();
+ continue;
+ }
+
+ // check bundle location of configuration
+ if ( cfg.getBundleLocation() == null )
+ {
+ // bind to the location of the service if unbound
+ cfg.setBundleLocation( bundleLocation );
+ }
+ else if ( !bundleLocation.equals( cfg.getBundleLocation() ) )
+ {
+ // configuration is bound to another bundle
+ log( LogService.LOG_ERROR, "Configuration " + pid + " (factory " + factoryPid
+ + ") belongs to bundle " + cfg.getBundleLocation() + " but was requested for bundle "
+ + bundleLocation, null );
+ continue;
+ }
+
+ // prepare the configuration for the service (call plugins)
+ Dictionary dictionary = callPlugins( sr, cfg );
+
+ // update the service with the configuration
+ try
+ {
+ service.updated( pid, dictionary );
+ }
+ catch ( ConfigurationException ce )
+ {
+ if ( ce.getProperty() != null )
+ {
+ log( LogService.LOG_ERROR, sr + ": Updating configuration property " + ce.getProperty()
+ + " caused a problem: " + ce.getReason(), ce );
+ }
+ else
+ {
+ log( LogService.LOG_ERROR, sr + ": Updating configuration caused a problem: " + ce.getReason(),
+ ce );
+
+ }
+ }
+ catch ( Throwable t )
+ {
+ log( LogService.LOG_ERROR, sr + ": Unexpected problem updating configuration", t );
+ }
+ }
+ }
+ }
+
+ private class UpdateConfiguration implements Runnable
+ {
+
+ private ConfigurationImpl config;
+
+
+ UpdateConfiguration( ConfigurationImpl config )
+ {
+ this.config = config;
+ }
+
+
+ public void run()
+ {
+ try
+ {
+ if ( config.getFactoryPid() == null )
+ {
+ ServiceReference[] sr = bundleContext.getServiceReferences( ManagedService.class.getName(), "("
+ + Constants.SERVICE_PID + "=" + config.getPid() + ")" );
+ if ( sr != null && sr.length > 0 )
+ {
+ ManagedService srv = ( ManagedService ) bundleContext.getService( sr[0] );
+ try
+ {
+ // bind the configuration, fail if bound to another
+ // bundle !!
+ // check bundle location of configuration
+ String bundleLocation = sr[0].getBundle().getLocation();
+ if ( config.getBundleLocation() == null )
+ {
+ // bind to the location of the service if
+ // unbound
+ config.setBundleLocation( bundleLocation );
+ }
+ else if ( !bundleLocation.equals( config.getBundleLocation() ) )
+ {
+ // configuration is bound to another bundle
+ log( LogService.LOG_ERROR, "Configuration " + config.getPid() + " belongs to bundle "
+ + config.getBundleLocation() + " but was requested for bundle " + bundleLocation,
+ null );
+ return;
+ }
+
+ srv.updated( config.getProperties() );
+ }
+ finally
+ {
+ bundleContext.ungetService( sr[0] );
+ }
+ }
+ }
+ else
+ {
+ ServiceReference[] sr = bundleContext.getServiceReferences( ManagedServiceFactory.class.getName(),
+ "(" + Constants.SERVICE_PID + "=" + config.getFactoryPid() + ")" );
+ if ( sr != null && sr.length > 0 )
+ {
+ ManagedServiceFactory srv = ( ManagedServiceFactory ) bundleContext.getService( sr[0] );
+ try
+ {
+ // bind the configuration, fail if bound to another
+ // bundle !!
+ // check bundle location of configuration
+ String bundleLocation = sr[0].getBundle().getLocation();
+ if ( config.getBundleLocation() == null )
+ {
+ // bind to the location of the service if
+ // unbound
+ config.setBundleLocation( bundleLocation );
+ }
+ else if ( !bundleLocation.equals( config.getBundleLocation() ) )
+ {
+ // configuration is bound to another bundle
+ log( LogService.LOG_ERROR, "Configuration " + config.getPid() + " (factory "
+ + config.getFactoryPid() + ") belongs to bundle " + config.getBundleLocation()
+ + " but was requested for bundle " + bundleLocation, null );
+ return;
+ }
+
+ srv.updated( config.getPid(), config.getProperties() );
+ }
+ finally
+ {
+ bundleContext.ungetService( sr[0] );
+ }
+ }
+ }
+ }
+ catch ( ConfigurationException ce )
+ {
+ if ( ce.getProperty() != null )
+ {
+ log( LogService.LOG_ERROR, "Updating configuration property " + ce.getProperty()
+ + " caused a problem: " + ce.getReason(), ce );
+ }
+ else
+ {
+ log( LogService.LOG_ERROR, "Updating configuration caused a problem: " + ce.getReason(), ce );
+
+ }
+ }
+ catch ( Throwable t )
+ {
+ log( LogService.LOG_ERROR, "Unexpected problem updating configuration", t );
+ }
+
+ fireConfigurationEvent( ConfigurationEvent.CM_UPDATED, config );
+ }
+ }
+
+ private class DeleteConfiguration implements Runnable
+ {
+ private ConfigurationImpl config;
+
+
+ DeleteConfiguration( ConfigurationImpl config )
+ {
+ this.config = config;
+ }
+
+
+ public void run()
+ {
+ try
+ {
+ if ( config.getFactoryPid() == null )
+ {
+ ServiceReference[] sr = bundleContext.getServiceReferences( ManagedService.class.getName(), "("
+ + Constants.SERVICE_PID + "=" + config.getPid() + ")" );
+ if ( sr != null && sr.length > 0 )
+ {
+ ManagedService srv = ( ManagedService ) bundleContext.getService( sr[0] );
+ try
+ {
+ srv.updated( null );
+ }
+ finally
+ {
+ bundleContext.ungetService( sr[0] );
+ }
+ }
+ }
+ else
+ {
+ // remove the pid from the factory
+ Factory factory = getFactory( config.getFactoryPid() );
+ factory.removePID( config.getPid() );
+ factory.store();
+
+ ServiceReference[] sr = bundleContext.getServiceReferences( ManagedServiceFactory.class.getName(),
+ "(" + Constants.SERVICE_PID + "=" + config.getFactoryPid() + ")" );
+ if ( sr != null && sr.length > 0 )
+ {
+ ManagedServiceFactory srv = ( ManagedServiceFactory ) bundleContext.getService( sr[0] );
+ try
+ {
+ srv.deleted( config.getPid() );
+ }
+ finally
+ {
+ bundleContext.ungetService( sr[0] );
+ }
+ }
+ }
+ }
+ catch ( ConfigurationException ce )
+ {
+ if ( ce.getProperty() != null )
+ {
+ log( LogService.LOG_ERROR, "Updating configuration property " + ce.getProperty()
+ + " caused a problem: " + ce.getReason(), ce );
+ }
+ else
+ {
+ log( LogService.LOG_ERROR, "Updating configuration caused a problem: " + ce.getReason(), ce );
+
+ }
+ }
+ catch ( Throwable t )
+ {
+ log( LogService.LOG_ERROR, "Unexpected problem updating configuration", t );
+ }
+
+ fireConfigurationEvent( ConfigurationEvent.CM_DELETED, config );
+ }
+ }
+
+ private class FireConfigurationEvent implements Runnable
+ {
+ private int type;
+
+ private ConfigurationImpl config;
+
+
+ FireConfigurationEvent( int type, ConfigurationImpl config )
+ {
+ this.type = type;
+ this.config = config;
+ }
+
+
+ public void run()
+ {
+ // get the listeners
+ ServiceReference[] srs = configurationListenerTracker.getServiceReferences();
+ if ( srs == null || srs.length == 0 )
+ {
+ return;
+ }
+
+ ConfigurationEvent event = new ConfigurationEvent( configurationAdminReference, type, config
+ .getFactoryPid(), config.getPid() );
+
+ for ( int i = 0; i < srs.length; i++ )
+ {
+ ConfigurationListener cl = ( ConfigurationListener ) configurationListenerTracker.getService( srs[i] );
+ try
+ {
+ cl.configurationEvent( event );
+ }
+ catch ( Throwable t )
+ {
+ log( LogService.LOG_ERROR, "Unexpected problem delivery configuration event to " + srs[i], t );
+ }
+ }
+ }
+ }
+
+ private abstract class AbstractManagedServiceTracker extends ServiceTracker
+ {
+ AbstractManagedServiceTracker( String className )
+ {
+ super( bundleContext, className, null );
+ open();
+ }
+
+
+ public void removedService( ServiceReference reference, Object service )
+ {
+ // check whether we can take back the configuration object
+ String pid = ( String ) reference.getProperty( Constants.SERVICE_PID );
+ if ( pid != null )
+ {
+ ConfigurationImpl cfg = getCachedConfiguration( pid );
+ if ( cfg != null && reference.equals( cfg.getServiceReference() ) )
+ {
+ cfg.setServiceReference( null );
+ }
+ }
+
+ super.removedService( reference, service );
+ }
+ }
+
+ private class ManagedServiceTracker extends AbstractManagedServiceTracker
+ {
+ ManagedServiceTracker()
+ {
+ super( ManagedService.class.getName() );
+ }
+
+
+ public Object addingService( ServiceReference reference )
+ {
+ ManagedService service = ( ManagedService ) super.addingService( reference );
+
+ // configure the managed service
+ if ( service != null )
+ {
+ configure( reference, service );
+ }
+
+ return service;
+ }
+ }
+
+ private class ManagedServiceFactoryTracker extends AbstractManagedServiceTracker
+ {
+ ManagedServiceFactoryTracker()
+ {
+ super( ManagedServiceFactory.class.getName() );
+ }
+
+
+ public Object addingService( ServiceReference reference )
+ {
+ ManagedServiceFactory service = ( ManagedServiceFactory ) super.addingService( reference );
+
+ // configure the managed service
+ if ( service != null )
+ {
+ configure( reference, service );
+ }
+
+ return service;
+ }
+ }
+}
diff --git a/configadmin/src/main/java/org/apache/felix/cm/impl/Factory.java b/configadmin/src/main/java/org/apache/felix/cm/impl/Factory.java
new file mode 100644
index 0000000..f8e98ef
--- /dev/null
+++ b/configadmin/src/main/java/org/apache/felix/cm/impl/Factory.java
@@ -0,0 +1,185 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.felix.cm.impl;
+
+
+import java.io.IOException;
+import java.util.Dictionary;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.Set;
+
+import org.apache.felix.cm.PersistenceManager;
+import org.osgi.service.cm.ConfigurationAdmin;
+import org.osgi.service.log.LogService;
+
+
+/**
+ * The <code>Factory</code> class is used to manage mappings between factory PIDs
+ * the configuration PID belonging to it.
+ *
+ * @author fmeschbe
+ */
+class Factory
+{
+
+ public static final String FACTORY_PID_LIST = "factory.pidList";
+
+ // the persistence manager storing this factory mapping
+ private PersistenceManager persistenceManager;
+
+ // the factory PID of this factory
+ private String factoryPid;
+
+ // the bundle location to which factory PID mapping is bound
+ private String bundleLocation;
+
+ // the set of configuration PIDs belonging to this factory
+ private Set pids;
+
+
+ static boolean exists( PersistenceManager persistenceManager, String factoryPid )
+ {
+ return persistenceManager.exists( factoryPidToIdentifier( factoryPid ) );
+ }
+
+
+ static Factory load( PersistenceManager persistenceManager, String factoryPid ) throws IOException
+ {
+ Dictionary dict = persistenceManager.load( factoryPidToIdentifier( factoryPid ) );
+ return new Factory( persistenceManager, factoryPid, dict );
+ }
+
+
+ private static String factoryPidToIdentifier( String factoryPid )
+ {
+ return factoryPid + ".factory";
+ }
+
+
+ Factory( PersistenceManager persistenceManager, String factoryPid )
+ {
+ this.persistenceManager = persistenceManager;
+ this.factoryPid = factoryPid;
+ this.pids = new HashSet();
+ }
+
+
+ Factory( PersistenceManager persistenceManager, String factoryPid, Dictionary props )
+ {
+ this( persistenceManager, factoryPid );
+
+ // set bundle location
+ setBundleLocation( ( String ) props.get( ConfigurationAdmin.SERVICE_BUNDLELOCATION ) );
+
+ // set pids
+ String[] pidList = ( String[] ) props.get( FACTORY_PID_LIST );
+ if ( pidList != null )
+ {
+ for ( int i = 0; i < pidList.length; i++ )
+ {
+ addPID( pidList[i] );
+ }
+ }
+ }
+
+
+ PersistenceManager getPersistenceManager()
+ {
+ return persistenceManager;
+ }
+
+
+ String getFactoryPid()
+ {
+ return factoryPid;
+ }
+
+
+ String getBundleLocation()
+ {
+ return bundleLocation;
+ }
+
+
+ void setBundleLocation( String bundleLocation )
+ {
+ this.bundleLocation = bundleLocation;
+
+ // 104.15.2.8 The bundle location will be set persistently
+ storeSilently();
+ }
+
+
+ Set getPIDs()
+ {
+ return new HashSet( pids );
+ }
+
+
+ boolean addPID( String pid )
+ {
+ return pids.add( pid );
+ }
+
+
+ boolean removePID( String pid )
+ {
+ return pids.remove( pid );
+ }
+
+
+ void store() throws IOException
+ {
+ Hashtable props = new Hashtable();
+
+ if ( bundleLocation != null )
+ {
+ props.put( ConfigurationAdmin.SERVICE_BUNDLELOCATION, getBundleLocation() );
+ }
+
+ if ( !pids.isEmpty() )
+ {
+ props.put( FACTORY_PID_LIST, pids.toArray( new String[pids.size()] ) );
+ }
+
+ String id = factoryPidToIdentifier( getFactoryPid() );
+ if ( props.isEmpty() )
+ {
+ persistenceManager.delete( id );
+ }
+ else
+ {
+ persistenceManager.store( id, props );
+ }
+ }
+
+ void storeSilently()
+ {
+ try
+ {
+ store();
+ }
+ catch ( IOException ioe )
+ {
+ // should actually log this problem
+ // configurationManager.log( LogService.LOG_ERROR, "Persisting new bundle location failed", ioe );
+ }
+ }
+}
diff --git a/configadmin/src/main/java/org/apache/felix/cm/impl/PersistenceManagerProxy.java b/configadmin/src/main/java/org/apache/felix/cm/impl/PersistenceManagerProxy.java
new file mode 100644
index 0000000..24ee315
--- /dev/null
+++ b/configadmin/src/main/java/org/apache/felix/cm/impl/PersistenceManagerProxy.java
@@ -0,0 +1,120 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.felix.cm.impl;
+
+
+import java.io.IOException;
+import java.util.Dictionary;
+import java.util.Enumeration;
+
+import org.apache.felix.cm.PersistenceManager;
+
+
+/**
+ * The <code>PersistenceManagerProxy</code> TODO
+ *
+ * @author fmeschbe
+ */
+class PersistenceManagerProxy implements PersistenceManager
+{
+
+ private PersistenceManager delegatee;
+
+
+ PersistenceManagerProxy( PersistenceManager delegatee )
+ {
+ setPersistenceManager( delegatee );
+ }
+
+
+ /**
+ * @param pid
+ * @throws IOException
+ * @see org.apache.felix.cm.PersistenceManager#delete(java.lang.String)
+ */
+ public void delete( String pid ) throws IOException
+ {
+ checkDelegatee();
+ delegatee.delete( pid );
+ }
+
+
+ /**
+ * @param pid
+ * @return
+ * @see org.apache.felix.cm.PersistenceManager#exists(java.lang.String)
+ */
+ public boolean exists( String pid )
+ {
+ return delegatee != null && delegatee.exists( pid );
+ }
+
+
+ /**
+ * @return
+ * @throws IOException
+ * @see org.apache.felix.cm.PersistenceManager#getDictionaries()
+ */
+ public Enumeration getDictionaries() throws IOException
+ {
+ checkDelegatee();
+ return delegatee.getDictionaries();
+ }
+
+
+ /**
+ * @param pid
+ * @return
+ * @throws IOException
+ * @see org.apache.felix.cm.PersistenceManager#load(java.lang.String)
+ */
+ public Dictionary load( String pid ) throws IOException
+ {
+ checkDelegatee();
+ return delegatee.load( pid );
+ }
+
+
+ /**
+ * @param pid
+ * @param properties
+ * @throws IOException
+ * @see org.apache.felix.cm.PersistenceManager#store(java.lang.String, java.util.Dictionary)
+ */
+ public void store( String pid, Dictionary properties ) throws IOException
+ {
+ checkDelegatee();
+ delegatee.store( pid, properties );
+ }
+
+
+ void setPersistenceManager( PersistenceManager delegatee )
+ {
+ this.delegatee = delegatee;
+ }
+
+
+ void checkDelegatee() throws IOException
+ {
+ if ( delegatee == null )
+ {
+ throw new IOException( "PersistenceManager not valid" );
+ }
+ }
+}
diff --git a/configadmin/src/main/java/org/apache/felix/cm/impl/RankingComparator.java b/configadmin/src/main/java/org/apache/felix/cm/impl/RankingComparator.java
new file mode 100644
index 0000000..657154a
--- /dev/null
+++ b/configadmin/src/main/java/org/apache/felix/cm/impl/RankingComparator.java
@@ -0,0 +1,98 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.felix.cm.impl;
+
+
+import java.util.Comparator;
+
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.cm.ConfigurationPlugin;
+
+
+/**
+ * The <code>RankingComparator</code> TODO
+ *
+ * @author fmeschbe
+ */
+public class RankingComparator implements Comparator
+{
+
+ private final boolean naturalOrder;
+ private final String rankProperty;
+
+
+ public RankingComparator(boolean naturalOrder)
+ {
+ this( naturalOrder, Constants.SERVICE_RANKING );
+ }
+
+
+ public RankingComparator( boolean naturalOrder, String rankProperty )
+ {
+ this.naturalOrder = naturalOrder;
+ this.rankProperty = rankProperty;
+ }
+
+
+ public int compare( Object obj1, Object obj2 )
+ {
+ if ( obj1.equals( obj2 ) )
+ {
+ return 0;
+ }
+
+ int rank1 = getInt( ( ServiceReference ) obj1, rankProperty );
+ int rank2 = getInt( ( ServiceReference ) obj2, rankProperty );
+
+ // use service id, if rankings are equal
+ if ( rank1 == rank2 )
+ {
+ rank1 = getInt( ( ServiceReference ) obj1, Constants.SERVICE_ID );
+ rank2 = getInt( ( ServiceReference ) obj2, Constants.SERVICE_ID );
+ }
+
+ if ( rank1 == rank2 )
+ {
+ return 0;
+ }
+ else if ( naturalOrder && rank1 > rank2)
+ {
+ return 1;
+ }
+ else
+ {
+ return -1;
+ }
+ }
+
+
+ private int getInt( ServiceReference sr, String property )
+ {
+ Object rankObj = sr.getProperty( property );
+ if ( rankObj instanceof Integer )
+ {
+ return ( ( Integer ) rankObj ).intValue();
+ }
+
+ // null or not an integer
+ return 0;
+ }
+
+}
diff --git a/configadmin/src/main/java/org/apache/felix/cm/impl/UpdateThread.java b/configadmin/src/main/java/org/apache/felix/cm/impl/UpdateThread.java
new file mode 100644
index 0000000..9a5f47d
--- /dev/null
+++ b/configadmin/src/main/java/org/apache/felix/cm/impl/UpdateThread.java
@@ -0,0 +1,119 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.felix.cm.impl;
+
+
+import java.util.LinkedList;
+
+import org.osgi.service.log.LogService;
+
+
+/**
+ * The <code>UpdateThread</code> is the thread used to update managed services
+ * and managed service factories as well as to send configuration events.
+ *
+ * @author fmeschbe
+ */
+public class UpdateThread extends Thread
+{
+
+ // the configuration manager on whose behalf this thread is started
+ // (this is mainly used for logging)
+ private ConfigurationManager configurationManager;
+
+ // the queue of Runnable instances to be run
+ private LinkedList updateTasks;
+
+
+ public UpdateThread( ConfigurationManager configurationManager )
+ {
+ super( "Configuration Updater" );
+
+ this.configurationManager = configurationManager;
+ this.updateTasks = new LinkedList();
+ }
+
+
+ // waits on Runnable instances coming into the queue. As instances come
+ // in, this method calls the Runnable.run method, logs any exception
+ // happening and keeps on waiting for the next Runnable. If the Runnable
+ // taken from the queue is this thread instance itself, the thread
+ // terminates.
+ public void run()
+ {
+ for ( ;; )
+ {
+ Runnable task;
+ synchronized ( updateTasks )
+ {
+ while ( updateTasks.isEmpty() )
+ {
+ try
+ {
+ updateTasks.wait();
+ }
+ catch ( InterruptedException ie )
+ {
+ // don't care
+ }
+ }
+
+ task = ( Runnable ) updateTasks.removeFirst();
+ }
+
+ // return if the task is this thread itself
+ if ( task == this )
+ {
+ return;
+ }
+
+ // otherwise execute the task, log any issues
+ try
+ {
+ task.run();
+ }
+ catch ( Throwable t )
+ {
+ configurationManager.log( LogService.LOG_ERROR, "Unexpected problem executing task", t );
+ }
+ }
+ }
+
+
+ // cause this thread to terminate by adding this thread to the end
+ // of the queue
+ void terminate()
+ {
+ schedule( this );
+ }
+
+
+ // queue the given runnable to be run as soon as possible
+ void schedule( Runnable update )
+ {
+ synchronized ( updateTasks )
+ {
+ // append to the task queue
+ updateTasks.add( update );
+
+ // notify the waiting thread
+ updateTasks.notifyAll();
+ }
+ }
+}