FELIX-3481 Add helper class to manage configurations with targeted PIDs
FELIX-3577 Start factoring out some inner classes
git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1356232 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/configadmin/src/main/java/org/apache/felix/cm/impl/helper/BaseTracker.java b/configadmin/src/main/java/org/apache/felix/cm/impl/helper/BaseTracker.java
new file mode 100644
index 0000000..d57bd41
--- /dev/null
+++ b/configadmin/src/main/java/org/apache/felix/cm/impl/helper/BaseTracker.java
@@ -0,0 +1,209 @@
+/*
+ * 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.helper;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.List;
+
+import org.apache.felix.cm.impl.CaseInsensitiveDictionary;
+import org.apache.felix.cm.impl.ConfigurationImpl;
+import org.apache.felix.cm.impl.ConfigurationManager;
+import org.apache.felix.cm.impl.RankingComparator;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.log.LogService;
+import org.osgi.util.tracker.ServiceTracker;
+
+/**
+ * The <code>BaseTracker</code> is the base class for tracking
+ * <code>ManagedService</code> and <code>ManagedServiceFactory</code>
+ * services. It maps their <code>ServiceRegistration</code> to the
+ * {@link ConfigurationMap} mapping their service PIDs to provided
+ * configuration.
+ */
+public abstract class BaseTracker<S> extends ServiceTracker<S, ConfigurationMap>
+{
+ protected final ConfigurationManager cm;
+
+
+ protected BaseTracker( ConfigurationManager cm, Class<S> type )
+ {
+ super( cm.getBundleContext(), type.getName(), null );
+ this.cm = cm;
+ open();
+ }
+
+
+ public ConfigurationMap addingService( ServiceReference<S> reference )
+ {
+ final String[] pids = getServicePid( reference );
+ configure( reference, pids );
+ return new ConfigurationMap( pids );
+ }
+
+
+ @Override
+ public void modifiedService( ServiceReference<S> reference, ConfigurationMap service )
+ {
+ String[] pids = getServicePid( reference );
+ if ( service.isDifferentPids( pids ) )
+ {
+ configure( reference, pids );
+ service.setConfiguredPids( pids );
+ }
+ }
+
+
+// public void removedService( ServiceReference<ManagedServiceFactory> reference,
+// ServiceHolder<ManagedServiceFactory> holder )
+// {
+// // nothing really to do
+// }
+
+
+ // cm.configure( pids, reference, msf);
+ protected void configure( ServiceReference<S> reference, String[] pids )
+ {
+ if ( pids != null )
+ {
+ S service = getRealService( reference );
+ if ( service != null )
+ {
+ try
+ {
+ if ( this.cm.isLogEnabled( LogService.LOG_DEBUG ) )
+ {
+ this.cm.log( LogService.LOG_DEBUG, "configure(ManagedService {0})", new Object[]
+ { ConfigurationManager.toString( reference ) } );
+ }
+
+ for ( int i = 0; i < pids.length; i++ )
+ {
+ this.cm.configure( pids[i], reference, service, isFactory() );
+ }
+ }
+ finally
+ {
+ ungetRealService( reference );
+ }
+ }
+ }
+ }
+
+
+ public final List<ServiceReference<S>> getServices( final TargetedPID pid )
+ {
+ ServiceReference<S>[] refs = this.getServiceReferences();
+ if ( refs != null )
+ {
+ ArrayList<ServiceReference<S>> result = new ArrayList<ServiceReference<S>>( refs.length );
+ for ( ServiceReference<S> ref : refs )
+ {
+ if ( pid.matchesTarget( ref ) )
+ {
+ ConfigurationMap map = this.getService( ref );
+ if ( map != null && map.accepts( pid.getServicePid() ) )
+ {
+ result.add( ref );
+ }
+ }
+ }
+
+ if ( result.size() > 1 )
+ {
+ Collections.sort( result, RankingComparator.SRV_RANKING );
+ }
+
+ return result;
+ }
+
+ return Collections.emptyList();
+ }
+
+
+ protected abstract boolean isFactory();
+
+ // Updates
+ public abstract void provide( ServiceReference<S> service, ConfigurationImpl config, Dictionary<String, ?> properties );
+
+
+ public abstract void remove( ServiceReference<S> service, ConfigurationImpl config );
+
+
+ protected final S getRealService( ServiceReference<S> reference )
+ {
+ return this.context.getService( reference );
+ }
+
+
+ protected final void ungetRealService( ServiceReference<S> reference )
+ {
+ this.context.ungetService( reference );
+ }
+
+ protected final Dictionary getProperties( Dictionary<String, ?> rawProperties, String targetPid, ServiceReference service )
+ {
+ Dictionary props = new CaseInsensitiveDictionary( rawProperties );
+ this.cm.callPlugins( props, targetPid, service, null /* config */ );
+ return props;
+ }
+
+ /**
+ * Returns the <code>service.pid</code> property of the service reference as
+ * an array of strings or <code>null</code> if the service reference does
+ * not have a service PID property.
+ * <p>
+ * The service.pid property may be a single string, in which case a single
+ * element array is returned. If the property is an array of string, this
+ * array is returned. If the property is a collection it is assumed to be a
+ * collection of strings and the collection is converted to an array to be
+ * returned. Otherwise (also if the property is not set) <code>null</code>
+ * is returned.
+ *
+ * @throws NullPointerException
+ * if reference is <code>null</code>
+ * @throws ArrayStoreException
+ * if the service pid is a collection and not all elements are
+ * strings.
+ */
+ private static String[] getServicePid( ServiceReference reference )
+ {
+ Object pidObj = reference.getProperty( Constants.SERVICE_PID );
+ if ( pidObj instanceof String )
+ {
+ return new String[]
+ { ( String ) pidObj };
+ }
+ else if ( pidObj instanceof String[] )
+ {
+ return ( String[] ) pidObj;
+ }
+ else if ( pidObj instanceof Collection )
+ {
+ Collection pidCollection = ( Collection ) pidObj;
+ return ( String[] ) pidCollection.toArray( new String[pidCollection.size()] );
+ }
+
+ return null;
+ }
+
+}
\ No newline at end of file
diff --git a/configadmin/src/main/java/org/apache/felix/cm/impl/helper/ConfigurationMap.java b/configadmin/src/main/java/org/apache/felix/cm/impl/helper/ConfigurationMap.java
new file mode 100644
index 0000000..553a111
--- /dev/null
+++ b/configadmin/src/main/java/org/apache/felix/cm/impl/helper/ConfigurationMap.java
@@ -0,0 +1,82 @@
+/*
+ * 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.helper;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+public class ConfigurationMap
+{
+ private Map<String, Object> configurations;
+
+
+ public ConfigurationMap( final String[] configuredPids )
+ {
+ this.configurations = Collections.emptyMap();
+ setConfiguredPids( configuredPids );
+ }
+
+ public boolean accepts(final String servicePid) {
+ return configurations.containsKey( servicePid );
+ }
+
+ public void setConfiguredPids( String[] configuredPids )
+ {
+ HashMap<String, Object> newConfigs = new HashMap<String, Object>();
+ if ( configuredPids != null )
+ {
+ for ( String pid : configuredPids )
+ {
+ newConfigs.put( pid, this.configurations.get( pid ) );
+ }
+ }
+ this.configurations = newConfigs;
+ }
+
+
+ public boolean isDifferentPids( final String[] pids )
+ {
+ if ( this.configurations.isEmpty() && pids == null )
+ {
+ return false;
+ }
+ else if ( this.configurations.isEmpty() )
+ {
+ return true;
+ }
+ else if ( pids == null )
+ {
+ return true;
+ }
+ else if ( this.configurations.size() != pids.length )
+ {
+ return true;
+ }
+ else
+ {
+ Set<String> thisPids = this.configurations.keySet();
+ HashSet<String> otherPids = new HashSet<String>( Arrays.asList( pids ) );
+ return !thisPids.equals( otherPids );
+ }
+ }
+}
\ No newline at end of file
diff --git a/configadmin/src/main/java/org/apache/felix/cm/impl/helper/ManagedServiceFactoryTracker.java b/configadmin/src/main/java/org/apache/felix/cm/impl/helper/ManagedServiceFactoryTracker.java
new file mode 100644
index 0000000..c9a36ca
--- /dev/null
+++ b/configadmin/src/main/java/org/apache/felix/cm/impl/helper/ManagedServiceFactoryTracker.java
@@ -0,0 +1,87 @@
+/*
+ * 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.helper;
+
+import java.util.Dictionary;
+
+import org.apache.felix.cm.impl.ConfigurationImpl;
+import org.apache.felix.cm.impl.ConfigurationManager;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.cm.ManagedServiceFactory;
+
+public class ManagedServiceFactoryTracker extends BaseTracker<ManagedServiceFactory>
+{
+
+ public ManagedServiceFactoryTracker( ConfigurationManager cm )
+ {
+ super( cm, ManagedServiceFactory.class );
+ }
+
+
+ @Override
+ protected boolean isFactory()
+ {
+ return true;
+ }
+
+
+ @Override
+ public void provide( ServiceReference<ManagedServiceFactory> reference, final ConfigurationImpl config, final Dictionary<String, ?> rawProps )
+ {
+ ManagedServiceFactory service = getRealService( reference );
+ if ( service != null )
+ {
+ try
+ {
+ Dictionary props = getProperties( rawProps, config.getFactoryPid(), reference );
+ service.updated( config.getPid(), props );
+ }
+ catch ( Throwable t )
+ {
+ this.cm.handleCallBackError( t, reference, config );
+ }
+ finally
+ {
+ this.ungetRealService( reference );
+ }
+ }
+ }
+
+
+ @Override
+ public void remove( ServiceReference<ManagedServiceFactory> reference, final ConfigurationImpl config )
+ {
+ ManagedServiceFactory service = this.getRealService( reference );
+ if ( service != null )
+ {
+ try
+ {
+ service.deleted( config.getPid() );
+ }
+ catch ( Throwable t )
+ {
+ this.cm.handleCallBackError( t, reference, config );
+ }
+ finally
+ {
+ this.ungetRealService( reference );
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/configadmin/src/main/java/org/apache/felix/cm/impl/helper/ManagedServiceTracker.java b/configadmin/src/main/java/org/apache/felix/cm/impl/helper/ManagedServiceTracker.java
new file mode 100644
index 0000000..2b6bbea
--- /dev/null
+++ b/configadmin/src/main/java/org/apache/felix/cm/impl/helper/ManagedServiceTracker.java
@@ -0,0 +1,84 @@
+/*
+ * 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.helper;
+
+import java.util.Dictionary;
+
+import org.apache.felix.cm.impl.ConfigurationImpl;
+import org.apache.felix.cm.impl.ConfigurationManager;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.cm.ManagedService;
+
+public class ManagedServiceTracker extends BaseTracker<ManagedService>
+{
+
+
+ public ManagedServiceTracker( ConfigurationManager cm )
+ {
+ super( cm, ManagedService.class );
+ }
+
+
+ @Override
+ protected boolean isFactory()
+ {
+ return false;
+ }
+
+
+ @Override
+ public void provide( ServiceReference<ManagedService> service, final ConfigurationImpl config, Dictionary<String, ?> properties )
+ {
+ ManagedService srv = this.getRealService( service );
+ if ( srv != null )
+ {
+ try
+ {
+ Dictionary props = getProperties( properties, config.getPid(), service );
+ srv.updated( props );
+ }
+ catch ( Throwable t )
+ {
+ this.cm.handleCallBackError( t, service, config );
+ }
+ finally
+ {
+ this.ungetRealService( service );
+ }
+ }
+ }
+
+ @Override
+ public void remove( ServiceReference<ManagedService> service, final ConfigurationImpl config )
+ {
+ ManagedService srv = this.getRealService( service );
+ try
+ {
+ srv.updated( null );
+ }
+ catch ( Throwable t )
+ {
+ this.cm.handleCallBackError( t, service, config );
+ }
+ finally
+ {
+ this.ungetRealService( service );
+ }
+ }
+}
\ No newline at end of file
diff --git a/configadmin/src/main/java/org/apache/felix/cm/impl/helper/TargetedPID.java b/configadmin/src/main/java/org/apache/felix/cm/impl/helper/TargetedPID.java
new file mode 100644
index 0000000..2d31da5
--- /dev/null
+++ b/configadmin/src/main/java/org/apache/felix/cm/impl/helper/TargetedPID.java
@@ -0,0 +1,267 @@
+/*
+ * 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.helper;
+
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceReference;
+
+
+/**
+ * The <code>TargetedPID</code> class represents a targeted PID as read
+ * from a configuration object.
+ * <p>
+ * For a factory configuration the <code>TargetedPID</code> represents
+ * the factory PID of the configuration. Otherwise it represents the
+ * PID itself of the configuration.
+ */
+public class TargetedPID
+{
+
+ private final String rawPid;
+
+ private final String servicePid;
+
+ private final String symbolicName;
+ private final String version;
+ private final String location;
+
+
+ public TargetedPID( final String rawPid )
+ {
+ this.rawPid = rawPid;
+
+ if ( rawPid.indexOf( '|' ) < 0 )
+ {
+ this.servicePid = rawPid;
+ this.symbolicName = null;
+ this.version = null;
+ this.location = null;
+ }
+ else
+ {
+ int start = 0;
+ int end = rawPid.indexOf( '|' );
+ this.servicePid = rawPid.substring( start, end );
+
+ start = end + 1;
+ end = rawPid.indexOf( '|', start );
+ if ( end >= 0 )
+ {
+ this.symbolicName = rawPid.substring( start, end );
+ start = end + 1;
+ end = rawPid.indexOf( '|', start );
+ if ( end >= 0 )
+ {
+ this.version = rawPid.substring( start, end );
+ this.location = rawPid.substring( end + 1 );
+ }
+ else
+ {
+ this.version = rawPid.substring( start );
+ this.location = null;
+ }
+ }
+ else
+ {
+ this.symbolicName = rawPid.substring( start );
+ this.version = null;
+ this.location = null;
+ }
+ }
+ }
+
+
+ /**
+ * Returns true if the target of this PID (bundle symbolic name,
+ * version, and location) match the bundle registering the referenced
+ * service.
+ * <p>
+ * This method just checks the target not the PID value itself, so
+ * this method returning <code>true</code> does not indicate whether
+ * the service actually is registered with a service PID equal to the
+ * raw PID of this targeted PID.
+ * <p>
+ * This method also returns <code>false</code> if the service has
+ * concurrently been unregistered and the registering bundle is now
+ * <code>null</code>.
+ *
+ * @param reference <code>ServiceReference</code> to the registered
+ * service
+ * @return <code>true</code> if the referenced service matches the
+ * target of this PID.
+ */
+ public boolean matchesTarget( ServiceReference<?> reference )
+ {
+ // This is not really targeted
+ if ( this.symbolicName == null )
+ {
+ return true;
+ }
+
+ final Bundle serviceBundle = reference.getBundle();
+
+ // already unregistered
+ if ( serviceBundle == null )
+ {
+ return false;
+ }
+
+ // bundle symbolic names don't match
+ if ( !this.symbolicName.equals( serviceBundle.getSymbolicName() ) )
+ {
+ return false;
+ }
+
+ // assert version match
+ if ( this.version == null )
+ {
+ return true;
+ }
+ else if ( !this.version.equals( serviceBundle.getVersion().toString() ) )
+ {
+ return false;
+ }
+
+ // assert bundle location match
+ return this.location == null || this.location.equals( serviceBundle.getLocation() );
+ }
+
+
+ /**
+ * Returns the service PID of this targeted PID which basically is
+ * the targeted PID without the targeting information.
+ * @return
+ */
+ public String getServicePid()
+ {
+ return servicePid;
+ }
+
+ /**
+ * Returns how string this <code>TargetedPID</code> matches the
+ * given <code>ServiceReference</code>. The return value is one of
+ * those listed in the table:
+ *
+ * <table>
+ * <tr><th>level</th><th>Description</th></tr>
+ * <tr><td>-1</td><td>The targeted PID does not match at all. This means
+ * that either the service PID, the bundle's symbolic name, the
+ * bundle's version or bundle's location does not match the
+ * respective non-<code>null</code> parts of the targeted PID.
+ * This value is also returned if the raw PID fully matches the
+ * service PID.</td></tr>
+ * <tr><td>0</td><td>The targeted PID only has the service PID which
+ * also matches. The bundle's symbolic name, version, and
+ * location are not considered.</td></tr>
+ * <tr><td>1</td><td>The targeted PID only has the service PID and
+ * bundle symbolic name which both match. The bundle's version and
+ * location are not considered.</td></tr>
+ * <tr><td>2</td><td>The targeted PID only has the service PID, bundle
+ * symbolic name and version which all match. The bundle's
+ * location is not considered.</td></tr>
+ * <tr><td>3</td><td>The targeted PID has the service PID as well as
+ * the bundle symbolic name, version, and location which all
+ * match.</td></tr>
+ * </table>
+ *
+ * @param ref
+ * @return
+ */
+ public int matchLevel( final ServiceReference ref )
+ {
+ final Object servicePid = ref.getProperty( Constants.SERVICE_PID );
+
+ // in case the service PID contains | characters, this allows
+ // it to match the raw version of the targeted PID
+ if ( this.rawPid.equals( servicePid ) )
+ {
+ return 1;
+ }
+
+ if ( !this.servicePid.equals( servicePid ) )
+ {
+ return -1;
+ }
+
+ if ( this.symbolicName == null )
+ {
+ return 0;
+ }
+
+ final Bundle refBundle = ref.getBundle();
+ if ( !this.symbolicName.equals( refBundle.getSymbolicName() ) )
+ {
+ return -1;
+ }
+
+ if ( this.version == null )
+ {
+ return 1;
+ }
+
+ if ( !this.version.equals( refBundle.getHeaders().get( Constants.BUNDLE_VERSION ) ) )
+ {
+ return -1;
+ }
+
+ if ( this.location == null )
+ {
+ return 2;
+ }
+
+ if ( !this.location.equals( refBundle.getLocation() ) )
+ {
+ return -1;
+ }
+
+ return 3;
+ }
+
+
+ @Override
+ public int hashCode()
+ {
+ return this.rawPid.hashCode();
+ }
+
+
+ @Override
+ public boolean equals( Object obj )
+ {
+ if ( obj == null )
+ {
+ return true;
+ }
+ else if ( obj == this )
+ {
+ return true;
+ }
+
+ // assume equality if same class and raw PID equals
+ if ( this.getClass() == obj.getClass() )
+ {
+ return this.rawPid.equals( ( ( TargetedPID ) obj ).rawPid );
+ }
+
+ // not the same class or different raw PID
+ return false;
+ }
+}