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

org.tentackle.dbms.rmi.RemoteDbSessionImpl Maven / Gradle / Ivy

There is a newer version: 21.16.1.0
Show newest version
/*
 * Tentackle - https://tentackle.org
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package org.tentackle.dbms.rmi;

import org.tentackle.dbms.Db;
import org.tentackle.dbms.DbUtilities;
import org.tentackle.io.ServerSocketConfigurator;
import org.tentackle.io.ServerSocketConfiguratorHolder;
import org.tentackle.io.SocketConfigurator;
import org.tentackle.io.SocketConfiguratorHolder;
import org.tentackle.log.Logger;
import org.tentackle.log.Logger.Level;
import org.tentackle.log.LoggerFactory;
import org.tentackle.log.MethodStatistics;
import org.tentackle.misc.DiagnosticUtilities;
import org.tentackle.misc.TimeKeeper;
import org.tentackle.session.LoginFailedException;
import org.tentackle.session.MasterSerialEvent;
import org.tentackle.session.MultiUserSessionPool;
import org.tentackle.session.PersistenceException;
import org.tentackle.session.Session;
import org.tentackle.session.SessionFactory;
import org.tentackle.session.SessionInfo;
import org.tentackle.session.SessionPool;
import org.tentackle.session.SessionPoolProvider;

import java.lang.ref.Cleaner;
import java.lang.ref.Cleaner.Cleanable;
import java.lang.ref.WeakReference;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.server.RMIClientSocketFactory;
import java.rmi.server.RMIServerSocketFactory;
import java.rmi.server.ServerNotActiveException;
import java.rmi.server.UnicastRemoteObject;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;


/**
 * User session within the application server.
 *
 * @author  harald
 */
public abstract class RemoteDbSessionImpl implements RemoteDbSession, Exportable {

  private static final Logger LOGGER = Logger.get(RemoteDbSessionImpl.class);


  /**
   * We keep an internal set of all sessions via WeakReferences, so the
   * sessions still will be finalized when the session isn't used anymore.
   * The set of sessions is used to determine stale sessions, i.e. ones
   * with timed out sessions. These are typically caused by clients
   * not properly closing the rmi-session.
   */
  private static final Set> SESSIONS = ConcurrentHashMap.newKeySet();


  /**
   * The session cleanup thread.
   */
  private static RemoteDbSessionCleanupThread cleanupThread;


  /**
   * Starts the optional cleanup thread that will
   * monitor the sessions for database activity.
   *
   * @param checkInterval is the interval in ms
   */
  public static void startCleanupThread(long checkInterval) {
    cleanupThread = new RemoteDbSessionCleanupThread(checkInterval, SESSIONS, 3);
    cleanupThread.start();
  }


  /**
   * Stops the cleanup thread.
   */
  public static synchronized void stopCleanupThread() {
    if (cleanupThread != null && cleanupThread.isAlive()) {
      cleanupThread.requestTermination();
      cleanupThread = null;
    }
  }


  // each session gets a unique number (only for identification in log files)
  private static final AtomicLong LAST_SESSION_NUMBER = new AtomicLong();


  /**
   * Returns a list of all open sessions.
   *
   * @return the sessions, never null
   */
  public static Collection getOpenSessions() {
    Collection openSessions = new ArrayList<>();
    for (WeakReference ref : SESSIONS) {
      RemoteDbSessionImpl session = ref.get();
      if (session != null && session.isOpen()) {
        openSessions.add(session);
      }
    }
    return openSessions;
  }


  /**
   * Checks if the user is already logged in.
   *
   * @param sessionInfo the user's session info
   * @return the session info if already logged in, null if not logged in
   */
  public static SessionInfo isUserLoggedIn(SessionInfo sessionInfo) {
    for (RemoteDbSessionImpl session: getOpenSessions()) {
      SessionInfo sessionSessionInfo = session.getClientSessionInfo();
      if (sessionInfo != sessionSessionInfo && sessionSessionInfo.equals(sessionInfo)) {
        return sessionSessionInfo;
      }
    }
    return null;
  }


