/*
 * #%L
 * ACS AEM Commons Bundle
 * %%
 * Copyright (C) 2013 - 2014 Adobe
 * %%
 * Licensed 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. 
 * #L%
 */
package com.adobe.acs.commons.util.impl;

import org.apache.felix.scr.annotations. Activate;
import org.apache.felix.scr.annotations. ConfigurationPolicy;
import org.apache.felix.scr.annotations. Property;
import org.apache.felix.scr.annotations. Service;
import org.apache.sling.commons.osgi. PropertiesUtil;
import org.osgi.framework. Bundle;
import org.osgi.framework. BundleContext;
import org.osgi.framework. BundleException;
import org.osgi.service.component. ComponentContext;
import org.osgi.service.event. Event;
import org.osgi.service.event. EventHandler;
import org.slf4j. Logger;
import org.slf4j. LoggerFactory;

import java.util. Arrays;
import java.util. Collections;
import java.util. List;
import java.util. Map;

/**
 * Component disabler service
 * <p>
 * In the AEM security checklist, there are some bundles which should be disabled in production environents. 
 * Whilst these bundles can be manually stopped, this component will do that as part of a deployment.   It will also
 * ensure that if they are manually started once more, then they will be immediately stopped. 
 */
@org.apache.felix.scr.annotations. Component(immediate = true, metatype = true,
        label = "ACS AEM Commons - OSGI Bundle Disabler", description = "Disables bundles by configuration",
        policy = ConfigurationPolicy. REQUIRE)
@Service()
@Property(name = "event.topics", value = { "org/osgi/framework/BundleEvent/STARTED" }, propertyPrivate = true)
public class BundleDisabler implements EventHandler {

    private static final Logger log = LoggerFactory.getLogger(BundleDisabler.class);

    @Property(label = "Disabled bundles", description = "The symbolic names of the bundles you want to disable",
            cardinality = Integer. MAX_VALUE)
    private static final String DISABLED_BUNDLES = "bundles";

    private BundleContext bundleContext;

    private List<String> disabledBundles = Collections.emptyList();

    @Activate
    protected void activate(ComponentContext componentContext, Map<String, Object> properties) {
        this.bundleContext = componentContext.getBundleContext();
        this.disabledBundles = getDisabledBundles(properties);
        disableBundles();
    }

    @Override
    public void handleEvent(Event event) {
        // We don't care about the event, we just need iterate all configured bundles and try to disable them
        disableBundles();
    }

    private void disableBundles() {
        if (disabledBundles.isEmpty()) {
            log.info("No bundles specified.  Consider specifying bundles or removing this service config");
            return;
        }

        log.trace("Disabling bundles {}", disabledBundles);
        for (Bundle bundle : bundleContext.getBundles()) {
            if (isOnBundleStopList(bundle)) {
                try {
                    disableBundle(bundle);
                } catch (BundleException be) {
                    log.error("Unable to stop bundle {}", bundle.getSymbolicName(), be);
                }
            }
        }
    }

    private void disableBundle(final Bundle bundle) throws BundleException {
        if (isBundleStoppable(bundle) && isNotOwnBundle(bundle)) {
            log.info("Bundle {} disabled by configuration (name={}) ",
                    bundle.getSymbolicName(), bundle.getBundleId());
            bundle.stop();
        }
    }

    private List<String> getDisabledBundles(final Map<String, Object> properties) {
        final String[] bundlesProperty = PropertiesUtil.toStringArray(properties.get(DISABLED_BUNDLES), new String[0]);
        return Arrays.asList(PropertiesUtil.toStringArray(bundlesProperty, new String[0]));
    }

    private boolean isOnBundleStopList(final Bundle bundle) {
        return disabledBundles.contains(bundle.getSymbolicName());
    }

    private boolean isBundleStoppable(final Bundle bundle) {
        return bundle.getState() ! = Bundle. UNINSTALLED;
    }

    private boolean isNotOwnBundle(final Bundle bundle) {
        return ! bundle.equals(bundleContext.getBundle());
    }
}
