All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.felix.cm.impl.ConfigurationImpl Maven / Gradle / Ivy

There is a newer version: 1.9.26
Show newest version
/*
 * 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.Arrays;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.Hashtable;

import org.apache.felix.cm.PersistenceManager;
import org.apache.felix.cm.impl.helper.TargetedPID;
import org.osgi.framework.Constants;
import org.osgi.service.cm.Configuration;
import org.osgi.service.cm.ConfigurationAdmin;
import org.osgi.service.log.LogService;


/**
 * The ConfigurationImpl is the backend implementation of the
 * Configuration Admin Service Specification Configuration object
 * (section 104.4). Instances of this class are shared by multiple instances of
 * the {@link ConfigurationAdapter} class, whose instances are actually returned
 * to clients.
 */
public class ConfigurationImpl
{

    /*
     * Concurrency note: There is a slight (but real) chance of a race condition
     * between a configuration update and a ManagedService[Factory] registration.
     * Per the specification a ManagedService must be called with configuration
     * or null when registered and a ManagedService must be called with currently
     * existing configuration when registered. Also the ManagedService[Factory]
     * must be updated when the configuration is updated.
     *
     * Consider now this situation of two threads T1 and T2:
     *
     *    T1. create and update configuration
     *      ConfigurationImpl.update persists configuration and sets field
     *      Thread preempted
     *
     *    T2. ManagedServiceUpdate constructor reads configuration
     *      Uses configuration already persisted by T1 for update
     *      Schedules task to update service with the configuration
     *
     *    T1. Runs again creating the UpdateConfiguration task with the
     *      configuration persisted before being preempted
     *      Schedules task to update service
     *
     *    Update Thread:
     *      Updates ManagedService with configuration prepared by T2
     *      Updates ManagedService with configuration prepared by T1
     *
     * The correct behaviour would be here, that the second call to update
     * would not take place. We cannot at this point in time easily fix
     * this issue. Also, it seems that changes for this to happen are
     * small.
     *
     * This class provides modification counter (lastModificationTime)
     * which is incremented on each change of the configuration. This
     * helps the update tasks in the ConfigurationManager to log the
     * revision of the configuration supplied.
     */

