org.liveSense.misc.configloader.ConfigurationLoader.java Source code

Java tutorial

Introduction

Here is the source code for org.liveSense.misc.configloader.ConfigurationLoader.java

Source

/*
 * 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.liveSense.misc.configloader;

import java.io.BufferedInputStream;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Properties;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.lang.StringUtils;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleEvent;
import org.osgi.framework.SynchronousBundleListener;
import org.osgi.service.cm.Configuration;
import org.osgi.service.cm.ConfigurationAdmin;

/**
 * The <code>ContfigurationLoader</code> is the service
 * providing the following functionality:
 * <ul>
 * <li>Bundle listener to load initial felix configurations for bundles.
 * <li>Fires OSGi EventAdmin events on behalf of internal helper objects
 * </ul>
 **/

public class ConfigurationLoader implements SynchronousBundleListener, BundleActivator {

    private final static String CONFIGURATION_PROPERTY_NAME = "felix.configurationloader.name";

    ConfigurationAdmin configurationAdmin;
    ServiceMediator services;
    private final PersistencyManager persistence = new PersistencyManager();

    private final Set delayedBundles = new HashSet();

    // ---------- BundleListener -----------------------------------------------
    /**
     * Loads and unloads any configuration provided by the bundle whose state
     * changed. If the bundle has been started, the configuration is loaded. If
     * the bundle is about to stop, the configurations are unloaded.
     *
     * @param event The <code>BundleEvent</code> representing the bundle state
     *            change.
     */
    @Override
    public void bundleChanged(BundleEvent event) {

        //
        // NOTE:
        // This is synchronous - take care to not block the system !!
        //

        switch (event.getType()) {
        case BundleEvent.STARTING:
            try {
                registerBundle(event.getBundle());
            } catch (Throwable t) {
                services.error("bundleChanged: Problem loading initial configuration of bundle "
                        + event.getBundle().getSymbolicName() + " (" + event.getBundle().getBundleId() + ")", t);
            } finally {
            }
            break;
        case BundleEvent.STOPPED:
            try {
                unregisterBundle(event.getBundle());
            } catch (Throwable t) {
                services.error("bundleChanged: Problem unloading initial configuration of bundle "
                        + event.getBundle().getSymbolicName() + " (" + event.getBundle().getBundleId() + ")", t);
            } finally {
            }
            break;
        }
    }

    @Override
    public void start(BundleContext context) throws Exception {

        services = new ServiceMediator(context);
        configurationAdmin = services.getConfigurationAdminService(ServiceMediator.NO_WAIT);
        context.addBundleListener(this);

        int ignored = 0;
        try {
            Bundle[] bundles = context.getBundles();
            for (int i = 0; i < bundles.length; i++) {
                Bundle bundle = bundles[i];

                if ((bundle.getState() & (Bundle.ACTIVE)) != 0) {
                    // load configurations from bundles which are ACTIVE
                    try {
                        registerBundle(bundle);
                    } catch (Throwable t) {
                        services.error("Problem loading initial configuration of bundle " + bundle.getSymbolicName()
                                + " (" + bundle.getBundleId() + ")", t);
                    } finally {
                    }
                } else {
                    ignored++;
                }

                if ((bundle.getState() & (Bundle.ACTIVE)) == 0) {
                    // remove configurations from bundles which are not ACTIVE
                    try {
                        unregisterBundle(bundle);
                    } catch (Throwable t) {
                        services.error("Problem loading initial configuration of bundle " + bundle.getSymbolicName()
                                + " (" + bundle.getBundleId() + ")", t);
                    } finally {
                    }
                } else {
                    ignored++;
                }

            }
            services.debug("Out of " + bundles.length + " bundles, " + ignored
                    + " were not in a suitable state for initial config loading");
        } catch (Throwable t) {
            services.error("activate: Problem while loading initial configuration", t);
        } finally {
        }

    }

    @Override
    public void stop(BundleContext context) throws Exception {
        context.removeBundleListener(this);

        if (services != null) {
            services.deactivate();
            services = null;
        }
    }

    // ---------- Implementation helpers --------------------------------------
    /**
     * Register a bundle and install the configurations included them.
     *
     * @param bundle
     */
    public void registerBundle(final Bundle bundle) throws Exception {
        // if this is an update, we have to uninstall the old content first

        services.debug("Registering bundle " + bundle.getSymbolicName() + " for configuration loading.");

        registerBundleInternal(bundle);

        /*
          if (registerBundleInternal(bundle)) {
            
        // handle delayed bundles, might help now
        int currentSize = -1;
        for (int i = delayedBundles.size(); i > 0
                && currentSize != delayedBundles.size()
                && !delayedBundles.isEmpty(); i--) {
            
            Iterator di = delayedBundles.iterator();
            while (di.hasNext()) {
                Bundle delayed = (Bundle)di.next();
                if (registerBundleInternal(delayed)) {
                    di.remove();
                }
            }
            currentSize = delayedBundles.size();
        }
            
          } else {
        // add to delayed bundles - if this is not an update!
        delayedBundles.add(bundle);
          }
         */
    }

    private boolean registerBundleInternal(final Bundle bundle) throws Exception {

        // check if bundle has initial configuration
        final Iterator pathIter = PathEntry.getContentPaths(bundle);
        if (pathIter == null) {
            services.debug("Bundle " + bundle.getSymbolicName() + " has no initial configuration");
            return true;
        }

        while (pathIter.hasNext()) {
            PathEntry path = (PathEntry) pathIter.next();
            Enumeration entries = bundle.getEntryPaths(path.getPath());

            if (entries != null) {
                while (entries.hasMoreElements()) {
                    URL url = bundle.getEntry((String) entries.nextElement());
                    if (canHandle(url)) {
                        install(url);
                    }
                }
            }
        }

        return false;
    }

    /**
     * Unregister a bundle. Remove installed content.
     *
     * @param bundle The bundle.
     */
    public void unregisterBundle(final Bundle bundle) throws Exception {

        // check if bundle has initial configuration
        final Iterator pathIter = PathEntry.getContentPaths(bundle);
        if (pathIter == null) {
            services.debug("Bundle " + bundle.getSymbolicName() + " has no initial configuration");
            return;
        }

        // TODO : A boolean scr options if checked the configuration loaded by configurationloader is removed when the bundle contains remved. Now the configuuration stays untouched
        /*
          while (pathIter.hasNext()) {
        PathEntry path = (PathEntry)pathIter.next();
        Enumeration entries = bundle.getEntryPaths(path.getPath());
            
        if (entries != null) {
            while (entries.hasMoreElements()) {
                URL url = bundle.getEntry((String)entries.nextElement());
                if (canHandle(url)) {
                    uninstall(url);
                }
            }
        }
          } */

    }

    public boolean canHandle(URL artifact) {
        return artifact.getFile().endsWith(".cfg") || artifact.getFile().endsWith(".config");
    }

    public void install(URL artifact) throws Exception {
        setConfig(artifact);
    }

    public void update(URL artifact) throws Exception {
        setConfig(artifact);
    }

    public void uninstall(URL artifact) throws Exception {
        deleteConfig(artifact);
    }

    private String getPidName(String pid, String factoryPid) {
        String suffix = factoryPid == null ? ".cfg" : "-" + factoryPid + ".cfg";
        return pid + suffix;
    }

    /**
     * Set the configuration based on the config file.
     *
     * @param f
     *            Configuration file
     * @return
     * @throws Exception
     */
    @SuppressWarnings("unchecked")
    boolean setConfig(URL f) throws Exception {
        Properties p = new Properties();

        @SuppressWarnings("rawtypes")
        Dictionary ht = new Hashtable();

        InputStream in = new BufferedInputStream(f.openStream());
        try {
            // If the file name ends with .config, we using the Felix configuration format
            if (f.getFile().endsWith(".config")) {
                ht = ConfigurationHandler.read(in);
            } else {
                in.mark(1);
                boolean isXml = in.read() == '<';
                in.reset();
                if (isXml) {
                    p.loadFromXML(in);
                } else {
                    p.load(in);
                }
                ((Hashtable) ht).putAll(p);
            }
        } finally {
            in.close();
        }

        // Searching for templated config entry.
        // If we found one we get Java System properties
        // named as the macros. The config became activated if that
        // system proprty is set.

        Pattern macros = Pattern.compile("\\$\\{(.*?)\\}");
        boolean valid = true;

        Enumeration enumr = ht.keys();
        while (enumr.hasMoreElements()) {
            Object key = enumr.nextElement();
            if (ht.get(key) instanceof String) {
                String str = (String) ht.get(key);
                if (str != null) {
                    Matcher matcher = macros.matcher(str);

                    HashSet<String> propNames = new HashSet<String>();
                    while (matcher.find()) {
                        propNames.add(matcher.group(1));
                    }

                    for (String prop : propNames) {
                        String sysProp = System.getProperty(prop);
                        if (sysProp == null) {
                            valid = false;
                        }
                        if (valid) {
                            str = StringUtils.replace(str, "${" + prop + "}", sysProp);
                            //str = str.replaceAll("\\$\\{"+prop+"\\}", sysProp);
                        }
                    }
                    if (valid) {
                        ht.put(key, str);
                    }
                }
            }
        }

        if (valid) {
            Util.performSubstitution(p);
            String pid[] = parsePid(getName(f.getFile()));
            ht.put(CONFIGURATION_PROPERTY_NAME, getPidName(pid[0], pid[1]));

            Configuration config = getConfiguration(pid[0], pid[1]);

            /*
            // Backuping parameters for restore
            String persistanceName = pid[0]+(pid[1] == null ? "" : "-" + pid[1]);
            if (config.getProperties() != null && config.getProperties().get(CONFIGURATION_PROPERTY_NAME) == null) {
               if (persistence.load(persistanceName).isEmpty()) {
                  persistence.store(persistanceName, config.getProperties());
               }
            }
             */
            if (config.getBundleLocation() != null) {
                config.setBundleLocation(null);
            }

            // If the configuration does not created by configuration loader we update it
            // In other cases (for example the user modified the loaded config) there is no configuration overwrite
            if (config.getProperties() == null || config.getProperties().get(CONFIGURATION_PROPERTY_NAME) == null
                    || !config.getProperties().get(CONFIGURATION_PROPERTY_NAME).equals(getName(f.getFile()))) {
                config.update(ht);
            }
        }
        return true;
    }

    /**
     * Remove the configuration.
     *
     * @param f
     *            File where the configuration in whas defined.
     * @return
     * @throws Exception
     */
    boolean deleteConfig(URL f) throws Exception {
        String pid[] = parsePid(getName(f.getFile()));
        Configuration config = getConfiguration(pid[0], pid[1]);
        config.delete();

        /*
          // Restore config if there is stored configuration presented
          String persistanceName = pid[0]+(pid[1] == null ? "" : "-" + pid[1]);
            
          Dictionary dict = persistence.load(persistanceName);
          if (!dict.isEmpty()) {
        config.update(dict);
        persistence.delete(persistanceName);
          }
         */
        return true;
    }

    String[] parsePid(String path) {
        String pid = null;

        if (path.endsWith(".cfg")) {
            pid = path.substring(0, path.length() - 4);
        } else if (path.endsWith(".config")) {
            pid = path.substring(0, path.length() - 7);
        }

        int n = pid.indexOf('-');
        if (n > 0) {
            String factoryPid = pid.substring(n + 1);
            pid = pid.substring(0, n);
            return new String[] { pid, factoryPid };
        } else {
            return new String[] { pid, null };
        }
    }

    Configuration getConfiguration(String pid, String factoryPid) throws Exception {
        Configuration oldConfiguration = findExistingConfiguration(pid, factoryPid);
        if (oldConfiguration != null) {
            services.debug("Updating configuration from " + getPidName(pid, factoryPid));
            return oldConfiguration;
        } else {
            Configuration newConfiguration;
            if (factoryPid != null) {
                newConfiguration = configurationAdmin.createFactoryConfiguration(pid, null);
            } else {
                newConfiguration = configurationAdmin.getConfiguration(pid, null);
            }
            return newConfiguration;
        }
    }

    private Configuration findExistingConfiguration(String pid, String factoryPid) throws Exception {
        String filter = "(" + CONFIGURATION_PROPERTY_NAME + "=" + getPidName(pid, factoryPid) + ")";
        Configuration[] configurations = configurationAdmin.listConfigurations(filter);
        if (configurations != null && configurations.length > 0) {
            return configurations[0];
        } else {
            return null;
        }
    }

    /**
     * Gets and decodes the name part of the <code>path</code>. The name is
     * the part of the path after the last slash (or the complete path if no
     * slash is contained). To support names containing unsupported characters
     * such as colon (<code>:</code>), names may be URL encoded (see
     * <code>java.net.URLEncoder</code>) using the <i>UTF-8</i> character
     * encoding. In this case, this method decodes the name using the
     * <code>java.netURLDecoder</code> class with the <i>UTF-8</i> character
     * encoding.
     *
     * @param path The path from which to extract the name part.
     * @return The URL decoded name part.
     */
    private String getName(String path) {
        int lastSlash = path.lastIndexOf('/');
        String name = (lastSlash < 0) ? path : path.substring(lastSlash + 1);

        // check for encoded characters (%xx)
        // has encoded characters, need to decode
        if (name.indexOf('%') >= 0) {
            try {
                return URLDecoder.decode(name, "UTF-8");
            } catch (UnsupportedEncodingException uee) {
                // actually unexpected because UTF-8 is required by the spec
                services.error(
                        "Cannot decode " + name + " beause the platform has no support for UTF-8, using undecoded");
            } catch (Exception e) {
                // IllegalArgumentException or failure to decode
                services.error("Cannot decode " + name + ", using undecoded", e);
            }
        }

        // not encoded or problems decoding, return the name unmodified
        return name;
    }
}