All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.redhat.lightblue.migrator.facade.ServiceFacade Maven / Gradle / Ivy

There is a newer version: 2.56.1
Show newest version
package com.redhat.lightblue.migrator.facade;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import com.redhat.lightblue.migrator.facade.methodcallstringifier.LazyMethodCallStringifier;
import com.redhat.lightblue.migrator.facade.methodcallstringifier.MethodCallStringifier;
import com.redhat.lightblue.migrator.facade.sharedstore.SharedStore;
import com.redhat.lightblue.migrator.facade.sharedstore.SharedStoreException;
import com.redhat.lightblue.migrator.facade.sharedstore.SharedStoreImpl;
import com.redhat.lightblue.migrator.facade.sharedstore.SharedStoreSetter;
import com.redhat.lightblue.migrator.features.LightblueMigration;
import com.redhat.lightblue.migrator.features.TogglzRandomUsername;

/**
 * A helper base class for migrating services from legacy datastore to lightblue. It lets you call any service/dao method, using togglz switches to choose which
 * service/dao to use and verifying returned data.
 *
 * @author mpatercz
 *
 */
@SuppressWarnings("all")
public class ServiceFacade implements SharedStoreSetter {

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

    protected final D legacySvc, lightblueSvc;

    private SharedStore sharedStore = null;

    private Map,ModelMixIn> modelMixIns;

    private Properties properties = new Properties();

    private TimeoutConfiguration timeoutConfiguration;

    // default timeout is 2 seconds
    public static final long DEFAULT_TIMEOUT_MS = 2000;

    private ConsistencyChecker consistencyChecker;

    // used to associate inconsistencies with the service in the logs
    private final String implementationName;

    public SharedStore getSharedStore() {
        return sharedStore;
    }

    public void setSharedStore(SharedStore shareStore) {
        this.sharedStore = shareStore;

        legacySvc.setSharedStore(shareStore);
        lightblueSvc.setSharedStore(shareStore);
    }

    public ConsistencyChecker getConsistencyChecker() {
        if (consistencyChecker == null) {
            consistencyChecker = new ConsistencyChecker(implementationName);
        }
        return consistencyChecker;
    }

    public void setConsistencyChecker(ConsistencyChecker consistencyChecker) {
        this.consistencyChecker = consistencyChecker;
    }

    public ServiceFacade(D legacySvc, D lightblueSvc, String implementationName) {
        this(legacySvc, lightblueSvc, implementationName, null);
    }

    public ServiceFacade(D legacySvc, D lightblueSvc, String implementationName, Properties properties) {
        super();
        this.legacySvc = legacySvc;
        this.lightblueSvc = lightblueSvc;
        setSharedStore(new SharedStoreImpl(implementationName));
        this.implementationName = implementationName;
        if (properties != null)
            this.properties = properties;

        timeoutConfiguration = new TimeoutConfiguration(DEFAULT_TIMEOUT_MS, implementationName, this.properties);

        log.info("Initialized facade for "+implementationName);
    }

    private long getLightblueExecutionTimeout(String methodName) {
        String timeout = properties.getProperty("com.redhat.lightblue.migrator.facade.timeout."+implementationName+"."+methodName);
        if (timeout == null)
            timeout = properties.getProperty("com.redhat.lightblue.migrator.facade.timeout."+implementationName, ""+DEFAULT_TIMEOUT_MS);

        return Long.parseLong(timeout);
    }

