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

net.sf.jstuff.integration.serviceregistry.impl.DefaultServiceRegistry Maven / Gradle / Ivy

/*
 * Copyright 2010-2022 by Sebastian Thomschke and contributors.
 * SPDX-License-Identifier: EPL-2.0
 */
package net.sf.jstuff.integration.serviceregistry.impl;

import static net.sf.jstuff.core.validation.NullAnalysisHelper.*;

import java.lang.management.ManagementFactory;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import javax.management.MBeanServer;
import javax.management.ObjectName;

import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;

import net.sf.jstuff.core.collection.WeakHashSet;
import net.sf.jstuff.core.logging.Logger;
import net.sf.jstuff.core.reflection.Proxies;
import net.sf.jstuff.core.validation.Args;
import net.sf.jstuff.integration.serviceregistry.ServiceEndpoint;
import net.sf.jstuff.integration.serviceregistry.ServiceProxy;
import net.sf.jstuff.integration.serviceregistry.ServiceRegistry;
import net.sf.jstuff.integration.serviceregistry.ServiceUnavailableException;

/**
 * @author Sebastian Thomschke
 */
public class DefaultServiceRegistry implements ServiceRegistry, DefaultServiceRegistryMBean {
   public final class ServiceEndpointState {
      private final String serviceEndpointId;

      @Nullable
      Object activeService;
      @Nullable
      Class activeServiceInterface;

      /**
       * All service proxy instances handed out to service consumers for the given end point and still referenced somewhere in the JVM
       */
      private final WeakHashSet> issuedServiceProxiesWeak = WeakHashSet.create();
      private final HashSet> issuedServiceProxiesStrong = new HashSet<>();

      private ServiceEndpointState(final String serviceEndpointId) {
         Args.notNull("serviceEndpointId", serviceEndpointId);

         this.serviceEndpointId = serviceEndpointId;
      }

      private void checkStrongProxies() {
         for (final var it = issuedServiceProxiesStrong.iterator(); it.hasNext();) {
            final var serviceProxy = it.next();
            if (serviceProxy.getListenerCount() == 0) {
               it.remove();
               issuedServiceProxiesWeak.add(serviceProxy);
            }
         }
      }

      private <@NonNull T> ServiceProxyInternal findOrCreateServiceProxy(final Class serviceInterface) {
         var proxy = findServiceProxy(serviceInterface);
         if (proxy == null) {
            proxy = createServiceProxy(this, serviceInterface);
            issuedServiceProxiesWeak.add(proxy);
         }
         return proxy;
      }

      @SuppressWarnings("unchecked")
      private @Nullable  ServiceProxyInternal findServiceProxy(final Class serviceInterface) {
         for (final var serviceProxy : issuedServiceProxiesStrong) {
            if (serviceProxy.getServiceInterface() == serviceInterface) //
               return (ServiceProxyInternal) serviceProxy;
         }

         for (final var serviceProxy : issuedServiceProxiesWeak) {
            if (serviceProxy.getServiceInterface() == serviceInterface) //
               return (ServiceProxyInternal) serviceProxy;
         }
         return null;
      }

      @SuppressWarnings("unchecked")
      public @Nullable  T getActiveServiceIfCompatible(final Class serviceInterface) {
         if (activeService != null && serviceInterface.isAssignableFrom(activeService.getClass())) //
            return (T) activeService;
         return null;
      }

      public @Nullable Class getActiveServiceInterface() {
         return activeServiceInterface;
      }

      public String getServiceEndpointId() {
         return serviceEndpointId;
      }

      public void onListenerAdded(final ServiceProxyInternal proxy) {
         serviceEndpoints_WRITE.lock();
         try {
            issuedServiceProxiesWeak.remove(proxy);
            issuedServiceProxiesStrong.add(proxy);
         } finally {
            serviceEndpoints_WRITE.unlock();
         }
      }

      private void removeActiveService() {
         LOG.info("Removing service:\n  serviceEndpointId : %s\n  serviceInterface  : %s [%s]\n  serviceInstance   : %s [%s]",
            serviceEndpointId, //
            asNonNullUnsafe(activeServiceInterface).getName(), toString(asNonNullUnsafe(activeServiceInterface).getClassLoader()), //
            toString(activeService), toString(asNonNullUnsafe(activeService).getClass().getClassLoader()));
         activeService = null;
         activeServiceInterface = null;

         for (final ServiceProxyInternal proxy : issuedServiceProxiesStrong) {
            proxy.onServiceUnavailable();
         }
         for (final ServiceProxyInternal proxy : issuedServiceProxiesWeak) {
            proxy.onServiceUnavailable();
         }
      }

