/* * 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.security.AccessControlContext; import java.security.AccessController; import java.security.DomainCombiner; import java.security.Permission; import java.security.ProtectionDomain; 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.ConfigurationManager; import org.apache.felix.cm.impl.Log; import org.apache.felix.cm.impl.RankingComparator; import org.osgi.framework.Bundle; import org.osgi.framework.Constants; import org.osgi.framework.ServiceReference; import org.osgi.service.cm.ConfigurationException; 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>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; private final boolean managedServiceFactory; protected BaseTracker( final ConfigurationManager cm, final boolean managedServiceFactory ) { super( cm.getBundleContext(), ( managedServiceFactory ? ManagedServiceFactory.class.getName() : ManagedService.class.getName() ), null ); this.cm = cm; this.managedServiceFactory = managedServiceFactory; open(); } @Override public ConfigurationMap<?> addingService( ServiceReference<S> reference ) { Log.logger.log( LogService.LOG_DEBUG, "Registering service {0}", new Object[] { reference } ); final String[] pids = getServicePid( reference ); final ConfigurationMap<?> configurations = createConfigurationMap( pids ); configure( reference, pids, configurations ); return configurations; } @Override public void modifiedService( ServiceReference<S> reference, ConfigurationMap<?> service ) { Log.logger.log( LogService.LOG_DEBUG, "Modified service {0}", new Object[] { reference} ); String[] pids = getServicePid( reference ); if ( service.isDifferentPids( pids ) ) { service.setConfiguredPids( pids ); configure( reference, pids, service ); } } @Override public void removedService( ServiceReference<S> reference, ConfigurationMap<?> service ) { // just log Log.logger.log( LogService.LOG_DEBUG, "Unregistering service {0}", new Object[] { reference } ); } private void configure( ServiceReference<S> reference, String[] pids, ConfigurationMap<?> configurations ) { if ( pids != null ) { this.cm.configure( pids, reference, managedServiceFactory, configurations ); } } 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 ) { ConfigurationMap map = this.getService( ref ); if ( map != null && ( map.accepts( pid.getRawPid() ) || ( map.accepts( pid.getServicePid() ) && pid .matchesTarget( ref ) ) ) ) { result.add( ref ); } } if ( result.size() > 1 ) { Collections.sort( result, RankingComparator.SRV_RANKING ); } return result; } return Collections.emptyList(); } protected abstract ConfigurationMap<?> createConfigurationMap( String[] pids ); /** * Returns the String to be used as the PID of the service PID for the * {@link TargetedPID pid} retrieved from the configuration. * <p> * This method will return {@link TargetedPID#getServicePid()} most of * the time except if the service PID used for the consumer's service * registration contains one or more pipe symbols (|). In this case * {@link TargetedPID#getRawPid()} might be returned. * * @param service The reference ot the service for which the service * PID is to be returned. * @param pid The {@link TargetedPID} for which to return the service * PID. * @return The service PID or <code>null</code> if the service does not * respond to the targeted PID at all. */ public abstract String getServicePid( ServiceReference<S> service, TargetedPID pid ); /** * Updates the given service with the provided configuration. * <p> * See the implementations of this method for more information. * * @param service The reference to the service to update * @param configPid The targeted configuration PID * @param factoryPid The targeted factory PID or <code>null</code> for * a non-factory configuration * @param properties The configuration properties, which may be * <code>null</code> if this is the provisioning call upon * service registration of a ManagedService * @param revision The configuration revision or -1 if there is no * configuration actually to provide. * @param configurationMap The PID to configuration map for PIDs * used by the service to update * * @see ManagedServiceTracker#provideConfiguration(ServiceReference, TargetedPID, TargetedPID, Dictionary, long, ConfigurationMap) * @see ManagedServiceFactoryTracker#provideConfiguration(ServiceReference, TargetedPID, TargetedPID, Dictionary, long, ConfigurationMap) */ public abstract void provideConfiguration( ServiceReference<S> service, TargetedPID configPid, TargetedPID factoryPid, Dictionary<String, ?> properties, long revision, ConfigurationMap<?> configurationMap); /** * Remove the configuration indicated by the {@code configPid} from * the service. * * @param service The reference to the service from which the * configuration is to be removed. * @param configPid The {@link TargetedPID} of the configuration * @param factoryPid The {@link TargetedPID factory PID} of the * configuration. This may be {@code null} for a non-factory * configuration. */ public abstract void removeConfiguration( ServiceReference<S> service, TargetedPID configPid, TargetedPID factoryPid); 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<String, Object> getProperties( Dictionary<String, ?> rawProperties, ServiceReference<?> service, String configPid, String factoryPid ) { Dictionary<String, Object> props = new CaseInsensitiveDictionary( rawProperties ); this.cm.callPlugins( props, service, configPid, factoryPid ); return props; } protected final void handleCallBackError( final Throwable error, final ServiceReference target, final TargetedPID pid ) { if ( error instanceof ConfigurationException ) { final ConfigurationException ce = ( ConfigurationException ) error; if ( ce.getProperty() != null ) { Log.logger.log( LogService.LOG_ERROR, "{0}: Updating property {1} of configuration {2} caused a problem: {3}", new Object[] { target , ce.getProperty(), pid, ce.getReason(), ce } ); } else { Log.logger.log( LogService.LOG_ERROR, "{0}: Updating configuration {1} caused a problem: {2}", new Object[] { target, pid, ce.getReason(), ce } ); } } else { { Log.logger.log( LogService.LOG_ERROR, "{0}: Unexpected problem updating configuration {1}", new Object[] { target, pid, error } ); } } } /** * 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; } AccessControlContext getAccessControlContext( final Bundle bundle ) { return new AccessControlContext(AccessController.getContext(), new CMDomainCombiner(bundle)); } private static class CMDomainCombiner implements DomainCombiner { private final Bundle bundle; CMDomainCombiner(Bundle bundle) { this.bundle = bundle; } @Override public ProtectionDomain[] combine(ProtectionDomain[] arg0, ProtectionDomain[] arg1) { return new ProtectionDomain[] { new CMProtectionDomain(bundle) }; } } private static class CMProtectionDomain extends ProtectionDomain { private final Bundle bundle; CMProtectionDomain(Bundle bundle) { super(null, null); this.bundle = bundle; } @Override public boolean implies(Permission permission) { try { return bundle.hasPermission(permission); } catch (IllegalStateException e) { return false; } } } }