# 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();
                }
             }
          }
       }
    }