com.mx.path.gateway.Gateway Maven / Gradle / Ivy
package com.mx.path.gateway;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import lombok.Getter;
import lombok.Setter;
import lombok.Singular;
import lombok.experimental.SuperBuilder;
import com.google.common.collect.ImmutableList;
import com.mx.path.connect.messaging.remote.RemoteService;
import com.mx.path.core.common.collection.ObjectMap;
import com.mx.path.core.common.event.EventBus;
import com.mx.path.core.common.reflection.Annotations;
import com.mx.path.core.context.RequestContext;
import com.mx.path.core.context.facility.Facilities;
import com.mx.path.gateway.accessor.Accessor;
import com.mx.path.gateway.accessor.AccessorResponse;
import com.mx.path.gateway.behavior.GatewayBehavior;
import com.mx.path.gateway.behavior.StartBehavior;
import com.mx.path.gateway.configuration.AccessorDescriber;
import com.mx.path.gateway.configuration.RootGateway;
import com.mx.path.gateway.context.GatewayRequestContext;
import com.mx.path.gateway.event.AfterAccessorEvent;
import com.mx.path.gateway.event.BeforeAccessorEvent;
import com.mx.path.gateway.service.GatewayService;
@SuperBuilder
public abstract class Gateway {
@Getter
private String clientId;
@Getter
private T baseAccessor;
@Setter
private Gateway parent;
@Getter
@Setter
private RemoteService> remote;
@Getter
@Singular
private List behaviors = Collections.emptyList();
@Getter
@Singular
private List services;
public Gateway() {
}
public Gateway(String clientId) {
this.clientId = clientId;
}
public final boolean isTopLevel() {
return Annotations.hasAnnotation(getClass(), RootGateway.class);
}
/**
* Use reflection to discover all child gateways belonging to this
*
* Gateways must be exposed via a getter
*
* @return List of BaseGateway
*/
public ImmutableList gateways() {
return ImmutableList.copyOf(Arrays.stream(getClass().getMethods())
.filter(method -> Gateway.class.isAssignableFrom(method.getReturnType()))
.filter(method -> method.getReturnType() != Gateway.class)
.map(method -> {
try {
return (Gateway) method.invoke(this);
} catch (InvocationTargetException | IllegalAccessException e) {
return null;
}
})
.filter(Objects::nonNull)
.collect(Collectors.toList()));
}
/**
* Registers all remote gateways
*/
public void registerRemotes() {
if (remote != null) {
remote.register();
}
gateways().forEach(Gateway::registerRemotes);
}
/**
* Start all services
*/
public void startServices() {
services.forEach(service -> {
if (service.getGateway() == null) {
service.setGateway(this);
}
service.start();
});
gateways().forEach(Gateway::startServices);
}
/**
* Describe this gateway
*
* @return new description
*/
public final ObjectMap describe() {
ObjectMap description = new ObjectMap();
describe(description);
return description;
}
/**
* Fill in description
*
* Override and call super to get complete description.
*
* @param description description object
*/
public void describe(ObjectMap description) {
if (isTopLevel()) {
Facilities.describe(clientId, description.createMap("facilities"));
} else {
try {
Method getAccessorMethod = this.getClass().getDeclaredMethod("getAccessor");
Accessor accessor = (Accessor) getAccessorMethod.invoke(this);
describeAccessors(accessor, description.createMap("accessor"));
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ignored) {
}
}
description.put("services", services.stream().map(GatewayService::describe).collect(Collectors.toList()));
description.put("behaviors", behaviors.stream().map(GatewayBehavior::describe).collect(Collectors.toList()));
if (baseAccessor == null) {
throw new RuntimeException("Base accessor not provided to gateway " + getClass().getTypeName());
}
}
/**
* Generate description for all accessors in given
*
* @param accessorToDescribe accessor to describe
* @param description description being built
*/
public void describeAccessors(Accessor accessorToDescribe, ObjectMap description) {
AccessorDescriber accessorDescriber = new AccessorDescriber();
accessorDescriber.describe(accessorToDescribe, description);
}
@SuppressWarnings("unchecked")
public final T getParent() {
return (T) parent;
}
/**
* Build the behavior call stack (decorator).
*
* todo: We had this setup as a lazy loaded singleton to avoid rebuilding it with every request.
* It was causing collisions. Not sure why. Just building fresh, with every request, for now.
* The collision exposed itself when behaviors "cross-called" other gateway actions.
* Seemed like the first gateway to execute had it's behavior set in stone. Doesn't seem like that should
* have happened. ¯\_(ツ)_/¯
*
* @return head of behavior stack
*/
protected final GatewayBehavior buildStack() {
GatewayBehavior stack = new StartBehavior();
GatewayBehavior previous = stack;
for (GatewayBehavior behavior : behaviors) {
previous.setNextBehavior(behavior);
previous = behavior;
}
return stack;
}
protected final AccessorResponse executeBehaviorStack(Class responseType, GatewayRequestContext request, GatewayBehavior terminatingBehavior) {
return buildStack().execute(responseType, request, terminatingBehavior);
}
public final Gateway root() {
if (parent != null) {
return parent.root();
}
return isTopLevel() ? this : null;
}
/**
* Emit afterAccessorEvent
*
* @param gateway current gateway
* @param callingAccessor the current accessor
* @param requestContext request context
*/
public final void afterAccessor(Gateway gateway, Accessor callingAccessor, RequestContext requestContext) {
if (requestContext.getClientId() == null) {
return;
}
EventBus eventBus = Facilities.getEventBus(requestContext.getClientId());
if (eventBus == null) {
return;
}
eventBus.post(AfterAccessorEvent.builder()
.currentAccessor(callingAccessor)
.gateway(gateway)
.requestContext(requestContext)
.build());
}
/**
* Emit beforeAccessorEvent
*
* @param gateway current gateway
* @param callingAccessor current accessor
* @param requestContext request context
*/
public final void beforeAccessor(Gateway gateway, Accessor callingAccessor, RequestContext requestContext) {
if (requestContext.getClientId() == null) {
return;
}
EventBus eventBus = Facilities.getEventBus(requestContext.getClientId());
if (eventBus == null) {
return;
}
eventBus.post(BeforeAccessorEvent.builder()
.currentAccessor(callingAccessor)
.gateway(gateway)
.requestContext(requestContext)
.build());
}
}