    private ListeningExecutorService createExecutor() {
        return MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(1));
    }

    private Class[] toClasses(Object[] objects) {
        List classes = new ArrayList<>();
        for (Object o: objects) {
            classes.add(o.getClass());
        }
        return classes.toArray(new Class[]{});
    }

    private  ListenableFuture callLightblueSvc(final Method method, final Object[] values, final FacadeOperation op, final MethodCallStringifier callStringifier) {
        ListeningExecutorService executor = createExecutor();
        try {
        // fetch from lightblue using future (asynchronously)
        final long parentThreadId = Thread.currentThread().getId();
        return executor.submit(new Callable(){
            @Override
            public T call() throws Exception {
                Timer dest = new Timer("destination."+method.getName());
                if (sharedStore != null)
                    sharedStore.copyFromThread(parentThreadId);
                try {
                    return (T) method.invoke(lightblueSvc, values);
                } finally {
                    long callTook = dest.complete();
                    long timeout = timeoutConfiguration.getTimeoutMS(method.getName(), op);
                    long slowWarning = timeoutConfiguration.getSlowWarningMS(method.getName(), op);

                    if (callTook >= slowWarning && callTook < timeout) {
                        // call is slow but not slow enough to trigger timeout
                        log.warn("Slow call warning: {}.{} took {}ms",implementationName, callStringifier.toString(), callTook);
                    }
                }
            }
        });
        } finally {
            executor.shutdown();
        }
    }

    /**
     * Call destination (lightblue) using a timeout in dual read/write phases. Do not use facade
     * timeout during lightblue proxy phase.
     *
     * @param listenableFuture
     * @param methodName method name is used to read method specific timeout configuration
     * @param shouldSource true if source operation is supposed to be performed for this call (i.e. this is a dual phase)
     * @return
     * @throws InterruptedException
     * @throws ExecutionException
     * @throws TimeoutException
     */
    private  T getWithTimeout(ListenableFuture listenableFuture, String methodName, FacadeOperation facadeOperation, boolean shouldSource) throws InterruptedException, ExecutionException, TimeoutException {
        if (!shouldSource || timeoutConfiguration.getTimeoutMS(methodName, facadeOperation) <= 0) {
            return listenableFuture.get();
        } else {
            return listenableFuture.get(timeoutConfiguration.getTimeoutMS(methodName, facadeOperation), TimeUnit.MILLISECONDS);
        }
    }

    private Throwable extractUnderlyingException(Throwable t) {
        if ((t instanceof ExecutionException || t instanceof InvocationTargetException) && t.getCause() != null) {
            return extractUnderlyingException(t.getCause());
        }
        else {
            return t;
        }
    }

    // which togglz flags to use
    public enum FacadeOperation {
        READ, WRITE;
    }

    private boolean shouldDestination(FacadeOperation facadeOperation) {
        switch (facadeOperation) {
            case READ: return LightblueMigration.shouldReadDestinationEntity();
            case WRITE: return LightblueMigration.shouldWriteDestinationEntity();
            default: throw new IllegalArgumentException(facadeOperation.toString());
        }
    }

    private boolean shouldSource(FacadeOperation facadeOperation) {
        switch (facadeOperation) {
            case READ: return LightblueMigration.shouldReadSourceEntity();
            case WRITE: return LightblueMigration.shouldWriteSourceEntity();
            default: throw new IllegalArgumentException(facadeOperation.toString());
        }
    }

    private boolean shouldCheckConsistencyConsistency(FacadeOperation facadeOperation) {
        switch (facadeOperation) {
            case READ: return LightblueMigration.shouldCheckReadConsistency();
            case WRITE: return LightblueMigration.shouldCheckWriteConsistency();
            default: throw new IllegalArgumentException(facadeOperation.toString());
        }
    }

    /**
     * Call service method according to settings specified in togglz.
     *
     * @param facadeOperation READ/WRITE operation, used by togglz flags
     * @param callInParallel true means both oracle and lightblue will be called in parallel and state sharing between services will not be possible
     * @param returnedType type of the returned object
     * @param methodName method name to call
     * @param types List of parameter types
     * @param values List of parameters
     * @return Object returned by dao
     * @throws Exception
     */
    public  T callSvcMethod(final FacadeOperation facadeOperation, final boolean callInParallel, final Method methodCalled, final Object ... values) throws Throwable {

        final Class returnedType = (Class) methodCalled.getReturnType();
        final String methodName = methodCalled.getName();
        final Class[] types = methodCalled.getParameterTypes();

        LazyMethodCallStringifier callStringifier = new LazyMethodCallStringifier(methodCalled, values);

        if (log.isDebugEnabled()) {
            log.debug("Calling {}.{} ({} {})", implementationName, callStringifier, callInParallel ? "parallel": "serial", facadeOperation);
        }

        TogglzRandomUsername.init();

        if (sharedStore != null)
            sharedStore.clear(); // make sure no data is left from previous calls

        if (sharedStore != null && shouldSource(facadeOperation) && shouldDestination(facadeOperation)) {
            sharedStore.setDualMigrationPhase(true);
        } else if (sharedStore != null){
            sharedStore.setDualMigrationPhase(false);
        }

        T legacyEntity = null, lightblueEntity = null;

        ListenableFuture listenableFuture = null;

        if (callInParallel) {
            if (shouldDestination(facadeOperation)) {
                Method method = lightblueSvc.getClass().getMethod(methodName, types);
                listenableFuture = callLightblueSvc(method, values, facadeOperation, callStringifier);
            }
        }

        if (shouldSource(facadeOperation)) {
            // perform operation in oracle, synchronously
            log.debug("Calling legacy {}.{}", implementationName, methodName);
            Method method = legacySvc.getClass().getMethod(methodName,types);
            Timer source = new Timer("source."+methodName);
            try {
                legacyEntity = (T) method.invoke(legacySvc, values);
            } catch (InvocationTargetException e) {
                throw e.getCause();
            } finally {
                source.complete();
            }
        }

        if (shouldDestination(facadeOperation)) {
            log.debug("Calling lightblue {}.{}", implementationName, methodName);

            try {
                if (callInParallel) {
                    // lightblue was called before source/legacy, now just getting results
                    lightblueEntity = getWithTimeout(listenableFuture, methodName, facadeOperation, shouldSource(facadeOperation));
                } else {
                    // call lightblue after source/legacy finished
                    Method method = lightblueSvc.getClass().getMethod(methodName, types);
                    listenableFuture = callLightblueSvc(method, values, facadeOperation, callStringifier);
                    lightblueEntity = getWithTimeout(listenableFuture, methodName, facadeOperation, shouldSource(facadeOperation));
                }
            } catch (TimeoutException te) {
                if (shouldSource(facadeOperation)) {
                    log.warn("Lightblue call "+implementationName+"."+callStringifier+" is taking too long (longer than "+timeoutConfiguration.getTimeoutMS(methodName, facadeOperation)+"s). Returning data from legacy.");
                    return legacyEntity;
                } else {
                    throw te;
                }
            } catch (Throwable e) {
                if (shouldSource(facadeOperation)) {
                    log.warn("Error when calling lightblue DAO. Returning data from legacy.", e);
                    return legacyEntity;
                } else {
                    throw extractUnderlyingException(e);
                }
            }
        }

        if (shouldCheckConsistencyConsistency(facadeOperation) && shouldSource(facadeOperation) && shouldDestination(facadeOperation)) {
            // make sure that response from lightblue and oracle are the same
            log.debug("."+methodName+" checking returned entity's consistency");

            // check if entities match
            if (getConsistencyChecker().checkConsistency(legacyEntity, lightblueEntity, methodName, callStringifier)) {
                // return lightblue data if they are
                return lightblueEntity;
            } else {
                // return oracle data if they aren't and log data inconsistency
                return legacyEntity;
            }
        }

        return lightblueEntity != null ? lightblueEntity : legacyEntity;
    }

    private SharedStoreException extractSharedStoreExceptionIfExists(ExecutionException ee) {
        try {
            if (ee.getCause().getCause() instanceof SharedStoreException) {
                return (SharedStoreException)ee.getCause().getCause();
            } else {
                return null;
            }
        } catch (NullPointerException e) {
            return null;
        }
    }

    @Deprecated
    public int getTimeoutSeconds() {
        throw new UnsupportedOperationException();
    }

    @Deprecated
    public void setTimeoutSeconds(int timeoutSeconds) {
        timeoutConfiguration = new TimeoutConfiguration(timeoutSeconds*1000, implementationName, properties);
    }

    public void setLogResponseDataEnabled(boolean logResponsesEnabled) {
        getConsistencyChecker().setLogResponseDataEnabled(logResponsesEnabled);
    }

    public void setMaxInconsistencyLogLength(int length) {
        getConsistencyChecker().setMaxInconsistencyLogLength(length);
    }

    public D getLegacySvc() {
        return legacySvc;
    }

    public D getLightblueSvc() {
        return lightblueSvc;
    }

    public TimeoutConfiguration getTimeoutConfiguration() {
        return timeoutConfiguration;
    }

    public void setTimeoutConfiguration(TimeoutConfiguration timeoutConfiguration) {
        this.timeoutConfiguration = timeoutConfiguration;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy