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

com.cyc.session.SessionManagerImpl Maven / Gradle / Ivy

Go to download

Session API implementation for managing configurations and connections to Cyc servers.

There is a newer version: 1.2.2
Show newest version
package com.cyc.session;

/*
 * #%L
 * File: SessionManagerImpl.java
 * Project: Session Client
 * %%
 * Copyright (C) 2013 - 2017 Cycorp, Inc.
 * %%
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * #L%
 */

import com.cyc.session.CycSession.ConnectionStatus;
import com.cyc.session.CycSession.SessionListener;
import com.cyc.session.configuration.ConfigurationValidator;
import com.cyc.session.exception.SessionCommunicationException;
import com.cyc.session.exception.SessionConfigurationException;
import com.cyc.session.exception.SessionException;
import com.cyc.session.exception.SessionInitializationException;
import com.cyc.session.exception.SessionRuntimeException;
import com.cyc.session.exception.SessionServiceException;
import com.cyc.session.internal.ConfigurationCache;
import com.cyc.session.internal.ConfigurationLoaderManager;
import com.cyc.session.internal.CurrentObjectCache;
import com.cyc.session.internal.CycSessionCache;
import com.cyc.session.selection.CycServerSelector;
import com.cyc.session.selection.SessionSelector;
import com.cyc.session.services.EnvironmentConfigurationLoader;
import com.cyc.session.spi.SessionFactory;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.ServiceLoader;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Reference implementation of SessionManager.
 * @author nwinant
 * @param 
 */
public class SessionManagerImpl implements SessionManager {
  
  // Static
  
  /**
   * Returns a list of all implementations of SessionFactory which can be located
   * via the {@link java.util.ServiceLoader}.
   * @return the available session factories
   */
  static protected List loadAllSessionFactories() {
    // Note: The relevant service provider file in META-INF/services
    //       is generated by the serviceloader-maven-plugin, specified
    //       in the pom.xml file.
    final List factories = new ArrayList<>();
    final ServiceLoader loader =
            ServiceLoader.load(SessionFactory.class);
    for (SessionFactory factory : loader) {
      factories.add(factory);
    }
    return factories;
  }
  
  static final private Logger LOGGER = LoggerFactory.getLogger(SessionManagerImpl.class);
  
  
  // Fields
  
  private final SessionManagerConfiguration configuration;
  private final ConfigurationLoaderManager loaderMgr;
  private final SessionFactory sessionFactory;
  private final ConfigurationCache configCache;
  private final CycSessionCache sessionCache;
  private final CurrentObjectCache currentObjectCache;
  private final EnvironmentConfigurationLoader environmentLoader;
  private boolean closed = false;
  
  
  // Constructors
  
  public SessionManagerImpl(SessionManagerConfiguration configuration) 
          throws SessionServiceException, SessionConfigurationException {
    this.configuration = configuration;
    this.loaderMgr = new ConfigurationLoaderManager();
    this.sessionFactory = loadSessionFactory();
    this.configCache = new ConfigurationCache();
    this.sessionCache = new CycSessionCache<>();
    this.currentObjectCache = new CurrentObjectCache<>();
    this.environmentLoader = new EnvironmentConfigurationLoader();
    LOGGER.debug("SessionManager instance created: {}", this);
  }
  
  
  // Public
  
  @Override
  public SessionManagerConfiguration getManagerConfiguration() {
    return this.configuration;
  }
  
  //@Override
  public EnvironmentConfiguration getEnvironmentConfiguration()
          throws SessionConfigurationException {
    errorIfClosed("Cannot retrieve EnvironmentConfiguration.");
    return environmentLoader.getConfiguration();
  }
  
  @Override
  public CycSessionConfiguration getSessionConfiguration() throws SessionConfigurationException {
    errorIfClosed("Cannot retrieve configuration.");
    final EnvironmentConfiguration environment = getEnvironmentConfiguration();
    final CycSessionConfiguration cachedConfig = configCache.get(environment);
    if (cachedConfig != null) {
      LOGGER.debug("Retrieving config from cache: {}", cachedConfig);
      return cachedConfig;
    }
    LOGGER.debug("#getConfiguration() Loading new configuration");
    CycSessionConfiguration config = loaderMgr.getConfiguration(environment);
    if (!(config instanceof EnvironmentConfiguration)) {
      configCache.put(environment, config);
    }
    return config;
  }
  