  /**
   * relation between a class and their delegates.
   */
  private static class DelegateClasses {

    private final Class clazz;                                   // client side class
    private final Class effectiveClass;                          // the effective class, null if same as clazz
    private final Class delegateClass;              // its delegate interface
    private final Class> delegateImplClass;   // delegate implementation class

    private DelegateClasses(Class effectiveClass, Class clazz,
                            Class delegateClass, Class> delegateImplClass) {
      this.effectiveClass = effectiveClass == clazz ? null : effectiveClass;
      this.clazz = clazz;
      this.delegateClass = delegateClass;
      this.delegateImplClass = delegateImplClass;
    }

    private DelegateClasses(Class effectiveClass, Class clazz, RemoteDelegateLocator.Result delegates) {
      this(clazz,
           effectiveClass == null ? delegates.effectiveClass() : effectiveClass,
           delegates.remoteDelegate(), delegates.remoteDelegateImpl());
    }

    @Override
    public String toString() {
      return clazz.getName() + " -> " + delegateClass.getName() + " / " + delegateImplClass.getName();
    }
  }

  // maps classnames to delegate classes
  private static final ConcurrentHashMap DELEGATE_CLASSES_MAP = new ConcurrentHashMap<>();


  private static final Cleaner CLEANER = Cleaner.create();        // instead of deprecated finalize()

  /**
   * Holds the resources and state to be cleaned up.
   */
  private class Resources implements Runnable {

    private RemoteDbSessionImpl me;                   // != null if programmatic close
    private String name;                              // the session name
    private Db db;                                    // the local session
    private String mdcInfo;                           // the cached MDC info
    private final RemoteDbConnectionImpl con;         // the connection object
    private final Collection> exportedDelegates;  // delegates to un-export on close
    private final MethodStatistics methodStats;       // statistics for method invocations
                                                      // (managed by invocation handler, null if no stats)

    Resources(Db db, RemoteDbConnectionImpl con) {
      this.db = db;
      this.con = con;
      exportedDelegates = new ArrayList<>();
      methodStats = new MethodStatistics();
    }

    @Override
    public void run() {
      try {
        if (db != null) {
          if (me == null) {
            LOGGER.warning("cleaning up unreferenced remote session {0}", name);
          }
          cleanup(false);
          closeDb(false);
        }
        for (WeakReference ref : exportedDelegates) {
          RemoteDelegate delegate = ref.get();
          if (delegate != null) {
            con.unexportRemoteObject(delegate);
          }
        }
        exportedDelegates.clear();
        if (me != null) {
          unExportMe(me);
        }
      }
      catch (RemoteException | RuntimeException ex) {
        LOGGER.severe("closing session failed", ex);
      }
      finally {
        db = null;
        me = null;
      }
    }

    void cleanup(boolean crashed) {
      if (crashed) {
        LOGGER.warning("cleaning up crashed {0}{1}", name, DiagnosticUtilities.getInstance().createStackDump());
      }
      if (db != null) {
        db.setCrashed(crashed);
        if (!db.isRemote()) {
          db.rollbackImmediately(null);   // roll back if anything pending
        }
      }
      doLogStatistics(Level.INFO, true);
    }

    void closeDb(boolean cleanup) {
      if (db != null) {
        if (db.isPooled()) {
          if (!db.isRemote()) {
            db.rollbackImmediately(null); // roll back if anything pending
          }

          if (cleanup) {
            db.close(); // closed db will be removed from the pool in putDb
            LOGGER.warning("pooled {0} closed due to session cleanup", db);
          }
          else {
            // only return the db to the pool, don't close it
            LOGGER.info("returning {0} to pool", db);
          }

          if (db.isRemote()) {
            // remote sessions are closed and will be removed from the pool.
            // this closes the remote session in the remote server as well
            db.close();
          }
          else if (db.isOpen()) {
            // reset the session info for this Db for sure (the application may have changed it to clientInfo)
            db.setSessionInfo(serverInfo);
            db.setExportedSessionGroupId(0);      // remove from session group
          }

          db.getPool().putSession(db);    // this will work for MultiUserDbPool as well!
        }
        else {
          db.close();
        }
        db = null;    // closed -> to GC
      }
    }

    void unExportMe(RemoteDbSessionImpl remoteSession) throws RemoteException {
      LOGGER.info("end {0}", remoteSession);
      con.unexportRemoteObject(remoteSession);
    }

    void doLogStatistics(Level level, boolean clear) {
      methodStats.logStatistics(mdcInfo, level, "    >RMI-Stats: ", clear);
    }
  }


  private final Resources resources;                          // resources to be cleaned up
  private final Cleanable cleanable;                          // to clean up the connection
  private final SessionInfo clientInfo;                       // saved client info
  private final SessionInfo serverInfo;                       // the server info for creating a new session
  private final long sessionNumber;                           // unique session number
  private final String clientHost;                            // the client host string
  private final Map loggers;                  // logger-name :: logger
  private int timeout;                                        // timeout in polling intervals of the RemoteDbSessionCleanupThread
  private int port;                                           // port for all delegates
  private RMIClientSocketFactory csf;                         // client socket factory for all delegates
  private RMIServerSocketFactory ssf;                         // server socket factory for all delegates
  private int timeoutCount;                                   // consecutive timeouts
  private long closedSince;                                   // session closed since, 0 if still open
  private final AtomicBoolean modificationTrackerSession;     // true if this the remote ModificationTracker's session
  private final Queue masterSerialEvents;  // master serial event queue


  /**
   * Creates a session on a given connection.
   *
   * @param con the connection
   * @param clientInfo the SessionInfo from the client
   * @param serverInfo the SessionInfo to establish the connection to the database server
   *
   * @throws PersistenceException if the session could not be initiated.
   */
  public RemoteDbSessionImpl(RemoteDbConnectionImpl con, SessionInfo clientInfo, SessionInfo serverInfo) {

    // use copies of the session infos to avoid side effects due to subsequent modifications
    this.clientInfo = clientInfo.clone();
    this.clientInfo.setSessionName(clientInfo.getSessionName());  // sessionName was cleared in clone()
    if (!clientInfo.isCloned()) {
      this.clientInfo.clearCloned();    // un-clone, if original session wasn't
    }

    this.serverInfo = serverInfo.clone();
    this.serverInfo.setSessionName(serverInfo.getSessionName());
    if (!serverInfo.isCloned()) {
      this.serverInfo.clearCloned();
    }

    sessionNumber = LAST_SESSION_NUMBER.incrementAndGet();

    Db db = null;
    try {
      clientHost = UnicastRemoteObject.getClientHost();
      db = openDb();
      resources = new Resources(db, con);
      synchronized (SESSIONS) {   // prevents race condition between verifySessionInfo and SESSIONS.add
        this.clientInfo.setSince(System.currentTimeMillis());
        verifySessionInfo(this.clientInfo);
        db.setSessionInfo(this.clientInfo);  // switch to client info for the lifetime of this remote session
        SESSIONS.add(new WeakReference<>(this));
      }
    }
    catch (ServerNotActiveException | RuntimeException ex) {
      if (db != null) {
        try {
          cleanup(false);
          closeDb(false);
        }
        catch (RuntimeException rx) {
          // may be some overridden stuff failed in application
          LOGGER.warning("could not close session after failed login -> ignored", rx);
        }
      }
      if (ex instanceof LoginFailedException) {
        throw (LoginFailedException) ex;
      }
      throw new PersistenceException("could not setup remote session", ex);
    }

    // config for delegates
    port = determinePort(db, clientInfo, serverInfo);
    csf = determineClientSocketFactory(db, clientInfo, serverInfo);
    ssf = determineServerSocketFactory(db, clientInfo, serverInfo);
    timeout = determineTimeout(db, clientInfo, serverInfo);
    loggers = new ConcurrentHashMap<>();

    modificationTrackerSession = new AtomicBoolean();
    masterSerialEvents = new ConcurrentLinkedQueue<>();

    resources.name = toString();    // chicken egg...
    cleanable = CLEANER.register(this, resources);
  }


