/* * 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.store.cookie.impl; import static com.alibaba.citrus.util.ArrayUtil.*; import static com.alibaba.citrus.util.Assert.*; import static com.alibaba.citrus.util.CollectionUtil.*; import static com.alibaba.citrus.util.StringUtil.*; import java.util.List; import java.util.Map; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import com.alibaba.citrus.service.requestcontext.session.ExactMatchesOnlySessionStore; import com.alibaba.citrus.service.requestcontext.session.store.cookie.AbstractCookieStore; import com.alibaba.citrus.service.requestcontext.session.valueencoder.SessionValueEncoder; import com.alibaba.citrus.service.requestcontext.session.valueencoder.impl.SimpleValueEncoder; import com.alibaba.citrus.util.ToStringBuilder; import com.alibaba.citrus.util.ToStringBuilder.MapBuilder; /** * 将Session状态保存在cookie中。 * <ul> * <li>每个store只能保存一个值。</li> * <li>将仅有的session attribute value用<code>SessionValueEncoder</code>编码成字符串。</li> * </ul> * * @author Michael Zhou */ public class SingleValuedCookieStoreImpl extends AbstractCookieStore implements ExactMatchesOnlySessionStore { private String[] attrNames; private SessionValueEncoder[] encoders; public void initAttributeNames(String[] attrNames) { this.attrNames = attrNames; assertTrue(attrNames.length <= 1, "Session store %s supports only 1 mapping to attribute name", getName()); } public void setValueEncoders(SessionValueEncoder[] encoders) { this.encoders = encoders; } @Override protected void init() throws Exception { if (isEmptyArray(encoders)) { encoders = new SessionValueEncoder[] { createDefaultSessionValueEncoder() }; } } protected SessionValueEncoder createDefaultSessionValueEncoder() throws Exception { SimpleValueEncoder simpleValueEncoder = new SimpleValueEncoder(); simpleValueEncoder.afterPropertiesSet(); return simpleValueEncoder; } public Iterable<String> getAttributeNames(String sessionID, StoreContext storeContext) { State state = getState(storeContext); return state.attributes.keySet(); } public Object loadAttribute(String attrName, String sessionID, StoreContext storeContext) { State state = getState(storeContext); return state.attributes.get(attrName); } public void invalidate(String sessionID, StoreContext storeContext) { State state = getState(storeContext); if (!isSurvivesInInvalidating()) { state.attributes.clear(); } } public void commit(Map<String, Object> modifiedAttrs, String sessionID, StoreContext storeContext) { State state = getState(storeContext); if (state.cookieCommitted) { return; } state.cookieCommitted = true; String attrName = attrNames[0]; if (modifiedAttrs.containsKey(attrName)) { Object attrValue = modifiedAttrs.get(attrName); if (attrValue == null) { if (log.isDebugEnabled()) { log.debug("Remove from session: {}", attrName); } state.attributes.remove(attrName); } else { if (log.isDebugEnabled()) { log.debug("Set to session: {} = {}", attrName, attrValue); } state.attributes.put(attrName, attrValue); } } String cookieState = null; if (!state.attributes.isEmpty()) { try { cookieState = encoders[0].encode(state.attributes.get(attrName), storeContext); } catch (Exception e) { log.warn("Failed to encode session state", e); } } cookieState = trimToEmpty(cookieState); writeCookie(storeContext.getSessionRequestContext().getResponse(), getName(), cookieState); } /** 取得cookie store的状态。 */ private State getState(StoreContext storeContext) { State state = (State) storeContext.getState(); if (state == null) { state = new State(); storeContext.setState(state); } ensureCookieLoading(state, storeContext.getSessionRequestContext().getRequest(), storeContext); return state; } /** 确保cookie被装载。 */ private void ensureCookieLoading(State state, HttpServletRequest request, StoreContext storeContext) { if (state.cookieLoaded) { return; } state.cookieLoaded = true; // 读取cookie state.requestCookieValue = readCookie(request); // 依次使用所有encoders,试着对cookieValue解码,如果失败,则返回空表 // 如果成功,则返回单值map。 state.attributes = decodeCookieValue(state.requestCookieValue, storeContext); } /** 读取cookies。 */ private String readCookie(HttpServletRequest request) { Cookie[] cookies = request.getCookies(); if (cookies == null) { cookies = new Cookie[0]; } // 扫描cookie。 String cookieValue = null; for (Cookie cookie : cookies) { String cookieName = cookie.getName(); if (getName().equals(cookieName)) { cookieValue = cookie.getValue(); if (log.isDebugEnabled()) { log.debug("[{}] Loading cookie: {}[length={}]={}", new Object[] { getStoreName(), getName(), cookie.getValue().length(), cookie.getValue() }); } break; } } return cookieValue; } private Map<String, Object> decodeCookieValue(String cookieValue, StoreContext storeContext) { Map<String, Object> attrs = createHashMap(4); if (cookieValue == null) { return attrs; // empty map } List<Exception> encoderExceptions = null; for (SessionValueEncoder encoder : encoders) { try { attrs.put(attrNames[0], encoder.decode(cookieValue, storeContext)); log.debug("Succeeded decoding cookieValue using {}", encoder); break; } catch (Exception e) { if (encoderExceptions == null) { encoderExceptions = createLinkedList(); } encoderExceptions.add(e); log.trace("Failure decoding cookieValue using {}: {}", encoder, e.getMessage()); } } // 如果失败,记录日志 if (attrs.isEmpty() && encoderExceptions != null) { if (log.isWarnEnabled()) { ToStringBuilder buf = new ToStringBuilder(); buf.append("Failed to decode cookie value: ").append(cookieValue); int i = 0; for (Exception e : encoderExceptions) { buf.format("\n Encoder #%d - %s threw %s", i + 1, encoders[i].getClass().getSimpleName(), e); } log.warn(buf.toString()); } } else { if (log.isDebugEnabled()) { int attrCount = attrs.size(); ToStringBuilder buf = new ToStringBuilder(); buf.format("Found %d attributes:", attrCount); if (!attrs.isEmpty()) { buf.append(new MapBuilder().setPrintCount(true).setSortKeys(true).appendAll(attrs)); } log.debug(buf.toString()); } } return attrs; } @Override protected void toString(MapBuilder mb) { super.toString(mb); mb.append("encoders", encoders); } /** 存放cookie的状态。 */ private class State { private boolean cookieLoaded; private boolean cookieCommitted; private String requestCookieValue; private Map<String, Object> attributes; } }