      private <@NonNull SERVICE_INTERFACE> void setActiveService(final Class serviceInterface,
         final SERVICE_INTERFACE service) {
         LOG.info("Registering service:\n  serviceEndpointId : %s\n  serviceInterface  : %s [%s]\n  serviceInstance   : %s [%s]",
            serviceEndpointId, //
            serviceInterface.getName(), toString(serviceInterface.getClassLoader()), //
            toString(service), toString(service.getClass().getClassLoader()));
         activeServiceInterface = serviceInterface;
         activeService = service;
         findOrCreateServiceProxy(serviceInterface);
         for (final ServiceProxyInternal proxy : issuedServiceProxiesStrong) {
            proxy.onServiceAvailable();
         }
         for (final ServiceProxyInternal proxy : issuedServiceProxiesWeak) {
            proxy.onServiceAvailable();
         }
      }

      private String toString(final @Nullable Object obj) {
         if (obj == null)
            return "null";
         return obj.getClass().getName() + "@" + System.identityHashCode(obj);
      }
   }

   private static final Logger LOG = Logger.create();

   private final Map serviceEndpoints = new HashMap<>();
   private final Lock serviceEndpoints_READ;

   private final Lock serviceEndpoints_WRITE;

   public DefaultServiceRegistry() {
      LOG.infoNew(this);

      final var lock = new ReentrantReadWriteLock();
      serviceEndpoints_READ = lock.readLock();
      serviceEndpoints_WRITE = lock.writeLock();
   }

   private void _cleanup() {
      LOG.debug("Cleaning up service endpoints...");
      for (final Iterator> it = serviceEndpoints.entrySet().iterator(); it.hasNext();) {
         final Entry entry = it.next();
         final ServiceEndpointState cfg = entry.getValue();
         if (cfg.activeService == null) {
            cfg.checkStrongProxies();
            if (cfg.issuedServiceProxiesWeak.isEmpty() && cfg.issuedServiceProxiesStrong.isEmpty()) {
               LOG.debug("Purging endpoint config for [%s]", cfg.serviceEndpointId);
               it.remove();
            }
         }
      }
   }

   @Override
   public <@NonNull SERVICE_INTERFACE> boolean addService(final Class serviceInterface,
      final SERVICE_INTERFACE serviceInstance) throws IllegalArgumentException, IllegalStateException {
      Args.notNull("serviceInterface", serviceInterface);
      Args.notNull("serviceInstance", serviceInstance);

      return addService(serviceInterface.getName(), serviceInterface, serviceInstance);
   }

   @Override
   public <@NonNull SERVICE_INTERFACE> boolean addService(final String serviceEndpointId, final Class serviceInterface,
      final SERVICE_INTERFACE serviceInstance) throws IllegalArgumentException, IllegalStateException {
      Args.notNull("serviceEndpointId", serviceEndpointId);
      Args.notNull("serviceInterface", serviceInterface);
      Args.notNull("serviceInstance", serviceInstance);

      if (!serviceInterface.isInterface())
         throw new IllegalArgumentException("[serviceInterface] must be an interface");
      serviceEndpoints_WRITE.lock();
      try {
         ServiceEndpointState srvConfig = serviceEndpoints.get(serviceEndpointId);
         if (srvConfig == null) {
            srvConfig = new ServiceEndpointState(serviceEndpointId);
            serviceEndpoints.put(serviceEndpointId, srvConfig);
         }

         if (srvConfig.activeService == null) {
            srvConfig.setActiveService(serviceInterface, serviceInstance);

            _cleanup();

            return true;
         }

         if (srvConfig.activeService == serviceInstance)
            return false;

         throw new IllegalStateException("Cannot register service [" + serviceInstance + "] at endpoint [" + serviceEndpointId
            + "] because service [" + srvConfig.activeService + "] is already registered.");
      } finally {
         serviceEndpoints_WRITE.unlock();
      }
   }