  @Override
  public void exportMe() throws RemoteException {
    RmiServer rmiServer = resources.con.getRmiServer();
    resources.con.exportRemoteObject(this,
                                     rmiServer.getLoginPort(),
                                     rmiServer.getLoginClientSocketFactory(),
                                     rmiServer.getLoginServerSocketFactory());
    LOGGER.info("begin {0}", this);
    clientInfo.clearPassword();   // no more needed
  }

  @Override
  public void unExportMe() throws RemoteException {
    resources.unExportMe(this);
  }


  /**
   * Exports the given delegate.
   * 

* Notice that the delegate must not extend {@link UnicastRemoteObject}! * * @param delegate the delegate * @throws RemoteException if export failed */ public void exportRemoteDelegate(RemoteDelegate delegate) throws RemoteException { resources.con.exportRemoteObject(delegate, getPort(), getClientSocketFactory(), getServerSocketFactory()); resources.exportedDelegates.add(new WeakReference<>(delegate)); } /** * Un-exports given remote delegate. *

* Notice: when the session is closed, all still exported objects will be un-exported. * This method is only necessary if a delegate must be explicitly un-exported. * * @param delegate the delegate * @throws RemoteException if object not exported */ public void unExportRemoteDelegate(RemoteDelegate delegate) throws RemoteException { resources.con.unexportRemoteObject(delegate); for (Iterator> iter = resources.exportedDelegates.iterator(); iter.hasNext(); ) { WeakReference ref = iter.next(); RemoteDelegate dg = ref.get(); if (dg == null) { iter.remove(); } else if (dg == delegate) { iter.remove(); break; } } } /** * Adds a master serial event. * * @param masterSerialEvent the event */ public void addMasterSerialEvent(MasterSerialEvent masterSerialEvent) { if (isModificationTrackerSession()) { masterSerialEvents.add(masterSerialEvent); } else { throw new PersistenceException("not a modification tracker session"); } } /** * Gets the next master serial event.
* The method is invoked by the {@link org.tentackle.session.ModificationTracker}. * * @return the next master serial event, null if none */ public MasterSerialEvent pollMasterSerialEvent() { return masterSerialEvents.poll(); } /** * Returns whether there is at least one master serial event pending. * * @return true if at least one event hasn't been retrieved by the client yet */ public boolean isMasterSerialEventPending() { return masterSerialEvents.peek() != null; } /** * Verifies and updates the client's session info.
* Needs to be implemented by the application.
* Checks login credentials and sets the user id. *

* Throws LoginFailedException if login is not allowed for whatever reason. * * @param sessionInfo the session info */ public abstract void verifySessionInfo(SessionInfo sessionInfo); /** * Gets the unique session number. * * @return the number of this session */ public long getSessionNumber() { return sessionNumber; } /** * Gets the session timeout. * * @return the timeout in timeout intervals */ public int getTimeout() { return timeout; } /** * Sets the session timeout. * * @param timeout the timeout in timeout intervals */ public void setTimeout(int timeout) { this.timeout = timeout; } /** * Gets the session. * * @return the session */ public Db getSession() { return resources.db; } /** * Gets the server connection. * * @return the connection */ public RemoteDbConnectionImpl getConnection() { return resources.con; } /** * Gets the client session info. * * @return the client session info */ @Override public SessionInfo getClientSessionInfo() { return clientInfo; } /** * Gets the server session info. * * @return the server session info */ public SessionInfo getServerSessionInfo() { return serverInfo; } /** * Gets the epochal time when the session was closed. * * @return the time when closed, 0 if still open */ public long getClosedSince() { return closedSince; } /** * Gets the port for all delegates. * * @return the port for all delegates */ public int getPort() { return port; } /** * Sets Gets the port for all delegates. * * @param port the port */ public void setPort(int port) { this.port = port; } /** * Gets the default client socket factory for all delegates. * * @return the default csf for all delegates */ public RMIClientSocketFactory getClientSocketFactory() { return csf; } /** * Sets the client socket factory for all delegates. * * @param csf the socket factory */ public void setClientSocketFactory(RMIClientSocketFactory csf) { this.csf = csf; } /** * Gets the default server socket factory for all delegates. * * @return the default ssf for all delegates */ public RMIServerSocketFactory getServerSocketFactory() { return ssf; } /** * Sets the server socket factory for all delegates. * * @param ssf the socket factory */ public void setServerSocketFactory(RMIServerSocketFactory ssf) { this.ssf = ssf; } /** * Returns whether this is the remote session of the modification tracker. * * @return true if tracker's session */ public boolean isModificationTrackerSession() { return modificationTrackerSession.get(); } /** * Sets the modification tracker session flag. * * @param modificationTrackerSession true if tracker's session */ public void setModificationTrackerSession(boolean modificationTrackerSession) { this.modificationTrackerSession.set(modificationTrackerSession); } /** * Counts the invocation of a delegate method. * * @param method the method invoked * @param servicedClass the serviced class * @param timeKeeper execution duration */ public void countMethodInvocation(Method method, Class servicedClass, TimeKeeper timeKeeper) { resources.methodStats.countMethodInvocation(method, servicedClass, timeKeeper); } /** * Determines the tcp port for the delegates.
* The default implementation returns {@link RmiServer#getPort()}. * * @param session the session attached to the {@link RemoteDbSession} * @param clientInfo the session info from the client * @param serverInfo the session info to establish the connection to the database rmiServer * @return the tcp-port for this connection, 0 = system default */ protected int determinePort(Session session, SessionInfo clientInfo, SessionInfo serverInfo) { return resources.con.getRmiServer().getPort(); } /** * Determines the client socket factory for the delegates.
* The default implementation returns {@link RmiServer#getClientSocketFactory()}. * * @param session the session attached to the {@link RemoteDbSession} * @param clientInfo the session info from the client * @param serverInfo the session info to establish the connection to the database rmiServer * @return the client socket factory for this connection, null = system default */ protected RMIClientSocketFactory determineClientSocketFactory(Session session, SessionInfo clientInfo, SessionInfo serverInfo) { return resources.con.getRmiServer().getClientSocketFactory(); } /** * Determines the server socket factory for the delegates.
* The default implementation returns {@link RmiServer#getServerSocketFactory()}. * * @param session the session attached to the {@link RemoteDbSession} * @param clientInfo the session info from the client * @param serverInfo the session info to establish the connection to the database rmiServer * @return the rmiServer socket factory for this connection, null = system default */ protected RMIServerSocketFactory determineServerSocketFactory(Session session, SessionInfo clientInfo, SessionInfo serverInfo) { return resources.con.getRmiServer().getServerSocketFactory(); } /** * Determines the session timeout count.
* The default implementation returns {@link RmiServer#getSessionTimeout()}. * * @param session the session attached to the {@link RemoteDbSession} * @param clientInfo the session info from the client * @param serverInfo the session info to establish the connection to the database rmiServer * @return the timeout */ protected int determineTimeout(Session session, SessionInfo clientInfo, SessionInfo serverInfo) { return resources.con.getRmiServer().getSessionTimeout(); } /** * Gets an open db session for this remote client session. * * @return the db session * @throws LoginFailedException if opening the session failed */ protected Db openDb() { try { Db pooledDb = null; SessionPoolProvider sessionPoolProvider = DbUtilities.getInstance().getSessionPoolProvider(); if (sessionPoolProvider != null) { SessionPool sessionPool = sessionPoolProvider.getSessionPool(); if (sessionPool != null) { pooledDb = (Db) sessionPool.getSession(); } else { MultiUserSessionPool remoteSessionPool = sessionPoolProvider.getRemoteSessionPool(); if (remoteSessionPool != null) { SessionInfo sessionInfo = getClientSessionInfo(); sessionInfo.setProperties(serverInfo.getProperties()); pooledDb = (Db) remoteSessionPool.get(sessionInfo); } } } if (pooledDb == null) { pooledDb = (Db) SessionFactory.getInstance().create(serverInfo); } LOGGER.info("using {0}", pooledDb); return pooledDb; } catch (LoginFailedException lx) { throw lx; } catch (RuntimeException ex) { throw new LoginFailedException("obtaining session failed", ex); } } /** * Cleanup the session. *

* The method is invoked whenever the session is closed * due to an ordinary logout or client crash. * The default implementation rolls back any pending transaction. * * @param crashed true if client crashed, else regular logout */ protected void cleanup(boolean crashed) { DbUtilities.getInstance().cleanupRemoteSession(this); if (resources != null) { resources.cleanup(crashed); } } /** * Logs the RMI-statistics. * * @param level the logging level * @param clear true if clear statistics after dump */ protected void doLogStatistics(Level level, boolean clear) { resources.doLogStatistics(level, clear); } /** * Closes the database connection (and thus rolls back any pending transaction). * If the db is pooled, it will be returned to the pool instead of being closed. * * @param cleanup true if db must be physically closed even if pooled, due to clean up */ protected void closeDb(boolean cleanup) { if (closedSince == 0) { if (resources != null) { resources.me = this; // programmatic cleanup resources.closeDb(cleanup); } closedSince = System.currentTimeMillis(); } if (cleanable != null) { cleanable.clean(); } } /** * Forces a cleanup if all cleanup and closing failed. *

* Simply marks the session closed and moves all references to GC. */ protected void forceCleanup() { closedSince = System.currentTimeMillis(); Db zombieDb = resources.db; if (zombieDb != null) { resources.db = null; // block any further RMI requests (isOpen() == false) try { zombieDb.close(); // should work if (zombieDb.isPooled()) { zombieDb.getPool().putSession(zombieDb); } } catch (RuntimeException ex) { LOGGER.warning("forced cleanup may be incomplete", ex); } } } /** * Gets the string representation of the client host connected to this session. * * @return the client host string */ public String getClientHostString() { return clientHost; } /** * Returns an application-specific option string.
* For diagnostic purposes only. * * @return null if no options */ public String getOptions() { return null; } @Override public String toString() { StringBuilder buf = new StringBuilder(); buf.append(getClass().getSimpleName()).append('#').append(sessionNumber).append(": csf="); if (csf == null) { buf.append(""); } else { buf.append(csf.getClass().getSimpleName()); if (csf instanceof SocketConfiguratorHolder) { SocketConfigurator csc = ((SocketConfiguratorHolder) csf).getSocketConfigurator(); if (csc != null && csc.isValid()) { buf.append('[').append(csc).append(']'); } } } buf.append(", ssf="); if (ssf == null) { buf.append(""); } else { buf.append(ssf.getClass().getSimpleName()); if (ssf instanceof ServerSocketConfiguratorHolder) { ServerSocketConfigurator ssc = ((ServerSocketConfiguratorHolder) ssf).getSocketConfigurator(); if (ssc != null && ssc.isValid()) { buf.append('[').append(ssc).append(']'); } } } buf.append(", port="); if (port == 0) { buf.append(""); } else { buf.append(port); } buf.append(", timeout=").append(timeout) .append(", client=").append(clientInfo) .append(", host=").append(clientHost); return buf.toString(); } /** * Gets a string useful for mapped diagnostic context info. * * @return the MDC info */ public String getMdcInfo() { if (resources.mdcInfo == null) { StringBuilder buf = new StringBuilder(); buf.append(sessionNumber).append(':'); String userName = clientInfo.getUserName(); if (userName != null && !userName.isEmpty()) { buf.append(userName); } String applicationName = clientInfo.getApplicationName(); if (applicationName != null && !applicationName.isEmpty()) { buf.append('@').append(applicationName); } if (clientInfo.getApplicationId() != 0) { buf.append('#').append(clientInfo.getApplicationId()); } if (clientInfo.isCloned()) { buf.append('*'); } resources.mdcInfo = buf.toString(); } return resources.mdcInfo; } /** * Determines whether the session is open. * * @return true if session is open */ public boolean isOpen() { return resources.db != null; } // ----------------- implements RemoteDbSession ------------------------ @Override public void close() throws RemoteException { cleanup(false); closeDb(false); } @Override public void log(String name, Level level, String message) throws RemoteException { try { Logger logger = name == null ? LOGGER : loggers.computeIfAbsent(name, LoggerFactory::getLogger); logger.log(level, message, null); } catch (RuntimeException ex) { throw new RemoteException("log() failed", ex); } } @Override public void logStatistics(Level level, boolean clear) throws RemoteException { try { doLogStatistics(level, clear); } catch (RuntimeException ex) { throw new RemoteException("logStatistics failed", ex); } } @SuppressWarnings("unchecked") @Override public T getRemoteDelegate(String classname) throws RemoteException { try { DelegateClasses delegateClasses = DELEGATE_CLASSES_MAP.get(classname); if (delegateClasses == null) { // try to find remote class. // Use superclass if no direct implementation found ClassNotFoundException nfe = null; // first exception thrown Class clazz = Class.forName(classname); Class servicedClass = null; Class currentClazz = clazz; while (currentClazz != null) { if (servicedClass == null) { Class nextServicedClass = DbUtilities.getInstance().getServicedClass(currentClazz); if (nextServicedClass != null) { servicedClass = nextServicedClass; } } try { delegateClasses = new DelegateClasses(servicedClass, clazz, RemoteDelegateLocator.getInstance().findRemoteDelegate(currentClazz)); LOGGER.info("created remote delegate class mapping {0}", delegateClasses); DELEGATE_CLASSES_MAP.put(classname, delegateClasses); break; } catch (ClassNotFoundException e) { if (currentClazz == Object.class && nfe != null) { // abort with first exception thrown throw nfe; } if (nfe == null) { nfe = e; // remember } // try superclass currentClazz = currentClazz.getSuperclass(); } } if (delegateClasses == null) { throw new RemoteException("no delegate class found for " + clazz, nfe); } } T delegate = (T) createRemoteDelegate(delegateClasses.delegateClass, delegateClasses.delegateImplClass, delegateClasses.clazz, delegateClasses.effectiveClass); exportRemoteDelegate(delegate); LOGGER.fine("Delegate created for session={0}, class={1}, port={2}, csf={3}, ssf={4}", this, delegateClasses.clazz, port, csf == null ? "" : csf.getClass().getName(), ssf == null ? "" : ssf.getClass().getName()); return delegate; } catch (ClassNotFoundException | IllegalAccessException | IllegalArgumentException | InstantiationException | NoSuchMethodException | InvocationTargetException ex) { throw new RemoteException("couldn't create delegate for " + classname, ex); } } @Override public DbRemoteDelegate getDbRemoteDelegate() throws RemoteException { try { DbRemoteDelegate delegate = createRemoteDelegate(DbRemoteDelegate.class, DbRemoteDelegateImpl.class, Db.class, null); exportRemoteDelegate(delegate); return delegate; } catch (IllegalAccessException | IllegalArgumentException | InstantiationException | NoSuchMethodException | InvocationTargetException | RemoteException ex) { throw new RemoteException("couldn't create delegate for " + resources.db, ex); } } /** * Creates the classname of the remote delegate interface from the * serviced classname.
* The default implementation returns: * package-name + ".rmi." + class-basename + "RemoteDelegate" * * @param className the name of the class to look for a delegate * @return the classname of the remote delegate for given class */ public String createRemoteDelegateClassName(String className) { int ndx = className.lastIndexOf('.'); String pkgName = className.substring(0, ndx); String clsName = className.substring(ndx + 1); return pkgName + ".rmi." + clsName + "RemoteDelegate"; } /** * Creates the classname of the remote delegate implementation from the * serviced classname.
* The default implementation returns * {@link #createRemoteDelegateClassName(java.lang.String) + "Impl"}. * * @param className the name of the class to look for a delegate * @return the classname of the remote delegate for given class */ public String createRemoteDelegateImplClassName(String className) { return createRemoteDelegateClassName(className) + "Impl"; } /** * Creates a remote delegate for the given class. *

* If the optional configuration arguments are given, the delegate must provide * a method with the following signature: *

   *  public void configureDelegate(Object... args) {
   *    // whatever args stands for
   *  }
   * 
* The optional configuration parameters allow passing additional objects * to the delegate without having to declare the setters in the remote interface. * * @param the delegate class * @param the delegate implementation class * @param delegateClass the interface class * @param delegateImplClass the implementing class * @param clazz the serviced class * @param effectiveClass the optional effective serviced class, null if clazz * @param configArgs optional arguments to configure the delegate * @return the created delegate * @throws InstantiationException if delegate could not be instantiated * @throws NoSuchMethodException if delegate does not provide the necessary constructor * @throws IllegalAccessException if access denied * @throws IllegalArgumentException if wrong arguments * @throws InvocationTargetException if invocation failed */ @SuppressWarnings("unchecked") public > T createRemoteDelegateInstance( Class delegateClass, Class delegateImplClass, Class clazz, Class effectiveClass, Object... configArgs) throws InstantiationException, NoSuchMethodException, IllegalAccessException, InvocationTargetException { // get the constructor with args (RemoteDbSessionImpl session, Class clazz) Constructor constructor = effectiveClass == null ? delegateImplClass.getConstructor(RemoteDbSessionImpl.class, Class.class) : delegateImplClass.getConstructor(RemoteDbSessionImpl.class, Class.class, Class.class); // create instance of delegate for the session db T delegate = (T) (effectiveClass == null ? constructor.newInstance(this, clazz) : constructor.newInstance(this, effectiveClass, clazz)); ((RemoteDelegateImpl) delegate).initialize(); // optionally configure the created delegate if (configArgs != null && configArgs.length > 0) { Method configureMethod = delegateImplClass.getMethod("configureDelegate", Object[].class); configureMethod.invoke(delegate, new Object[] { configArgs }); } return delegate; } /** * Creates a remote delegate for the given class.
* Same as {@link #createRemoteDelegateInstance} but with dynamic proxy to allow intercepting. * * @param the remote delegate class * @param the delegate implementation class * @param delegateClass the interface class * @param delegateImplClass the implementing class * @param clazz the serviced class * @param effectiveClass the effectively serviced class, null if clazz * @param configArgs optional configuration arguments passed to {@link #createRemoteDelegateInstance} * @return the created * @throws InstantiationException if delegate could not be instantiated * @throws NoSuchMethodException if delegate does not provide the necessary constructor * @throws IllegalAccessException if access denied * @throws IllegalArgumentException if wrong arguments * @throws InvocationTargetException if invocation failed */ @SuppressWarnings({ "rawtypes", "unchecked" }) public > T createRemoteDelegate( Class delegateClass, Class delegateImplClass, Class clazz, Class effectiveClass, Object... configArgs) throws InstantiationException, NoSuchMethodException, IllegalAccessException, InvocationTargetException { return (T) Proxy.newProxyInstance( delegateImplClass.getClassLoader(), new Class[] { delegateClass, Remote.class }, new RemoteDelegateInvocationHandler((RemoteDelegateImpl) createRemoteDelegateInstance(delegateClass, delegateImplClass, clazz, effectiveClass, configArgs))); } /** * Checks for timeout. * Will internally increment a counter until timeout has reached. * * @return true if timed out */ protected boolean hasTimedOut() { Db ldb = resources.db; // local copy in case of race cond avoids NPE if (ldb == null || ldb.isAlive()) { timeoutCount = 0; } else { timeoutCount++; } return timeoutCount > timeout; } /** * Gets the current timeout counter. * * @return the timeout counter */ protected int getTimeoutCount() { return timeoutCount; } /** * Sets this session as being polled for timeout. */ protected void polled() { Db ldb = resources.db; // local copy in case of race cond avoids NPE if (ldb != null) { ldb.setAlive(false); } } }