/*
 * Copyright 2006-2021 DLR, Germany
 * 
 * SPDX-License-Identifier: EPL-1.0
 * 
 * https://rcenvironment.de/
 */

package de.rcenvironment.core.communication.rpc.internal;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.osgi.framework.BundleContext;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;

import de.rcenvironment.core.communication.rpc.spi.LocalServiceResolver;
import de.rcenvironment.core.utils.common.StringUtils;
import de.rcenvironment.core.utils.common.rpc.RemoteOperationException;

/**
 * Resolves service requests against the OSGi service registry, using the containing bundle's {@link BundleContext}.
 * 
 * @author Robert Mischke
 */
public class OSGiLocalServiceResolver implements LocalServiceResolver {

    private static final int SINGLETON_SERVICE_RESOLUTION_NUM_ATTEMPTS = 10;

    private static final int SERVICE_RESOLUTION_RETRY_DELAY_MSEC = 1000;

    private static final String ERROR_GET_SERVICE = "Failed to acquire OSGi service ";

    private final Log log = LogFactory.getLog(getClass());

    private BundleContext bundleContext;

    private final int retryCount;

    /**
     * Default constructor; uses a retry count of {@link #SINGLETON_SERVICE_RESOLUTION_NUM_ATTEMPTS}.
     */
    public OSGiLocalServiceResolver() {
        this.retryCount = SINGLETON_SERVICE_RESOLUTION_NUM_ATTEMPTS;
    }

    /**
     * Constructor that allows setting a custom retry count (intended to speed up unit testing).
     * 
     * @param retryCount the number of lookup retries before giving up
     */
    public OSGiLocalServiceResolver(int retryCount) {
        this.retryCount = retryCount;
    }

    /**
     * OSGi life cycle method.
     * 
     * @param context the {@link BundleContext} to resolve against
     */
    public void activate(BundleContext context) {
        this.bundleContext = context;
    }

    @Override
    public Object getLocalService(String serviceName) {
        // allow several retries as the service may be still starting up
        return getLocalSingletonService(serviceName, retryCount, SERVICE_RESOLUTION_RETRY_DELAY_MSEC);
    }

    /**
     * 
     * Gets the service from the OSGi service registry.
     * 
     * @param service The name of the service to get.
     * @param numAttempts the number of attempts before the service is considered unavailable
     * @param delayBetweenAttemptsMsec the delay (in milliseconds) between attempts to acquire the service
     * @return the service object
     * @throws RemoteOperationException if the service could not be got.
     */
    protected Object getLocalSingletonService(String service, int numAttempts, int delayBetweenAttemptsMsec) {

        ServiceReference<?>[] serviceReferences = null;

        int attempt = 1;
        while (attempt <= numAttempts) {
            try {
                serviceReferences = bundleContext.getServiceReferences(service, null);
            } catch (InvalidSyntaxException e) {
                log.error(ERROR_GET_SERVICE + service, e);
                return null;
            }

            Object serviceObject;
            if (serviceReferences != null && serviceReferences.length > 0) {
                if (serviceReferences.length > 1) {
                    log.error("More than one OSGi service reference matched (request: service=" + service + ")");
                    return null;
                }
                serviceObject = bundleContext.getService(serviceReferences[0]);
                if (serviceObject != null) {
                    // success
                    return serviceObject;
                }
            }

            // prepare for retry
            attempt++;
            if (attempt <= numAttempts) {
                log.warn(StringUtils.format("Failed to acquire OSGi service on attempt #%d; "
                    + "it may not have started yet, retrying after %d msec (request: service=%s)", attempt - 1,
                    delayBetweenAttemptsMsec, service));
                try {
                    Thread.sleep(delayBetweenAttemptsMsec);
                } catch (InterruptedException e) {
                    log.error("Interrupted while waiting for retry");
                    return null;
                }
            }
        }
        log.error(ERROR_GET_SERVICE + service + " - service not available; made " + numAttempts + " attempt(s)");
        return null;
    }
}
