ExportTracker.java

/*
** Module   : ExportTracker.java
** Abstract : Tracks the active sessions which have invoked exported APIs. 
**
** Copyright (c) 2011-2024, Golden Code Development Corporation.
**
** -#- -I- --Date-- -------------------------------- Description --------------------------------
** 001 CA  20111130 Initial version.  Currently is used to track only static exported APIs.
** 002 OM  20131022 Updated to conform the new SessionListener interface.
** 003 GBB 20240404 Method extractJarName moved to JarUtil to be reused. 
*/
/*
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU Affero General Public License as
** published by the Free Software Foundation, either version 3 of the
** License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU Affero General Public License for more details.
**
** You may find a copy of the GNU Affero GPL version 3 at the following
** location: https://www.gnu.org/licenses/agpl-3.0.en.html
** 
** Additional terms under GNU Affero GPL version 3 section 7:
** 
**   Under Section 7 of the GNU Affero GPL version 3, the following additional
**   terms apply to the works covered under the License.  These additional terms
**   are non-permissive additional terms allowed under Section 7 of the GNU
**   Affero GPL version 3 and may not be removed by you.
** 
**   0. Attribution Requirement.
** 
**     You must preserve all legal notices or author attributions in the covered
**     work or Appropriate Legal Notices displayed by works containing the covered
**     work.  You may not remove from the covered work any author or developer
**     credit already included within the covered work.
** 
**   1. No License To Use Trademarks.
** 
**     This license does not grant any license or rights to use the trademarks
**     Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
**     of Golden Code Development Corporation. You are not authorized to use the
**     name Golden Code, FWD, or the names of any author or contributor, for
**     publicity purposes without written authorization.
** 
**   2. No Misrepresentation of Affiliation.
** 
**     You may not represent yourself as Golden Code Development Corporation or FWD.
** 
**     You may not represent yourself for publicity purposes as associated with
**     Golden Code Development Corporation, FWD, or any author or contributor to
**     the covered work, without written authorization.
** 
**   3. No Misrepresentation of Source or Origin.
** 
**     You may not represent the covered work as solely your work.  All modified
**     versions of the covered work must be marked in a reasonable way to make it
**     clear that the modified work is not originating from Golden Code Development
**     Corporation or FWD.  All modified versions must contain the notices of
**     attribution required in this license.
*/

package com.goldencode.p2j.net;

import com.goldencode.p2j.classloader.*;
import com.goldencode.p2j.util.*;
import com.goldencode.p2j.util.logging.*;

import java.lang.reflect.*;
import java.net.*;
import java.util.*;

/**
 * A session tracker for each static exported API.  The exported APIs are
 * tracked by:
 * <ul>
 *    <li>the defining jar</li>
 *    <li>the interface which exports the API</li>
 *    <li>the interface method for this API</li>
 * </ul>
 */