   /**
    * This method is intended for sub-classing
    */
   protected <@NonNull SERVICE_INTERFACE> ServiceProxyInternal createServiceProxy(
      final ServiceEndpointState serviceEndpointState, final Class serviceInterface) {
      final var advice = new DefaultServiceProxyAdvice<>(serviceEndpointState, serviceInterface);
      final ServiceProxyInternal serviceProxy = Proxies.create((proxy, method, args) -> {
         if (method.getDeclaringClass() == ServiceProxy.class || method.getDeclaringClass() == ServiceProxyInternal.class)
            return method.invoke(advice, args);

         final String methodName = method.getName();
         final int methodParamCount = method.getParameterTypes().length;
         if (methodParamCount == 0) {
            if ("hashCode".equals(methodName))
               return advice.hashCode();
            if ("toString".equals(methodName))
               return advice.toString();
         } else if (methodParamCount == 1) {
            if ("equals".equals(methodName))
               return proxy == asNonNullUnsafe(args)[0];
         }

         final Object service = serviceEndpointState.getActiveServiceIfCompatible(serviceInterface);
         if (service == null)
            throw new ServiceUnavailableException(advice.serviceEndpointId, serviceInterface);
         try {
            return method.invoke(service, args);
         } catch (final InvocationTargetException ex) {
            throw ex.getTargetException();
         }
      }, ServiceProxyInternal.class, serviceInterface);
      advice.setProxy(serviceProxy);
      return serviceProxy;
   }

   @Override
   public List getActiveServiceEndpoints() {
      serviceEndpoints_READ.lock();
      try {
         final var result = new ArrayList();
         for (final ServiceEndpointState sep : serviceEndpoints.values()) {
            if (sep.activeServiceInterface != null) {
               result.add(new DefaultServiceEndpoint(sep.serviceEndpointId, sep.activeServiceInterface));
            }
         }
         return result;
      } finally {
         serviceEndpoints_READ.unlock();
      }
   }

   @Override
   public <@NonNull SERVICE_INTERFACE> ServiceProxy getService(final String serviceEndpointId,
      final Class serviceInterface) {
      Args.notNull("serviceEndpointId", serviceEndpointId);
      Args.notNull("serviceInterface", serviceInterface);

      serviceEndpoints_READ.lock();
      try {
         final ServiceEndpointState srvConfig = serviceEndpoints.get(serviceEndpointId);
         if (srvConfig != null) {
            final ServiceProxy proxy = srvConfig.findServiceProxy(serviceInterface);
            if (proxy != null)
               return proxy;
         }
      } finally {
         serviceEndpoints_READ.unlock();
      }

      serviceEndpoints_WRITE.lock();
      try {
         ServiceEndpointState srvConfig = serviceEndpoints.get(serviceEndpointId);
         if (srvConfig == null) {
            srvConfig = new ServiceEndpointState(serviceEndpointId);
            serviceEndpoints.put(serviceEndpointId, srvConfig);
         }
         final ServiceProxy proxy = srvConfig.findOrCreateServiceProxy(serviceInterface);
         return proxy;
      } finally {
         serviceEndpoints_WRITE.unlock();
      }
   }

   @Override
   public Set getServiceEndpointIds() {
      serviceEndpoints_READ.lock();
      try {
         final var result = new TreeSet();
         for (final ServiceEndpointState sep : serviceEndpoints.values()) {
            if (sep.activeService != null) {
               result.add(sep.serviceEndpointId);
            }
         }
         return result;
      } finally {
         serviceEndpoints_READ.unlock();
      }
   }

   /**
    * for testing garbage collection
    */
   protected int getServiceEndpointsCount() {
      return serviceEndpoints.size();
   }

   /**
    * @param mbeanServer if null, the platform mbeanServer is used
    * @param mbeanName if null, an mbean name based on the package and class name of the registry is used
    */
   public void registerAsMBean(@Nullable MBeanServer mbeanServer, @Nullable String mbeanName) {
      try {
         if (mbeanName == null) {
            mbeanName = asNonNullUnsafe(getClass().getPackage()).getName() + ":type=" + getClass().getSimpleName();
         }
         final var mbeanObjectName = new ObjectName(mbeanName);
         LOG.info("Registering MBean %s", mbeanName);
         if (mbeanServer == null) {
            mbeanServer = ManagementFactory.getPlatformMBeanServer();
         }
         mbeanServer.registerMBean(this, mbeanObjectName);
      } catch (final Exception ex) {
         LOG.error(ex);
      }
   }

   @Override
   public boolean removeService(final String serviceEndpointId, final Object serviceInstance) throws IllegalArgumentException {
      Args.notNull("serviceEndpointId", serviceEndpointId);
      Args.notNull("serviceInstance", serviceInstance);

      serviceEndpoints_WRITE.lock();
      try {
         final ServiceEndpointState srvConfig = serviceEndpoints.get(serviceEndpointId);
         if (srvConfig == null || srvConfig.activeService != serviceInstance)
            return false;

         srvConfig.removeActiveService();

         _cleanup();

         return true;
      } finally {
         serviceEndpoints_WRITE.unlock();
      }
   }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy