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