class ExportTracker
implements SessionListener
{
   /** Logger */
   private static final CentralLogger LOG = CentralLogger.get(ExportTracker.class);
   
   /** 
    * The tracker instance, we need only one; needed to notify the tracker
    * when a session is closed, to remove it. 
    */
   private static final ExportTracker tracker = new ExportTracker();
   
   /** The tracked data. */
   private static final HashMap<URL, Map<Class<?>, Map<Method, Set<Session>>>> 
      data = new HashMap<URL, Map<Class<?>, Map<Method, Set<Session>>>>();
   
   /**
    * C'tor, forces a singleton.
    */
   private ExportTracker()
   {
      // no-op
   }
   
   /**
    * List the active sessions which have invoked APIs in the given jar. If
    * the jar name is <code>null</code>, list all active tracked sessions.
    * 
    * @param   jar
    *          The jar name; if <code>null</code>, list all active tracked
    *          sessions.
    * @param   iface
    *          The interface for which we need the sessions; 
    *          if <code>null</code>, search through the entire jar.
    *          
    * @return  See above.
    */
   static Set<Session> listSessions(String jar, String iface)
   {
      Set<Session> sessions = new HashSet<Session>();

      synchronized (data)
      {
         Iterator<URL> iter = data.keySet().iterator();
         while (iter.hasNext())
         {
            URL url = iter.next();
            String jarName = JarUtil.extractJarName(LOG, url);
            if (jar == null || jar.equals(jarName))
            {
               Map<Class<?>, Map<Method, Set<Session>>> ifcs = data.get(url);
               for (Class<?> ifc : ifcs.keySet())
               {
                  if (iface == null || ifc.getName().equals(iface))
                  {
                     Map<Method, Set<Session>> methods = ifcs.get(ifc); 
                     for (Set<Session> s : methods.values())
                     {
                        sessions.addAll(s);
                     }
                  }
               }
            }
         }
      }
      
      return sessions;
   }

   /**
    * Get the singleton tracker instance.
    * 
    * @return   See above.
    */
   static ExportTracker getTracker()
   {
      return tracker;
   }
   
   /**
    * Check if there are active sessions which have invoked the given method.
    * 
    * @param   iface
    *          The interface used to register the method as an API. 
    * @param   method
    *          The exported method.
    *          
    * @return  See above.
    */
   static boolean hasActiveSessions(Class<?> iface, Method method)
   {
      synchronized (data)
      {
         Iterator<Map<Class<?>, Map<Method, Set<Session>>>> iter = 
            data.values().iterator();
         while (iter.hasNext())
         {
            Map<Class<?>, Map<Method, Set<Session>>> ifcs = iter.next();
            
            Map<Method, Set<Session>> methods = ifcs.get(iface);
            if (methods != null)
            {
               Set<Session> s = methods.get(method);
               if (s != null && !s.isEmpty())
               {
                  return true;
               }
            }
         }
      }

      return false;
   }
   
   /**
    * Check if there are active sessions which have invoked an exported method 
    * in the given class.
    * 
    * @param   iface
    *          The interface used to register the method as an API. 
    *          
    * @return  See above.
    */
   static boolean hasActiveSessions(Class<?> iface)
   {
      synchronized (data)
      {
         Iterator<Map<Class<?>, Map<Method, Set<Session>>>> iter = 
            data.values().iterator();
         while (iter.hasNext())
         {
            Map<Class<?>, Map<Method, Set<Session>>> ifcs = iter.next();
            for (Class<?> ifc : ifcs.keySet())
            {
               if (ifc.equals(iface))
               {
                  Map<Method, Set<Session>> methods = ifcs.get(ifc);
                  if (!methods.isEmpty())
                  {
                     return true;
                  }
               }
            }
         }
      }

      return false;
   }
   
   /**
    * List the active sessions which have invoked exported methods in the 
    * given class.
    * 
    * @param   iface
    *          The interface used to register the method as an API. 
    *          
    * @return  See above.
    */
   static Set<Session> listSessions(Class<?> iface)
   {
      Set<Session> sessions = new HashSet<Session>();
      synchronized (data)
      {
         Iterator<Map<Class<?>, Map<Method, Set<Session>>>> iter = 
            data.values().iterator();
         while (iter.hasNext())
         {
            Map<Class<?>, Map<Method, Set<Session>>> ifcs = iter.next();
            for (Class<?> ifc : ifcs.keySet())
            {
               if (!ifc.equals(iface))
                  continue;
               
               Map<Method, Set<Session>> methods = ifcs.get(ifc);
               for (Method m : methods.keySet())
               {
                  Set<Session> s = methods.get(m);
                  sessions.addAll(s);
               }
            }
         }
      }
      
      return sessions;
   }

   /**
    * List the active sessions which have invoked the given exported method.
    * 
    * @param   iface
    *          The interface used to register the method as an API. 
    * @param   method
    *          The exported method.
    *          
    * @return  See above.
    */
   static Set<Session> listSessions(Class<?> iface, Method method)
   {
      Set<Session> sessions = new HashSet<Session>();
      synchronized (data)
      {
         Iterator<Map<Class<?>, Map<Method, Set<Session>>>> iter = 
            data.values().iterator();
         while (iter.hasNext())
         {
            Map<Class<?>, Map<Method, Set<Session>>> ifcs = iter.next();

            Map<Method, Set<Session>> methods = ifcs.get(iface);
            Set<Session> s = methods.get(method);

            if (s != null)
            {
               sessions.addAll(s);
            }
         }
      }
      
      return sessions;
   }
   
   /**
    * Track the request to the given method.
    * 
    * @param   iface
    *          The interface used to register the method as an API. 
    * @param   method
    *          The invoked exported method.
    * @param   session
    *          The sessions which is invoking the exported method.
    */
   static void trackRequest(Class<?> iface, Method method, Session session)
   {
      ClassLoader classLoader = iface.getClassLoader();
      URL jar;
      if (classLoader instanceof JarClassLoader)
      {
         try
         {
            jar = new URL("file", "",
                          ((JarClassLoader) classLoader).getJarName());
         }
         catch (MalformedURLException e)
         {
            throw new IllegalStateException(e);
         }
      }
      else
      {
         jar = iface.getProtectionDomain().getCodeSource().getLocation();
      }

      synchronized (data)
      {
         Map<Class<?>, Map<Method, Set<Session>>> requests = data.get(jar);
         if (requests == null)
         {
            requests = new HashMap<Class<?>, Map<Method, Set<Session>>>();
            data.put(jar, requests);
         }
         
         Map<Method, Set<Session>> methods = requests.get(iface);
         if (methods == null)
         {
            methods = new HashMap<Method, Set<Session>>();
            requests.put(iface, methods);
         }
         
         Set<Session> s = methods.get(method);
         if (s == null)
         {
            methods.put(method, s = new HashSet<Session>());
         }

         s.add(session);
      }
   }

   /**
    * This method is called when the session is starting.
    *
    * @param    session
    *           The session that is starting.
    */
   @Override
   public void initialize(Session session)
   {
      // no-ops
   }

   /**
    * This method is called when the session is ending. It removes all tracked
    * data for this session.
    *
    * @param    session
    *           The session that is ending.
    */
   @Override
   public void terminate(Session session)
   {
      // remove session from all API's
      synchronized (data)
      {
         Iterator<Map<Class<?>, Map<Method, Set<Session>>>> iterJ = 
            data.values().iterator();
         while (iterJ.hasNext())
         {
            Map<Class<?>, Map<Method, Set<Session>>> ifcs = iterJ.next();

            Iterator<Map<Method, Set<Session>>> iterI = 
               ifcs.values().iterator();
            while (iterI.hasNext())
            {
               Map<Method, Set<Session>> methods = iterI.next();
               
               Iterator<Set<Session>> iterM = methods.values().iterator();
               while (iterM.hasNext())
               {
                  Set<Session> s = iterM.next();
                  s.remove(session);

                  if (s.isEmpty())
                  {
                     iterM.remove();
                  }
               }
               
               if (methods.isEmpty())
               {
                  iterI.remove();
               }
            }
            
            if (ifcs.isEmpty())
            {
               iterJ.remove();
            }
         }
      }
   }
}