/* * Copyright (c) 2002-2012 Alibaba Group Holding Limited. * 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.alibaba.citrus.service.requestcontext.session.impl; import static com.alibaba.citrus.util.Assert.*; import static com.alibaba.citrus.util.CollectionUtil.*; import static com.alibaba.citrus.util.ObjectUtil.*; import static com.alibaba.citrus.util.StringUtil.*; import static java.util.Collections.*; import java.util.Date; import java.util.Enumeration; import java.util.Iterator; import java.util.Map; import java.util.Set; import javax.servlet.ServletContext; import javax.servlet.http.HttpSession; import com.alibaba.citrus.service.requestcontext.session.HttpHeaderSessionStore; import com.alibaba.citrus.service.requestcontext.session.SessionAttributeInterceptor; import com.alibaba.citrus.service.requestcontext.session.SessionConfig; import com.alibaba.citrus.service.requestcontext.session.SessionInterceptor; import com.alibaba.citrus.service.requestcontext.session.SessionLifecycleListener; import com.alibaba.citrus.service.requestcontext.session.SessionModel; import com.alibaba.citrus.service.requestcontext.session.SessionModelEncoder; import com.alibaba.citrus.service.requestcontext.session.SessionRequestContext; import com.alibaba.citrus.service.requestcontext.session.SessionStore; import com.alibaba.citrus.service.requestcontext.session.SessionStore.StoreContext; import com.alibaba.citrus.util.ToStringBuilder; import com.alibaba.citrus.util.ToStringBuilder.MapBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * 实现了<code>HttpSession</code>接口。 * <p> * 注意,每个request均会创建独立的session对象,此对象本身不是线程安全的,不能被多线程同时访问。但其后备的session * store是线程安全的。 * </p> */ public class SessionImpl implements HttpSession { private final static Logger log = LoggerFactory.getLogger(SessionImpl.class); private final HttpSessionInternal sessionInternal = new HttpSessionInternal(); private String sessionID; private SessionRequestContext requestContext; private String modelKey; private SessionModelImpl model; private boolean isNew; private Map<String, SessionAttribute> attrs = createHashMap(); private Map<String, Object> storeStates = createHashMap(); private boolean invalidated = false; private boolean cleared = false; private Set<String> clearingStores = createHashSet(); /** 创建一个session对象。 */ public SessionImpl(String sessionID, SessionRequestContext requestContext, boolean isNew, boolean create) { this.sessionID = assertNotNull(sessionID, "no sessionID"); this.requestContext = requestContext; this.modelKey = requestContext.getSessionConfig().getModelKey(); EventType event; // 进到这里的session可能有四种情况: // 1. Requested sessionID为空 // 2. Requested sessionID所对应的session不存在 // 3. Requested sessionID所对应的session已经过期 // 3.5 Requested sessionID和model中的session ID不匹配,视作session过期 // 4. Requested sessionID所对应的session存在且合法 if (isNew) { event = EventType.CREATED; // 情况1:创建新的model,并保存之。 log.debug("No session ID was found in cookie or URL. A new session will be created."); sessionInternal.invalidate(); } else { model = (SessionModelImpl) sessionInternal.getAttribute(modelKey); if (model == null) { event = EventType.CREATED; // 情况2:创建新的model,并保存之。 log.debug("Session state was not found for sessionID \"{}\". A new session will be created.", sessionID); isNew = true; sessionInternal.invalidate(); } else { boolean expired = false; String modelSessionID = trimToNull(model.getSessionID()); // 检查SessionID的值是否相等,并兼容SessionModel中没有设置SessionID的场景 // 特殊情况:model中未包含session ID,这时,不作检查,以request中的sessionID为准 if (modelSessionID != null && !modelSessionID.equals(sessionID)) { // 情况3.5 视作过期 expired = true; log.warn("Requested session ID \"{}\" does not match the ID in session model \"{}\". " + "Force expired the session.", sessionID, modelSessionID); } // Session model被返回前,会先被decode。因此,修改所返回的session model对象,并不会影响store中的对象值。 model.setSession(this); // update the session config & session id in model expired |= model.isExpired(); if (expired) { event = EventType.RECREATED; // 情况3:更新model如同新建的一样,同时清除老数据。 if (log.isDebugEnabled()) { log.debug( "Session has expired: sessionID={}, created at {}, last accessed at {}, " + "maxInactiveInterval={}, forceExpirationPeriod={}", new Object[] { modelSessionID, new Date(model.getCreationTime()), new Date(model.getLastAccessedTime()), model.getMaxInactiveInterval(), getSessionRequestContext().getSessionConfig().getForceExpirationPeriod() }); } isNew = true; sessionInternal.invalidate(); } else { event = EventType.VISITED; // 情况4:更新model的最近访问时间。 if (log.isTraceEnabled()) { log.trace( "Activate session: sessionID={}, last accessed at {}, maxInactiveInterval={}", new Object[] { modelSessionID, new Date(model.getLastAccessedTime()), model.getMaxInactiveInterval() }); } model.touch(); } } } this.isNew = isNew; // 确保model attribute的modified=true sessionInternal.setAttribute(modelKey, model); // 调用session lifecycle listener fireEvent(event); } /** * 取得创建该session的request context。 * * @return request context */ public SessionRequestContext getSessionRequestContext() { return requestContext; } /** * 取得当前的model。 * * @return model对象 */ public SessionModel getSessionModel() { return model; } /** * 取得session ID。 * * @return session ID */ public String getId() { return sessionID; } /** * 取得session的创建时间。 * * @return 创建时间戮 * @throws IllegalStateException 如果session已经invalidated */ public long getCreationTime() { assertValid("getCreationTime"); return sessionInternal.getCreationTime(); } /** * 取得最近访问时间。 * * @return 最近访问时间戮 * @throws IllegalStateException 如果session已经invalidated */ public long getLastAccessedTime() { assertValid("getLastAccessedTime"); return model.getLastAccessedTime(); } /** * 取得session的最大不活动期限,超过此时间,session就会失效。 * * @return 不活动期限的秒数 */ public int getMaxInactiveInterval() { assertModel("getMaxInactiveInterval"); return model.getMaxInactiveInterval(); } /** * 设置session的最大不活动期限,超过此时间,session就会失效。 * * @param maxInactiveInterval 不活动期限的秒数 */ public void setMaxInactiveInterval(int maxInactiveInterval) { assertModel("setMaxInactiveInterval"); model.setMaxInactiveInterval(maxInactiveInterval); } /** * 取得当前session所属的servlet context。 * * @return <code>ServletContext</code>对象 */ public ServletContext getServletContext() { return requestContext.getServletContext(); } /** * 取得指定名称的attribute值。 * * @param name attribute名称 * @return attribute的值 * @throws IllegalStateException 如果session已经invalidated */ public Object getAttribute(String name) { assertValid("getAttribute"); return sessionInternal.getAttribute(name); } /** * 取得所有attributes的名称。 * * @return attribute名称列表 * @throws IllegalStateException 如果session已经invalidated */ public Enumeration<String> getAttributeNames() { assertValid("getAttributeNames"); Set<String> attrNames = getAttributeNameSet(); final Iterator<String> i = attrNames.iterator(); return new Enumeration<String>() { public boolean hasMoreElements() { return i.hasNext(); } public String nextElement() { return i.next(); } }; } private Set<String> getAttributeNameSet() { SessionConfig sessionConfig = requestContext.getSessionConfig(); String[] storeNames = sessionConfig.getStores().getStoreNames(); Set<String> attrNames = createLinkedHashSet(); for (String storeName : storeNames) { SessionStore store = sessionConfig.getStores().getStore(storeName); for (String attrName : store.getAttributeNames(getId(), new StoreContextImpl(storeName))) { if (!isEquals(attrName, modelKey)) { attrNames.add(attrName); } } } for (SessionAttribute attr : attrs.values()) { if (attr.getValue() == null) { attrNames.remove(attr.getName()); } else { attrNames.add(attr.getName()); } } attrNames.remove(modelKey); return attrNames; } /** * 设置指定名称的attribute值。 * * @param name attribute名称 * @param value attribute的值 * @throws IllegalStateException 如果session已经invalidated * @throws IllegalArgumentException 如果指定的attribute名称不被支持 */ public void setAttribute(String name, Object value) { assertValid("setAttribute"); assertAttributeNameForModification("setAttribute", name); sessionInternal.setAttribute(name, value); } /** * 删除一个attribute。 * * @param name 要删除的attribute名称 * @throws IllegalStateException 如果session已经invalidated */ public void removeAttribute(String name) { assertValid("removeAttribute"); assertAttributeNameForModification("removeAttribute", name); setAttribute(name, null); } /** * 使一个session作废。 * * @throws IllegalStateException 如果session已经invalidated */ public void invalidate() { assertValid("invalidate"); sessionInternal.invalidate(); invalidated = true; fireEvent(EventType.INVALIDATED); } /** * 清除一个session。 * * @throws IllegalStateException 如果session已经invalidated */ public void clear() { assertValid("clear"); sessionInternal.invalidate(); } /** 判断当前session是否非法。 */ public boolean isInvalidated() { return invalidated; } /** * 当前session是否为新的? * * @return 如果是新的,则返回<code>true</code> * @throws IllegalStateException 如果session已经invalidated */ public boolean isNew() { assertValid("isNew"); return isNew; } /** * 确保model已经被取得,即session已被初始化。 * * @param methodName 当前正要执行的方法 */ protected void assertModel(String methodName) { if (model == null) { throw new IllegalStateException("Cannot call method " + methodName + ": the session has not been initialized"); } } /** * 确保session处于valid状态。 * * @param methodName 当前正要执行的方法 */ protected void assertValid(String methodName) { assertModel(methodName); if (invalidated) { throw new IllegalStateException("Cannot call method " + methodName + ": the session has already invalidated"); } } /** 检查将要更改的attr name是否合法。 */ protected void assertAttributeNameForModification(String methodName, String attrName) { if (modelKey.equals(attrName)) { throw new IllegalArgumentException("Cannot call method " + methodName + " with attribute " + attrName); } } /** 临时存储session store以及要提交的数据。 */ private static class StoreData { private final String storeName; private final SessionStore store; private final Map<String, Object> attrs = createHashMap(); private StoreData(String storeName, SessionStore store) { this.storeName = storeName; this.store = store; } } /** 提交session的内容,删除的、新增的、修改的内容被保存。 */ public void commit(boolean commitHeaders) { String[] storeNames = requestContext.getSessionConfig().getStores().getStoreNames(); Map<String, StoreData> mappings = createHashMap(); Map<String, StringBuilder> mayNotBeCommitted = null; // 按store对attrs进行分堆。 boolean modified = false; for (Map.Entry<String, SessionAttribute> entry : attrs.entrySet()) { String attrName = entry.getKey(); SessionAttribute attr = entry.getValue(); if (attr.isModified()) { String storeName = attr.getStoreName(); SessionStore store = attr.getStore(); if (isApplicableToCommit(store, commitHeaders)) { StoreData data = mappings.get(storeName); if (data == null) { data = new StoreData(storeName, store); mappings.put(storeName, data); } Map<String, Object> storeAttrs = data.attrs; Object attrValue = attr.getValue(); // 特殊处理model,将其转换成store中的值。 if (attrValue instanceof SessionModel) { attrValue = requestContext.getSessionConfig().getSessionModelEncoders()[0] .encode((SessionModel) attrValue); } else { // 只检查非session model对象的modified状态 modified = true; } storeAttrs.put(attrName, attrValue); attr.setModified(false); // 恢复modified状态,以防多余的警告信息 } else if (!commitHeaders) { if (mayNotBeCommitted == null) { mayNotBeCommitted = createLinkedHashMap(); } StringBuilder buf = mayNotBeCommitted.get(storeName); if (buf == null) { buf = new StringBuilder(); mayNotBeCommitted.put(storeName, buf); } if (buf.length() > 0) { buf.append(", "); } buf.append(attrName); } } } if (mayNotBeCommitted != null) { for (Map.Entry<String, StringBuilder> entry : mayNotBeCommitted.entrySet()) { String storeName = entry.getKey(); SessionStore store = requestContext.getSessionConfig().getStores().getStore(storeName); String attrNames = entry.getValue().toString(); log.warn("The following attributes may not be saved in {}[id={}], because the response has already been committed: {}", new Object[] { store.getClass().getSimpleName(), storeName, attrNames }); } } // 如果既没有参数改变(即没有调用setAttribute和removeAttribute), // 也没有被清除(即没有调用invalidate和clear),并且isKeepInTouch=false, // 则不提交了,直接退出。 if (!modified && !cleared && !requestContext.getSessionConfig().isKeepInTouch()) { return; } // 对每一个store分别操作。 for (StoreData data : mappings.values()) { data.store.commit(data.attrs, getId(), new StoreContextImpl(data.storeName)); clearingStores.remove(data.storeName); // 如果先clear后又设值,则一会儿不需要再进行clear,故清除clearing标记 } // 假如invalidate和clear被调用,则检查剩余的store,通知它们清除当前的数据。 if (cleared) { for (Iterator<String> i = clearingStores.iterator(); i.hasNext(); ) { String storeName = i.next(); SessionStore store = requestContext.getSessionConfig().getStores().getStore(storeName); if (isApplicableToCommit(store, commitHeaders)) { Map<String, Object> storeAttrs = emptyMap(); store.commit(storeAttrs, sessionID, new StoreContextImpl(storeName)); i.remove(); // 清除clearing标记,以防重复clear } else if (!commitHeaders) { log.warn("Session was cleared, but the data in {}[id={}] may not be cleared, " + "because the response has already been committed.", store.getClass().getSimpleName(), storeName); } } } } private boolean isApplicableToCommit(SessionStore store, boolean commitHeaders) { boolean isHttpHeaderStore = store instanceof HttpHeaderSessionStore; return commitHeaders == isHttpHeaderStore; } /** @deprecated no replacement */ @Deprecated public javax.servlet.http.HttpSessionContext getSessionContext() { throw new UnsupportedOperationException("No longer supported method: getSessionContext"); } /** @deprecated use getAttribute instead */ @Deprecated public Object getValue(String name) { return getAttribute(name); } /** @deprecated use getAttributeNames instead */ @Deprecated public String[] getValueNames() { assertValid("getValueNames"); Set<String> names = getAttributeNameSet(); return names.toArray(new String[names.size()]); } /** @deprecated use setAttribute instead */ @Deprecated public void putValue(String name, Object value) { setAttribute(name, value); } /** @deprecated use removeAttribute instead */ @Deprecated public void removeValue(String name) { removeAttribute(name); } @Override public String toString() { MapBuilder mb = new MapBuilder(); MapBuilder attrsBuilder = new MapBuilder().setPrintCount(true).setSortKeys(true); mb.append("sessionID", sessionID); mb.append("model", model); mb.append("isNew", isNew); mb.append("invalidated", invalidated); attrsBuilder.appendAll(attrs); attrsBuilder.remove(modelKey); mb.append("attrs", attrsBuilder); return new ToStringBuilder().append("HttpSession").append(mb).toString(); } private void fireEvent(EventType event) { for (SessionInterceptor l : getSessionRequestContext().getSessionConfig().getSessionInterceptors()) { if (l instanceof SessionLifecycleListener) { SessionLifecycleListener listener = (SessionLifecycleListener) l; try { switch (event) { case RECREATED: listener.sessionInvalidated(this); case CREATED: listener.sessionCreated(this); case VISITED: listener.sessionVisited(this); break; case INVALIDATED: listener.sessionInvalidated(this); break; default: unreachableCode(); } } catch (Exception e) { // 避免因listener出错导致应用的退出。 log.error("Listener \"" + listener.getClass().getSimpleName() + "\" failed", e); } } } } /** Session事件的类型。 */ private enum EventType { CREATED, RECREATED, // 先invalidate然后再create INVALIDATED, VISITED } /** 存放session store的状态。 */ private class StoreContextImpl implements StoreContext { private String storeName; public StoreContextImpl(String storeName) { this.storeName = storeName; } public Object getState() { return storeStates.get(storeName); } public void setState(Object stateObject) { if (stateObject == null) { storeStates.remove(storeName); } else { storeStates.put(storeName, stateObject); } } public StoreContext getStoreContext(String storeName) { return new StoreContextImpl(storeName); } public SessionRequestContext getSessionRequestContext() { return SessionImpl.this.getSessionRequestContext(); } public HttpSession getHttpSession() { return sessionInternal; } } /** 内部使用的session对象,不会抛出<code>IllegalStateException</code>异常。 */ private class HttpSessionInternal implements HttpSession { public String getId() { return SessionImpl.this.getId(); } public long getCreationTime() { return model == null ? 0 : model.getCreationTime(); } public long getLastAccessedTime() { return SessionImpl.this.getLastAccessedTime(); } public int getMaxInactiveInterval() { return SessionImpl.this.getMaxInactiveInterval(); } public void setMaxInactiveInterval(int maxInactiveInterval) { SessionImpl.this.setMaxInactiveInterval(maxInactiveInterval); } public ServletContext getServletContext() { return SessionImpl.this.getServletContext(); } public Object getAttribute(String name) { SessionAttribute attr = attrs.get(name); SessionConfig sessionConfig = requestContext.getSessionConfig(); Object value; if (attr == null) { String storeName = sessionConfig.getStoreMappings().getStoreNameForAttribute(name); if (storeName == null) { value = null; } else { attr = new SessionAttribute(name, SessionImpl.this, storeName, new StoreContextImpl(storeName)); value = attr.getValue(); // 对于session model,需要对其解码 if (value != null && modelKey.equals(name)) { value = decodeSessionModel(value); // 如果解码失败,则返回null attr.updateValue(value); } // 只有当value非空(store中包含了此对象),才把它放到attrs表中,否则可能会产生很多垃圾attr对象 if (value != null) { attrs.put(name, attr); } } } else { value = attr.getValue(); } return interceptGet(name, value); } private Object interceptGet(String name, Object value) { for (SessionInterceptor l : getSessionRequestContext().getSessionConfig().getSessionInterceptors()) { if (l instanceof SessionAttributeInterceptor) { SessionAttributeInterceptor interceptor = (SessionAttributeInterceptor) l; value = interceptor.onRead(name, value); } } return value; } private Object decodeSessionModel(Object value) { SessionModel.Factory factory = new SessionModel.Factory() { public SessionModel newInstance(String sessionID, long creationTime, long lastAccessedTime, int maxInactiveInterval) { return new SessionModelImpl(sessionID, creationTime, lastAccessedTime, maxInactiveInterval); } }; SessionModel model = null; SessionModelEncoder[] encoders = requestContext.getSessionConfig().getSessionModelEncoders(); for (SessionModelEncoder encoder : encoders) { model = encoder.decode(value, factory); if (model != null) { break; } } if (model == null) { log.warn("Could not decode session model {} by {} encoders", value, encoders.length); } return model; } public Enumeration<String> getAttributeNames() { return SessionImpl.this.getAttributeNames(); } public void setAttribute(String name, Object value) { value = interceptSet(name, value); SessionAttribute attr = attrs.get(name); SessionConfig sessionConfig = requestContext.getSessionConfig(); if (attr == null) { String storeName = sessionConfig.getStoreMappings().getStoreNameForAttribute(name); if (storeName == null) { throw new IllegalArgumentException("No storage configured for session attribute: " + name); } else { attr = new SessionAttribute(name, SessionImpl.this, storeName, new StoreContextImpl(storeName)); attrs.put(name, attr); } } attr.setValue(value); } private Object interceptSet(String name, Object value) { for (SessionInterceptor l : getSessionRequestContext().getSessionConfig().getSessionInterceptors()) { if (l instanceof SessionAttributeInterceptor) { SessionAttributeInterceptor interceptor = (SessionAttributeInterceptor) l; value = interceptor.onWrite(name, value); } } return value; } public void removeAttribute(String name) { SessionImpl.this.removeAttribute(name); } public void invalidate() { SessionConfig sessionConfig = requestContext.getSessionConfig(); String[] storeNames = sessionConfig.getStores().getStoreNames(); // 清除session数据 attrs.clear(); cleared = true; clearingStores.addAll(asList(storeNames)); // 通知所有的store过期其数据 for (String storeName : storeNames) { SessionStore store = sessionConfig.getStores().getStore(storeName); store.invalidate(sessionID, new StoreContextImpl(storeName)); } // 清除model if (model == null) { model = new SessionModelImpl(SessionImpl.this); } else { model.reset(); } } public boolean isNew() { return SessionImpl.this.isNew(); } /** @deprecated */ @Deprecated public javax.servlet.http.HttpSessionContext getSessionContext() { return SessionImpl.this.getSessionContext(); } /** @deprecated */ @Deprecated public Object getValue(String name) { return SessionImpl.this.getValue(name); } /** @deprecated */ @Deprecated public String[] getValueNames() { return SessionImpl.this.getValueNames(); } /** @deprecated */ @Deprecated public void putValue(String name, Object value) { SessionImpl.this.putValue(name, value); } /** @deprecated */ @Deprecated public void removeValue(String name) { SessionImpl.this.removeValue(name); } } }