    /**
     * The name of a synthetic property stored in the persisted configuration
     * data to indicate that the configuration data is new, that is created but
     * never updated (value is "_felix_.cm.newConfiguration").
     * 

* This special property is stored by the * {@link #ConfigurationImpl(ConfigurationManager, PersistenceManager, String, String, String)} * constructor, when the configuration is first created and persisted and is * interpreted by the * {@link #ConfigurationImpl(ConfigurationManager, PersistenceManager, Dictionary)} * method when the configuration data is loaded in a new object. *

* The goal of this property is to keep the information on whether * configuration data is new (but persisted as per the spec) or has already * been assigned with possible no data. */ private static final String CONFIGURATION_NEW = "_felix_.cm.newConfiguration"; private static final String PROPERTY_LOCKED = ":org.apache.felix.configadmin.locked:"; private static final String PROPERTY_REVISION = ":org.apache.felix.configadmin.revision:"; /** * The factory PID of this configuration or null if this * is not a factory configuration. */ private final TargetedPID factoryPID; /** * The statically bound bundle location, which is set explicitly by calling * the Configuration.setBundleLocation(String) method or when the * configuration was created with the two-argument method. */ private volatile String staticBundleLocation; /** * The bundle location from dynamic binding. This value is set as the * configuration or factory is assigned to a ManagedService[Factory]. */ private volatile String dynamicBundleLocation; /** * 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 * null if the configuration has been created and never been * updated with acutal configuration properties. */ private volatile CaseInsensitiveDictionary properties; /** * Flag indicating that this configuration has been deleted. * * @see #isDeleted() */ private volatile boolean isDeleted; /** * Configuration revision counter incremented each time the * {@link #properties} is set (in the constructor or the * {@link #configure(Dictionary)} method. This counter is * persisted transparently so that {@link NotCachablePersistenceManager} * can provide a proper change count. The persistence is forward * compatible such that previously persisted configurations are * handled gracefully. */ private volatile long revision; private volatile boolean locked; /** * The {@link ConfigurationManager configuration manager} instance which * caused this configuration object to be created. */ private final ConfigurationManager configurationManager; // the persistence manager storing this factory mapping private final PersistenceManager persistenceManager; // the basic ID of this instance private final TargetedPID baseId; public ConfigurationImpl( ConfigurationManager configurationManager, PersistenceManager persistenceManager, Dictionary properties ) { if ( configurationManager == null ) { throw new IllegalArgumentException( "ConfigurationManager must not be null" ); } if ( persistenceManager == null ) { throw new IllegalArgumentException( "PersistenceManager must not be null" ); } this.configurationManager = configurationManager; this.persistenceManager = persistenceManager; this.baseId = new TargetedPID( ( String ) properties.remove( Constants.SERVICE_PID ) ); final String factoryPid = ( String ) properties.remove( ConfigurationAdmin.SERVICE_FACTORYPID ); this.factoryPID = ( factoryPid == null ) ? null : new TargetedPID( factoryPid ); this.isDeleted = false; // set bundle location from persistence and/or check for dynamic binding this.staticBundleLocation = ( String ) properties.remove( ConfigurationAdmin.SERVICE_BUNDLELOCATION ) ; this.dynamicBundleLocation = configurationManager.getDynamicBundleLocation( this.baseId.toString() ); // set the properties internally configureFromPersistence( properties ); } ConfigurationImpl( ConfigurationManager configurationManager, PersistenceManager persistenceManager, String pid, String factoryPid, String bundleLocation ) throws IOException { if ( configurationManager == null ) { throw new IllegalArgumentException( "ConfigurationManager must not be null" ); } if ( persistenceManager == null ) { throw new IllegalArgumentException( "PersistenceManager must not be null" ); } this.configurationManager = configurationManager; this.persistenceManager = persistenceManager; this.baseId = new TargetedPID( pid ); this.factoryPID = ( factoryPid == null ) ? null : new TargetedPID( factoryPid ); this.isDeleted = false; // set bundle location from persistence and/or check for dynamic binding this.staticBundleLocation = bundleLocation; this.dynamicBundleLocation = configurationManager.getDynamicBundleLocation( this.baseId.toString() ); // first "update" this.properties = null; this.revision = 1; // this is a new configuration object, store immediately unless // the new configuration object is created from a factory, in which // case the configuration is only stored when first updated if ( factoryPid == null ) { storeNewConfiguration(); } } /** * Returns true if the ConfigurationManager of this * configuration is still active. */ boolean isActive() { return configurationManager.isActive(); } void storeSilently() { try { this.store(); } catch ( IOException ioe ) { Log.logger.log( LogService.LOG_ERROR, "Persisting ID {0} failed", new Object[] { this.baseId, ioe } ); } } static protected void replaceProperty( Dictionary properties, String key, String value ) { if ( value == null ) { properties.remove( key ); } else { properties.put( key, value ); } } public void delete() throws IOException { this.isDeleted = true; this.persistenceManager.delete( this.getPidString() ); configurationManager.setDynamicBundleLocation( this.getPidString(), null ); configurationManager.deleted( this ); } public String getPidString() { return this.baseId.toString(); } public TargetedPID getPid() { return this.baseId; } public String getFactoryPidString() { return (factoryPID == null) ? null : factoryPID.toString(); } public TargetedPID getFactoryPid() { return factoryPID; } /** * Returns the "official" bundle location as visible from the outside * world of code calling into the Configuration.getBundleLocation() method. *

* In other words: The {@link #getStaticBundleLocation()} is returned if * not null. Otherwise the {@link #getDynamicBundleLocation()} * is returned (which may also be null). */ String getBundleLocation() { if ( staticBundleLocation != null ) { return staticBundleLocation; } return dynamicBundleLocation; } String getDynamicBundleLocation() { return dynamicBundleLocation; } String getStaticBundleLocation() { return staticBundleLocation; } void setStaticBundleLocation( final String bundleLocation ) { // CM 1.4; needed for bundle location change at the end final String oldBundleLocation = getBundleLocation(); // 104.15.2.8 The bundle location will be set persistently this.staticBundleLocation = bundleLocation; storeSilently(); // FELIX-3360: Always clear dynamic binding if a new static // location is set. The static location is the relevant binding // for a configuration unless it is not explicitly set. setDynamicBundleLocation( null, false ); // CM 1.4 this.configurationManager.locationChanged( this, oldBundleLocation ); } void setDynamicBundleLocation( final String bundleLocation, final boolean dispatchConfiguration ) { // CM 1.4; needed for bundle location change at the end final String oldBundleLocation = getBundleLocation(); this.dynamicBundleLocation = bundleLocation; this.configurationManager.setDynamicBundleLocation( this.getPidString(), bundleLocation ); // CM 1.4 if ( dispatchConfiguration ) { this.configurationManager.locationChanged( this, oldBundleLocation ); } } /** * Dynamically binds this configuration to the given location unless * the configuration is already bound (statically or dynamically). In * the case of this configuration to be dynamically bound a * CM_LOCATION_CHANGED event is dispatched. */ void tryBindLocation( final String bundleLocation ) { if ( this.getBundleLocation() == null ) { Log.logger.log( LogService.LOG_DEBUG, "Dynamically binding config {0} to {1}", new Object[] { getPidString(), bundleLocation } ); setDynamicBundleLocation( bundleLocation, true ); } } /** * Returns an optionally deep copy of the properties of this configuration * instance. *

* This method returns a copy of the internal dictionary. If the * deepCopy parameter is true array and collection values are * copied into new arrays or collections. Otherwise just a new dictionary * referring to the same objects is returned. * * @param deepCopy * true if a deep copy is to be returned. * @return the configuration properties */ public Dictionary getProperties( boolean deepCopy ) { // no properties yet if ( properties == null ) { return null; } CaseInsensitiveDictionary props = new CaseInsensitiveDictionary( properties, deepCopy ); // fix special properties (pid, factory PID, bundle location) setAutoProperties( props, false ); return props; } /* (non-Javadoc) * @see org.osgi.service.cm.Configuration#update() */ public void update() throws IOException { // read configuration from persistence (again) if ( persistenceManager.exists( getPidString() ) ) { @SuppressWarnings("unchecked") Dictionary properties = persistenceManager.load( getPidString() ); // ensure serviceReference pid String servicePid = ( String ) properties.get( Constants.SERVICE_PID ); if ( servicePid != null && !getPidString().equals( servicePid ) ) { throw new IOException( "PID of configuration file does match requested PID; expected " + getPidString() + ", got " + servicePid ); } // we're doing a local update, so override the properties revision properties.put( PROPERTY_REVISION, Long.valueOf(getRevision()) ); configureFromPersistence( properties ); } // update the service but do not fire an CM_UPDATED event configurationManager.updated( this, false ); } /** * @see org.osgi.service.cm.Configuration#update(java.util.Dictionary) */ public void update( Dictionary properties ) throws IOException { CaseInsensitiveDictionary newProperties = new CaseInsensitiveDictionary( properties ); Log.logger.log( LogService.LOG_DEBUG, "Updating config {0} with {1}", new Object[] { getPidString(), newProperties } ); setAutoProperties( newProperties, true ); // persist new configuration newProperties.put( PROPERTY_REVISION, Long.valueOf(getRevision()) ); persistenceManager.store( getPidString(), newProperties ); // finally assign the configuration for use configure( newProperties ); // update the service and fire an CM_UPDATED event configurationManager.updated( this, true ); } //---------- Object overwrites -------------------------------------------- @Override public boolean equals( Object obj ) { if ( obj == this ) { return true; } if ( obj instanceof Configuration ) { return getPidString().equals( ( ( Configuration ) obj ).getPid() ); } return false; } @Override public int hashCode() { return getPidString().hashCode(); } @Override public String toString() { return "Configuration PID=" + getPidString() + ", factoryPID=" + factoryPID + ", bundleLocation=" + getBundleLocation(); } // ---------- private helper ----------------------------------------------- /** * Stores the configuration if it is a newly factory configuration * which has not been persisted yet. *

* This is used to ensure a configuration c as in *

     * Configuration cf = cm.createFactoryConfiguration(factoryPid);
     * Configuration c = cm.getConfiguration(cf.getPid());
     * 
* is persisted after getConfiguration while * createConfiguration alone does not persist yet. */ void ensureFactoryConfigPersisted() throws IOException { if ( this.factoryPID != null && isNew() && !persistenceManager.exists( getPidString() ) ) { storeNewConfiguration(); } } /** * Persists a new (freshly created) configuration with a marker for * it to be a new configuration. * * @throws IOException If an error occurrs storing the configuraiton */ private void storeNewConfiguration() throws IOException { Dictionary props = new Hashtable<>(); setAutoProperties( props, true ); props.put( CONFIGURATION_NEW, Boolean.TRUE ); props.put( PROPERTY_REVISION, Long.valueOf(getRevision()) ); persistenceManager.store( getPidString(), props ); } void store() throws IOException { // we don't need a deep copy, since we are not modifying // any value in the dictionary itself. we are just adding // properties to it, which are required for storing Dictionary props = getProperties( false ); // 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 // statically bound) setAutoProperties( props, true ); } else { replaceProperty( props, ConfigurationAdmin.SERVICE_BUNDLELOCATION, getStaticBundleLocation() ); } if ( this.locked ) { props.put(PROPERTY_LOCKED, this.locked); } else { props.remove(PROPERTY_LOCKED); } // only store now, if this is not a new configuration props.put( PROPERTY_REVISION, Long.valueOf(getRevision()) ); persistenceManager.store( getPidString(), props ); } /** * Returns the revision of this configuration object. *

* When getting both the configuration properties and this revision * counter, the two calls should be synchronized on this instance to * ensure configuration values and revision counter match. */ public long getRevision() { return revision; } /** * Returns false if this configuration contains configuration * properties. Otherwise true is returned and this is a * newly creted configuration object whose {@link #update(Dictionary)} * method has never been called. */ boolean isNew() { return properties == null; } /** * Returns true if this configuration has already been deleted * on the persistence. */ boolean isDeleted() { return isDeleted; } private void configureFromPersistence( Dictionary properties ) { // if the this is not an empty/new configuration, accept the properties // otherwise just set the properties field to null if ( properties.get( CONFIGURATION_NEW ) == null ) { configure( properties ); } else { configure( null ); } } private void configure( final Dictionary properties ) { final Object revisionValue = properties == null ? null : properties.get(PROPERTY_REVISION); final Object lockedValue = properties == null ? null : properties.get(PROPERTY_LOCKED); if ( lockedValue != null ) { this.locked = true; } final CaseInsensitiveDictionary newProperties; if ( properties == null ) { newProperties = null; } else { // remove predefined properties clearAutoProperties( properties ); // ensure CaseInsensitiveDictionary if ( properties instanceof CaseInsensitiveDictionary ) { newProperties = ( CaseInsensitiveDictionary ) properties; } else { newProperties = new CaseInsensitiveDictionary( properties ); } } synchronized ( this ) { this.properties = newProperties; this.revision = (revisionValue != null) ? 1 + ((Long)revisionValue).longValue() : ++revision ; } } void setAutoProperties( Dictionary properties, boolean withBundleLocation ) { // set pid and factory pid in the properties replaceProperty( properties, Constants.SERVICE_PID, getPidString() ); replaceProperty( properties, ConfigurationAdmin.SERVICE_FACTORYPID, getFactoryPidString() ); // bundle location is not set here if ( withBundleLocation ) { replaceProperty( properties, ConfigurationAdmin.SERVICE_BUNDLELOCATION, getStaticBundleLocation() ); } else { properties.remove( ConfigurationAdmin.SERVICE_BUNDLELOCATION ); } properties.remove( PROPERTY_LOCKED ); properties.remove( PROPERTY_REVISION ); } static void setAutoProperties( Dictionary properties, String pid, String factoryPid ) { replaceProperty( properties, Constants.SERVICE_PID, pid ); replaceProperty( properties, ConfigurationAdmin.SERVICE_FACTORYPID, factoryPid ); properties.remove( ConfigurationAdmin.SERVICE_BUNDLELOCATION ); properties.remove( PROPERTY_LOCKED ); properties.remove( PROPERTY_REVISION ); } private static final String[] AUTO_PROPS = new String[] { Constants.SERVICE_PID, ConfigurationAdmin.SERVICE_FACTORYPID, ConfigurationAdmin.SERVICE_BUNDLELOCATION, PROPERTY_LOCKED, PROPERTY_REVISION }; static void clearAutoProperties( Dictionary properties ) { for(final String p : AUTO_PROPS) { properties.remove( p ); } } public void setLocked(final boolean flag) throws IOException { this.locked = flag; store(); } /** * Compare the two properties, ignoring auto properties * @param props1 Set of properties * @param props2 Set of properties * @return {@code true} if the set of properties is equal */ static boolean equals( Dictionary props1, Dictionary props2) { if (props1 == null) { if (props2 == null) { return true; } else { return false; } } else if (props2 == null) { return false; } final int count1 = getCount(props1); final int count2 = getCount(props2); if ( count1 != count2 ) { return false; } final Enumeration keys = props1.keys(); while ( keys.hasMoreElements() ) { final String key = keys.nextElement(); if ( !isAutoProp(key) ) { final Object val1 = props1.get(key); final Object val2 = props2.get(key); if ( val1 == null ) { if ( val2 != null ) { return false; } } else { if ( val2 == null ) { return false; } // arrays are compared using Arrays.equals if ( val1.getClass().isArray() ) { if ( !val2.getClass().isArray() ) { return false; } final Object[] a1 = convertToObjectArray(val1); final Object[] a2 = convertToObjectArray(val2); if ( ! Arrays.equals(a1, a2) ) { return false; } } else if ( !val1.equals(val2) ) { return false; } } } } return true; } /** * Convert the object to an array * @param value The array * @return an object array */ private static Object[] convertToObjectArray(final Object value) { final Object[] values; if (value instanceof long[]) { final long[] a = (long[])value; values = new Object[a.length]; for(int i=0;i props ) { int count = (props == null ? 0 : props.size()); if ( props != null ) { for(final String p : AUTO_PROPS) { if ( props.get(p) != null ) { count--; } } } return count; } public boolean isLocked() { return this.locked; } final ConfigurationManager getConfigurationManager() { return this.configurationManager; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy