package com.github.aesteve.vertx.nubes.services; import com.github.aesteve.vertx.nubes.Config; import com.github.aesteve.vertx.nubes.annotations.services.Consumer; import com.github.aesteve.vertx.nubes.annotations.services.PeriodicTask; import com.github.aesteve.vertx.nubes.annotations.services.Proxify; import com.github.aesteve.vertx.nubes.annotations.services.ServiceProxy; import com.github.aesteve.vertx.nubes.utils.async.MultipleFutures; import io.vertx.codegen.annotations.ProxyGen; import io.vertx.core.Future; import io.vertx.core.Vertx; import io.vertx.core.VertxException; import io.vertx.core.eventbus.Message; import io.vertx.core.logging.Logger; import io.vertx.core.logging.LoggerFactory; import io.vertx.serviceproxy.ProxyHelper; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.*; public class ServiceRegistry { private static final Logger LOG = LoggerFactory.getLogger(ServiceRegistry.class); private final Map<String, Object> services; private final Map<String, Object> serviceProxies; private final Set<Long> timerIds; private final Config config; private final Vertx vertx; public ServiceRegistry(Vertx vertx, Config config) { this.vertx = vertx; this.config = config; services = new HashMap<>(); serviceProxies = new HashMap<>(); timerIds = new HashSet<>(); } public void registerService(String name, Object service) { services.put(name, service); } public Object get(String name) { return services.get(name); } public Object get(Field field) { com.github.aesteve.vertx.nubes.annotations.services.Service annot = field.getAnnotation(com.github.aesteve.vertx.nubes.annotations.services.Service.class); if (annot != null) { return get(annot.value()); } ServiceProxy proxyAnnot = field.getAnnotation(ServiceProxy.class); if (proxyAnnot == null) { return null; } Class<?> serviceInterface = getInterface(field.getType()); if (serviceInterface == null) { LOG.error("Could not inject service for : " + field.getName() + " could not find the matching proxified service using @ProxyGen"); return null; } String address = proxyAnnot.value(); if (serviceProxies.get(address) != null) { return serviceProxies.get(address); } else { Object service = createEbProxyClass(serviceInterface, address); serviceProxies.put(address, service); return service; } } public Collection<Object> services() { return services.values(); } public boolean isEmpty() { return services.isEmpty(); } public void startAll(Future<Void> future) { if (isEmpty()) { future.complete(); return; } MultipleFutures<Void> futures = new MultipleFutures<>(future); futures.addAll(services(), obj -> { try { introspectService(obj); } catch (Exception e) { return res -> res.fail(e); } if (obj instanceof Service) { Service service = (Service) obj; service.init(vertx, config.json()); return service::start; } return null; }); futures.start(); } public void stopAll(Future<Void> future) { if (isEmpty()) { future.complete(); return; } timerIds.forEach(vertx::cancelTimer); MultipleFutures<Void> futures = new MultipleFutures<>(future); futures.addAll(services(), obj -> { if (obj instanceof Service) { Service service = (Service) obj; return service::stop; } return null; }); futures.start(); } private void introspectService(Object service) { Class<?> serviceClass = service.getClass(); Proxify annot = serviceClass.getAnnotation(Proxify.class); if (annot != null) { createServiceProxy(annot.value(), service); } for (Method method : service.getClass().getMethods()) { PeriodicTask periodicTask = method.getAnnotation(PeriodicTask.class); if (periodicTask != null) { createPeriodicTask(service, periodicTask, method); } Consumer consumes = method.getAnnotation(Consumer.class); if (consumes != null) { createConsumer(service, consumes, method); } } } private void createConsumer(Object service, Consumer consumes, Method method) { Class<?>[] parameterTypes = method.getParameterTypes(); if (parameterTypes.length != 1 || !parameterTypes[0].equals(Message.class)) { String msg = "Cannot register consumer on method : " + getFullName(service, method); msg += " .Method should only declare one parameter of io.vertx.core.eventbus.Message type."; throw new VertxException(msg); } vertx.eventBus().consumer(consumes.value(), message -> { try { method.invoke(service, message); } catch (Exception e) { LOG.error("Exception happened during message handling on method : " + getFullName(service, method), e); } }); } private void createPeriodicTask(Object service, PeriodicTask annotation, Method method) { if (method.getParameterTypes().length > 0) { throw new VertxException("Periodic tasks should not have parameters"); } vertx.setPeriodic(annotation.value(), timerId -> { timerIds.add(timerId); try { method.invoke(service); } catch (Exception e) { LOG.error("Error while running periodic task", e); } }); } private static String getFullName(Object service, Method method) { return service.getClass().getName() + "." + method.getName(); } private <T> void createServiceProxy(String address, T service) { Class<T> serviceClass = getInterface(service.getClass()); if (serviceClass == null) { LOG.error("Could not find a @ProxyGen super interface for class : " + service.getClass().getName() + " cannot proxy it ver the eventBus"); return; } ProxyHelper.registerService(serviceClass, vertx, service, address); } @SuppressWarnings("unchecked") public static <T> Class<T> getInterface(Class<?> serviceClass) { if (serviceClass.isAnnotationPresent(ProxyGen.class)) { return (Class<T>) serviceClass; } Class<?>[] interfaces = serviceClass.getInterfaces(); for (Class<?> someInterface : interfaces) { if (someInterface.isAnnotationPresent(ProxyGen.class)) { // it must be it return (Class<T>) someInterface; } } return null; } public Object createEbProxyClass(Class<?> serviceInterface, String address) { String name = serviceInterface.getName() + "VertxEBProxy"; try { return Class.forName(name).getConstructor(Vertx.class, String.class).newInstance(vertx, address); } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException | ClassNotFoundException e) { throw new VertxException("Could not create your service proxy for class : " + serviceInterface, e); } } }