Commit 9a48355b authored by Matija Obreza's avatar Matija Obreza

Hazelcast upgrade to 3.4 and admin cache stats overview (note the Hazelcast group name change!)

parent 47a282b5
...@@ -61,6 +61,7 @@ ...@@ -61,6 +61,7 @@
<org.springframework.social-google-version>1.0.0.M3</org.springframework.social-google-version> <org.springframework.social-google-version>1.0.0.M3</org.springframework.social-google-version>
<mysql.version>5.1.31</mysql.version> <mysql.version>5.1.31</mysql.version>
<hazelcast.version>3.4</hazelcast.version>
<oval.version>1.81</oval.version> <oval.version>1.81</oval.version>
<jackson.version>2.2.1</jackson.version> <jackson.version>2.2.1</jackson.version>
...@@ -384,17 +385,22 @@ ...@@ -384,17 +385,22 @@
<dependency> <dependency>
<groupId>com.hazelcast</groupId> <groupId>com.hazelcast</groupId>
<artifactId>hazelcast-spring</artifactId> <artifactId>hazelcast-spring</artifactId>
<version>3.1.7</version> <version>${hazelcast.version}</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.hazelcast</groupId> <groupId>com.hazelcast</groupId>
<artifactId>hazelcast-cloud</artifactId> <artifactId>hazelcast-cloud</artifactId>
<version>3.1.7</version> <version>${hazelcast.version}</version>
</dependency>
<dependency>
<groupId>com.hazelcast</groupId>
<artifactId>hazelcast-hibernate4</artifactId>
<version>${hazelcast.version}</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.hazelcast</groupId> <groupId>com.hazelcast</groupId>
<artifactId>hazelcast-wm</artifactId> <artifactId>hazelcast-wm</artifactId>
<version>3.1.7</version> <version>${hazelcast.version}</version>
</dependency> </dependency>
<dependency> <dependency>
......
/*
* Copyright (c) 2008-2013, Hazelcast, Inc. All Rights Reserved.
*
* 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 com.hazelcast.web;
import static com.hazelcast.web.HazelcastInstanceLoader.CLIENT_CONFIG_LOCATION;
import static com.hazelcast.web.HazelcastInstanceLoader.CONFIG_LOCATION;
import static com.hazelcast.web.HazelcastInstanceLoader.INSTANCE_NAME;
import static com.hazelcast.web.HazelcastInstanceLoader.USE_CLIENT;
import java.io.IOException;
import java.io.NotSerializableException;
import java.io.Serializable;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.logging.Level;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionContext;
import com.hazelcast.config.Config;
import com.hazelcast.config.MapConfig;
import com.hazelcast.core.EntryEvent;
import com.hazelcast.core.EntryListener;
import com.hazelcast.core.HazelcastInstance;
import com.hazelcast.core.IMap;
import com.hazelcast.logging.ILogger;
import com.hazelcast.logging.Logger;
import com.hazelcast.nio.serialization.Data;
/**
* Request dispatcher forward+include not wrapping incoming request.
* See https://github.com/hazelcast/hazelcast/pull/1981
* https://github.com/hazelcast/hazelcast/issues/540
*
* @author matijaobreza
*/
public class WebFilter implements Filter {
private static final ILogger logger = Logger.getLogger(WebFilter.class);
private static final String HAZELCAST_REQUEST = "*hazelcast-request";
private static final String HAZELCAST_SESSION_COOKIE_NAME = "hazelcast.sessionId";
private static final ConcurrentMap<String, String> mapOriginalSessions = new ConcurrentHashMap<String, String>(1000);
private static final ConcurrentMap<String, HazelcastHttpSession> mapSessions = new ConcurrentHashMap<String, HazelcastHttpSession>(1000);
private HazelcastInstance hazelcastInstance;
private SerializationHelper serializationHelper;
private String clusterMapName = "none";
private String sessionCookieName = HAZELCAST_SESSION_COOKIE_NAME;
private String sessionCookieDomain = null;
private boolean sessionCookieSecure = false;
private boolean sessionCookieHttpOnly = false;
private boolean stickySession = true;
private boolean debug = false;
private boolean shutdownOnDestroy = true;
private Properties properties;
protected ServletContext servletContext;
protected FilterConfig filterConfig;
public WebFilter() {
}
public WebFilter(Properties properties) {
this();
this.properties = properties;
}
@Override
public final void init(final FilterConfig config) throws ServletException {
filterConfig = config;
servletContext = config.getServletContext();
initInstance();
final String debugParam = getParam("debug");
if (debugParam != null) {
debug = Boolean.valueOf(debugParam);
}
final String mapName = getParam("map-name");
if (mapName != null) {
clusterMapName = mapName;
} else {
clusterMapName = "_web_" + servletContext.getServletContextName();
}
try {
final Config hzConfig = hazelcastInstance.getConfig();
final String sessionTTL = getParam("session-ttl-seconds");
if (sessionTTL != null) {
final MapConfig mapConfig = new MapConfig(clusterMapName);
mapConfig.setTimeToLiveSeconds(Integer.valueOf(sessionTTL));
hzConfig.addMapConfig(mapConfig);
}
} catch (final UnsupportedOperationException ignored) {
// client cannot access Config.
}
final String cookieName = getParam("cookie-name");
if (cookieName != null) {
sessionCookieName = cookieName;
}
final String cookieDomain = getParam("cookie-domain");
if (cookieDomain != null) {
sessionCookieDomain = cookieDomain;
}
final String cookieSecure = getParam("cookie-secure");
if (cookieSecure != null) {
sessionCookieSecure = Boolean.valueOf(cookieSecure);
}
final String cookieHttpOnly = getParam("cookie-http-only");
if (cookieHttpOnly != null) {
sessionCookieHttpOnly = Boolean.valueOf(cookieHttpOnly);
}
final String stickySessionParam = getParam("sticky-session");
if (stickySessionParam != null) {
stickySession = Boolean.valueOf(stickySessionParam);
}
final String shutdownOnDestroyParam = getParam("shutdown-on-destroy");
if (shutdownOnDestroyParam != null) {
shutdownOnDestroy = Boolean.valueOf(shutdownOnDestroyParam);
}
if (!stickySession) {
getClusterMap().addEntryListener(new EntryListener() {
@Override
public void entryAdded(EntryEvent entryEvent) {
}
@Override
public void entryRemoved(EntryEvent entryEvent) {
if (entryEvent.getMember() == null || // client events has
// no owner member
!entryEvent.getMember().localMember()) {
removeSessionLocally((String) entryEvent.getKey());
}
}
@Override
public void entryUpdated(EntryEvent entryEvent) {
if (entryEvent.getMember() == null || // client events has
// no owner member
!entryEvent.getMember().localMember()) {
markSessionDirty((String) entryEvent.getKey());
}
}
@Override
public void entryEvicted(EntryEvent entryEvent) {
entryRemoved(entryEvent);
}
}, false);
}
log("sticky:" + stickySession + ", debug: " + debug + ", shutdown-on-destroy: " + shutdownOnDestroy + ", map-name: " + clusterMapName);
}
private void initInstance() throws ServletException {
if (properties == null) {
properties = new Properties();
}
setProperty(CONFIG_LOCATION);
setProperty(INSTANCE_NAME);
setProperty(USE_CLIENT);
setProperty(CLIENT_CONFIG_LOCATION);
hazelcastInstance = getInstance(properties);
serializationHelper = new SerializationHelper(hazelcastInstance);
}
private void setProperty(String propertyName) {
final String value = getParam(propertyName);
if (value != null) {
properties.setProperty(propertyName, value);
}
}
private void removeSessionLocally(String sessionId) {
final HazelcastHttpSession hazelSession = mapSessions.remove(sessionId);
if (hazelSession != null) {
mapOriginalSessions.remove(hazelSession.originalSession.getId());
log("Destroying session locally " + hazelSession);
hazelSession.destroy();
}
}
private void markSessionDirty(String sessionId) {
final HazelcastHttpSession hazelSession = mapSessions.get(sessionId);
if (hazelSession != null) {
hazelSession.setDirty(true);
}
}
static void destroyOriginalSession(HttpSession originalSession) {
final String hazelcastSessionId = mapOriginalSessions.remove(originalSession.getId());
if (hazelcastSessionId != null) {
final HazelcastHttpSession hazelSession = mapSessions.remove(hazelcastSessionId);
if (hazelSession != null) {
hazelSession.webFilter.destroySession(hazelSession, false);
}
}
}
protected void log(final Object obj) {
Level level = Level.FINEST;
if (debug) {
level = Level.INFO;
}
logger.log(level, obj.toString());
}
private HazelcastHttpSession createNewSession(RequestWrapper requestWrapper, String existingSessionId) {
final String id = existingSessionId != null ? existingSessionId : generateSessionId();
if (requestWrapper.getOriginalSession(false) != null) {
log("Original session exists!!!");
}
final HttpSession originalSession = requestWrapper.getOriginalSession(true);
final HazelcastHttpSession hazelcastSession = new HazelcastHttpSession(WebFilter.this, id, originalSession);
mapSessions.put(hazelcastSession.getId(), hazelcastSession);
final String oldHazelcastSessionId = mapOriginalSessions.put(originalSession.getId(), hazelcastSession.getId());
if (oldHazelcastSessionId != null) {
log("!!! Overriding an existing hazelcastSessionId " + oldHazelcastSessionId);
}
log("Created new session with id: " + id);
log(mapSessions.size() + " is sessions.size and originalSessions.size: " + mapOriginalSessions.size());
addSessionCookie(requestWrapper, id);
return hazelcastSession;
}
/**
* Destroys a session, determining if it should be destroyed clusterwide
* automatically or via expiry.
*
* @param session
* The session to be destroyed
* @param removeGlobalSession
* boolean value - true if the session should be destroyed
* irrespective of active time
*/
private void destroySession(HazelcastHttpSession session, boolean removeGlobalSession) {
log("Destroying local session: " + session.getId());
mapSessions.remove(session.getId());
mapOriginalSessions.remove(session.originalSession.getId());
session.destroy();
if (removeGlobalSession) {
log("Destroying cluster session: " + session.getId() + " => Ignore-timeout: true");
getClusterMap().remove(session.getId());
}
}
private IMap getClusterMap() {
return hazelcastInstance.getMap(clusterMapName);
}
private HazelcastHttpSession getSessionWithId(final String sessionId) {
HazelcastHttpSession session = mapSessions.get(sessionId);
if (session != null && !session.isValid()) {
destroySession(session, true);
session = null;
}
return session;
}
private class RequestWrapper extends HttpServletRequestWrapper {
HazelcastHttpSession hazelcastSession = null;
final ResponseWrapper res;
String requestedSessionId;
public RequestWrapper(final HttpServletRequest req, final ResponseWrapper res) {
super(req);
this.res = res;
req.setAttribute(HAZELCAST_REQUEST, this);
}
public void setHazelcastSession(HazelcastHttpSession hazelcastSession, String requestedSessionId) {
this.hazelcastSession = hazelcastSession;
this.requestedSessionId = requestedSessionId;
}
HttpSession getOriginalSession(boolean create) {
return super.getSession(create);
}
public String fetchHazelcastSessionId() {
if (requestedSessionId != null) {
return requestedSessionId;
}
requestedSessionId = getSessionCookie(this);
return requestedSessionId;
}
@Override
public HttpSession getSession() {
return getSession(true);
}
@Override
public HazelcastHttpSession getSession(final boolean create) {
if (hazelcastSession != null && !hazelcastSession.isValid()) {
log("Session is invalid!");
destroySession(hazelcastSession, true);
hazelcastSession = null;
}
if (hazelcastSession == null) {
final HttpSession originalSession = getOriginalSession(false);
if (originalSession != null) {
final String hazelcastSessionId = mapOriginalSessions.get(originalSession.getId());
if (hazelcastSessionId != null) {
hazelcastSession = mapSessions.get(hazelcastSessionId);
}
if (hazelcastSession == null) {
mapOriginalSessions.remove(originalSession.getId());
originalSession.invalidate();
} else if (hazelcastSession.isDirty()) {
hazelcastSession = null;
}
}
}
if (hazelcastSession != null) {
return hazelcastSession;
}
final String requestedSessionId = fetchHazelcastSessionId();
if (requestedSessionId != null) {
hazelcastSession = getSessionWithId(requestedSessionId);
if (hazelcastSession == null) {
final Map mapSession = (Map) getClusterMap().get(requestedSessionId);
if (mapSession != null) {
// we already have the session in the cluster
// loading it...
hazelcastSession = createNewSession(RequestWrapper.this, requestedSessionId);
overrideSession(hazelcastSession, mapSession);
}
}
}
if (hazelcastSession == null && create) {
hazelcastSession = createNewSession(RequestWrapper.this, null);
} else if (hazelcastSession != null && !stickySession && requestedSessionId != null && hazelcastSession.isDirty()) {
log(requestedSessionId + " is dirty reloading.");
final Map mapSession = (Map) getClusterMap().get(requestedSessionId);
overrideSession(hazelcastSession, mapSession);
}
return hazelcastSession;
}
private void overrideSession(HazelcastHttpSession session, Map mapSession) {
if (session == null || mapSession == null) {
return;
}
final Enumeration<String> atts = session.getAttributeNames();
while (atts.hasMoreElements()) {
session.removeAttribute(atts.nextElement());
}
Map mapData = null;
final Set<Map.Entry> entries = mapSession.entrySet();
for (final Map.Entry entry : entries) {
session.setAttribute((String) entry.getKey(), entry.getValue());
if (mapData == null) {
mapData = new HashMap<String, Object>();
}
mapData.put(entry.getKey(), entry.getValue());
}
session.sessionChanged(session.writeObject(mapData));
session.setDirty(false);
}
} // END of RequestWrapper
private class ResponseWrapper extends HttpServletResponseWrapper {
RequestWrapper req = null;
public ResponseWrapper(final HttpServletResponse original) {
super(original);
}
public void setRequest(final RequestWrapper req) {
this.req = req;
}
}
private class HazelcastHttpSession implements HttpSession {
private Data currentSessionData = null;
volatile boolean valid = true;
volatile boolean dirty = false;
final String id;
final HttpSession originalSession;
final WebFilter webFilter;
public HazelcastHttpSession(WebFilter webFilter, final String sessionId, HttpSession originalSession) {
this.webFilter = webFilter;
this.id = sessionId;
this.originalSession = originalSession;
}
@Override
public Object getAttribute(final String name) {
return originalSession.getAttribute(name);
}
@Override
public Enumeration getAttributeNames() {
return originalSession.getAttributeNames();
}
@Override
public String getId() {
return id;
}
@Override
public ServletContext getServletContext() {
return servletContext;
}
@Override
public HttpSessionContext getSessionContext() {
return originalSession.getSessionContext();
}
@Override
public Object getValue(final String name) {
return getAttribute(name);
}
@Override
public String[] getValueNames() {
return originalSession.getValueNames();
}
public boolean isDirty() {
return dirty;
}
public void setDirty(boolean dirty) {
this.dirty = dirty;
}
@Override
public void invalidate() {
originalSession.invalidate();
destroySession(this, true);
}
@Override
public boolean isNew() {
return originalSession.isNew();
}
@Override
public void putValue(final String name, final Object value) {
setAttribute(name, value);
}
@Override
public void removeAttribute(final String name) {
originalSession.removeAttribute(name);
}
@Override
public void setAttribute(final String name, final Object value) {
if (value != null && !(value instanceof Serializable)) {
throw new IllegalArgumentException(new NotSerializableException(value.getClass().getName()));
}
originalSession.setAttribute(name, value);
}
@Override
public void removeValue(final String name) {
removeAttribute(name);
}
public boolean sessionChanged(final Data data) {
try {
if (data == null) {
return currentSessionData != null;
}
return currentSessionData == null || !data.equals(currentSessionData);
} finally {
currentSessionData = data;
}
}
@Override
public long getCreationTime() {
return originalSession.getCreationTime();
}
@Override
public long getLastAccessedTime() {
return originalSession.getLastAccessedTime();
}
@Override
public int getMaxInactiveInterval() {
return originalSession.getMaxInactiveInterval();
}
@Override
public void setMaxInactiveInterval(int maxInactiveSeconds) {
originalSession.setMaxInactiveInterval(maxInactiveSeconds);
}
private Data writeObject(final Object obj) {
if (obj == null) {
return null;
}
return serializationHelper.toData(obj);
}
void destroy() {
valid = false;
}
public boolean isValid() {
return valid;
}
}// END of HazelSession
private static synchronized String generateSessionId() {
final String id = UUID.randomUUID().toString();
final StringBuilder sb = new StringBuilder("HZ");