content: /**
   * 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.aries.blueprint.compendium.cm;
  
  import java.io.IOException;
  import java.util.Arrays;
  import java.util.Collections;
  import java.util.Comparator;
  import java.util.Dictionary;
  import java.util.Enumeration;
  import java.util.Hashtable;
  
  import org.osgi.framework.Bundle;
  import org.osgi.framework.BundleContext;
  import org.osgi.framework.Constants;
  import org.osgi.framework.InvalidSyntaxException;
  import org.osgi.framework.ServiceReference;
  import org.osgi.service.cm.Configuration;
  import org.osgi.service.cm.ConfigurationAdmin;
  import org.osgi.service.cm.ConfigurationPlugin;
  
  public class CmUtils {
  
      private CmUtils() {        
      }
      
      public static Configuration getConfiguration(ConfigurationAdmin configAdmin, String persistentId) throws IOException {
          String filter = '(' + Constants.SERVICE_PID + '=' + persistentId + ')';
          Configuration[] configs;
          try {
              configs = configAdmin.listConfigurations(filter);
          } catch (InvalidSyntaxException e) {
              // this should not happen
              throw new RuntimeException("Invalid filter: " + filter);
          }
          if (configs != null && configs.length > 0) {
              return configs[0];
          } else {
              // TODO: what should we do?
              // throw new RuntimeException("No configuration object for pid=" + persistentId);
              return null;
          }
      }
  
      public static Dictionary<String, Object> getProperties(ServiceReference service, String persistentId) throws IOException {
          BundleContext bc = service.getBundle().getBundleContext();
          ServiceReference<ConfigurationAdmin> caRef = bc.getServiceReference(ConfigurationAdmin.class);
          try {
              ConfigurationAdmin ca = bc.getService(caRef);
              Configuration config = getConfiguration(ca, persistentId);
              if (config != null) {
                  Dictionary<String, Object> props = new CaseInsensitiveDictionary(config.getProperties());
                  Bundle bundle = caRef.getBundle();
                  if (bundle != null) {
                      BundleContext caBc = bundle.getBundleContext();
                      if (caBc != null) {
                          try {
                              callPlugins(caBc, props, service, persistentId, null);
                          } catch (IllegalStateException ise) {
                              // we don't care it doesn't exist so, shrug.
                          }
                      }
                  }
                  return props;
              } else {
                  return null;
              }
          } finally {
              bc.ungetService(caRef);
          }
      }
  
      private static void callPlugins(final BundleContext bundleContext,
                                      final Dictionary<String, Object> props,
                                      final ServiceReference sr,
                                      final String configPid,
                                      final String factoryPid) {
          ServiceReference[] plugins = null;
          try {
              final String targetPid = (factoryPid == null) ? configPid : factoryPid;
              String filter = "(|(!(cm.target=*))(cm.target=" + targetPid + "))";
              plugins = bundleContext.getServiceReferences(ConfigurationPlugin.class.getName(), filter);
          } catch (InvalidSyntaxException ise) {
              // no filter, no exception ...
          }
  
          // abort early if there are no plugins
          if (plugins == null || plugins.length == 0) {
              return;
          }
  
          // sort the plugins by their service.cmRanking
          if (plugins.length > 1) {
              Arrays.sort(plugins, CM_RANKING);
          }
  
          // call the plugins in order
          for (ServiceReference pluginRef : plugins) {
              ConfigurationPlugin plugin = (ConfigurationPlugin) bundleContext.getService(pluginRef);
              if (plugin != null) {
                  try {
                      plugin.modifyConfiguration(sr, props);
                  } catch (Throwable t) {
                      // Ignore
                  } finally {
                      // ensure ungetting the plugin
                      bundleContext.ungetService(pluginRef);
                  }
                  setAutoProperties(props, configPid, factoryPid);
              }
          }
      }
  
      private static void setAutoProperties( Dictionary<String, Object> properties, String pid, String factoryPid )
      {
          replaceProperty(properties, Constants.SERVICE_PID, pid);
          replaceProperty(properties, ConfigurationAdmin.SERVICE_FACTORYPID, factoryPid);
          properties.remove(ConfigurationAdmin.SERVICE_BUNDLELOCATION);
      }
  
      private static void replaceProperty(Dictionary<String, Object> properties, String key, String value) {
          if (value == null) {
              properties.remove(key);
          } else {
              properties.put(key, value);
          }
      }
  
      private static Comparator<ServiceReference> CM_RANKING = new Comparator<ServiceReference>() {
          @Override
          public int compare(ServiceReference sr1, ServiceReference sr2) {
              final long rank1 = getLong(sr1, ConfigurationPlugin.CM_RANKING);
              final long rank2 = getLong(sr2, ConfigurationPlugin.CM_RANKING);
              if (rank1 == rank2) {
                  return 0;
              }
              return (rank1 < rank2) ? -1 : 1;
          }
  
          protected long getLong(ServiceReference sr, String property) {
              Object rankObj = sr.getProperty(property);
              if (rankObj instanceof Number) {
                  return ((Number) rankObj).longValue();
              }
              return 0;
          }
      };
  
      private static class CaseInsensitiveDictionary extends Dictionary<String, Object> {
          private final Hashtable<String, Object> internalMap = new Hashtable<String, Object>();
          private final Hashtable<String, String> originalKeys = new Hashtable<String, String>();
  
          public CaseInsensitiveDictionary(Dictionary<String, Object> props) {
              if (props != null) {
                  Enumeration<String> keys = props.keys();
                  while (keys.hasMoreElements()) {
                      // check the correct syntax of the key
                      String key = checkKey(keys.nextElement());
                      // check uniqueness of key
                      String lowerCase = key.toLowerCase();
                      if (internalMap.containsKey(lowerCase)) {
                          throw new IllegalArgumentException("Key [" + key + "] already present in different case");
                      }
                      // check the value
                      Object value = props.get(key);
                      checkValue(value);
                      // add the key/value pair
                      internalMap.put(lowerCase, value);
                      originalKeys.put(lowerCase, key);
                  }
              }
          }
  
          public Enumeration<Object> elements() {
              return Collections.enumeration(internalMap.values());
          }
  
          public Object get(Object keyObj) {
              String lowerCase = checkKey(keyObj == null ? null : keyObj.toString()).toLowerCase();
              return internalMap.get(lowerCase);
          }
  
          public boolean isEmpty() {
              return internalMap.isEmpty();
          }
  
          public Enumeration<String> keys() {
              return Collections.enumeration(originalKeys.values());
          }
  
          public Object put(String key, Object value) {
              String lowerCase = checkKey(key).toLowerCase();
              checkValue(value);
              originalKeys.put(lowerCase, key);
              return internalMap.put(lowerCase, value);
          }
  
          public Object remove(Object keyObj) {
              String lowerCase = checkKey(keyObj == null ? null : keyObj.toString()).toLowerCase();
              originalKeys.remove(lowerCase);
              return internalMap.remove(lowerCase);
          }
  
          public int size() {
              return internalMap.size();
          }
  
          static String checkKey(String key) {
              if (key == null || key.length() == 0) {
                  throw new IllegalArgumentException("Key must not be null nor an empty string");
              }
              return key;
          }
  
          static Object checkValue(Object value) {
              if (value == null) {
                  throw new IllegalArgumentException("Value must not be null");
              }
              return value;
          }
  
          public String toString() {
              return internalMap.toString();
          }
  
      }
  
  }
  