org.codice.ddf.admin.core.impl.ConfigurationAdminImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.codice.ddf.admin.core.impl.ConfigurationAdminImpl.java

Source

/**
 * Copyright (c) Codice Foundation
 *
 * <p>This is free software: you can redistribute it and/or modify it under the terms of the GNU
 * Lesser General Public License as published by the Free Software Foundation, either version 3 of
 * the License, or any later version.
 *
 * <p>This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details. A copy of the GNU Lesser General Public
 * License is distributed along with this program and can be found at
 * <http://www.gnu.org/licenses/lgpl.html>.
 */
package org.codice.ddf.admin.core.impl;

import static org.osgi.framework.Constants.SERVICE_PID;
import static org.osgi.service.cm.ConfigurationAdmin.SERVICE_FACTORYPID;

import com.google.common.collect.Sets;
import ddf.security.permission.KeyValueCollectionPermission;
import ddf.security.permission.KeyValuePermission;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.commons.lang.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;
import org.codice.ddf.admin.core.api.ConfigurationDetails;
import org.codice.ddf.admin.core.api.ConfigurationProperties;
import org.codice.ddf.admin.core.api.ConfigurationStatus;
import org.codice.ddf.admin.core.api.Metatype;
import org.codice.ddf.admin.core.api.MetatypeAttribute;
import org.codice.ddf.admin.core.api.Service;
import org.codice.ddf.ui.admin.api.plugin.ConfigurationAdminPlugin;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.Filter;
import org.osgi.framework.FrameworkUtil;
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.ManagedService;
import org.osgi.service.cm.ManagedServiceFactory;
import org.osgi.service.metatype.AttributeDefinition;
import org.osgi.service.metatype.MetaTypeInformation;
import org.osgi.service.metatype.MetaTypeService;
import org.osgi.service.metatype.ObjectClassDefinition;
import org.osgi.util.tracker.ServiceTracker;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ConfigurationAdminImpl implements org.codice.ddf.admin.core.api.ConfigurationAdmin {

    /**
     * The implementation of the {@link IdGetter} interface returning the PIDs listed in the meta type
     * information.
     *
     * @see #getPidObjectClasses()
     */
    private static final IdGetter PID_GETTER = MetaTypeInformation::getPids;

    /**
     * The implementation of the {@link IdGetter} interface returning the factory PIDs listed in the
     * meta type information.
     */
    private static final IdGetter FACTORY_PID_GETTER = MetaTypeInformation::getFactoryPids;

    private static final Logger LOGGER = LoggerFactory.getLogger(ConfigurationAdminImpl.class);

    private static final Map<Long, String> BUNDLE_LOCATIONS = new ConcurrentHashMap<>();

    private static final String CONFIGURATION_REGISTRY_ID_PROPERTY = "registry-id";

    private final ConfigurationAdmin configurationAdmin;

    private final Map<String, ServiceTracker> services = new HashMap<String, ServiceTracker>();

    private List<ConfigurationAdminPlugin> configurationAdminPluginList;

    /**
     * @param configurationAdmin
     * @throws ClassCastException if {@code service} is not a MetaTypeService instances
     */
    public ConfigurationAdminImpl(final Object configurationAdmin,
            List<ConfigurationAdminPlugin> configurationAdminPluginList) {
        this.configurationAdmin = (ConfigurationAdmin) configurationAdmin;
        this.configurationAdminPluginList = configurationAdminPluginList;
    }

    private static String getBundleLocation(Bundle bundle) {
        return BUNDLE_LOCATIONS.computeIfAbsent(bundle.getBundleId(), id -> bundle.getLocation());
    }

    static Bundle getBundle(final BundleContext bundleContext, final String bundleLocation) {
        if (bundleLocation == null) {
            return null;
        }

        Bundle[] bundles = bundleContext.getBundles();
        for (Bundle bundle : bundles) {
            if (bundleLocation.equals(getBundleLocation(bundle))) {
                return bundle;
            }
        }

        return null;
    }

    BundleContext getBundleContext() {
        Bundle cxfBundle = FrameworkUtil.getBundle(ConfigurationAdminImpl.class);
        if (cxfBundle != null) {
            return cxfBundle.getBundleContext();
        }
        return null;
    }

    public final Configuration getConfiguration(String pid) {
        if (pid != null) {
            try {
                // we use listConfigurations to not create configuration
                // objects persistently without the user providing actual
                // configuration
                String filter = '(' + SERVICE_PID + '=' + pid + ')';
                Configuration[] configs = this.configurationAdmin.listConfigurations(filter);
                if (configs != null && configs.length > 0 && isPermittedToViewService(pid)) {
                    return configs[0];
                }
            } catch (InvalidSyntaxException ise) {
                LOGGER.info("Invalid LDAP filter", ise);
            } catch (IOException ioe) {
                LOGGER.info("Unable to retrieve list of configurations.", ioe);
            }
        }

        // fallback to no configuration at all
        return null;
    }

    private Bundle getBoundBundle(Configuration config) {

        if (null == config) {
            return null;
        }

        final String location = config.getBundleLocation();
        if (null == location) {
            return null;
        }

        final Bundle bundles[] = getBundleContext().getBundles();
        for (int i = 0; bundles != null && i < bundles.length; i++) {
            if (location.equals(getBundleLocation(bundles[i]))) {
                return bundles[i];
            }
        }
        return null;
    }

    public List<Service> listServices(String serviceFactoryFilter, String serviceFilter) {
        List<Service> serviceList = null;
        List<Service> serviceFactoryList = null;

        Map<Long, MetaTypeInformation> metaTypeInformationByBundle = new HashMap<>();

        try {
            // Get ManagedService instances
            serviceList = getServices(ManagedService.class.getName(), serviceFilter, true);

            Map<String, ObjectClassDefinition> configPidToOcdMap = getPidObjectClasses(metaTypeInformationByBundle);

            // Get ManagedService Metatypes
            List<Metatype> metatypeList = addMetaTypeNamesToMap(configPidToOcdMap, serviceFilter, SERVICE_PID);

            // Get ManagedServiceFactory instances
            serviceFactoryList = getServices(ManagedServiceFactory.class.getName(), serviceFactoryFilter, true);

            // Get ManagedServiceFactory Metatypes
            metatypeList.addAll(addMetaTypeNamesToMap(getFactoryPidObjectClasses(metaTypeInformationByBundle),
                    serviceFactoryFilter, SERVICE_FACTORYPID));

            for (Service service : serviceFactoryList) {

                service.setFactory(true);

                for (Metatype metatype : metatypeList) {
                    if (metatype.getId() != null && metatype.getId().equals(service.getId())) {
                        service.putAll(metatype);
                    }
                }

                Configuration[] configs = configurationAdmin.listConfigurations("(|(service.factoryPid="
                        + service.getId() + ")(service.factoryPid=" + service.getId() + "_disabled))");
                if (configs != null) {
                    addConfigurationData(service, configs, configPidToOcdMap);
                }
            }

            for (Service service : serviceList) {
                service.setFactory(false);

                for (Metatype metatype : metatypeList) {
                    if (metatype.getId() != null && metatype.getId().equals(service.getId())) {
                        service.putAll(metatype);
                    }
                }

                Configuration[] configs = configurationAdmin
                        .listConfigurations("(" + SERVICE_PID + "=" + service.getId() + ")");
                if (configs != null) {
                    addConfigurationData(service, configs, configPidToOcdMap);
                }
            }

            serviceList.addAll(serviceFactoryList);
        } catch (IOException e) {
            LOGGER.warn("Unable to obtain list of Configuration objects from ConfigurationAdmin.", e);
        } catch (InvalidSyntaxException e) {
            LOGGER.info("Provided LDAP filter is incorrect: {}", serviceFilter, e);
        }

        if (serviceList != null) {
            return serviceList.stream().filter(service -> isPermittedToViewService(service.getId()))
                    .collect(Collectors.toList());
        } else {
            return new ArrayList<>();
        }
    }

    private void addConfigurationData(Service service, Configuration[] configs,
            Map<String, ObjectClassDefinition> objectClassDefinitions) {
        for (Configuration config : configs) {
            // ignore configuration object if it is invalid
            final String pid = config.getPid();
            if (!isAllowedPid(pid)) {
                continue;
            }

            ConfigurationDetails configData = new ConfigurationDetailsImpl();
            configData.setId(pid);
            String fpid = config.getFactoryPid();
            if (fpid != null) {
                configData.setFactoryPid(fpid);
            }
            // insert an entry for the PID
            try {
                ObjectClassDefinition ocd = objectClassDefinitions.get(config.getPid());
                if (ocd != null) {
                    configData.setName(ocd.getName());
                } else {
                    // no object class definition, use plain PID
                    configData.setName(pid);
                }
            } catch (IllegalArgumentException t) {
                // Catch exception thrown by getObjectClassDefinition so other configurations
                // are displayed
                // no object class definition, use plain PID
                configData.setName(pid);
            }

            final Bundle bundle = getBoundBundle(config);
            if (null != bundle) {
                configData.setBundle(bundle.getBundleId());
                configData.setBundleName(getName(bundle));
                configData.setBundleLocation(bundle.getLocation());
            }

            ConfigurationProperties propertiesTable = new ConfigurationPropertiesImpl();
            Dictionary<String, Object> properties = config.getProperties();
            if (properties != null) {
                Enumeration<String> keys = properties.keys();
                while (keys.hasMoreElements()) {
                    String key = keys.nextElement();
                    propertiesTable.put(key, properties.get(key));
                }
            }

            // If the configuration property is a password that has been set,
            // mask its value to "password" so that the real password value will be hidden.
            List<MetatypeAttribute> metatypeList = service.getAttributeDefinitions();
            metatypeList.stream().filter(metatype -> AttributeDefinition.PASSWORD == metatype.getType())
                    .forEach(metatype -> {
                        setPasswordMask(metatype, propertiesTable);
                    });

            configData.setConfigurationProperties(propertiesTable);

            Map<String, Object> pluginDataMap = getConfigurationPluginData(configData.getId(),
                    Collections.unmodifiableMap(configData));
            if (pluginDataMap != null && !pluginDataMap.isEmpty()) {
                configData.putAll(pluginDataMap);
            }

            List<ConfigurationDetails> configurationDetails;
            if (service.containsKey(Service.CONFIGURATIONS)) {
                configurationDetails = service.getConfigurations();
            } else if (service.containsKey(Service.DISABLED_CONFIGURATIONS)) {
                configurationDetails = service.getDisabledConfigurations();
            } else {
                configurationDetails = new ArrayList<>();
            }

            configurationDetails.add(configData);
            if (configData.getId().contains(ConfigurationDetails.DISABLED_SERVICE_IDENTIFIER)) {
                configData.setEnabled(false);
            } else {
                configData.setEnabled(true);
            }
            service.setConfigurations(configurationDetails);
        }
    }

    private void setPasswordMask(MetatypeAttribute metatype, ConfigurationProperties propertiesTable) {
        String passwordProperty = metatype.getId();
        if (propertiesTable.get(passwordProperty) == null) {
            propertiesTable.put(passwordProperty, "");
        } else if (!(propertiesTable.get(passwordProperty).toString().equals(""))) {
            propertiesTable.put(passwordProperty, "password");
        }
    }

    private Map<String, Object> getConfigurationPluginData(String servicePid, Map<String, Object> dataMap) {
        Map<String, Object> allPluginMap = new HashMap<>();
        if (configurationAdminPluginList != null) {
            for (ConfigurationAdminPlugin plugin : configurationAdminPluginList) {
                Map<String, Object> pluginDataMap = plugin.getConfigurationData(servicePid, dataMap,
                        getBundleContext());
                allPluginMap.putAll(pluginDataMap);
            }
        }
        return allPluginMap;
    }

    /**
     * Return a display name for the given <code>bundle</code>:
     *
     * <ol>
     *   <li>If the bundle has a non-empty <code>Bundle-Name</code> manifest header that value is
     *       returned.
     *   <li>Otherwise the symbolic name is returned if set
     *   <li>Otherwise the bundle's location is returned if defined
     *   <li>Finally, as a last resort, the bundles id is returned
     * </ol>
     *
     * @param bundle the bundle which name to retrieve
     * @return the bundle name - see the description of the method for more details.
     */
    public String getName(Bundle bundle) {
        Locale locale = Locale.getDefault();
        final String loc = locale == null ? null : locale.toString();
        String name = bundle.getHeaders(loc).get(Constants.BUNDLE_NAME);
        if (name == null || name.length() == 0) {
            name = bundle.getSymbolicName();
            if (name == null) {
                name = bundle.getLocation();
                if (name == null) {
                    name = String.valueOf(bundle.getBundleId());
                }
            }
        }
        return name;
    }

    private boolean isAllowedPid(final String pid) {
        if (pid == null) {
            return false;
        } else {
            for (int i = 0; i < pid.length(); i++) {
                final char c = pid.charAt(i);
                if (c == '&' || c == '<' || c == '>' || c == '"' || c == '\'') {
                    return false;
                }
            }
            return true;
        }
    }

    public String getDefaultFactoryLdapFilter() {
        return "(" + SERVICE_FACTORYPID + "=" + "*)";
    }

    public String getDefaultLdapFilter() {
        return "(" + SERVICE_PID + "=" + "*)";
    }

    void setConfigurationAdminPluginList(List<ConfigurationAdminPlugin> configurationAdminPluginList) {
        this.configurationAdminPluginList = configurationAdminPluginList;
    }

    public Metatype findMetatypeForConfig(Configuration config) {
        List<Service> services = listServices(getDefaultFactoryLdapFilter(), getDefaultLdapFilter());
        for (Service service : services) {
            String id = service.getId();
            if (id.equals(config.getPid())
                    || ((id.equals(config.getFactoryPid()) || (id + "_disabled").equals(config.getFactoryPid()))
                            && Boolean.valueOf(String.valueOf(service.isFactory())))) {
                return service;
            }
        }

        return null;
    }

    /**
     * Returns a map of PIDs and providing bundles of MetaType information. The map is indexed by PID
     * and the value of each entry is the bundle providing the MetaType information for that PID.
     *
     * @return see the method description
     */
    private Map<String, ObjectClassDefinition> getPidObjectClasses(
            Map<Long, MetaTypeInformation> metaTypeInformationByBundle) {
        return getObjectClassDefinitions(PID_GETTER, metaTypeInformationByBundle);
    }

    /**
     * Returns the <code>ObjectClassDefinition</code> objects for the IDs returned by the <code>
     * idGetter</code>. Depending on the <code>idGetter</code> implementation this will be for factory
     * PIDs or plain PIDs.
     *
     * @param idGetter The {@link IdGetter} used to get the list of factory PIDs or PIDs from <code>
     *     MetaTypeInformation</code> objects.
     * @return Map of <code>ObjectClassDefinition</code> objects indexed by the PID (or factory PID)
     *     to which they pertain
     */
    private Map<String, ObjectClassDefinition> getObjectClassDefinitions(final IdGetter idGetter,
            Map<Long, MetaTypeInformation> metaTypeInformationByBundle) {
        final Map<String, ObjectClassDefinition> objectClassesDefinitions = new HashMap<>();
        final MetaTypeService mts = this.getMetaTypeService();
        if (mts == null) {
            return objectClassesDefinitions;
        }
        final Bundle[] bundles = this.getBundleContext().getBundles();
        for (Bundle bundle : bundles) {
            final MetaTypeInformation mti = metaTypeInformationByBundle.computeIfAbsent(bundle.getBundleId(),
                    id -> mts.getMetaTypeInformation(bundle));
            objectClassesDefinitions.putAll(findOcdById(idGetter, mti));
        }
        return objectClassesDefinitions;
    }

    private Map<String, ObjectClassDefinition> findOcdById(IdGetter idGetter, MetaTypeInformation mti) {
        if (mti == null) {
            return Collections.emptyMap();
        }
        Map<String, ObjectClassDefinition> objectClassesDefinitions = new HashMap<>();
        final String[] idList = idGetter.getIds(mti);
        for (int j = 0; idList != null && j < idList.length; j++) {
            // After getting the list of PIDs, a configuration might be
            // removed. So the getObjectClassDefinition will throw
            // an exception, and this will prevent ALL configuration from
            // being displayed. By catching it, the configurations will be
            // visible
            ObjectClassDefinition ocd = null;
            try {
                ocd = mti.getObjectClassDefinition(idList[j], Locale.getDefault().toString());
            } catch (IllegalArgumentException ignore) {
                // ignore - just don't show this configuration
            }
            if (ocd != null) {
                objectClassesDefinitions.put(idList[j], ocd);
            }
        }
        return objectClassesDefinitions;
    }

    public ObjectClassDefinition getObjectClassDefinition(Configuration config) {
        // if the configuration is bound, try to get the object class
        // definition from the bundle installed from the given location
        if (config.getBundleLocation() != null) {
            Bundle bundle = getBundle(this.getBundleContext(), config.getBundleLocation());
            if (bundle != null) {
                String id = config.getFactoryPid();
                if (null == id) {
                    id = config.getPid();
                }
                return getObjectClassDefinition(bundle, id);
            }
        }

        // get here if the configuration is not bound or if no
        // bundle with the bound location is installed. We search
        // all bundles for a matching [factory] PID
        // if the configuration is a factory one, use the factory PID
        if (config.getFactoryPid() != null) {
            return getObjectClassDefinition(config.getFactoryPid());
        }

        // otherwise use the configuration PID
        return getObjectClassDefinition(config.getPid());
    }

    public ObjectClassDefinition getObjectClassDefinition(Bundle bundle, String pid) {
        Locale locale = Locale.getDefault();
        if (bundle != null) {
            MetaTypeService mts = this.getMetaTypeService();
            if (mts != null) {
                MetaTypeInformation mti = mts.getMetaTypeInformation(bundle);
                if (mti != null) {
                    // see #getObjectClasses( final IdGetter idGetter, final String locale )
                    try {
                        return mti.getObjectClassDefinition(pid, locale.toString());
                    } catch (IllegalArgumentException e) {
                        // MetaTypeProvider.getObjectClassDefinition might throw illegal
                        // argument exception. So we must catch it here, otherwise the
                        // other configurations will not be shown
                        // See https://issues.apache.org/jira/browse/FELIX-2390
                        // https://issues.apache.org/jira/browse/FELIX-3694
                    }
                }
            }
        }

        // fallback to nothing found
        return null;
    }

    MetaTypeService getMetaTypeService() {
        return (MetaTypeService) getService(MetaTypeService.class.getName());
    }

    /**
     * Gets the service with the specified class name. Will create a new {@link ServiceTracker} if the
     * service is not already retrieved.
     *
     * @param serviceName the service name to obtain
     * @return the service or <code>null</code> if missing.
     */
    private Object getService(String serviceName) {
        ServiceTracker serviceTracker = services.get(serviceName);
        if (serviceTracker == null) {
            serviceTracker = new ServiceTracker(getBundleContext(), serviceName, null);
            serviceTracker.open();

            services.put(serviceName, serviceTracker);
        }

        return serviceTracker.getService();
    }

    public ObjectClassDefinition getObjectClassDefinition(String pid) {
        Bundle[] bundles = this.getBundleContext().getBundles();
        for (Bundle bundle : bundles) {
            try {
                ObjectClassDefinition ocd = this.getObjectClassDefinition(bundle, pid);
                if (ocd != null) {
                    return ocd;
                }
            } catch (IllegalArgumentException iae) {
                // don't care
            }
        }
        return null;
    }

    /**
     * Returns a map of factory PIDs and providing bundles of MetaType information. The map is indexed
     * by factory PID and the value of each entry is the bundle providing the MetaType information for
     * that factory PID.
     *
     * @return see the method description
     */
    private Map<String, ObjectClassDefinition> getFactoryPidObjectClasses(
            Map<Long, MetaTypeInformation> metaTypeInformationByBundle) {
        return getObjectClassDefinitions(FACTORY_PID_GETTER, metaTypeInformationByBundle);
    }

    private List<Service> getServices(String serviceClass, String serviceFilter, boolean ocdRequired)
            throws InvalidSyntaxException {
        List<Service> serviceList = new ArrayList<>();

        // service.factoryPid cannot be searched, but service.pid can be searched,
        // and can contain a factoryPid
        String newFilter = null;
        if (serviceFilter != null) {
            newFilter = serviceFilter.replace("service.factoryPid", "service.pid");
        }

        // find all ManagedServiceFactories to get the factoryPIDs
        ServiceReference[] refs = this.getBundleContext().getAllServiceReferences(serviceClass, newFilter);

        for (int i = 0; refs != null && i < refs.length; i++) {
            Object pidObject = refs[i].getProperty(SERVICE_PID);
            // only include valid PIDs
            if (pidObject instanceof String && isAllowedPid((String) pidObject)) {
                String pid = (String) pidObject;
                String name = pid;
                boolean haveOcd = !ocdRequired;
                final ObjectClassDefinition ocd = getObjectClassDefinition(refs[i].getBundle(), pid);
                if (ocd != null) {
                    name = ocd.getName();
                    haveOcd = true;
                }

                if (haveOcd && ocd != null) {
                    Service service = new ServiceImpl();
                    String description = ocd.getDescription();
                    service.setId(pid);
                    if (StringUtils.isNotEmpty(description)) {
                        service.setDescription(description);
                    }
                    service.setName(name);
                    serviceList.add(service);
                }
            }
        }

        return serviceList.stream().filter(service -> isPermittedToViewService(service.getId()))
                .collect(Collectors.toList());
    }

    private List<Metatype> addMetaTypeNamesToMap(final Map<String, ObjectClassDefinition> objectClassDefinitions,
            final String filterSpec, final String type) {
        Filter filter = null;
        if (filterSpec != null) {
            try {
                filter = getBundleContext().createFilter(filterSpec);
            } catch (InvalidSyntaxException ignore) {
                // don't care
            }
        }

        List<Metatype> metatypeList = new ArrayList<>();
        for (Entry<String, ObjectClassDefinition> ociEntry : objectClassDefinitions.entrySet()) {
            final String pid = ociEntry.getKey();
            final ObjectClassDefinition ocd = ociEntry.getValue();
            if (filter == null) {
                AttributeDefinition[] defs = ocd.getAttributeDefinitions(ObjectClassDefinition.ALL);
                metatypeList.add(new MetatypeImpl(pid, ocd.getName(), createMetatypeMap(defs)));
            } else {
                final Dictionary<String, String> props = new Hashtable<>();
                props.put(type, pid);
                if (filter.match(props)) {
                    AttributeDefinition[] defs = ocd.getAttributeDefinitions(ObjectClassDefinition.ALL);
                    metatypeList.add(new MetatypeImpl(pid, ocd.getName(), createMetatypeMap(defs)));
                }
            }
        }
        return metatypeList;
    }

    private List<MetatypeAttribute> createMetatypeMap(AttributeDefinition[] definitions) {
        List<MetatypeAttribute> metatypeList;

        if (definitions != null) {
            metatypeList = Arrays.stream(definitions).map(MetatypeAttributeImpl::new).collect(Collectors.toList());
        } else {
            metatypeList = new ArrayList<>();
        }

        return metatypeList;
    }

    public boolean isPermittedToViewService(String servicePid, Subject subject) {
        KeyValueCollectionPermission serviceToCheck = new KeyValueCollectionPermission("view-service.pid",
                new KeyValuePermission("service.pid", Sets.newHashSet(servicePid)));
        return subject.isPermitted(serviceToCheck);
    }

    public boolean isPermittedToViewService(String servicePid) {
        return isPermittedToViewService(servicePid, SecurityUtils.getSubject());
    }

    public ConfigurationStatus disableManagedServiceFactoryConfiguration(String servicePid,
            Configuration originalConfig) throws IOException {
        Dictionary<String, Object> properties = originalConfig.getProperties();
        String originalFactoryPid = (String) properties
                .get(org.osgi.service.cm.ConfigurationAdmin.SERVICE_FACTORYPID);
        if (originalFactoryPid == null) {
            throw new IOException("Configuration does not belong to a managed service factory.");
        }
        if (StringUtils.endsWith(originalFactoryPid, ConfigurationStatus.DISABLED_EXTENSION)) {
            throw new IOException("Configuration is already disabled.");
        }

        // Copy configuration from the original configuration and change its factory PID to end with
        // "disabled"
        Dictionary<String, Object> disabledProperties = copyConfigProperties(properties, originalFactoryPid);
        String disabledServiceFactoryPid = originalFactoryPid + ConfigurationStatus.DISABLED_EXTENSION;
        disabledProperties.put(org.osgi.service.cm.ConfigurationAdmin.SERVICE_FACTORYPID,
                disabledServiceFactoryPid);
        Configuration disabledConfig = configurationAdmin.createFactoryConfiguration(disabledServiceFactoryPid,
                null);
        disabledConfig.update(disabledProperties);

        // remove original configuration
        originalConfig.delete();
        return new ConfigurationStatusImpl(disabledServiceFactoryPid, disabledConfig.getPid(), originalFactoryPid,
                servicePid);
    }

    public ConfigurationStatus enableManagedServiceFactoryConfiguration(String servicePid,
            Configuration disabledConfig) throws IOException {
        Dictionary<String, Object> properties = disabledConfig.getProperties();
        String disabledFactoryPid = (String) properties
                .get(org.osgi.service.cm.ConfigurationAdmin.SERVICE_FACTORYPID);
        if (disabledFactoryPid == null) {
            throw new IOException("Configuration does not belong to a managed service factory.");
        }
        if (!StringUtils.endsWith(disabledFactoryPid, ConfigurationStatus.DISABLED_EXTENSION)) {
            throw new IOException("Configuration is already enabled.");
        }

        String enabledFactoryPid = StringUtils.removeEnd(disabledFactoryPid,
                ConfigurationStatus.DISABLED_EXTENSION);
        Dictionary<String, Object> enabledProperties = copyConfigProperties(properties, enabledFactoryPid);
        enabledProperties.put(org.osgi.service.cm.ConfigurationAdmin.SERVICE_FACTORYPID, enabledFactoryPid);
        Configuration enabledConfiguration = configurationAdmin.createFactoryConfiguration(enabledFactoryPid, null);
        enabledConfiguration.update(enabledProperties);

        disabledConfig.delete();

        return new ConfigurationStatusImpl(enabledFactoryPid, enabledConfiguration.getPid(), disabledFactoryPid,
                servicePid);
    }

    private Dictionary<String, Object> copyConfigProperties(Dictionary<String, Object> properties,
            String factoryPid) {
        final Dictionary<String, Object> copiedProperties = new Hashtable<>();
        ObjectClassDefinition objectClassDefinition = getObjectClassDefinition(factoryPid);
        if (objectClassDefinition == null) {
            LOGGER.debug("ObjectClassDefinition not found for factoryPid: {}. Unable to copy properties.",
                    factoryPid);
            throw new IllegalStateException("ObjectClassDefinition not found for factoryPid: " + factoryPid);
        }
        Stream.of(objectClassDefinition).map(ocd -> ocd.getAttributeDefinitions(ObjectClassDefinition.ALL))
                .flatMap(Arrays::stream).map(AttributeDefinition::getID)
                .forEach(id -> copyIfDefined(id, properties, copiedProperties));
        copyIfDefined(CONFIGURATION_REGISTRY_ID_PROPERTY, properties, copiedProperties);
        return copiedProperties;
    }

    private void copyIfDefined(String id, Dictionary<String, Object> source,
            Dictionary<String, Object> destination) {
        final Object value = source.get(id);

        if (value != null) {
            destination.put(id, value);
        }
    }

    /**
     * The <code>IdGetter</code> interface is an internal helper to abstract retrieving object class
     * definitions from all bundles for either pids or factory pids.
     *
     * @see #PID_GETTER
     * @see #FACTORY_PID_GETTER
     */
    private interface IdGetter {
        String[] getIds(MetaTypeInformation metaTypeInformation);
    }
}