Package org.apache.felix.cm.impl

Source Code of org.apache.felix.cm.impl.ConfigurationImpl

/*
* 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.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 <code>ConfigurationImpl</code> is the backend implementation of the
* Configuration Admin Service Specification <i>Configuration object</i>
* (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 extends ConfigurationBase
{

    /*
     * 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").
     * <p>
     * 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.
     * <p>
     * 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";

    /**
     * The factory PID of this configuration or <code>null</code> 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
     * <code>null</code> 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 transient
     * and not persisted. Thus it is restarted from zero each time
     * an instance of this class is created.
     */
    private volatile long revision;


    ConfigurationImpl( ConfigurationManager configurationManager, PersistenceManager persistenceManager,
        Dictionary properties )
    {
        super( configurationManager, persistenceManager, ( 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( getBaseId().toString() );

        // set the properties internally
        configureFromPersistence( properties );
    }


    ConfigurationImpl( ConfigurationManager configurationManager, PersistenceManager persistenceManager, String pid,
        String factoryPid, String bundleLocation ) throws IOException
    {
        super( configurationManager, persistenceManager, 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( getBaseId().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();
        }
    }


    public void delete() throws IOException
    {
        this.isDeleted = true;
        getPersistenceManager().delete( this.getPidString() );
        getConfigurationManager().setDynamicBundleLocation( this.getPidString(), null );
        getConfigurationManager().deleted( this );
    }


    public String getPidString()
    {
        return getBaseId().toString();
    }


    public TargetedPID getPid()
    {
        return getBaseId();
    }


    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.
     * <p>
     * In other words: The {@link #getStaticBundleLocation()} is returned if
     * not <code>null</code>. Otherwise the {@link #getDynamicBundleLocation()}
     * is returned (which may also be <code>null</code>).
     */
    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.getConfigurationManager().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.getConfigurationManager().setDynamicBundleLocation( this.getPidString(), bundleLocation );

        // CM 1.4
        if ( dispatchConfiguration )
        {
            this.getConfigurationManager().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
     * <code>CM_LOCATION_CHANGED</code> event is dispatched.
     */
    void tryBindLocation( final String bundleLocation )
    {
        if ( this.getBundleLocation() == null )
        {
            getConfigurationManager().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.
     * <p>
     * This method returns a copy of the internal dictionary. If the
     * <code>deepCopy</code> 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
     *            <code>true</code> 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
    {
        PersistenceManager localPersistenceManager = getPersistenceManager();
        if ( localPersistenceManager != null )
        {
            // read configuration from persistence (again)
            if ( localPersistenceManager.exists( getPidString() ) )
            {
                Dictionary properties = localPersistenceManager.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 );
                }

                configureFromPersistence( properties );
            }

            // update the service but do not fire an CM_UPDATED event
            getConfigurationManager().updated( this, false );
        }
    }


    /* (non-Javadoc)
     * @see org.osgi.service.cm.Configuration#update(java.util.Dictionary)
     */
    public void update( Dictionary properties ) throws IOException
    {
        PersistenceManager localPersistenceManager = getPersistenceManager();
        if ( localPersistenceManager != null )
        {
            CaseInsensitiveDictionary newProperties = new CaseInsensitiveDictionary( properties );

            getConfigurationManager().log( LogService.LOG_DEBUG, "Updating config {0} with {1}", new Object[]
                { getPidString(), newProperties } );

            setAutoProperties( newProperties, true );

            // persist new configuration
            localPersistenceManager.store( getPidString(), newProperties );

            // finally assign the configuration for use
            configure( newProperties );

            // if this is a factory configuration, update the factory with
            // do this only after configuring with current properties such
            // that a concurrently registered ManagedServiceFactory service
            // does not receive a new/unusable configuration
            updateFactory();

            // update the service and fire an CM_UPDATED event
            getConfigurationManager().updated( this, true );
        }
    }


    //---------- Object overwrites --------------------------------------------

    public boolean equals( Object obj )
    {
        if ( obj == this )
        {
            return true;
        }

        if ( obj instanceof Configuration )
        {
            return getPidString().equals( ( ( Configuration ) obj ).getPid() );
        }

        return false;
    }


    public int hashCode()
    {
        return getPidString().hashCode();
    }


    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.
     * <p>
     * This is used to ensure a configuration c as in
     * <pre>
     * Configuration cf = cm.createFactoryConfiguration(factoryPid);
     * Configuration c = cm.getConfiguration(cf.getPid());
     * </pre>
     * is persisted after <code>getConfiguration</code> while
     * <code>createConfiguration</code> alone does not persist yet.
     */
    void ensureFactoryConfigPersisted() throws IOException
    {
        if ( this.factoryPID != null && isNew() && !getPersistenceManager().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 );
        getPersistenceManager().store( getPidString(), props );
    }


    /**
     * Makes sure the configuration is added to the {@link Factory} (and
     * the factory be stored if updated) if this is a factory
     * configuration.
     *
     * @throws IOException If an error occurrs storing the {@link Factory}
     */
    private void updateFactory() throws IOException {
        String factoryPid = getFactoryPidString();
        if ( factoryPid != null )
        {
            Factory factory = getConfigurationManager().getOrCreateFactory( factoryPid );
            if ( factory.addPID( getPidString() ) )
            {
                // only write back if the pid was not already registered
                // with the factory
                try
                {
                    factory.store();
                }
                catch ( IOException ioe )
                {
                    getConfigurationManager().log( LogService.LOG_ERROR,
                        "Failure storing factory {0} with new configuration {1}", new Object[]
                            { factoryPid, getPidString(), ioe } );
                }
            }
        }
    }


    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() );
        }

        // only store now, if this is not a new configuration
        getPersistenceManager().store( getPidString(), props );
    }


    /**
     * Returns the revision of this configuration object.
     * <p>
     * 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 <code>false</code> if this configuration contains configuration
     * properties. Otherwise <code>true</code> 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 <code>true</code> 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 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++;
        }
    }


    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 );
        }
    }


    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 );
    }


    static void clearAutoProperties( Dictionary properties )
    {
        properties.remove( Constants.SERVICE_PID );
        properties.remove( ConfigurationAdmin.SERVICE_FACTORYPID );
        properties.remove( ConfigurationAdmin.SERVICE_BUNDLELOCATION );
    }
}
TOP

Related Classes of org.apache.felix.cm.impl.ConfigurationImpl

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.