View Javadoc

1   /*
2    * Copyright 2009-2012 Capgemini and others
3    * 
4    * Licensed under the Apache License, Version 2.0
5    * (the "License"); you may not use this file except in compliance with the
6    * License. You may obtain a copy of the License at
7    * 
8    * http://www.apache.org/licenses/LICENSE-2.0
9    * 
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations under
14   * the License.
15   */
16  package net.sourceforge.statelessfilter.wrappers;
17  
18  import java.io.IOException;
19  import java.security.NoSuchAlgorithmException;
20  import java.util.ArrayList;
21  import java.util.HashMap;
22  import java.util.List;
23  import java.util.Map;
24  
25  import javax.servlet.http.HttpServletRequest;
26  import javax.servlet.http.HttpServletRequestWrapper;
27  import javax.servlet.http.HttpServletResponse;
28  import javax.servlet.http.HttpSession;
29  
30  import net.sourceforge.statelessfilter.backend.ISessionBackend;
31  import net.sourceforge.statelessfilter.backend.ISessionData;
32  import net.sourceforge.statelessfilter.filter.Configuration;
33  import net.sourceforge.statelessfilter.session.SessionData;
34  import net.sourceforge.statelessfilter.session.StatelessSession;
35  
36  import org.apache.commons.lang.StringUtils;
37  import org.slf4j.Logger;
38  import org.slf4j.LoggerFactory;
39  
40  /**
41   * Stateless request wrapper
42   * 
43   * @author Nicolas Richeton
44   * 
45   */
46  public class StatelessRequestWrapper extends HttpServletRequestWrapper {
47  	private static final String INFO_USES = "{} uses {}"; //$NON-NLS-1$
48  	private static final String INFO_USES_DEFAULT = "{} uses default {}"; //$NON-NLS-1$
49  	//	private static final String WARN_SESSION_SYNC = "Session are not synchronized between backends. Reseting..."; //$NON-NLS-1$
50  	Configuration backends = null;
51  	Logger logger = LoggerFactory.getLogger(StatelessRequestWrapper.class);
52  	HttpServletRequest originalRequest = null;
53  
54  	StatelessSession session = null;
55  
56  	private boolean sessionWritten;
57  
58  	public boolean isSessionWritten() {
59  		return sessionWritten;
60  	}
61  
62  	/**
63  	 * Create a new request wrapper.
64  	 * 
65  	 * @param request
66  	 * @param backends
67  	 */
68  	public StatelessRequestWrapper(HttpServletRequest request,
69  			Configuration backends) {
70  		super(request);
71  		originalRequest = request;
72  		this.backends = backends;
73  	}
74  
75  	/**
76  	 * Returns real server session.
77  	 * <p>
78  	 * This method should only be used for pass-through implementations or to
79  	 * get Servlet context. Implementation should not store data in this session
80  	 * as Statelessfilter is intended to make application independant of the
81  	 * container session.
82  	 * 
83  	 * @return
84  	 */
85  	public HttpSession getServerSession() {
86  		return super.getSession();
87  	}
88  
89  	/**
90  	 * 
91  	 * 
92  	 * {@inheritDoc} javax.servlet.http.HttpServletRequestWrapper#getSession()
93  	 * 
94  	 * Returns a custom session object.
95  	 */
96  	@Override
97  	public HttpSession getSession() {
98  		if (session == null) {
99  			try {
100 				session = createSession();
101 			} catch (NoSuchAlgorithmException e) {
102 				throw new RuntimeException(e);
103 			}
104 		}
105 		return session;
106 	}
107 
108 	/**
109 	 * (non-Javadoc)
110 	 * 
111 	 * @see javax.servlet.http.HttpServletRequestWrapper#getSession(boolean)
112 	 */
113 	@Override
114 	public HttpSession getSession(boolean create) {
115 		if (create) {
116 			return getSession();
117 		}
118 
119 		if (session == null) {
120 			try {
121 				session = restoreSession();
122 			} catch (NoSuchAlgorithmException e) {
123 				throw new RuntimeException(e);
124 			}
125 		}
126 
127 		return session;
128 	}
129 
130 	/**
131 	 * Stores session in backends
132 	 * 
133 	 * @param myrequest
134 	 * @param myresponse
135 	 * @throws IOException
136 	 */
137 	public void writeSession(HttpServletRequest myrequest,
138 			HttpServletResponse myresponse) throws IOException {
139 
140 		// If there is a session (session requested by the application)
141 		if (session != null) {
142 
143 			// Session has changed ?
144 			if (backends.useDirty && !session.isDirty()) {
145 				if (logger.isDebugEnabled()) {
146 					logger.debug("Session has not changed."); //$NON-NLS-1$
147 				}
148 				return;
149 			}
150 
151 			long requestId = System.currentTimeMillis();
152 			session.setNew(false);
153 
154 			// Dispatch attributes between backends according to configuration
155 
156 			// Session attributes
157 			Map<String, Object> sessionAttributes = session.getContent();
158 			// Session attributes which were modified during the request
159 			List<String> modifiedAttributes = session.getDirtyAttributes();
160 
161 			// Backends flagged as dirty during dispatch
162 			List<String> modifiedBackends = new ArrayList<String>();
163 
164 			// Remaining modified attributes to process
165 			List<String> remainingModifiedAttributes = new ArrayList<String>(
166 					modifiedAttributes);
167 			// Attributes for each backend
168 			Map<String, ISessionData> attributesDispatched = new HashMap<String, ISessionData>();
169 			Map<String, List<String>> modifiedAttributesDispatched = new HashMap<String, List<String>>();
170 
171 			for (String name : sessionAttributes.keySet()) {
172 				if (isAttributeMapped(name)) {
173 					getBackendSessionData(attributesDispatched,
174 							backends.backendsAttributeMapping.get(name),
175 							requestId).getContent().put(name,
176 							sessionAttributes.get(name));
177 
178 					setModified(modifiedBackends, modifiedAttributes, name);
179 
180 					logger.info(INFO_USES, name,
181 							backends.backendsAttributeMapping.get(name));
182 
183 				} else {
184 					getBackendSessionData(attributesDispatched,
185 							backends.defaultBackend, requestId).getContent()
186 							.put(name, sessionAttributes.get(name));
187 
188 					setModified(modifiedBackends, modifiedAttributes, name);
189 					logger.info(INFO_USES_DEFAULT, name,
190 							backends.defaultBackend);
191 
192 				}
193 
194 				// Remove attribute from remaining attributes to process
195 				remainingModifiedAttributes.remove(name);
196 			}
197 
198 			// Process remaining attributes
199 			for (String name : remainingModifiedAttributes) {
200 				if (isAttributeMapped(name)) {
201 					setModified(modifiedBackends, modifiedAttributes, name);
202 					logger.info(INFO_USES, name,
203 							backends.backendsAttributeMapping.get(name));
204 				} else {
205 					setModified(modifiedBackends, modifiedAttributes, name);
206 					logger.info(INFO_USES_DEFAULT, name,
207 							backends.defaultBackend);
208 				}
209 			}
210 
211 			if (session.isPropertyDirty()) {
212 				// Force update on all backends.
213 				logger.info("Session properties have changed. Forcing update on all backends.");
214 
215 				for (String back : backends.backends.keySet()) {
216 					ISessionBackend backend = backends.backends.get(back);
217 					backend.save(
218 							getBackendSessionData(attributesDispatched, back,
219 									requestId), modifiedAttributesDispatched
220 									.get(back), originalRequest, myresponse);
221 				}
222 			} else {
223 				// Update only modified backends.
224 				for (String back : modifiedBackends) {
225 					ISessionBackend backend = backends.backends.get(back);
226 					backend.save(
227 							getBackendSessionData(attributesDispatched, back,
228 									requestId), modifiedAttributesDispatched
229 									.get(back), originalRequest, myresponse);
230 				}
231 			}
232 		}
233 
234 		sessionWritten = true;
235 	}
236 
237 	/**
238 	 * Create a session using all backends.
239 	 * 
240 	 * @return
241 	 * @throws NoSuchAlgorithmException
242 	 */
243 	private StatelessSession createSession() throws NoSuchAlgorithmException {
244 
245 		StatelessSession s = restoreSession();
246 
247 		if (s == null) {
248 			s = new StatelessSession(this);
249 			s.init(true);
250 		}
251 
252 		return s;
253 	}
254 
255 	/**
256 	 * Get current data for session backend. Creates a new ISessionData if
257 	 * necessary.
258 	 * 
259 	 * @param dispatched
260 	 * @param name
261 	 * @param requestId
262 	 * @return
263 	 */
264 	private ISessionData getBackendSessionData(
265 			Map<String, ISessionData> dispatched, String name, long requestId) {
266 
267 		if (dispatched.containsKey(name)) {
268 			return dispatched.get(name);
269 		}
270 
271 		SessionData data = new SessionData();
272 		data.setId(session.getId());
273 		data.setCreationTime(session.getCreationTime());
274 		data.setValid(session.isValid());
275 		data.setRequestId(requestId);
276 		dispatched.put(name, data);
277 		return data;
278 	}
279 
280 	/**
281 	 * Check if an attribute is mapped to a backend by configuration.
282 	 * <p>
283 	 * Returns null if there is no configuration for this attribute. The default
284 	 * backend should be used in that case.
285 	 * 
286 	 * @param attrName
287 	 * @return
288 	 */
289 	private boolean isAttributeMapped(String attrName) {
290 		if (backends != null && backends.backendsAttributeMapping != null) {
291 			return backends.backendsAttributeMapping.containsKey(attrName);
292 		}
293 
294 		return false;
295 	}
296 
297 	private StatelessSession restoreSession() throws NoSuchAlgorithmException {
298 		StatelessSession s = new StatelessSession(this);
299 		ISessionData data = null;
300 		boolean restored = false;
301 		// long requestId = -1;
302 
303 		s.init(false);
304 		for (ISessionBackend back : backends.backends.values()) {
305 			data = back.restore(originalRequest);
306 
307 			// Mark session restored if at least one backend returned a session
308 			if (data != null) {
309 				restored = true;
310 				s.merge(data);
311 			}
312 		}
313 
314 		// Reset session
315 		if (!restored) {
316 			return null;
317 		}
318 		return s;
319 	}
320 
321 	/**
322 	 * If attributeName is in modifiedAttributes, then the corresponding backend
323 	 * is added to modifiedBackends.
324 	 * 
325 	 * @param modifiedBackends
326 	 * @param modifiedAttributes
327 	 * @param attributeName
328 	 */
329 	private void setModified(List<String> modifiedBackends,
330 			List<String> modifiedAttributes, String attributeName) {
331 		String backend = backends.backendsAttributeMapping.get(attributeName);
332 
333 		if (StringUtils.isEmpty(backend)) {
334 			backend = backends.defaultBackend;
335 		}
336 
337 		if (modifiedAttributes.contains(attributeName)
338 				&& !modifiedBackends.contains(backend)) {
339 
340 			modifiedBackends.add(backend);
341 
342 			if (logger.isDebugEnabled()) {
343 				logger.info("Flagging backend {} as modified", backend);
344 			}
345 		}
346 	}
347 }