  @Override
  public T getCurrentSession() 
          throws SessionConfigurationException, SessionInitializationException, SessionCommunicationException {
    errorIfClosed("Cannot retrieve current session.");
    reapDeadSessions();
    if (!this.hasCurrentSession()) {
      if (!configuration.isSessionAutoCreationAllowed()) {
        throw new SessionInitializationException("No currrent session is set, and session auto-creation is not allowed: ");
      }
      LOGGER.debug("#getCurrentSession: There is no current session. Retrieving...");
      initCurrentSession();
    }
    return currentObjectCache.getCurrentSession();
  }
  
  public Set getSessions(SessionSelector criteria) throws SessionException {
    return sessionCache.getAll(criteria);
  }
  
  @Override
  public boolean isClosed() {
    return this.closed;
  }
  
  /**
   * Closes the SessionManagerImpl instance, releasing any underlying resources used by it, by all
   * of its caches and factories, and by all CycSessions which it has created.
   * 
   * @throws IOException 
   */
  @Override
  public void close() throws IOException {
    try {
      LOGGER.debug("Closing SessionManager instance {}", this);
      closed = true;
      // TODO: is there more cleanup which would be necessary? - nwinant, 2015-10-20
      clearCurrentSession();
      final Collection sessions = this.sessionCache.getAll();
      for (T session : sessions) {
        try {
          this.removeSession(session);
        } catch (SessionException ex) {
          LOGGER.error("Error releasing session " + session, ex);
        }
      }
    } finally {
      this.getSessionFactory().close();
      LOGGER.warn("{} closed: {}", SessionManager.class.getSimpleName(), this);
    }
  }
  
  
  // Protected
  
  protected T createSession(CycSessionConfiguration config) 
          throws SessionConfigurationException, SessionCommunicationException, SessionInitializationException {
    errorIfClosed("New sessions cannot be created.");
    final long startMillis = System.currentTimeMillis();
    final SessionFactory factory = getSessionFactory();
    final T session = factory.createSession(config);
    if (!isValidSession(session)) {
      throw new SessionConfigurationException(
              "Session " + session + " is not valid; could not retrieve a valid session from " + factory.getClass());
    }
    getSessionFactory().initializeSession(session);
    if (ConnectionStatus.UNINITIALIZED.equals(session.getConnectionStatus())) {
      throw new SessionInitializationException("Session " + session + " could not be initialized by " + factory);
    }
    session.addListener(new SessionListener() {
      @Override
      public void onClose(Thread thread) {
        try {
          onSessionRelease(session, thread);
        } catch (SessionException ex) {
          throw SessionRuntimeException.fromThrowable(ex);
        }
      }
    });
    sessionCache.add(session, getEnvironmentConfiguration());
    LOGGER.debug("Created new session in {}ms; {} sessions total. Session: {}", (System.currentTimeMillis() - startMillis), sessionCache.size(), session);
    return session;
  }
  
  protected T createSession() throws SessionConfigurationException, SessionCommunicationException, SessionInitializationException {
    // This method formerly implemented SessionManager#getSession before that method was removed (and this method was renamed) - nwinant, 2015-10-16
    final CycSessionConfiguration config = getSessionConfiguration();
    final ConfigurationValidator configUtils = new ConfigurationValidator(config);
    if (!configUtils.isSufficient()) {
      throw new SessionConfigurationException("Configuration " + config + " is not sufficient to create a valid session");
    }
    return createSession(config);
  }
  
  /**
   * Sets the current CycSession. It will not allow null values. If you need to
   * clear the current CycSession, call {@link #clearCurrentSession}.
   * 
   * @param session
   * @return the session
   * @throws SessionConfigurationException 
   */
  public T setCurrentSession(T session) throws SessionConfigurationException {
    if (!isValidSession(session)) {
      throw new SessionConfigurationException("Session " + session + " is not valid. Cannot set it to the current session.");
    }
    currentObjectCache.setCurrentSession(session);
    LOGGER.debug("Current session set: {}", session);
    return session;
  }
  
  protected boolean hasCurrentSession() {
    return currentObjectCache.hasCurrentSession();
  }
  
  protected SessionFactory getSessionFactory() {
    return this.sessionFactory;
  }
  
  
  // Private
  
  private boolean isValidSession(CycSession session) {
    // TODO: this could be just a biiiiiiiit more fleshed out...
    return (session != null) && !session.isClosed();
  }
  
