package com.redhat.lightblue.migrator.facade.proxy;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Properties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.redhat.lightblue.migrator.facade.ServiceFacade;
import com.redhat.lightblue.migrator.facade.ServiceFacade.FacadeOperation;
import com.redhat.lightblue.migrator.facade.sharedstore.SharedStoreSetter;
import com.redhat.lightblue.migrator.features.LightblueMigration;
/**
* Creates a dynamic proxy implementing given interface. The calls to the
* interface apis will be directed to the underlying {@link ServiceFacade}
* according to the type of the operation specified using annotations.
*
* @author mpatercz
*
*/
public class FacadeProxyFactory {
/**
* Indicates this api performs a read operation. See null null {@link ServiceFacade#callSvcMethod(FacadeOperation, boolean, Class, String, Class[], Object...) for details.
*
* Set parallel to true if services do not need to share state.
*
* @author mpatercz
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public static @interface ReadOperation {
boolean parallel() default false;
}
/**
* Indicates this api performs a write operation. See null null {@link ServiceFacade#callSvcMethod(FacadeOperation, boolean, Class, String, Class[], Object...) for details.
*
* Set parallel to true if services do not need to share state.
*
* @author mpatercz
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public static @interface WriteOperation {
boolean parallel() default false;
}
/**
* Pass the call directly to destination or source service. Ignores
* migration phases (togglz). Note that @Direct(target=SOURCE) is the same
* as no annotation.
*
* @author mpatercz
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public static @interface DirectOperation {
enum Target {
LEGACY, LIGHTBLUE;
}
Target target();
}
/**
* Secret parameters are logged as ****.
*
* @author mpatercz
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public static @interface Secret {
}
private static class FacadeInvocationHandler<D extends SharedStoreSetter> implements InvocationHandler {
private static final Logger log = LoggerFactory.getLogger(FacadeInvocationHandler.class);
private ServiceFacade<D> svcFacade;
public FacadeInvocationHandler(ServiceFacade<D> svcFacade) {
this.svcFacade = svcFacade;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.isAnnotationPresent(DirectOperation.class)) {
DirectOperation.Target t = method.getAnnotation(DirectOperation.class).target();
switch (t) {
case LEGACY: {
log.debug("Method \"{}\" is explicitly annotated to call legacy", method);
Method legacyMethod = svcFacade.getLegacySvc().getClass().getMethod(method.getName(), method.getParameterTypes());
return legacyMethod.invoke(svcFacade.getLegacySvc(), args);
}
case LIGHTBLUE: {
log.debug("Method \"{}\" is explicitly annotated to call lightblue", method);
Method destinationMethod = svcFacade.getLightblueSvc().getClass().getMethod(method.getName(), method.getParameterTypes());
return destinationMethod.invoke(svcFacade.getLightblueSvc(), args);
}
}
}
if (method.isAnnotationPresent(ReadOperation.class)) {
ReadOperation ro = method.getAnnotation(ReadOperation.class);
log.debug("Performing parallel=" + ro.parallel() + " " + FacadeOperation.READ + " operation");
return svcFacade.callSvcMethod(FacadeOperation.READ, ro.parallel(), method, args);
}
if (method.isAnnotationPresent(WriteOperation.class)) {
WriteOperation wo = method.getAnnotation(WriteOperation.class);
log.debug("Performing parallel=" + wo.parallel() + " " + FacadeOperation.WRITE + " operation");
return svcFacade.callSvcMethod(FacadeOperation.WRITE, wo.parallel(), method, args);
}
if (!LightblueMigration.shouldReadSourceEntity() && !LightblueMigration.shouldWriteSourceEntity()) {
// this method is not annotated and legacy/source is no more
throw new IllegalStateException("Method \""+method+"\" is not annotated for facade and legacy source is disabled!");
} else {
// this can cause problems when entering proxy phase
log.warn("Method \"{}\" is not annotated for facade. Proxy passing to legacy.", method);
Method legacyMethod = svcFacade.getLegacySvc().getClass().getMethod(method.getName(), method.getParameterTypes());
return legacyMethod.invoke(svcFacade.getLegacySvc(), args);
}
}
}
/**
* Create facade proxy from {@link ServiceFacade}.
*
* Java does not allow typed argument with both typed and static bounds,
* i.e. <D extends SharedStoreSetter & T> does not compile. To work around
* this limitation and to ensure that facaded services implement both the
* facade interface and {@link SharedStoreSetter}, I'm using
* <T extends D,D extends SharedStoreSetter>. This makes the facade
* interface - T - require to implement {@link SharedStoreSetter}. It does
* not logically belong to the facade interface, but I guess I can live with
* that.
*
* @param svcFacade initialized with services implementing the
* facadeInterface
* @param facadeInterface has to implement {@link SharedStoreSetter}
* @return facaded service proxy
* @throws InstantiationException
* @throws IllegalAccessException
*/
@SuppressWarnings("unchecked")
public static <T extends D, D extends SharedStoreSetter> D createFacadeProxy(ServiceFacade<D> svcFacade, Class<T> facadeInterface) throws InstantiationException, IllegalAccessException {
return (D) Proxy.newProxyInstance(facadeInterface.getClassLoader(), new Class[]{facadeInterface}, new FacadeInvocationHandler<D>(svcFacade));
}
/**
* Create a facade proxy.
*
* Java does not allow typed argument with both typed and static bounds,
* i.e. <D extends SharedStoreSetter & T> does not compile. To work around
* this limitation and to ensure that facaded services implement both the
* facade interface and {@link SharedStoreSetter}, I'm using
* <T extends D,D extends SharedStoreSetter>. This makes the facade
* interface - T - require to implement {@link SharedStoreSetter}. It does
* not logically belong to the facade interface, but I guess I can live with
* that.
*
* @param legacySvc has to implement facadeInterface
* @param lightblueSvc has to implement facadeInterface
* @param facadeInterface has to implement {@link SharedStoreSetter}
* @param properties
* @return facaded service proxy
* @throws InstantiationException
* @throws IllegalAccessException
*/
public static <T extends D, D extends SharedStoreSetter> D createFacadeProxy(D legacySvc, D lightblueSvc, Class<T> facadeInterface, Properties properties) throws InstantiationException, IllegalAccessException {
return createFacadeProxy(new ServiceFacade<D>(legacySvc, lightblueSvc, facadeInterface.getSimpleName(), properties), facadeInterface);
}
}