Skip to content

Commit

Permalink
extract a more generic approach
Browse files Browse the repository at this point in the history
  • Loading branch information
madness-inc committed Aug 24, 2022
1 parent 325610e commit 66de321
Show file tree
Hide file tree
Showing 10 changed files with 917 additions and 266 deletions.
2 changes: 1 addition & 1 deletion pom.xml
Expand Up @@ -4,7 +4,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>org.appng</groupId>
<artifactId>appng-tomcat-session</artifactId>
<version>0.3.2-SNAPSHOT</version>
<version>0.4.0-SNAPSHOT</version>
<description>appNG Tomcat Session</description>
<build>
<plugins>
Expand Down
Expand Up @@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.appng.tomcat.session.hazelcast;
package org.appng.tomcat.session;

import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
Expand All @@ -28,20 +28,17 @@
import java.util.Map;

import org.apache.catalina.Manager;
import org.apache.catalina.Session;
import org.appng.tomcat.session.SessionData;
import org.appng.tomcat.session.Utils;

/**
* A {@link Session} that can be flagged as dirty.
*/
public class HazelcastSession extends org.apache.catalina.session.StandardSession {
public class Session extends org.apache.catalina.session.StandardSession {

private static String DIRTY_FLAG = "__changed__";
private static final long serialVersionUID = -5219705900405324572L;
protected volatile transient boolean dirty = false;

public HazelcastSession(Manager manager) {
public Session(Manager manager) {
super(manager);
}

Expand Down Expand Up @@ -94,6 +91,7 @@ public int checksum() throws IOException {
attributes.put(key, getAttribute(key));
}
}
//attributes.put("lastAccess", getLastAccessedTimeInternal());

try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(new BufferedOutputStream(bos));) {
Expand All @@ -117,11 +115,11 @@ public SessionData serialize(String alternativeSiteName) throws IOException {
}
}

public static HazelcastSession create(Manager manager, SessionData sessionData)
public static Session create(Manager manager, SessionData sessionData)
throws IOException, ClassNotFoundException {
try (ByteArrayInputStream is = new ByteArrayInputStream(sessionData.getData());
ObjectInputStream ois = Utils.getObjectInputStream(is, sessionData.getSite(), manager.getContext())) {
HazelcastSession session = (HazelcastSession) manager.createEmptySession();
Session session = (Session) manager.createEmptySession();
session.readObjectData(ois);
session.access();
session.setClean();
Expand Down
175 changes: 175 additions & 0 deletions src/main/java/org/appng/tomcat/session/SessionManager.java
@@ -0,0 +1,175 @@
/*
* Copyright 2015-2022 the original author or authors.
*
* 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.
*/
package org.appng.tomcat.session;

import java.io.IOException;
import java.util.Arrays;
import java.util.Locale;
import java.util.concurrent.atomic.AtomicInteger;

import org.apache.catalina.LifecycleException;
import org.apache.catalina.LifecycleState;
import org.apache.catalina.session.ManagerBase;
import org.apache.juli.logging.Log;

public abstract class SessionManager<T> extends ManagerBase {

private static final double NANOS_TO_MILLIS = 1000000d;

protected abstract void updateSession(String id, SessionData sessionData) throws IOException;

protected abstract SessionData findSessionInternal(String id) throws IOException;

public abstract void removeInternal(org.apache.catalina.Session session, boolean update);

public abstract Log log();

protected abstract T getPersistentSessions();

@Override
protected void stopInternal() throws LifecycleException {
super.stopInternal();
setState(LifecycleState.STOPPING);
}

@Override
public void processExpires() {
long timeNow = System.currentTimeMillis();
org.apache.catalina.Session sessions[] = findSessions();
AtomicInteger expireHere = new AtomicInteger(0);
Arrays.asList(sessions).stream().filter(s -> !(s == null || s.isValid()))
.forEach(s -> expireHere.getAndIncrement());
long duration = System.currentTimeMillis() - timeNow;
if (log().isDebugEnabled()) {
log().debug(String.format("Expired %d of %d sessions in %dms.", expireHere.intValue(), sessions.length,
duration));
}
processingTime += duration;
}

@Override
public Session createEmptySession() {
return new Session(this);
}

@Override
public Session createSession(String sessionId) {
Session session = (Session) super.createSession(sessionId);
if (log().isTraceEnabled()) {
log().trace(String.format("Created %s (isNew: %s)", session.getId(), session.isNew()));
}
return session;
}

@Override
public final Session findSession(String id) throws IOException {
Session session = (Session) super.findSession(id);
if (null == session) {
long start = System.nanoTime();
SessionData sessionData = findSessionInternal(id);

if (null == sessionData) {
if (log().isDebugEnabled()) {
log().debug(String.format("Session %s not found!", id));
}
} else {
try {
session = Session.create(this, sessionData);
if (log().isDebugEnabled()) {
log().debug(
String.format(Locale.ENGLISH, "Loaded %s in %.2fms", sessionData, getMillis(start)));
}
} catch (ClassNotFoundException e) {
log().error("Error loading session" + id, e);
}
}
} else {
if (log().isDebugEnabled()) {
log().debug(String.format(Locale.ENGLISH, "Loaded %s form local store.", id));
}
session.access();
}
return session;
}

protected double getMillis(long nanoStart) {
return ((System.nanoTime() - nanoStart)) / NANOS_TO_MILLIS;
}

public final boolean commit(Session session) throws IOException {
return commit(session, null);
}

public final boolean commit(org.apache.catalina.Session session, String alternativeSiteName) throws IOException {
long start = System.nanoTime();
Session sessionInternal = Session.class.cast(session);
session.endAccess();
int oldChecksum = -1;
boolean sessionDirty = false;
if ((sessionDirty = sessionInternal.isDirty())
|| (oldChecksum = findSessionInternal(session.getId()).checksum()) != sessionInternal.checksum()) {
SessionData sessionData = sessionInternal.serialize(alternativeSiteName);
updateSession(sessionInternal.getId(), sessionData);
if (log().isDebugEnabled()) {
String reason = sessionDirty ? "dirty-flag was set" : String.format("checksum <> %s", oldChecksum);
log().debug(String.format(Locale.ENGLISH, "Saved %s (%s) in %.2fms", sessionData, reason,
getMillis(start)));
}
return true;
} else if (log().isDebugEnabled()) {
log().debug(
String.format("No changes in %s with checksum %s", session.getId(), sessionInternal.checksum()));
}
return false;
}

@Override
public void add(org.apache.catalina.Session session) {
super.add(session);
if (log().isTraceEnabled()) {
log().trace(String.format("Added %s", session.getId()));
}
}

@Override
public void remove(org.apache.catalina.Session session, boolean update) {
super.remove(session, update);
removeInternal(session, update);
}

/**
* Remove this Session from the active Sessions, but not from the {@link SessionPersister}
*
* @param session
* Session to be removed
*/
public void removeLocal(Session session) {
if (session.getIdInternal() != null) {
sessions.remove(session.getIdInternal());
}
}

@Override
public void load() throws ClassNotFoundException, IOException {
// don't load all sessions when starting
}

@Override
public void unload() throws IOException {
// don't save all sessions when stopping
}

}
86 changes: 86 additions & 0 deletions src/main/java/org/appng/tomcat/session/SessionTrackerValve.java
@@ -0,0 +1,86 @@
/*
* Copyright 2015-2022 the original author or authors.
*
* 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.
*/
package org.appng.tomcat.session;

import java.io.IOException;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

import javax.servlet.ServletException;

import org.apache.catalina.Session;
import org.apache.catalina.Valve;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.catalina.valves.ValveBase;
import org.apache.juli.logging.Log;

/**
* A {@link Valve} that uses {@link SessionManager} to store a {@link Session}
*/
public class SessionTrackerValve extends ValveBase {

private final Log log = Utils.getLog(SessionTrackerValve.class);
protected Pattern filter = Pattern.compile("^(/template/.*)|((/health)(\\?.*)?)$");
protected String siteNameHeader = "x-appng-site";

@Override
public void invoke(Request request, Response response) throws IOException, ServletException {
try {
getNext().invoke(request, response);
} finally {
Session session = request.getSessionInternal(false);
if (commitRequired(request.getDecodedRequestURI()) && null != session) {
long start = System.currentTimeMillis();
SessionManager<?> manager = (SessionManager<?>) request.getContext().getManager();
boolean committed = manager.commit(session, request.getHeader(siteNameHeader));
if (log.isDebugEnabled()) {
log.debug(String.format("Handling session %s for %s took %dms (committed: %s)", session.getId(),
request.getServletPath(), System.currentTimeMillis() - start, committed));
}
}
}
}

protected boolean commitRequired(String uri) {
return null == filter || !filter.matcher(uri).matches();
}

public String getSiteNameHeader() {
return siteNameHeader;
}

public void setSiteNameHeader(String siteNameHeader) {
this.siteNameHeader = siteNameHeader;
}

public String getFilter() {
return null == filter ? null : filter.toString();
}

public void setFilter(String filter) {
if (filter == null || filter.length() == 0) {
this.filter = null;
} else {
try {
this.filter = Pattern.compile(filter);
} catch (PatternSyntaxException pse) {
log.error("ivalid pattern", pse);
}
}
}

}

0 comments on commit 66de321

Please sign in to comment.