tomcat Session manager to support clustering using infinispan 66/6266/3
authorHarman Singh <harmasin@cisco.com>
Thu, 17 Apr 2014 23:43:22 +0000 (16:43 -0700)
committerHarman Singh <harmasin@cisco.com>
Mon, 21 Apr 2014 16:19:58 +0000 (09:19 -0700)
Change-Id: I65faff3a62f5c2cf3098e87b52649bf58a6e873a
Signed-off-by: Harman Singh <harmasin@cisco.com>
opendaylight/samples/clustersession/pom.xml [new file with mode: 0644]
opendaylight/samples/clustersession/src/main/java/org/opendaylight/controller/clustersession/ClusterSession.java [new file with mode: 0644]
opendaylight/samples/clustersession/src/main/java/org/opendaylight/controller/clustersession/ClusterSessionData.java [new file with mode: 0644]
opendaylight/samples/clustersession/src/main/java/org/opendaylight/controller/clustersession/ClusterSessionManager.java [new file with mode: 0644]
opendaylight/samples/clustersession/src/main/java/org/opendaylight/controller/clustersession/ClusterSessionUtil.java [new file with mode: 0644]
opendaylight/samples/clustersession/src/main/java/org/opendaylight/controller/clustersession/impl/ClusterSessionServiceImpl.java [new file with mode: 0644]
opendaylight/samples/clustersession/src/main/java/org/opendaylight/controller/clustersession/service/ClusterSessionService.java [new file with mode: 0644]
opendaylight/samples/clustersession/src/test/java/org/opendaylight/controller/clustersession/ClusterSessionManagerTest.java [new file with mode: 0644]
opendaylight/samples/clustersession/src/test/java/org/opendaylight/controller/clustersession/ClusterSessionServiceImplTest.java [new file with mode: 0644]
opendaylight/samples/clustersession/src/test/java/org/opendaylight/controller/clustersession/ClusterSessionUtilTest.java [new file with mode: 0644]

diff --git a/opendaylight/samples/clustersession/pom.xml b/opendaylight/samples/clustersession/pom.xml
new file mode 100644 (file)
index 0000000..f72e47b
--- /dev/null
@@ -0,0 +1,109 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>org.opendaylight.controller</groupId>
+    <artifactId>commons.opendaylight</artifactId>
+    <version>1.4.2-SNAPSHOT</version>
+    <relativePath>../../commons/opendaylight</relativePath>
+  </parent>
+  <groupId>org.opendaylight.controller</groupId>
+  <artifactId>clustersession</artifactId>
+  <version>1.0.0-SNAPSHOT</version>
+  <packaging>bundle</packaging>
+  <dependencies>
+    <dependency>
+      <groupId>equinoxSDK381</groupId>
+      <artifactId>javax.servlet</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>orbit</groupId>
+      <artifactId>org.apache.catalina</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>orbit</groupId>
+      <artifactId>org.apache.catalina.ha</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>orbit</groupId>
+      <artifactId>org.apache.coyote</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>orbit</groupId>
+      <artifactId>org.apache.juli.extras</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>orbit</groupId>
+      <artifactId>org.apache.tomcat.api</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>orbit</groupId>
+      <artifactId>org.apache.tomcat.util</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.opendaylight.controller</groupId>
+      <artifactId>clustering.services</artifactId>
+      <version>0.5.1-SNAPSHOT</version>
+    </dependency>
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>jcl-over-slf4j</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.osgi</groupId>
+      <artifactId>org.osgi.core</artifactId>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.mockito</groupId>
+      <artifactId>mockito-all</artifactId>
+      <version>1.9.5</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.powermock</groupId>
+      <artifactId>powermock-api-mockito</artifactId>
+      <version>1.5.4</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.powermock</groupId>
+      <artifactId>powermock-module-junit4</artifactId>
+      <version>1.5.4</version>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.felix</groupId>
+        <artifactId>maven-bundle-plugin</artifactId>
+        <version>2.4.0</version>
+        <extensions>true</extensions>
+        <configuration>
+          <instructions>
+            <Fragment-Host>org.eclipse.gemini.web.tomcat</Fragment-Host>
+            <Export-Package>org.opendaylight.controller.clustersession</Export-Package>
+            <Import-Package>org.apache.catalina,
+              org.apache.catalina.session,
+              org.apache.catalina.util,
+              org.apache.catalina.ha.session,
+              javax.servlet,
+              javax.servlet.http,
+              org.slf4j,
+              org.osgi.framework,
+              org.eclipse.osgi.framework.console,
+              org.opendaylight.controller.clustering.services</Import-Package>
+          </instructions>
+          <manifestLocation>${project.basedir}/META-INF</manifestLocation>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+</project>
diff --git a/opendaylight/samples/clustersession/src/main/java/org/opendaylight/controller/clustersession/ClusterSession.java b/opendaylight/samples/clustersession/src/main/java/org/opendaylight/controller/clustersession/ClusterSession.java
new file mode 100644 (file)
index 0000000..dc93700
--- /dev/null
@@ -0,0 +1,162 @@
+package org.opendaylight.controller.clustersession;
+
+import java.beans.PropertyChangeSupport;
+import java.io.Serializable;
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.catalina.Manager;
+import org.apache.catalina.SessionListener;
+import org.apache.catalina.session.StandardSession;
+import org.opendaylight.controller.clustersession.service.ClusterSessionService;
+
+public class ClusterSession extends StandardSession implements Serializable {
+
+  private static final long serialVersionUID = 1L;
+
+  private transient ClusterSessionService sessionService;
+
+  public ClusterSession(Manager manager, ClusterSessionService sessionService) {
+    super(manager);
+    this.sessionService = sessionService;
+  }
+
+  public void setSessionService(ClusterSessionService sessionService){
+    this.sessionService = sessionService;
+  }
+
+  @Override
+  public void setAuthType(String authType) {
+    super.setAuthType(authType);
+    sessionService.updateSession(this);
+  }
+
+  @Override
+  public void setCreationTime(long time) {
+    super.setCreationTime(time);
+    sessionService.updateSession(this);
+  }
+
+  @Override
+  public void setMaxInactiveInterval(int interval) {
+    super.setMaxInactiveInterval(interval);
+    sessionService.updateSession(this);
+  }
+
+  @Override
+  public void setNew(boolean isNew) {
+    super.setNew(isNew);
+    sessionService.updateSession(this);
+  }
+
+  @Override
+  public void setPrincipal(Principal principal) {
+    super.setPrincipal(principal);
+    sessionService.updateSession(this);
+  }
+
+  @Override
+  public void setValid(boolean isValid) {
+    super.setValid(isValid);
+    sessionService.updateSession(this);
+  }
+
+  @Override
+  public void access() {
+    super.access();
+    sessionService.updateSession(this);
+  }
+
+  @Override
+  public void endAccess() {
+    super.endAccess();
+    sessionService.updateSession(this);
+  }
+
+  @Override
+  public void removeAttribute(String name, boolean notify) {
+    super.removeAttribute(name, notify);
+    sessionService.updateSession(this);
+  }
+
+  @Override
+  public void setAttribute(String name, Object value, boolean notify) {
+    super.setAttribute(name, value, notify);
+    sessionService.updateSession(this);
+  }
+
+  @Override
+  public void recycle() {
+    super.recycle();
+    sessionService.updateSession(this);
+  }
+
+  @Override
+  public void removeNote(String name) {
+    super.removeNote(name);
+    sessionService.updateSession(this);
+  }
+
+  @Override
+  public void addSessionListener(SessionListener listener) {
+    super.addSessionListener(listener);
+    sessionService.updateSession(this);
+  }
+
+  @Override
+  public void removeSessionListener(SessionListener listener) {
+    super.removeSessionListener(listener);
+    sessionService.updateSession(this);
+  }
+
+  @Override
+  public void setNote(String name, Object value) {
+    super.setNote(name, value);
+    sessionService.updateSession(this);
+  }
+
+  /*
+   * Certain fields inside Standard session are not serialized, We need to process them here
+   */
+   public void afterDeserialization(){
+    if (listeners == null){
+      listeners = new ArrayList<SessionListener>();
+    }
+    if (notes == null){
+      notes = new ConcurrentHashMap<String, Object>();
+    }
+    if(support == null){
+      support = new PropertyChangeSupport(this);
+    }
+   }
+
+   @Override
+   public String toString() {
+     StringBuilder sb = new StringBuilder();
+     sb.append("ClusterSession[");
+     sb.append(id);
+     sb.append(", isNew : ");
+     sb.append(isNew);
+     sb.append(", isValid : ");
+     sb.append(isValid);
+     sb.append("]");
+     return sb.toString();
+   }
+
+   /*
+    * These methods are added for deserialization purpose
+    */
+
+   public void setAuthTypeInternal(String authType){
+     this.authType = authType;
+   }
+
+   public void setPrincipalInternal(Principal principal){
+     this.principal = principal;
+   }
+
+   public void setNoteInternal(String name, Object value) {
+     notes.put(name, value);
+   }
+}
diff --git a/opendaylight/samples/clustersession/src/main/java/org/opendaylight/controller/clustersession/ClusterSessionData.java b/opendaylight/samples/clustersession/src/main/java/org/opendaylight/controller/clustersession/ClusterSessionData.java
new file mode 100644 (file)
index 0000000..b5796d2
--- /dev/null
@@ -0,0 +1,79 @@
+package org.opendaylight.controller.clustersession;
+
+import java.io.Serializable;
+import java.util.Arrays;
+
+public class ClusterSessionData implements Serializable{
+
+  private static final long serialVersionUID = 1L;
+
+  private ClusterSession session;
+
+  private byte[] principalData;
+
+  private byte[] savedRequestData;
+
+  private byte[] savedPrincipalData;
+
+  private String authType;
+
+  private String userName;
+
+  private String password;
+
+  public ClusterSession getSession() {
+    return session;
+  }
+
+  public void setSession(final ClusterSession session) {
+    this.session = session;
+  }
+
+  public byte[] getPrincipalData() {
+    return principalData;
+  }
+
+  public void setPrincipalData(final byte[] principalData) {
+    this.principalData = Arrays.copyOf(principalData, principalData.length);
+  }
+
+  public String getAuthType() {
+    return authType;
+  }
+
+  public void setAuthType(String authType) {
+    this.authType = authType;
+  }
+
+  public byte[] getSavedRequestData() {
+    return savedRequestData;
+  }
+
+  public void setSavedRequestData(byte[] savedRequestData) {
+    this.savedRequestData = Arrays.copyOf(savedRequestData, savedRequestData.length);
+  }
+
+  public byte[] getSavedPrincipalData() {
+    return savedPrincipalData;
+  }
+
+  public void setSavedPrincipalData(byte[] savedPrincipalData) {
+    this.savedPrincipalData = Arrays.copyOf(savedPrincipalData, savedPrincipalData.length);
+  }
+
+  public String getUserName() {
+    return userName;
+  }
+
+  public void setUserName(String userName) {
+    this.userName = userName;
+  }
+
+  public String getPassword() {
+    return password;
+  }
+
+  public void setPassword(String password) {
+    this.password = password;
+  }
+}
diff --git a/opendaylight/samples/clustersession/src/main/java/org/opendaylight/controller/clustersession/ClusterSessionManager.java b/opendaylight/samples/clustersession/src/main/java/org/opendaylight/controller/clustersession/ClusterSessionManager.java
new file mode 100644 (file)
index 0000000..e285bb4
--- /dev/null
@@ -0,0 +1,222 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ */
+package org.opendaylight.controller.clustersession;
+
+import java.io.IOException;
+import java.util.HashMap;
+
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.LifecycleState;
+import org.apache.catalina.Session;
+import org.apache.catalina.session.ManagerBase;
+import org.apache.catalina.util.SessionIdGenerator;
+import org.opendaylight.controller.clustersession.impl.ClusterSessionServiceImpl;
+import org.opendaylight.controller.clustersession.service.ClusterSessionService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+/**
+ * ClusterSession Manager is a custom session manager, that is used to persist session data
+ * across cluster of a storage such as infinispan or memcache
+ * @author harman singh
+ *
+ */
+public class ClusterSessionManager extends ManagerBase{
+  /**
+   * Has this component been _started yet?
+   */
+  protected boolean started = false;
+
+  protected ClusterSessionService sessionService;
+
+  private static final Logger LOGGER = LoggerFactory.getLogger(ClusterSessionManager.class);
+  /**
+   * The descriptive information about this implementation.
+   */
+  protected static final String INFO = "ClusterSessionManager/1.0";
+
+  /**
+   * The descriptive name of this Manager implementation (for logging).
+   */
+  protected static final String NAME = "ClusterSessionManager";
+
+  public ClusterSessionManager(){
+    sessionService = new ClusterSessionServiceImpl(this);
+  }
+
+  /**
+   * Return descriptive information about this Manager implementation and
+   * the corresponding version number, in the format
+   * <code>&lt;description&gt;/&lt;version&gt;</code>.
+   */
+  @Override
+  public String getInfo(){
+    return INFO;
+  }
+
+  /**
+   * Return the descriptive short name of this Manager implementation.
+   */
+  @Override
+  public String getName(){
+    return NAME;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public void load() throws ClassNotFoundException, IOException {
+    // We are not persisting any session in database, infinispan does not persist data.
+    // loading of persisted session is not required.
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public void unload() throws IOException {
+    // We are not persisting any session in database, infinispan does not persist data.
+    // unloading of session to persistence layer is not required.
+  }
+
+  /**
+   * Start this component and implement the requirements
+   * of {@link org.apache.catalina.util.LifecycleBase#startInternal()}.
+   *
+   * @exception LifecycleException if this component detects a fatal error
+   *  that prevents this component from being used
+   */
+  @Override
+  protected synchronized void startInternal() throws LifecycleException {
+    sessionIdGenerator = new SessionIdGenerator();
+    sessionIdGenerator.setJvmRoute(getJvmRoute());
+    sessionIdGenerator.setSecureRandomAlgorithm(getSecureRandomAlgorithm());
+    sessionIdGenerator.setSecureRandomClass(getSecureRandomClass());
+    sessionIdGenerator.setSecureRandomProvider(getSecureRandomProvider());
+    sessionIdGenerator.setSessionIdLength(getSessionIdLength());
+    sessionService.startInternal(sessionIdGenerator);
+    setState(LifecycleState.STARTING);
+  }
+
+  /**
+   * Stop this component and implement the requirements
+   * of {@link org.apache.catalina.util.LifecycleBase#stopInternal()}.
+   *
+   * @exception LifecycleException if this component detects a fatal error
+   *  that prevents this component from being used
+   */
+  @Override
+  protected synchronized void stopInternal() throws LifecycleException {
+    setState(LifecycleState.STOPPING);
+
+    // Expire all active sessions
+    Session sessions[] = findSessions();
+    for (int i = 0; i < sessions.length; i++) {
+      Session session = sessions[i];
+      try {
+        if (session.isValid()) {
+          session.expire();
+        }
+      } catch (Exception e) {
+        LOGGER.warn(e.toString());
+      } finally {
+        // Measure against memory leaking if references to the session
+        // object are kept in a shared field somewhere
+        session.recycle();
+      }
+    }
+    // Require a new random number generator if we are restarted
+    super.stopInternal();
+    sessionService.stopInternal();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public void expireSession(final String sessionId){
+    LOGGER.debug("SESSION EXPIRE : ", sessionId);
+    sessionService.expireSession(sessionId);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public void remove(final Session session){
+    LOGGER.debug("SESSION REMOVE : ", session.getId());
+    sessionService.removeSession(session.getId());
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public void remove(Session session, boolean update) {
+    sessionService.removeSession(session.getId());
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public Session findSession(final String id) throws IOException{
+    return sessionService.findSession(id);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public Session createSession(final String sessionId){
+    LOGGER.debug("SESSION CREATE : ", sessionId);
+    if(sessionId != null){
+      Session session = sessionService.findSession(sessionId);
+      if(session != null){
+        return session;
+      }
+    }
+    return sessionService.createSession(sessionId);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public Session createEmptySession(){
+    return sessionService.createEmptySession();
+  }
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public void add(Session session){
+    LOGGER.debug("SESSION ADD : ", session.getId());
+    sessionService.addSession((ClusterSession)session);
+  }
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public HashMap<String, String> getSession(String sessionId){
+    return sessionService.getSession(sessionId);
+  }
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public Session[] findSessions() {
+    return sessionService.findSessions();
+  }
+
+  public ClusterSessionService getSessionService() {
+    return sessionService;
+  }
+
+  public void setSessionService(ClusterSessionService sessionService) {
+    this.sessionService = sessionService;
+  }
+
+}
diff --git a/opendaylight/samples/clustersession/src/main/java/org/opendaylight/controller/clustersession/ClusterSessionUtil.java b/opendaylight/samples/clustersession/src/main/java/org/opendaylight/controller/clustersession/ClusterSessionUtil.java
new file mode 100644 (file)
index 0000000..01fad1a
--- /dev/null
@@ -0,0 +1,269 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ */
+package org.opendaylight.controller.clustersession;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.OutputStream;
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.apache.catalina.authenticator.Constants;
+import org.apache.catalina.authenticator.SavedRequest;
+import org.apache.catalina.ha.session.SerializablePrincipal;
+import org.apache.catalina.realm.GenericPrincipal;
+import org.opendaylight.controller.clustersession.service.ClusterSessionService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * ClusterSessionUtil will be used to convert ClusterSession object into ClusterSessionData object,
+ * which is serializable and can be passed for storage. This class also perform deserialization to
+ * create ClusterSession object
+ * @author harman singh
+ *
+ */
+
+public class ClusterSessionUtil {
+
+  private static final Logger LOGGER = LoggerFactory.getLogger(ClusterSessionUtil.class);
+
+  private ClusterSessionUtil() {
+
+  }
+
+  /**
+   * Serialize the ClusterSession object to provide ClusterSessionData object,
+   * that will be used for storage like in inifinispan or memcache etc.
+   * @param session an instance of ClusterSession
+   * @return an instance of ClusterSessionData
+   */
+  public static ClusterSessionData getSerializableSession(ClusterSession session) {
+    if(session == null){
+      return null;
+    }
+    ClusterSessionData sessionData = new ClusterSessionData();
+    sessionData.setSession(session);
+    sessionData.setAuthType(session.getAuthType());
+    sessionData.setPrincipalData(serializePrincipal(session.getPrincipal()));
+    sessionData.setSavedRequestData(serializeSavedRequest(session.getNote(Constants.FORM_REQUEST_NOTE)));
+    Principal notePrincipal = (Principal) session.getNote(Constants.FORM_PRINCIPAL_NOTE);
+    byte[] principalBytes = serializePrincipal(notePrincipal);
+    sessionData.setSavedPrincipalData(principalBytes);
+    if(session.getPrincipal() == null && notePrincipal != null){
+      sessionData.setPrincipalData(principalBytes);
+    }
+    sessionData.setUserName((String) session.getNote(Constants.FORM_USERNAME));
+    sessionData.setPassword((String) session.getNote(Constants.FORM_PASSWORD));
+    return sessionData;
+  }
+
+  /**
+   * Deserialize the ClusterSessionData object that usually comes from storage
+   * to provide ClusterSession object,
+   * that will be used by Session Manager
+   * @param sessionData an instance of ClusterSessionData
+   * @param sessionService an instance of ClusterSessionService
+   * @param manager an instance of ClusterSessionManager
+   * @return an instance of ClusterSession
+   */
+
+  public static ClusterSession getDeserializedSession(ClusterSessionData sessionData, ClusterSessionService sessionService,
+      ClusterSessionManager manager) {
+    if(sessionData == null){
+      return null;
+    }
+    ClusterSession session = sessionData.getSession();
+    session.afterDeserialization();
+    session.setManager(manager);
+    session.setSessionService(sessionService);
+    if(sessionData.getAuthType() != null) {
+      session.setAuthTypeInternal(sessionData.getAuthType());
+    }
+    if(sessionData.getPrincipalData() != null && sessionData.getPrincipalData().length > 0){
+      session.setPrincipalInternal(deserializePrincipal(sessionData.getPrincipalData()));
+    }
+    if(sessionData.getSavedPrincipalData() != null && sessionData.getSavedPrincipalData().length > 0){
+      session.setNoteInternal(Constants.FORM_PRINCIPAL_NOTE, deserializePrincipal(sessionData.getSavedPrincipalData()));
+    }
+    if(sessionData.getSavedRequestData() != null && sessionData.getSavedRequestData().length > 0){
+      session.setNoteInternal(Constants.FORM_REQUEST_NOTE, deserializeSavedRequest(sessionData.getSavedRequestData()));
+    }
+    if(sessionData.getUserName() != null){
+      session.setNoteInternal(Constants.FORM_USERNAME, sessionData.getUserName());
+    }
+    if(sessionData.getPassword() != null){
+      session.setNoteInternal(Constants.FORM_PASSWORD, sessionData.getPassword());
+    }
+    return session;
+  }
+
+  private static byte[] serializePrincipal(final Principal principal){
+    if(principal == null) {
+      return new byte[0];
+    }
+    ByteArrayOutputStream bos = null;
+    ObjectOutputStream oos = null;
+    try {
+      bos = new ByteArrayOutputStream();
+      oos = new ObjectOutputStream(bos);
+      SerializablePrincipal.writePrincipal((GenericPrincipal) principal, oos );
+      oos.flush();
+      return bos.toByteArray();
+    } catch (IOException e) {
+      throw new IllegalArgumentException( "Non-serializable object", e);
+    } finally {
+      closeSilently(bos);
+      closeSilently(oos);
+    }
+  }
+
+  private static byte[] serializeSavedRequest(final Object obj) {
+    if(obj == null) {
+      return new byte[0];
+    }
+    final SavedRequest savedRequest = (SavedRequest) obj;
+    ByteArrayOutputStream bos = null;
+    ObjectOutputStream oos = null;
+    try {
+      bos = new ByteArrayOutputStream();
+      oos = new ObjectOutputStream(bos);
+      oos.writeObject(savedRequest.getContentType());
+      oos.writeObject(getHeaders(savedRequest));
+      oos.writeObject(newArrayList(savedRequest.getLocales()));
+      oos.writeObject(savedRequest.getMethod());
+      oos.writeObject(savedRequest.getQueryString());
+      oos.writeObject(savedRequest.getRequestURI());
+      oos.writeObject(savedRequest.getDecodedRequestURI());
+      oos.flush();
+      return bos.toByteArray();
+    } catch (IOException e) {
+      throw new IllegalArgumentException( "Non-serializable object", e);
+    } finally {
+      closeSilently(bos);
+      closeSilently(oos);
+    }
+  }
+
+  private static Principal deserializePrincipal(final byte[] data) {
+    ByteArrayInputStream bis = null;
+    ObjectInputStream ois = null;
+    try {
+      bis = new ByteArrayInputStream(data);
+      ois = new ObjectInputStream(bis);
+      return SerializablePrincipal.readPrincipal(ois);
+    } catch (IOException e) {
+      throw new IllegalArgumentException( "Could not deserialize principal", e);
+    } catch (ClassNotFoundException e) {
+      throw new IllegalArgumentException( "Could not deserialize principal", e);
+    } finally {
+      closeSilently(bis);
+      closeSilently(ois);
+    }
+  }
+
+  @SuppressWarnings("unchecked")
+  private static SavedRequest deserializeSavedRequest(final byte[] data) {
+    ByteArrayInputStream bis = null;
+    ObjectInputStream ois = null;
+    try {
+      bis = new ByteArrayInputStream(data);
+      ois = new ObjectInputStream(bis);
+      final SavedRequest savedRequest = new SavedRequest();
+      savedRequest.setContentType((String) ois.readObject());
+      setHeaders(savedRequest, (Map<String, List<String>>) ois.readObject());
+      setLocales(savedRequest, (List<Locale>) ois.readObject());
+      savedRequest.setMethod((String) ois.readObject());
+      savedRequest.setQueryString((String) ois.readObject());
+      savedRequest.setRequestURI((String) ois.readObject());
+      savedRequest.setDecodedRequestURI((String) ois.readObject());
+      return savedRequest;
+    } catch (final IOException e) {
+      throw new IllegalArgumentException( "Could not deserialize SavedRequest", e );
+    } catch (final ClassNotFoundException e) {
+      throw new IllegalArgumentException( "Could not deserialize SavedRequest", e );
+    } finally {
+      closeSilently(bis);
+      closeSilently(ois);
+    }
+  }
+
+  private static void setLocales(final SavedRequest savedRequest, final List<Locale> locales) {
+    if(locales != null && !locales.isEmpty()) {
+      for (final Locale locale : locales) {
+        savedRequest.addLocale(locale);
+      }
+    }
+  }
+
+  private static <T> List<T> newArrayList(final Iterator<T> iter) {
+    if(!iter.hasNext()) {
+      return Collections.emptyList();
+    }
+    final List<T> result = new ArrayList<T>();
+    while (iter.hasNext()) {
+      result.add(iter.next());
+    }
+    return result;
+  }
+
+  private static Map<String, List<String>> getHeaders(final SavedRequest obj) {
+    final Map<String, List<String>> result = new HashMap<String, List<String>>();
+    final Iterator<String> namesIter = obj.getHeaderNames();
+    while (namesIter.hasNext()) {
+      final String name = namesIter.next();
+      final List<String> values = new ArrayList<String>();
+      result.put(name, values);
+      final Iterator<String> valuesIter = obj.getHeaderValues(name);
+      while (valuesIter.hasNext()) {
+        final String value = valuesIter.next();
+        values.add(value);
+      }
+    }
+    return result;
+  }
+
+  private static void setHeaders(final SavedRequest obj, final Map<String, List<String>> headers) {
+    if(headers != null) {
+      for (final Entry<String, List<String>> entry : headers.entrySet()) {
+        final List<String> values = entry.getValue();
+        for (final String value : values) {
+          obj.addHeader(entry.getKey(), value);
+        }
+      }
+    }
+  }
+
+  private static void closeSilently(final OutputStream os) {
+    if (os != null) {
+      try {
+        os.close();
+      } catch (final IOException f) {
+        LOGGER.debug("Exception occurred while closing output stream",  f.toString());
+      }
+    }
+  }
+
+  private static void closeSilently(final InputStream is) {
+    if (is != null) {
+      try {
+        is.close();
+      } catch (final IOException f) {
+        LOGGER.debug("Exception occurred while closing input stream", f.toString());
+      }
+    }
+  }
+}
diff --git a/opendaylight/samples/clustersession/src/main/java/org/opendaylight/controller/clustersession/impl/ClusterSessionServiceImpl.java b/opendaylight/samples/clustersession/src/main/java/org/opendaylight/controller/clustersession/impl/ClusterSessionServiceImpl.java
new file mode 100644 (file)
index 0000000..e1c7dfd
--- /dev/null
@@ -0,0 +1,328 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ */
+package org.opendaylight.controller.clustersession.impl;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.EnumSet;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import org.apache.catalina.Session;
+import org.apache.catalina.util.SessionIdGenerator;
+import org.opendaylight.controller.clustering.services.CacheConfigException;
+import org.opendaylight.controller.clustering.services.CacheExistException;
+import org.opendaylight.controller.clustering.services.IClusterGlobalServices;
+import org.opendaylight.controller.clustering.services.IClusterServices;
+import org.opendaylight.controller.clustersession.ClusterSession;
+import org.opendaylight.controller.clustersession.ClusterSessionData;
+import org.opendaylight.controller.clustersession.ClusterSessionManager;
+import org.opendaylight.controller.clustersession.ClusterSessionUtil;
+import org.opendaylight.controller.clustersession.service.ClusterSessionService;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.ServiceReference;
+import org.osgi.util.tracker.ServiceTracker;
+import org.osgi.util.tracker.ServiceTrackerCustomizer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Implementation to persist and retrieve session data from infinispan cache
+ * @author harman singh
+ *
+ */
+public class ClusterSessionServiceImpl implements ClusterSessionService,
+  ServiceTrackerCustomizer<IClusterGlobalServices, IClusterGlobalServices>{
+
+  private IClusterGlobalServices clusterGlobalServices = null;
+  private static final Logger LOGGER = LoggerFactory.getLogger(ClusterSessionServiceImpl.class);
+  private ConcurrentMap<String, ClusterSessionData> sessions = null;
+  private static final String SESSION_CACHE = "customSessionManager.sessionData";
+  private ClusterSessionManager manager = null;
+  private SessionIdGenerator sessionIdGenerator = null;
+  private BundleContext context = null;
+  private ServiceTracker<IClusterGlobalServices, IClusterGlobalServices> clusterTracker;
+  public ClusterSessionServiceImpl(ClusterSessionManager manager) {
+    this.manager = manager;
+  }
+  /**
+   * This method initialize the cluster service of opendaylight and
+   * create a cache map in infinispan
+   */
+
+  @Override
+  public void startInternal(SessionIdGenerator sessionIdGenerator){
+    this.sessionIdGenerator = sessionIdGenerator;
+    context = FrameworkUtil.getBundle(ClusterSessionManager.class).getBundleContext();
+    getClusterService();
+    createCache();
+  }
+
+  /**
+   * Removes the cluster service tracker while shut down
+   */
+  @Override
+  public void stopInternal(){
+    if(clusterTracker != null){
+      clusterTracker.close();
+    }
+  }
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public Session findSession(final String id){
+    if(id == null) {
+      return null;
+    }
+    if(sessions == null) {
+      LOGGER.debug("Session cache not present, try to create.");
+      createCache();
+      return null;
+    }
+    ClusterSessionData sessionData = sessions.get(id);
+    if(sessionData != null) {
+      LOGGER.debug("SESSION FOUND : ", id);
+    } else {
+      LOGGER.debug("SESSION NOTFOUND : ", id);
+    }
+    return ClusterSessionUtil.getDeserializedSession(sessionData, this, this.manager);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public Session[] findSessions() {
+    if(sessions == null) {
+      LOGGER.debug("Session cache not present, try to create.");
+      createCache();
+      return new Session[0];
+    }
+    Collection<ClusterSessionData> sessionDataList = sessions.values();
+    ArrayList<ClusterSession> sessionList = new ArrayList<ClusterSession>();
+    for(ClusterSessionData sessionData : sessionDataList){
+      sessionList.add(ClusterSessionUtil.getDeserializedSession(sessionData, this, this.manager));
+    }
+    return sessionList.toArray(new Session[0]);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public void removeSession(final String id){
+    if(sessions == null) {
+      LOGGER.debug("Session cache not present, try to create.");
+      createCache();
+      return;
+    }
+    sessions.remove(id);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public void expireSession(final String id){
+    if(sessions == null) {
+      LOGGER.debug("Session cache not present, try to create.");
+      createCache();
+      return;
+    }
+    ClusterSessionData sessionData = sessions.get(id);
+    if(sessionData != null) {
+      sessionData.getSession().expire();
+      removeSession(id);
+    }
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public Session createSession(final String sessionId){
+    if(sessions == null) {
+      LOGGER.debug("Session cache not present, try to create.");
+      createCache();
+      return null;
+    }
+    Session session = createEmptySession();
+    session.setNew(true);
+    session.setValid(true);
+    session.setCreationTime(System.currentTimeMillis());
+    String id = sessionId;
+    if (id == null) {
+      id = generateSessionId();
+    }
+    session.setId(id);
+    return session;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public void addSession(final ClusterSession session){
+    if(sessions == null) {
+      LOGGER.debug("Session cache not present, try to create.");
+      createCache();
+      return;
+    }
+    ClusterSessionData sessionData = ClusterSessionUtil.getSerializableSession(session);
+    sessions.put(session.getId(), sessionData);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public Session createEmptySession(){
+    return getNewSession();
+  }
+
+  /**
+   * Returns information about the session with the given session id.
+   *
+   * <p>The session information is organized as a HashMap, mapping
+   * session attribute names to the String representation of their values.
+   *
+   * @param sessionId Session id
+   *
+   * @return HashMap mapping session attribute names to the String
+   * representation of their values, or null if no session with the
+   * specified id exists, or if the session does not have any attributes
+   */
+  public HashMap<String, String> getSession(String sessionId) {
+    if(sessions == null) {
+      LOGGER.debug("Session cache not present, try to create.");
+      createCache();
+      return null;
+    }
+    ClusterSessionData sessionData = sessions.get(sessionId);
+    if (sessionData == null) {
+      return null;
+    }
+    ClusterSession s = ClusterSessionUtil.getDeserializedSession(sessionData, this, this.manager);
+    Enumeration<String> ee = s.getAttributeNames();
+    if (ee == null || !ee.hasMoreElements()) {
+      return null;
+    }
+    HashMap<String, String> map = new HashMap<String, String>();
+    while (ee.hasMoreElements()) {
+      String attrName = ee.nextElement();
+      map.put(attrName, s.getAttribute(attrName).toString());
+    }
+    return map;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public void updateSession(ClusterSession session) {
+    if(sessions == null) {
+      LOGGER.debug("Session cache not present, try to create.");
+      createCache();
+      return;
+    }
+    if(session.getId() != null && sessions.get(session.getId()) != null){
+      ClusterSessionData sessionData = ClusterSessionUtil.getSerializableSession(session);
+      sessions.put(session.getId(), sessionData);
+    }
+  }
+
+  @Override
+  public IClusterGlobalServices addingService(ServiceReference<IClusterGlobalServices> reference) {
+      if (clusterGlobalServices == null) {
+        this.clusterGlobalServices = context.getService(reference);
+        createCache();
+        return clusterGlobalServices;
+      }
+      return null;
+  }
+
+  @Override
+  public void modifiedService(ServiceReference<IClusterGlobalServices> reference, IClusterGlobalServices service) {
+    // This method is added from ServiceTracker interface, We don't have to modify service.
+  }
+
+  @Override
+  public void removedService(ServiceReference<IClusterGlobalServices> reference, IClusterGlobalServices service) {
+      if (clusterGlobalServices == service) {
+          clusterGlobalServices = null;
+      }
+  }
+
+  /*
+   * Return an instance of Standard Session object with current session manager
+   */
+  private ClusterSession getNewSession() {
+    return new ClusterSession(this.manager, this);
+  }
+
+  /*
+   * Generate and return a new session identifier.
+   */
+  private String generateSessionId() {
+    String result = null;
+    do {
+      result = sessionIdGenerator.generateSessionId();
+    } while (sessions.containsKey(result));
+    return result;
+  }
+
+  private void createCache() {
+    allocateCache();
+    retrieveCache();
+  }
+
+  /*
+   * This is a fragment bundle, so We can't use Activator to set Service.
+   * This is the alternative to get registered clustered service
+   */
+  private void getClusterService(){
+    if (context != null) {
+      clusterTracker = new ServiceTracker<>(context, IClusterGlobalServices.class, this);
+      clusterTracker.open();
+    }
+  }
+
+  /*
+   * Allocate space in infinispan to persist session data
+   */
+  private void allocateCache() {
+    if (clusterGlobalServices == null) {
+      LOGGER.trace("un-initialized clusterGlobalService, can't create cache");
+      return;
+    }
+    try {
+      clusterGlobalServices.createCache(SESSION_CACHE,
+          EnumSet.of(IClusterServices.cacheMode.SYNC , IClusterServices.cacheMode.TRANSACTIONAL));
+
+    } catch (CacheConfigException cce) {
+      LOGGER.error("Cache configuration invalid - check cache mode", cce.toString());
+    } catch (CacheExistException ce) {
+      LOGGER.debug("Skipping cache creation as already present", ce.toString());
+    }
+  }
+
+  /*
+   * Fetch cached session data map object from infinispan
+   */
+  @SuppressWarnings("unchecked")
+  private void retrieveCache(){
+    if (clusterGlobalServices == null) {
+      LOGGER.trace("un-initialized clusterGlobalService, can't retrieve cache");
+      return;
+    }
+    sessions = (ConcurrentMap<String, ClusterSessionData>)clusterGlobalServices.getCache(SESSION_CACHE);
+    if(sessions == null){
+      LOGGER.warn("Failed to get session cache");
+    }
+  }
+}
\ No newline at end of file
diff --git a/opendaylight/samples/clustersession/src/main/java/org/opendaylight/controller/clustersession/service/ClusterSessionService.java b/opendaylight/samples/clustersession/src/main/java/org/opendaylight/controller/clustersession/service/ClusterSessionService.java
new file mode 100644 (file)
index 0000000..9991b33
--- /dev/null
@@ -0,0 +1,84 @@
+package org.opendaylight.controller.clustersession.service;
+
+import java.util.HashMap;
+
+import org.apache.catalina.Session;
+import org.apache.catalina.util.SessionIdGenerator;
+import org.opendaylight.controller.clustersession.ClusterSession;
+
+/**
+ * A service to handle session persistence and retrieval in any data store
+ *
+ * @author harman singh
+ *
+ */
+public interface ClusterSessionService {
+
+  /**
+   * This method performs all startup operations
+   */
+  void startInternal(SessionIdGenerator sessionIdGenerator);
+
+  /**
+   * Method to perform all clean up operations
+   */
+  void stopInternal();
+
+  /**
+   * Find Session object based on provided session id from persistance
+   * @param id
+   * @return an instance of Session
+   */
+  Session findSession(final String id);
+
+  /**
+   * Get an array of session objects available in storage
+   */
+  Session[] findSessions();
+
+  /**
+   * Remove a session object from persistence
+   * @param id of session object need to be removed
+   */
+  void removeSession(final String id);
+
+  /**
+   * Expire and remove a session object from persistence
+   * @param id of session object need to be expired
+   */
+  void expireSession(final String id);
+
+  /**
+   * Create a session object based on session id, if session is not present
+   * use random session id
+   * @param sessionId
+   * @return an instance of Session
+   */
+  Session createSession(final String sessionId);
+
+  /**
+   * Add a session object in persistence
+   * @param session an instance of ClusterSession
+   */
+  void addSession(final ClusterSession session);
+
+  /**
+   * Create an empty Session object
+   * @return session object
+   */
+  Session createEmptySession();
+
+  /**
+   * Fetch attributes of Session object fetched by supplied session id
+   * @param sessionId
+   * @return
+   */
+  HashMap<String, String> getSession(String sessionId);
+
+  /**
+   * update the session object in persistence
+   * @param session
+   */
+  void updateSession(final ClusterSession session);
+
+}
diff --git a/opendaylight/samples/clustersession/src/test/java/org/opendaylight/controller/clustersession/ClusterSessionManagerTest.java b/opendaylight/samples/clustersession/src/test/java/org/opendaylight/controller/clustersession/ClusterSessionManagerTest.java
new file mode 100644 (file)
index 0000000..9aae2bb
--- /dev/null
@@ -0,0 +1,157 @@
+package org.opendaylight.controller.clustersession;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import org.apache.catalina.Context;
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.Session;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.opendaylight.controller.clustering.services.IClusterGlobalServices;
+import org.opendaylight.controller.clustersession.impl.ClusterSessionServiceImpl;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.ServiceReference;
+import org.powermock.api.mockito.PowerMockito;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+@RunWith(PowerMockRunner.class)
+@PrepareForTest({FrameworkUtil.class})
+public class ClusterSessionManagerTest {
+  static ClusterSessionManager manager = null;
+  static ClusterSessionServiceImpl sessionService = null;
+  private static final String SESSION_CACHE = "customSessionManager.sessionData";
+  static ConcurrentMap<String, ClusterSessionData> sessions = new ConcurrentHashMap<String, ClusterSessionData>();
+  private String sessionId = "1234567";
+  final String AUTH_TYPE = "FORM";
+  final String ATTRIBUTE_NAME = "AuthType";
+  final int SESSION_ID_LENGTH = 7;
+  @SuppressWarnings("unchecked")
+  @BeforeClass
+  public static void init(){
+    Bundle bundle = mock(Bundle.class);
+    BundleContext context = mock(BundleContext.class);
+    IClusterGlobalServices clusterGlobalService = mock(IClusterGlobalServices.class);
+    ServiceReference<IClusterGlobalServices> serviceReference = mock(ServiceReference.class);
+    PowerMockito.mockStatic(FrameworkUtil.class);
+    when(FrameworkUtil.getBundle(ClusterSessionManager.class)).thenReturn(bundle);
+    when(bundle.getBundleContext()).thenReturn(context);
+    when(context.getService(serviceReference)).thenReturn(clusterGlobalService);
+    when((ConcurrentMap<String, ClusterSessionData>)clusterGlobalService.getCache(SESSION_CACHE)).thenReturn(sessions);
+    Context containerContext = mock(Context.class);
+    manager = new ClusterSessionManager();
+    manager.setContainer(containerContext);
+    try {
+      manager.startInternal();
+    } catch (LifecycleException e) {
+    }
+    sessionService = (ClusterSessionServiceImpl) manager.getSessionService();
+    sessionService.addingService(serviceReference);
+  }
+
+  @Test
+  public void checkSessionManagerCreated(){
+    assertEquals("session manager info does not match", "ClusterSessionManager/1.0", manager.getInfo());
+    assertEquals("session manager name does not match", "ClusterSessionManager", manager.getName());
+  }
+
+  @Test
+  public void testCreateEmptySession(){
+    Session session = manager.createEmptySession();
+    assertEquals("session manager does not match", manager, session.getManager());
+  }
+
+  @Test
+  public void testCreateRandomSessionId(){
+    Session session = manager.createSession(null);
+    assertEquals("Session should be valid", true, session.isValid());
+    manager.remove(session);
+  }
+
+  @Test
+  public void testCreateSession(){
+    Session session = manager.createSession(sessionId);
+    assertEquals("Session should be valid", true, session.isValid());
+    assertEquals("Session id does not match", sessionId, session.getId());
+    manager.remove(session);
+  }
+
+  @Test
+  public void testReCreateSession(){
+    Session session = manager.createSession(sessionId);
+    assertEquals("Session should be valid", true, session.isValid());
+    assertEquals("Session id does not match", sessionId, session.getId());
+    manager.createSession(sessionId);
+    manager.remove(session);
+  }
+
+  @Test
+  public void testSessionCRUD() throws IOException{
+    Session foundSession = manager.findSession(sessionId);
+    assertNull("Session should not exist here", foundSession);
+    Session session = manager.createSession(sessionId);
+    manager.add(session);
+    foundSession = manager.findSession(sessionId);
+    assertEquals("Session was not found, id does not match", sessionId, foundSession.getId());
+    manager.remove(session);
+    foundSession = manager.findSession(sessionId);
+    assertEquals("Session was not removed", null, foundSession);
+  }
+
+  @Test
+  public void testExpireSession() throws IOException{
+    Session session = manager.createSession(sessionId);
+    session.setAuthType(AUTH_TYPE);
+    manager.add(session);
+    Session foundSession = manager.findSession(sessionId);
+    assertEquals("Session was not found", sessionId, foundSession.getId());
+    manager.expireSession(sessionId);
+    foundSession = manager.findSession(sessionId);
+    assertEquals("Session was not expired", null, foundSession);
+  }
+
+  @Test
+  public void testFindSessions(){
+    Session session = manager.createSession(sessionId);
+    session.setAuthType(AUTH_TYPE);
+    manager.add(session);
+    Session[] sessions = manager.findSessions();
+    assertEquals("Session array size does not match", 1, sessions.length);
+    assertEquals("Session array size does not match", sessionId, sessions[0].getId());
+    manager.remove(session);
+  }
+
+  @Test
+  public void testGetSession(){
+    ClusterSession session = (ClusterSession) manager.createSession(sessionId);
+    session.setAttribute(ATTRIBUTE_NAME, AUTH_TYPE);
+    manager.add(session);
+    HashMap<String, String> sessionAttributes = manager.getSession(sessionId);
+    assertNotNull("Session attribute should not be null", sessionAttributes);
+    assertEquals("Session attribute size does not match", 1, sessionAttributes.size());
+    assertEquals("Session attribute size does not match", AUTH_TYPE, sessionAttributes.get(ATTRIBUTE_NAME));
+    manager.remove(session);
+  }
+
+  @AfterClass
+  public static void cleanup(){
+    try {
+      manager.stopInternal();
+    } catch (LifecycleException e) {
+    }
+  }
+
+}
\ No newline at end of file
diff --git a/opendaylight/samples/clustersession/src/test/java/org/opendaylight/controller/clustersession/ClusterSessionServiceImplTest.java b/opendaylight/samples/clustersession/src/test/java/org/opendaylight/controller/clustersession/ClusterSessionServiceImplTest.java
new file mode 100644 (file)
index 0000000..1835db5
--- /dev/null
@@ -0,0 +1,162 @@
+package org.opendaylight.controller.clustersession;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import org.apache.catalina.Context;
+import org.apache.catalina.Session;
+import org.apache.catalina.util.SessionIdGenerator;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.opendaylight.controller.clustering.services.IClusterGlobalServices;
+import org.opendaylight.controller.clustersession.impl.ClusterSessionServiceImpl;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.ServiceReference;
+import org.powermock.api.mockito.PowerMockito;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+@RunWith(PowerMockRunner.class)
+@PrepareForTest({FrameworkUtil.class})
+public class ClusterSessionServiceImplTest {
+  static ClusterSessionManager manager = null;
+  static ClusterSessionServiceImpl sessionService = null;
+  private static final String SESSION_CACHE = "customSessionManager.sessionData";
+  static ConcurrentMap<String, ClusterSessionData> sessions = new ConcurrentHashMap<String, ClusterSessionData>();
+  private String sessionId = "1234567";
+  final String AUTH_TYPE = "FORM";
+  final String ATTRIBUTE_NAME = "AuthType";
+
+  @SuppressWarnings("unchecked")
+  @BeforeClass
+  public static void init(){
+    Bundle bundle = mock(Bundle.class);
+    BundleContext context = mock(BundleContext.class);
+    IClusterGlobalServices clusterGlobalService = mock(IClusterGlobalServices.class);
+    ServiceReference<IClusterGlobalServices> serviceReference = mock(ServiceReference.class);
+    PowerMockito.mockStatic(FrameworkUtil.class);
+    when(FrameworkUtil.getBundle(ClusterSessionManager.class)).thenReturn(bundle);
+    when(bundle.getBundleContext()).thenReturn(context);
+    when(context.getService(serviceReference)).thenReturn(clusterGlobalService);
+    when((ConcurrentMap<String, ClusterSessionData>)clusterGlobalService.getCache(SESSION_CACHE)).thenReturn(sessions);
+    Context containerContext = mock(Context.class);
+    manager = new ClusterSessionManager();
+    sessionService = (ClusterSessionServiceImpl) manager.getSessionService();
+    manager.setContainer(containerContext);
+    sessionService.startInternal(new SessionIdGenerator());
+    sessionService.addingService(serviceReference);
+  }
+
+  @Test
+  public void testCreateEmptySession(){
+    Session session = sessionService.createEmptySession();
+    assertEquals("session manager does not match", manager, session.getManager());
+  }
+
+  @Test
+  public void testCreateSessionwithRandomId(){
+    Session session = sessionService.createSession(null);
+    assertEquals("Session should be valid", true, session.isValid());
+    sessionService.removeSession(session.getId());
+  }
+
+  @Test
+  public void testCreateSession(){
+    Session session = sessionService.createSession(sessionId);
+    assertEquals("Session should be valid", true, session.isValid());
+    assertEquals("Session id does not match", sessionId, session.getId());
+    sessionService.removeSession(sessionId);
+  }
+
+  @Test
+  public void testNullfindSession() {
+    Session session = sessionService.findSession(null);
+    assertNull("Session should be null", session);
+  }
+
+  @Test
+  public void testSessionCRUD(){
+    Session foundSession = sessionService.findSession(sessionId);
+    assertNull("Session should not exist here", foundSession);
+    Session session = sessionService.createSession(sessionId);
+    foundSession = sessionService.findSession(sessionId);
+    assertEquals("Session was not added", sessionId, foundSession.getId());
+    session.setAuthType(AUTH_TYPE);
+    sessionService.updateSession((ClusterSession)session);
+    foundSession = sessionService.findSession(sessionId);
+    assertEquals("Session was not found, id does not match", sessionId, foundSession.getId());
+    assertEquals("Session was not found, auth type does match", AUTH_TYPE, foundSession.getAuthType());
+    sessionService.removeSession(sessionId);
+    foundSession = sessionService.findSession(sessionId);
+    assertEquals("Session was not removed", null, foundSession);
+  }
+
+  @Test
+  public void testExpireSession(){
+    Session session = sessionService.createSession(sessionId);
+    session.setAuthType(AUTH_TYPE);
+    sessionService.addSession((ClusterSession)session);
+    Session foundSession = sessionService.findSession(sessionId);
+    assertEquals("Session was not found", sessionId, foundSession.getId());
+    sessionService.expireSession(sessionId);
+    foundSession = sessionService.findSession(sessionId);
+    assertEquals("Session was not expired", null, foundSession);
+  }
+
+  @Test
+  public void testFindSessions(){
+    Session session = sessionService.createSession(sessionId);
+    session.setAuthType(AUTH_TYPE);
+    sessionService.addSession((ClusterSession)session);
+    Session[] sessions = sessionService.findSessions();
+    assertEquals("Session array size does not match", 1, sessions.length);
+    assertEquals("Session array size does not match", sessionId, sessions[0].getId());
+    sessionService.removeSession(sessionId);
+  }
+
+  @Test
+  public void testGetSession(){
+    ClusterSession session = (ClusterSession) sessionService.createSession(sessionId);
+    session.setAttribute(ATTRIBUTE_NAME, AUTH_TYPE);
+    HashMap<String, String> sessionAttributes = sessionService.getSession(sessionId);
+    assertNotNull("Session attribute should not be null", sessionAttributes);
+    assertEquals("Session attribute size does not match", 1, sessionAttributes.size());
+    assertEquals("Session attribute size does not match", AUTH_TYPE, sessionAttributes.get(ATTRIBUTE_NAME));
+    sessionService.removeSession(sessionId);
+  }
+
+  @Test
+  public void testNullSessionCache(){
+    ClusterSessionManager clustermanager = new ClusterSessionManager();
+    ClusterSessionServiceImpl service = new ClusterSessionServiceImpl(clustermanager);
+    Session session = service.findSession(sessionId);
+    assertNull("Session should be null, as cache is null", session);
+    Session[] sessions = service.findSessions();
+    assertEquals("Session array should be empty", 0, sessions.length);
+    service.removeSession(sessionId);
+    service.expireSession(sessionId);
+    session = service.createSession(sessionId);
+    assertNull("Session should be null, as cache is null", session);
+    service.addSession(null);
+    Map<String,String> attributes = service.getSession(sessionId);
+    assertNull("Attributes should be null, as cache is null", attributes);
+    service.updateSession(null);
+  }
+
+  @AfterClass
+  public static void cleanup(){
+      sessionService.stopInternal();
+  }
+}
\ No newline at end of file
diff --git a/opendaylight/samples/clustersession/src/test/java/org/opendaylight/controller/clustersession/ClusterSessionUtilTest.java b/opendaylight/samples/clustersession/src/test/java/org/opendaylight/controller/clustersession/ClusterSessionUtilTest.java
new file mode 100644 (file)
index 0000000..563b2be
--- /dev/null
@@ -0,0 +1,156 @@
+package org.opendaylight.controller.clustersession;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInput;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutput;
+import java.io.ObjectOutputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+import org.apache.catalina.authenticator.Constants;
+import org.apache.catalina.authenticator.SavedRequest;
+import org.apache.catalina.realm.GenericPrincipal;
+import org.junit.Test;
+import org.opendaylight.controller.clustersession.impl.ClusterSessionServiceImpl;
+import org.opendaylight.controller.clustersession.service.ClusterSessionService;
+
+
+public class ClusterSessionUtilTest {
+
+  final String AUTH_TYPE = "FORM";
+  final String ADMIN = "admin";
+  final String REQUEST_URI = "/test";
+  final String BLANK = "";
+  final String HEADER_NAME = "ContentType";
+  final String HEADER_VALUE = "JSON";
+  final long creationTime = 54545454L;
+  final int interval = 0;
+  ClusterSessionManager manager = new ClusterSessionManager();
+  ClusterSessionService sessionService = new ClusterSessionServiceImpl(manager);
+
+  @Test
+  public void testNullSerializableClusterSession() {
+    ClusterSessionData sessionData = ClusterSessionUtil.getSerializableSession(null);
+    assertEquals("Session data should be null for null session", null, sessionData);
+  }
+
+  @Test
+  public void testSerializableClusterSession() {
+    ClusterSession customSession = createClusterSesion();
+    ClusterSessionData sessionData = ClusterSessionUtil.getSerializableSession(customSession);
+    assertEquals("Session authentication type not valid", AUTH_TYPE, sessionData.getAuthType());
+    assertEquals("Session username does not match", ADMIN, sessionData.getUserName());
+    assertEquals("Session password does not match", ADMIN, sessionData.getPassword());
+    assertEquals("Session prinicpal does not match", ADMIN, sessionData.getSession().getPrincipal().getName());
+  }
+
+  @Test
+  public void testNullDeserialzableclusterSession() {
+    ClusterSession session =  ClusterSessionUtil.getDeserializedSession(null, sessionService, manager);
+    assertEquals("Session should be null for null session data", null, session);
+  }
+
+  @Test
+  public void testDeserializableClusterSesion() {
+    ClusterSession customSession = createClusterSesion();
+    ClusterSessionData sessionData = ClusterSessionUtil.getSerializableSession(customSession);
+    customSession = sessionData.getSession();
+    customSession.setAuthType(AUTH_TYPE);
+    customSession.setNote(Constants.FORM_PRINCIPAL_NOTE, BLANK);
+    customSession.setNote(Constants.FORM_REQUEST_NOTE, BLANK);
+    ClusterSession session = ClusterSessionUtil.getDeserializedSession(sessionData, sessionService, manager);
+    assertEquals("Session authentication type not valid", AUTH_TYPE, session.getAuthType());
+    assertEquals("prinicpal name is not valid", ADMIN, session.getPrincipal().getName());
+    SavedRequest savedRequest = (SavedRequest)session.getNote(Constants.FORM_REQUEST_NOTE);
+    assertEquals("saved request uri does not match", REQUEST_URI, savedRequest.getRequestURI());
+    assertEquals("saved request header does not match", HEADER_VALUE, savedRequest.getHeaderValues(HEADER_NAME).next());
+    assertEquals("saved request header does not match", Locale.ENGLISH, savedRequest.getLocales().next());
+    String username = (String)session.getNote(Constants.FORM_USERNAME);
+    assertEquals("username does not match", ADMIN, username);
+    String password = (String)session.getNote(Constants.FORM_PASSWORD);
+    assertEquals("password does not match", ADMIN, password);
+    assertEquals("session manager does not match", manager, session.getManager());
+    assertEquals("session creation time does not match", creationTime, session.getCreationTime());
+    assertEquals("session man inactive interval does not match", interval, session.getMaxInactiveInterval());
+    assertEquals("is session new does not match", true, session.isNew());
+    assertEquals("is session valid does not match", true, session.isValid());
+  }
+
+  @Test
+  public void testSerializationtoFile(){
+    ClusterSession customSession = createClusterSesion();
+    ClusterSessionData sessionData = ClusterSessionUtil.getSerializableSession(customSession);
+    try(
+        OutputStream file = new FileOutputStream("sessionData.ser");
+        OutputStream buffer = new BufferedOutputStream(file);
+        ObjectOutput output = new ObjectOutputStream(buffer);
+        ){
+      output.writeObject(sessionData);
+    }
+    catch(IOException ex){
+      fail("IO exception while serializing object to a file.");
+    }
+    try(
+        InputStream file = new FileInputStream("sessionData.ser");
+        InputStream buffer = new BufferedInputStream(file);
+        ObjectInput input = new ObjectInputStream (buffer);
+        ){
+      //deserialize the session
+      ClusterSessionData recovedSession = (ClusterSessionData)input.readObject();
+      //display its data
+      ClusterSession session = ClusterSessionUtil.getDeserializedSession(recovedSession, sessionService, manager);
+      assertEquals("Session authentication type not valid", AUTH_TYPE, session.getAuthType());
+      assertEquals("prinicpal name is not valid", ADMIN, session.getPrincipal().getName());
+      SavedRequest savedRequest = (SavedRequest)session.getNote(Constants.FORM_REQUEST_NOTE);
+      assertEquals("saved request uri is not valid", REQUEST_URI, savedRequest.getRequestURI());
+      assertEquals("saved request header does not match", HEADER_VALUE, savedRequest.getHeaderValues(HEADER_NAME).next());
+      assertEquals("saved request header does not match", Locale.ENGLISH, savedRequest.getLocales().next());
+      String username = (String)session.getNote(Constants.FORM_USERNAME);
+      assertEquals("username does not match", ADMIN, username);
+      String password = (String)session.getNote(Constants.FORM_PASSWORD);
+      assertEquals("password does not match", ADMIN, password);
+    }
+    catch(ClassNotFoundException ex){
+      fail("Exception in object deserialization from file");
+    }
+    catch(IOException ex){
+      fail("Exception in object deserialization from file");
+    }
+    File serializedFile = new File("sessionData.ser");
+    serializedFile.delete();
+  }
+
+  private ClusterSession createClusterSesion(){
+    ClusterSession clusterSession = new ClusterSession(manager, sessionService);
+    clusterSession.setAuthType(AUTH_TYPE);
+    clusterSession.setCreationTime(creationTime);
+    clusterSession.setMaxInactiveInterval(interval);
+    clusterSession.setNew(true);
+    clusterSession.setValid(true);
+    List<String> roles = new ArrayList<String>();
+    roles.add(ADMIN);
+    GenericPrincipal principal = new GenericPrincipal(ADMIN, ADMIN, roles);
+    clusterSession.setPrincipal(principal);
+    clusterSession.setNote(Constants.FORM_PRINCIPAL_NOTE, principal);
+    SavedRequest savedRequest = new SavedRequest();
+    savedRequest.setRequestURI(REQUEST_URI);
+    savedRequest.addHeader(HEADER_NAME, HEADER_VALUE);
+    savedRequest.addLocale(Locale.ENGLISH);
+    clusterSession.setNote(Constants.FORM_REQUEST_NOTE, savedRequest);
+    clusterSession.setNote(Constants.FORM_USERNAME, ADMIN);
+    clusterSession.setNote(Constants.FORM_PASSWORD, ADMIN);
+    return clusterSession;
+  }
+}