  private void initCurrentSession() throws SessionConfigurationException, SessionInitializationException, SessionCommunicationException {
    LOGGER.debug("Initializing current session");
    setCurrentSession(createSession());
  }
  
  private void clearCurrentSession() {
    LOGGER.debug("Clearing current session");
    currentObjectCache.clearCurrentSession();
  }
    
  private void releaseCycServerIfAppropriate(T session) throws SessionException {
    // We skip this altogether when called during/after SessionManagerImpl#close(). It can no longer
    // retrieve a configuration, and it's unnecessary anyway: the servers get released when 
    // SessionManagerImpl#close() closes the SessionFactory.  - nwinant, 2016-08-03 
    if (!isClosed() && configuration.isServerReleasedWhenAllSessionsAreClosed()) {
      final CycAddress server = session.getServerInfo().getCycAddress();
      if (getSessions(new CycServerSelector(server)).isEmpty()) {
        LOGGER.info("Releasing all server resources for {}", server);
        this.getSessionFactory().releaseResourcesForServer(server);
      }
    }
  }
    
  synchronized private void removeSession(T session) throws SessionException {
    if (session == null) {
      return;
    }
    if (hasCurrentSession() && (currentObjectCache.getCurrentSession().equals(session))) {
      LOGGER.debug("Reaping current session with status {}: {}", session.getConnectionStatus(), session);
      // TODO: This behavior should be correct, but let's evaluate it more carefully. - nwinant, 2015-10-16
      this.clearCurrentSession();
    } else {
      LOGGER.debug("Reaping session with status {}: {}", session.getConnectionStatus(), session);
    }
    if (!session.isClosed()) {
      session.close();
    }
    sessionCache.remove(session);
    LOGGER.debug("{} sessions remaining", sessionCache.size());
    releaseCycServerIfAppropriate(session);
  }
  
  private void onSessionRelease(T session, Thread thread) throws SessionException {
    LOGGER.debug("Releasing session {} from thread {}", session, thread);
    removeSession(session);
  }
  
  private boolean isSessionDead(CycSession session) {
    return (session == null) 
            || session.isClosed()
            || !ConnectionStatus.CONNECTED.equals(session.getConnectionStatus());
  }
  
  synchronized private void reapDeadSessions() {
    try {
      // TODO: Should we consider allowing closed sessions to remain in the cache for diagnostics / debugging / least surprise?
      final long startMillis = System.currentTimeMillis();
      int numSessionsReaped = 0;
      if (hasCurrentSession()) {
        T currSession = currentObjectCache.getCurrentSession();
        if (isSessionDead(currSession)) {
          removeSession(currSession);
          numSessionsReaped++;
        }
      }
      final Collection cachedSessions = sessionCache.getAll();
      for (T cachedSession : cachedSessions) {
        if ((cachedSession != null) && isSessionDead(cachedSession)) {
          removeSession(cachedSession);
          numSessionsReaped++;
        }
      }
      final long duration = System.currentTimeMillis() - startMillis;
      if ((numSessionsReaped > 0) || (duration > 1)) {
        LOGGER.debug("Reaped {} sessions in {}ms; {} sessions remaining", numSessionsReaped, (System.currentTimeMillis() - startMillis), sessionCache.size());
      }
    } catch (SessionException ex) {
      throw SessionRuntimeException.fromThrowable(ex);
    }
  }
  
  /**
   * Returns an implementation of {@link SessionFactory}. It will return the first
   * implementation it finds which has been registered per {@link java.util.ServiceLoader}.
   * 
   * @return an object which implements SessionFactory.
   * @throws com.cyc.session.exception.SessionServiceException
   */
  private SessionFactory loadSessionFactory() throws SessionServiceException {
    final List factories = loadAllSessionFactories();
    if (factories.isEmpty()) {
      throw new SessionServiceException(SessionFactory.class, "Could not find a service implementation!");
    }
    for (SessionFactory factory : factories) {
      LOGGER.debug("Found {}: {}", SessionFactory.class.getSimpleName(), factory.getClass().getName());
    }
    return factories.get(0);
  }
  
  private void errorIfClosed(String msg) {
    if (isClosed()) {
      throw new SessionRuntimeException(getClass().getSimpleName() + " has been closed. " + msg + " " + this);
    }
  }
  
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy