org.codice.ddf.itests.common.ServiceManagerImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.codice.ddf.itests.common.ServiceManagerImpl.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.itests.common;

import static com.jayway.restassured.RestAssured.get;
import static com.jayway.restassured.RestAssured.given;
import static org.apache.karaf.features.FeaturesService.Option.NoAutoRefreshBundles;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.junit.Assert.fail;

import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.jayway.restassured.response.Response;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Dictionary;
import java.util.EnumSet;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import javax.management.NotCompliantMBeanException;
import org.apache.commons.lang.StringUtils;
import org.apache.karaf.bundle.core.BundleInfo;
import org.apache.karaf.bundle.core.BundleService;
import org.apache.karaf.bundle.core.BundleState;
import org.apache.karaf.features.Feature;
import org.apache.karaf.features.FeatureState;
import org.apache.karaf.features.FeaturesService;
import org.codice.ddf.admin.core.api.Service;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;
import org.osgi.framework.Constants;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.cm.Configuration;
import org.osgi.service.cm.ConfigurationEvent;
import org.osgi.service.cm.ConfigurationListener;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ServiceManagerImpl implements ServiceManager {
    private static final Map<Integer, String> BUNDLE_STATES = new ImmutableMap.Builder<Integer, String>()
            .put(Bundle.UNINSTALLED, "UNINSTALLED").put(Bundle.INSTALLED, "INSTALLED")
            .put(Bundle.RESOLVED, "RESOLVED").put(Bundle.STARTING, "STARTING").put(Bundle.STOPPING, "STOPPING")
            .put(Bundle.ACTIVE, "ACTIVE").build();

    public static final long MANAGED_SERVICE_TIMEOUT = AbstractIntegrationTest.GENERIC_TIMEOUT_MILLISECONDS;

    public static final long FEATURES_AND_BUNDLES_TIMEOUT = TimeUnit.MINUTES.toMillis(20);

    public static final long HTTP_ENDPOINT_TIMEOUT = AbstractIntegrationTest.GENERIC_TIMEOUT_MILLISECONDS;

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

    private static final int CONFIG_UPDATE_WAIT_INTERVAL_MILLIS = 5;

    private final MetaTypeService metatype;

    private final AdminConfig adminConfig;

    private BundleService bundleService;

    public ServiceManagerImpl(MetaTypeService metatype, AdminConfig adminConfig) {
        this.metatype = metatype;
        this.adminConfig = adminConfig;
    }

    @Override
    public BundleContext getBundleContext() {
        Bundle bundle = FrameworkUtil.getBundle(getClass());
        if (bundle != null) {
            return bundle.getBundleContext();
        }
        return null;
    }

    @Override
    public Configuration createManagedService(String factoryPid, Map<String, Object> properties)
            throws IOException {

        Configuration sourceConfig = adminConfig.createFactoryConfiguration(factoryPid, null);

        startManagedService(sourceConfig, properties);

        return sourceConfig;
    }

    @Override
    public void startManagedService(String servicePid, Map<String, Object> properties) throws IOException {
        Configuration sourceConfig = adminConfig.getConfiguration(servicePid, null);

        startManagedService(sourceConfig, properties);
    }

    @Override
    public void stopManagedService(String servicePid) throws IOException {
        Configuration sourceConfig = adminConfig.getConfiguration(servicePid, null);

        try {
            adminConfig.getAdminConsoleService().delete(sourceConfig.getPid());
        } catch (NotCompliantMBeanException e) {
            // ignore
        }
    }

    private void startManagedService(Configuration sourceConfig, Map<String, Object> properties)
            throws IOException {
        ServiceManagerImpl.ServiceConfigurationListener listener = new ServiceManagerImpl.ServiceConfigurationListener(
                sourceConfig.getPid());

        BundleContext bundleContext = getBundleContext();

        bundleContext = waitForBundleContext(bundleContext);

        if (bundleContext == null) {
            LOGGER.info("Unable to get bundle context.");
            return;
        }

        ServiceRegistration<?> serviceRegistration = bundleContext
                .registerService(ConfigurationListener.class.getName(), listener, null);

        try {
            waitForService(sourceConfig);
        } catch (NotCompliantMBeanException e) {
            // ignore
        }

        try {
            adminConfig.getAdminConsoleService().update(sourceConfig.getPid(), properties);
        } catch (NotCompliantMBeanException e) {
            // ignore
        }

        long millis = 0;
        while (!listener.isUpdated() && millis < MANAGED_SERVICE_TIMEOUT) {
            try {
                Thread.sleep(CONFIG_UPDATE_WAIT_INTERVAL_MILLIS);
                millis += CONFIG_UPDATE_WAIT_INTERVAL_MILLIS;
            } catch (InterruptedException e) {
                LOGGER.info("Interrupted exception while trying to sleep for configuration update", e);
            }
            LOGGER.info("Waiting for configuration to be updated...{}ms", millis);
        }

        serviceRegistration.unregister();

        if (!listener.isUpdated()) {
            throw new RuntimeException(
                    String.format("Service configuration %s was not updated within %d minute timeout.",
                            sourceConfig.getPid(), TimeUnit.MILLISECONDS.toMinutes(MANAGED_SERVICE_TIMEOUT)));
        }
    }

    private BundleContext waitForBundleContext(BundleContext bundleContext) {
        long millis = 0;
        while (bundleContext == null && millis < MANAGED_SERVICE_TIMEOUT) {
            try {
                Thread.sleep(CONFIG_UPDATE_WAIT_INTERVAL_MILLIS);
                millis += CONFIG_UPDATE_WAIT_INTERVAL_MILLIS;
            } catch (InterruptedException e) {
                LOGGER.info("Interrupted exception while trying to sleep for bundle context", e);
            }
            LOGGER.info("Waiting for bundle context...{}ms", millis);
            bundleContext = getBundleContext();
        }
        return bundleContext;
    }

    private void waitForService(Configuration sourceConfig) throws NotCompliantMBeanException {
        long waitForService = 0;
        boolean serviceStarted = false;
        List<Service> servicesList;
        do {
            try {
                Thread.sleep(CONFIG_UPDATE_WAIT_INTERVAL_MILLIS);
                waitForService += CONFIG_UPDATE_WAIT_INTERVAL_MILLIS;
            } catch (InterruptedException e) {
                LOGGER.info("Interrupted waiting for service to init");
            }

            if (waitForService >= MANAGED_SERVICE_TIMEOUT) {
                printInactiveBundles();
                throw new RuntimeException(String.format("Service %s not initialized within %d minute timeout",
                        sourceConfig.getPid(), TimeUnit.MILLISECONDS.toMinutes(MANAGED_SERVICE_TIMEOUT)));
            }

            servicesList = adminConfig.getAdminConsoleService().listServices();
            for (Service service : servicesList) {
                String id = String.valueOf(service.getId());
                if (id.equals(sourceConfig.getPid()) || id.equals(sourceConfig.getFactoryPid())) {
                    serviceStarted = true;
                    break;
                }
            }

        } while (!serviceStarted);
    }

    @Override
    public void startFeature(boolean wait, String... featureNames) throws Exception {
        for (String featureName : featureNames) {
            FeatureState state = getFeaturesService().getState(featureName);

            if (FeatureState.Installed != state) {
                getFeaturesService().installFeature(featureName, EnumSet.of(NoAutoRefreshBundles));
            }
        }

        if (wait) {
            for (String featureName : featureNames) {
                waitForFeature(featureName, state -> state == FeatureState.Started);
            }

            waitForAllBundles();
        }
    }

    @Override
    public void stopFeature(boolean wait, String... featureNames) throws Exception {
        List<String> waitFeatures = new ArrayList<>();
        for (String featureName : featureNames) {
            if (isFeatureInstalled(featureName)) {
                getFeaturesService().uninstallFeature(featureName, EnumSet.of(NoAutoRefreshBundles));
                waitFeatures.add(featureName);
            }
        }

        if (wait) {
            for (String featureName : waitFeatures) {
                waitForFeature(featureName, state -> state == FeatureState.Uninstalled);
            }

            waitForAllBundles();
        }
    }

    private boolean isFeatureInstalled(String featureName) throws Exception {
        Feature[] features = getFeaturesService().listInstalledFeatures();
        for (Feature feature : features) {
            if (feature.getName().equals(featureName)) {
                return true;
            }
        }
        return false;
    }

    // TODO - we should really make this a bundle and inject this.
    private FeaturesService getFeaturesService() throws InterruptedException {
        FeaturesService featuresService = null;
        boolean ready = false;
        long timeoutLimit = System.currentTimeMillis() + FEATURES_AND_BUNDLES_TIMEOUT;
        while (!ready) {
            Bundle bundle = FrameworkUtil.getBundle(getClass());
            if (bundle != null) {
                ServiceReference<FeaturesService> featuresServiceRef = bundle.getBundleContext()
                        .getServiceReference(FeaturesService.class);
                try {
                    if (featuresServiceRef != null) {
                        featuresService = getBundleContext().getService(featuresServiceRef);
                        if (featuresService != null) {
                            ready = true;
                        }
                    }
                } catch (NullPointerException e) {
                    // ignore
                }
            }

            if (!ready) {
                if (System.currentTimeMillis() > timeoutLimit) {
                    fail(String.format("Feature service could not be resolved within %d minutes.",
                            TimeUnit.MILLISECONDS.toMinutes(FEATURES_AND_BUNDLES_TIMEOUT)));
                }
                Thread.sleep(1000);
            }
        }

        return featuresService;
    }

    @Override
    public void restartBundles(String... bundleSymbolicNames) throws BundleException {
        LOGGER.debug("Restarting bundles {}", bundleSymbolicNames);

        Map<String, Bundle> bundleLookup = Arrays.stream(getBundleContext().getBundles())
                .collect(Collectors.toMap(Bundle::getSymbolicName, Function.identity(), (a, b) -> a));

        List<Bundle> bundles = Arrays.stream(bundleSymbolicNames).map(bundleLookup::get)
                .collect(Collectors.toList());

        for (Bundle bundle : bundles) {
            bundle.stop();
        }

        for (Bundle bundle : Lists.reverse(bundles)) {
            bundle.start();
        }
    }

    @Override
    public void stopBundle(String bundleSymbolicName) throws BundleException {
        for (Bundle bundle : getBundleContext().getBundles()) {
            if (bundleSymbolicName.equals(bundle.getSymbolicName())) {
                bundle.stop();
            }
        }
    }

    @Override
    public void startBundle(String bundleSymbolicName) throws BundleException {
        for (Bundle bundle : getBundleContext().getBundles()) {
            if (bundleSymbolicName.equals(bundle.getSymbolicName())) {
                bundle.start();
            }
        }
    }

    @Override
    public void uninstallBundle(String bundleSymbolicName) throws BundleException {
        for (Bundle bundle : getBundleContext().getBundles()) {
            if (bundleSymbolicName.equals(bundle.getSymbolicName())) {
                bundle.uninstall();
                WaitCondition.expect(String.format("Bundle %s uninstalled", bundleSymbolicName))
                        .within(FEATURES_AND_BUNDLES_TIMEOUT, TimeUnit.MILLISECONDS)
                        .until(() -> bundle.getState() == Bundle.UNINSTALLED);
                break;
            }
        }
    }

    @Override
    public void waitForAllBundles() throws InterruptedException {
        waitForRequiredBundles("");
    }

    @Override
    public void waitForRequiredBundles(String symbolicNamePrefix) throws InterruptedException {
        boolean ready = false;
        if (bundleService == null) {
            bundleService = getService(BundleService.class);
        }

        long timeoutLimit = System.currentTimeMillis() + FEATURES_AND_BUNDLES_TIMEOUT;
        while (!ready) {
            List<Bundle> bundles = Arrays.asList(getBundleContext().getBundles());

            ready = true;
            for (Bundle bundle : bundles) {
                if (bundle.getSymbolicName().startsWith(symbolicNamePrefix)) {
                    String bundleName = bundle.getHeaders().get(Constants.BUNDLE_NAME);
                    BundleInfo bundleInfo = bundleService.getInfo(bundle);
                    BundleState bundleState = bundleInfo.getState();
                    if (bundleInfo.isFragment()) {
                        if (!BundleState.Resolved.equals(bundleState)) {
                            LOGGER.info("{} bundle not ready yet", bundleName);
                            ready = false;
                        }
                    } else if (bundleState != null) {
                        if (BundleState.Failure.equals(bundleState)) {
                            printInactiveBundles();
                            fail("The bundle " + bundleName + " failed.");
                        } else if (!BundleState.Active.equals(bundleState)) {
                            LOGGER.info("{} bundle not ready with state {}", bundleName, bundleState);
                            ready = false;
                        }
                    }
                }
            }

            if (!ready) {
                if (System.currentTimeMillis() > timeoutLimit) {
                    printInactiveBundles();
                    fail(String.format("Bundles and blueprint did not start within %d minutes.",
                            TimeUnit.MILLISECONDS.toMinutes(FEATURES_AND_BUNDLES_TIMEOUT)));
                }
                LOGGER.info("Bundles not up, sleeping...");
                Thread.sleep(1000);
            }
        }
    }

    @Override
    public void waitForBundleUninstall(String... bundleSymbolicNames) {
        Set<String> symbolicNamesSet = Sets.newHashSet(bundleSymbolicNames);
        LOGGER.info("Waiting for bundles {} to be uninstalled...", symbolicNamesSet);

        List<Long> bundleIds = Arrays.stream(getBundleContext().getBundles())
                .filter(b -> symbolicNamesSet.contains(b.getSymbolicName())).map(Bundle::getBundleId)
                .collect(Collectors.toList());

        WaitCondition.expect(String.format("Bundles %s uninstalled", symbolicNamesSet))
                .within(FEATURES_AND_BUNDLES_TIMEOUT, TimeUnit.MILLISECONDS)
                .until(() -> bundleIds.stream().filter(id -> getBundleContext().getBundle(id) != null)
                        .collect(Collectors.toList()).isEmpty());

        LOGGER.info("Bundles {} uninstalled", symbolicNamesSet);
    }

    @Override
    public void waitForFeature(String featureName, Predicate<FeatureState> predicate) throws Exception {
        boolean ready = false;

        long timeoutLimit = System.currentTimeMillis() + FEATURES_AND_BUNDLES_TIMEOUT;
        FeaturesService featuresService = getFeaturesService();

        while (!ready) {
            FeatureState state = null;

            if (featuresService != null) {
                Feature feature = featuresService.getFeature(featureName);
                state = featuresService.getState(feature.getName() + "/" + feature.getVersion());

                if (state == null) {
                    LOGGER.debug("No Feature found for featureName: {}", featureName);
                    return;
                } else if (predicate.test(state)) {
                    ready = true;
                }
            }

            if (!ready) {
                if (System.currentTimeMillis() > timeoutLimit) {
                    printInactiveBundles();
                    fail(String.format("Feature did not change to State [" + predicate + "] within %d minutes.",
                            TimeUnit.MILLISECONDS.toMinutes(FEATURES_AND_BUNDLES_TIMEOUT)));
                }
                LOGGER.info("Still waiting on feature [{}], current state [{}]...", featureName, state);
                Thread.sleep(1000);
            }
        }
    }

    @Override
    public void waitForHttpEndpoint(String path) throws InterruptedException {
        LOGGER.info("Waiting for {}", path);

        long timeoutLimit = System.currentTimeMillis() + HTTP_ENDPOINT_TIMEOUT;
        boolean available = false;

        while (!available) {
            Response response = given().header("X-Requested-With", "XMLHttpRequest").header("Origin", path)
                    .get(path);
            available = response.getStatusCode() == 200 && response.getBody().asString().length() > 0;
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Response body: {}", response.getBody().asString());
            }
            if (!available) {
                if (System.currentTimeMillis() > timeoutLimit) {
                    printInactiveBundles();
                    fail(String.format("%s did not start within %d minutes.", path,
                            TimeUnit.MILLISECONDS.toMinutes(HTTP_ENDPOINT_TIMEOUT)));
                }
                Thread.sleep(100);
            }
        }

        LOGGER.info("{} ready.", path);
    }

    @Override
    public void waitForSourcesToBeAvailable(String restPath, String... sources) throws InterruptedException {
        String path = restPath + "sources";
        LOGGER.info("Waiting for sources at {}", path);

        long timeoutLimit = System.currentTimeMillis() + AbstractIntegrationTest.GENERIC_TIMEOUT_MILLISECONDS;
        boolean available = false;

        while (!available) {
            Response response = get(path);
            String body = response.getBody().asString();
            if (StringUtils.isNotBlank(body)) {
                available = response.getStatusCode() == 200 && body.length() > 0 && !body.contains("false")
                        && response.getBody().jsonPath().getList("id") != null;
                if (available) {
                    List<Object> ids = response.getBody().jsonPath().getList("id");
                    for (String source : sources) {
                        if (!ids.contains(source)) {
                            available = false;
                        }
                    }
                }
            }
            if (!available) {
                if (System.currentTimeMillis() > timeoutLimit) {
                    response.prettyPrint();
                    printInactiveBundles();
                    fail("Sources at " + path + " did not start in time.");
                }
                Thread.sleep(1000);
            }
        }

        LOGGER.info("Sources at {} ready.", path);
    }

    @Override
    public Map<String, Object> getMetatypeDefaults(String symbolicName, String factoryPid) {
        Map<String, Object> properties = new HashMap<>();
        ObjectClassDefinition bundleMetatype = getObjectClassDefinition(symbolicName, factoryPid);
        if (bundleMetatype != null) {
            for (AttributeDefinition attributeDef : bundleMetatype
                    .getAttributeDefinitions(ObjectClassDefinition.ALL)) {
                if (attributeDef.getID() != null) {
                    if (attributeDef.getDefaultValue() != null) {
                        if (attributeDef.getCardinality() == 0) {
                            properties.put(attributeDef.getID(),
                                    getAttributeValue(attributeDef.getDefaultValue()[0], attributeDef.getType()));
                        } else {
                            properties.put(attributeDef.getID(), attributeDef.getDefaultValue());
                        }
                    } else if (attributeDef.getCardinality() != 0) {
                        properties.put(attributeDef.getID(), new String[0]);
                    }
                }
            }
        } else {
            LOGGER.debug("Metatype was null, returning an empty properties Map");
        }

        return properties;
    }

    private Object getAttributeValue(String value, int type) {
        switch (type) {
        case AttributeDefinition.BOOLEAN:
            return Boolean.valueOf(value);
        case AttributeDefinition.BYTE:
            return Byte.valueOf(value);
        case AttributeDefinition.DOUBLE:
            return Double.valueOf(value);
        case AttributeDefinition.CHARACTER:
            return value.toCharArray()[0];
        case AttributeDefinition.FLOAT:
            return Float.valueOf(value);
        case AttributeDefinition.INTEGER:
            return Integer.valueOf(value);
        case AttributeDefinition.LONG:
            return Long.valueOf(value);
        case AttributeDefinition.SHORT:
            return Short.valueOf(value);
        case AttributeDefinition.PASSWORD:
        case AttributeDefinition.STRING:
        default:
            return value;
        }
    }

    private ObjectClassDefinition getObjectClassDefinition(String symbolicName, String pid) {
        Bundle[] bundles = getBundleContext().getBundles();
        for (Bundle bundle : bundles) {
            if (symbolicName.equals(bundle.getSymbolicName())) {
                try {
                    MetaTypeInformation mti = metatype.getMetaTypeInformation(bundle);
                    if (mti != null) {
                        try {
                            ObjectClassDefinition ocd = mti.getObjectClassDefinition(pid,
                                    Locale.getDefault().toString());
                            if (ocd != null) {
                                return ocd;
                            }
                        } catch (IllegalArgumentException e) {
                            // ignoring
                        }
                    }
                } catch (IllegalArgumentException iae) {
                    // ignoring
                }
            }
        }
        return null;
    }

    @Override
    public void printInactiveBundles() {
        printInactiveBundles(LOGGER::error, LOGGER::error);
    }

    @Override
    public void printInactiveBundlesInfo() {
        printInactiveBundles(LOGGER::info, LOGGER::info);
    }

    private void printInactiveBundles(Consumer<String> headerConsumer, BiConsumer<String, Object[]> logConsumer) {
        headerConsumer.accept("Listing inactive bundles");

        for (Bundle bundle : getBundleContext().getBundles()) {
            if (bundle.getState() != Bundle.ACTIVE) {
                Dictionary<String, String> headers = bundle.getHeaders();
                if (headers.get("Fragment-Host") != null) {
                    continue;
                }

                StringBuilder headerString = new StringBuilder("[ ");
                Enumeration<String> keys = headers.keys();

                while (keys.hasMoreElements()) {
                    String key = keys.nextElement();
                    headerString.append(key).append("=").append(headers.get(key)).append(", ");
                }

                headerString.append(" ]");
                logConsumer.accept("\n\tBundle: {}_v{} | {}\n\tHeaders: {}",
                        new Object[] { bundle.getSymbolicName(), bundle.getVersion(),
                                BUNDLE_STATES.getOrDefault(bundle.getState(), "UNKNOWN"), headerString });
            }
        }
    }

    @Override
    public <S> ServiceReference<S> getServiceReference(Class<S> aClass) {
        return getBundleContext().getServiceReference(aClass);
    }

    @Override
    public <S> Collection<ServiceReference<S>> getServiceReferences(Class<S> aClass, String s)
            throws InvalidSyntaxException {
        return getBundleContext().getServiceReferences(aClass, s);
    }

    @Override
    public <S> S getService(ServiceReference<S> serviceReference) {
        WaitCondition.expect("Service to be available: " + serviceReference)
                .within(AbstractIntegrationTest.GENERIC_TIMEOUT_SECONDS, TimeUnit.SECONDS)
                .until(() -> getBundleContext().getService(serviceReference), notNullValue());
        return getBundleContext().getService(serviceReference);
    }

    @Override
    public <S> S getService(Class<S> aClass) {
        return getService(getBundleContext().getServiceReference(aClass));
    }

    private class ServiceConfigurationListener implements ConfigurationListener {

        private final String pid;

        private boolean updated;

        public ServiceConfigurationListener(String pid) {
            this.pid = pid;
        }

        @Override
        public void configurationEvent(ConfigurationEvent event) {
            LOGGER.info("Configuration event received: {}", event);
            if (event.getPid().equals(pid) && ConfigurationEvent.CM_UPDATED == event.getType()) {
                updated = true;
            }
        }

        public boolean isUpdated() {
            return updated;
        }
    }
}