blob: 1eccdc0e9bb3b76ae8c5d29cbc8fa41e88a771fa [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more contributor license
* agreements. See the NOTICE file distributed with this work for additional information regarding
* copyright ownership. The ASF licenses this file to You 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.apache.geode.modules.session.internal.filter;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Collections;
import java.util.Enumeration;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.geode.DataSerializable;
import org.apache.geode.DataSerializer;
import org.apache.geode.Delta;
import org.apache.geode.Instantiator;
import org.apache.geode.InvalidDeltaException;
import org.apache.geode.modules.session.internal.filter.attributes.AbstractSessionAttributes;
import org.apache.geode.modules.session.internal.filter.attributes.SessionAttributes;
import org.apache.geode.modules.util.ClassLoaderObjectInputStream;
/**
* Class which implements a Gemfire persisted {@code HttpSession}
*/
@SuppressWarnings("deprecation")
public class GemfireHttpSession implements HttpSession, DataSerializable, Delta {
private static final transient Logger LOG =
LoggerFactory.getLogger(GemfireHttpSession.class.getName());
/**
* Serial id
*/
private static final long serialVersionUID = 238915238964017823L;
/**
* Id for the session
*/
private String id;
/**
* Attributes really hold the essence of persistence.
*/
private SessionAttributes attributes;
private transient SessionManager manager;
private ServletContext context;
/**
* A session becomes invalid if it is explicitly invalidated or if it expires.
*/
private boolean isValid = true;
private boolean isNew = true;
private boolean isDirty = false;
/**
* This is set during serialization and then reset by the SessionManager when it is retrieved from
* the attributes.
*/
private AtomicBoolean serialized = new AtomicBoolean(false);
// Register ourselves for de-serialization
static {
registerInstantiator();
}
public static void registerInstantiator() {
Instantiator.register(new Instantiator(GemfireHttpSession.class, 27315) {
@Override
public DataSerializable newInstance() {
return new GemfireHttpSession();
}
});
}
/**
* Constructor used for de-serialization
*/
private GemfireHttpSession() {}
/**
* Constructor
*/
GemfireHttpSession(String id, ServletContext context) {
this();
this.id = id;
this.context = context;
}
/**
* {@inheritDoc}
*/
@Override
public Object getAttribute(String name) {
if (!isValid) {
throw new IllegalStateException("Session is already invalidated");
}
Object obj = attributes.getAttribute(name);
if (obj != null) {
Object tmpObj = null;
ClassLoader loader = ((GemfireSessionManager) manager).getReferenceClassLoader();
if (obj.getClass().getClassLoader() != loader) {
LOG.debug("Attribute '{}' needs to be reconstructed with a new classloader", name);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(obj);
oos.close();
ObjectInputStream ois = new ClassLoaderObjectInputStream(
new ByteArrayInputStream(baos.toByteArray()), loader);
tmpObj = ois.readObject();
} catch (IOException | ClassNotFoundException e) {
LOG.error("Exception while recreating attribute '" + name + "'", e);
}
if (tmpObj != null) {
setAttribute(name, tmpObj);
obj = tmpObj;
}
}
}
return obj;
}
/**
* {@inheritDoc}
*/
@Override
@SuppressWarnings("unchecked")
public Enumeration getAttributeNames() {
checkValid();
return Collections.enumeration(attributes.getAttributeNames());
}
/**
* {@inheritDoc}
*/
@Override
public long getCreationTime() {
checkValid();
return attributes.getCreationTime();
}
/**
* {@inheritDoc}
*/
@Override
public String getId() {
return id;
}
/**
* {@inheritDoc}
*/
@Override
public long getLastAccessedTime() {
if (!isValid) {
throw new IllegalStateException("Session is already invalidated");
}
return attributes.getLastAccessedTime();
}
public void setServletContext(ServletContext context) {
this.context = context;
}
/**
* {@inheritDoc}
*/
@Override
public ServletContext getServletContext() {
return context;
}
/**
* {@inheritDoc}
*/
@Override
public HttpSessionContext getSessionContext() {
return null;
}
/**
* {@inheritDoc}
*/
@Override
public Object getValue(String name) {
return getAttribute(name);
}
/**
* {@inheritDoc}
*/
@Override
public String[] getValueNames() {
return attributes.getAttributeNames().toArray(new String[0]);
}
/**
* {@inheritDoc}
*/
@Override
public void invalidate() {
manager.destroySession(id);
isValid = false;
}
/**
* {@inheritDoc}
*/
@Override
public boolean isNew() {
if (!isValid) {
throw new IllegalStateException("Session is already invalidated");
}
return isNew;
}
public void setIsNew(boolean isNew) {
this.isNew = isNew;
}
/**
* {@inheritDoc}
*/
@Override
public void setMaxInactiveInterval(int interval) {
attributes.setMaxInactiveInterval(interval);
isDirty = true;
}
/**
* {@inheritDoc}
*/
@Override
public int getMaxInactiveInterval() {
return attributes.getMaxIntactiveInterval();
}
/**
* {@inheritDoc}
*/
@Override
public void putValue(String name, Object value) {
setAttribute(name, value);
}
/**
* {@inheritDoc}
*/
@Override
public void removeAttribute(final String name) {
checkValid();
LOG.debug("Session {} removing attribute {}", getId(), name);
attributes.removeAttribute(name);
}
/**
* {@inheritDoc}
*/
@Override
public void removeValue(String name) {
removeAttribute(name);
}
/**
* {@inheritDoc}
*/
@Override
public void setAttribute(final String name, final Object value) {
checkValid();
if (LOG.isDebugEnabled()) {
LOG.debug("Session {} setting attribute {} = '{}'", id, name, value);
}
isDirty = true;
if (value == null) {
removeAttribute(name);
} else {
attributes.putAttribute(name, value);
}
}
private void checkValid() {
if (!isValid()) {
throw new IllegalStateException("Session is invalid");
}
}
/**
* Gemfire serialization {@inheritDoc}
*/
@Override
public void toData(DataOutput out) throws IOException {
DataSerializer.writeString(id, out);
DataSerializer.writeObject(attributes, out);
}
/**
* Gemfire de-serialization {@inheritDoc}
*/
@Override
public void fromData(DataInput in) throws IOException, ClassNotFoundException {
id = DataSerializer.readString(in);
attributes = DataSerializer.readObject(in);
// Explicit sets
serialized.set(true);
attributes.setSession(this);
}
/**
* These three methods handle delta propagation and are deferred to the attribute object.
*/
@Override
public boolean hasDelta() {
return isDirty;
}
@Override
public void toDelta(DataOutput out) throws IOException {
if (attributes instanceof Delta) {
((Delta) attributes).toDelta(out);
} else {
toData(out);
}
}
@Override
public void fromDelta(DataInput in) throws IOException, InvalidDeltaException {
if (attributes instanceof Delta) {
((Delta) attributes).fromDelta(in);
} else {
try {
fromData(in);
} catch (ClassNotFoundException cex) {
throw new IOException("Unable to forward fromDelta() call " + "to fromData()", cex);
}
}
}
@Override
public String toString() {
return "[id=" + id + ", isNew=" + isNew + ", isValid="
+ isValid + ", hasDelta=" + hasDelta() + ", lastAccessedTime="
+ attributes.getLastAccessedTime() + ", jvmOwnerId="
+ attributes.getJvmOwnerId()
+ "]";
}
/**
* Flush the session object to the region
*/
public void putInRegion() {
manager.putSession(this);
isDirty = false;
}
/**
* Determine whether the session is still valid or whether it has expired.
*
* @return true or false
*/
public boolean isValid() {
if (!isValid) {
return false;
}
if (getMaxInactiveInterval() >= 0) {
long now = System.currentTimeMillis();
if (now - attributes.getLastAccessedTime() >= getMaxInactiveInterval() * 1000) {
return false;
}
}
return true;
}
public void setManager(SessionManager manager) {
this.manager = manager;
}
/**
* Update the last accessed time
*/
public void updateAccessTime() {
attributes.setLastAccessedTime(System.currentTimeMillis());
}
/**
* The {@code SessionManager} injects this when creating a new session.
*
*/
public void setAttributes(AbstractSessionAttributes attributes) {
this.attributes = attributes;
}
/**
* This is called on deserialization. You can only call it once to get a meaningful value as it
* resets the serialized state. In other words, this call is not idempotent.
*
* @return whether this object has just been serialized
*/
boolean justSerialized() {
return serialized.getAndSet(false);
}
/**
* Called when the session is about to go out of scope. If the session has been defined to use
* async queued attributes then they will be written out at this point.
*/
public void commit() {
attributes.setJvmOwnerId(manager.getJvmId());
attributes.flush();
}
String getJvmOwnerId() {
if (attributes != null) {
return attributes.getJvmOwnerId();
}
return null;
}
}