001: /******************************************************************************
002: * JBoss, a division of Red Hat *
003: * Copyright 2006, Red Hat Middleware, LLC, and individual *
004: * contributors as indicated by the @authors tag. See the *
005: * copyright.txt in the distribution for a full listing of *
006: * individual contributors. *
007: * *
008: * This is free software; you can redistribute it and/or modify it *
009: * under the terms of the GNU Lesser General Public License as *
010: * published by the Free Software Foundation; either version 2.1 of *
011: * the License, or (at your option) any later version. *
012: * *
013: * This software is distributed in the hope that it will be useful, *
014: * but WITHOUT ANY WARRANTY; without even the implied warranty of *
015: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
016: * Lesser General Public License for more details. *
017: * *
018: * You should have received a copy of the GNU Lesser General Public *
019: * License along with this software; if not, write to the Free *
020: * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA *
021: * 02110-1301 USA, or see the FSF site: http://www.fsf.org. *
022: ******************************************************************************/package org.jboss.portal.wsrp.consumer;
023:
024: import org.apache.commons.httpclient.Cookie;
025: import org.jboss.logging.Logger;
026: import org.jboss.portal.common.util.ParameterValidation;
027: import org.jboss.portal.common.util.Tools;
028: import org.jboss.portal.wsrp.WSRPConstants;
029: import org.jboss.portal.wsrp.core.SessionContext;
030:
031: import java.util.ArrayList;
032: import java.util.Collection;
033: import java.util.HashMap;
034: import java.util.List;
035: import java.util.Map;
036:
037: /**
038: * Records session and cookie information for a producer.
039: *
040: * @author <a href="mailto:chris.laprun@jboss.com">Chris Laprun</a>
041: * @version $Revision: 9360 $
042: * @since 2.4 (May 30, 2006)
043: */
044: public class ProducerSessionInformation {
045: private static Logger log = Logger
046: .getLogger(ProducerSessionInformation.class);
047:
048: private boolean initCookieDone = false;
049: private boolean perGroupCookies = false;
050:
051: /** group id -> Cookie[] */
052: private Map<String, Cookie[]> groupCookies;
053:
054: /** portlet handle -> SessionInfo */
055: private Map<String, SessionInfo> portletSessions;
056:
057: /** session id -> portlet handle */
058: private Map<String, String> sessionId2PortletHandle;
059:
060: /** Cookies sent by the remote producer */
061: private Cookie[] userCookie;
062:
063: /** Parent SessionHandler so that session mappings can be updated */
064: private SessionHandler parent;
065:
066: /** The identifier of the Session containing this ProducerSessionInformation */
067: private String parentSessionId;
068:
069: /**
070: * public only for tests
071: *
072: * @return
073: * @since 2.6
074: */
075: public String getParentSessionId() {
076: return parentSessionId;
077: }
078:
079: /**
080: * public only for tests
081: *
082: * @param parentSessionId
083: * @throws IllegalStateException if an attempt is made to set the parent session id to a different one when it has
084: * already been set.
085: * @since 2.6
086: */
087: public void setParentSessionId(String parentSessionId) {
088: if (this .parentSessionId != null
089: && !this .parentSessionId.equals(parentSessionId)) {
090: throw new IllegalStateException(
091: "Cannot modify Parent Session id once it has been set!");
092: }
093:
094: this .parentSessionId = parentSessionId;
095: }
096:
097: public String getUserCookie() {
098: if (userCookie == null) {
099: return null;
100: }
101:
102: userCookie = purgeExpiredCookies(userCookie);
103: if (userCookie.length == 0) {
104: setInitCookieDone(false);
105: }
106: return outputToExternalForm(userCookie);
107: }
108:
109: public void setUserCookie(Cookie[] userCookie) {
110: ParameterValidation.throwIllegalArgExceptionIfNullOrEmpty(
111: userCookie, "cookies");
112:
113: this .userCookie = userCookie;
114: }
115:
116: public boolean isInitCookieDone() {
117: return initCookieDone;
118: }
119:
120: public void setInitCookieDone(boolean initCookieDone) {
121: this .initCookieDone = initCookieDone;
122: }
123:
124: public boolean isPerGroupCookies() {
125: return perGroupCookies;
126: }
127:
128: public void setPerGroupCookies(boolean perGroupCookies) {
129: this .perGroupCookies = perGroupCookies;
130: }
131:
132: public void setGroupCookieFor(String groupId, Cookie[] cookies) {
133: if (!isPerGroupCookies()) {
134: throw new IllegalStateException(
135: "Cannot add group cookie when cookie protocol is perUser.");
136: }
137:
138: if (groupId == null) {
139: throw new IllegalArgumentException(
140: "Cannot set cookie for a null portlet group id!");
141: }
142:
143: ParameterValidation.throwIllegalArgExceptionIfNullOrEmpty(
144: cookies, "cookies");
145:
146: if (groupCookies == null) {
147: groupCookies = new HashMap<String, Cookie[]>();
148: }
149:
150: if (groupCookies.containsKey(groupId)) {
151: log.debug("Trying to set a cookie for an existing group: "
152: + groupId);
153: }
154:
155: groupCookies.put(groupId, cookies);
156: }
157:
158: public String getGroupCookieFor(String groupId) {
159: if (groupCookies == null) {
160: return null;
161: }
162:
163: // purge expired cookies
164: Cookie[] cookies = groupCookies.get(groupId);
165: cookies = purgeExpiredCookies(cookies);
166:
167: // if there are no non-expired cookies left, we will need to re-init them
168: if (cookies.length == 0) {
169: setInitCookieDone(false);
170: }
171:
172: // update cookies for the considered group id
173: groupCookies.put(groupId, cookies);
174:
175: return outputToExternalForm(cookies);
176: }
177:
178: public void clearGroupCookies() {
179: groupCookies = null;
180: }
181:
182: public void addSessionForPortlet(String portletHandle,
183: SessionContext sessionContext) {
184: // sessionContext is validated in SessionInfo constructor
185: SessionInfo info = new SessionInfo(sessionContext,
186: portletHandle);
187:
188: if (portletSessions == null) {
189: portletSessions = new HashMap<String, SessionInfo>();
190: sessionId2PortletHandle = new HashMap<String, String>();
191: }
192:
193: portletSessions.put(portletHandle, info);
194: sessionId2PortletHandle.put(sessionContext.getSessionID(),
195: portletHandle);
196:
197: if (parent != null) {
198: parent.addSessionMapping(sessionContext.getSessionID(),
199: this );
200: }
201: }
202:
203: /**
204: * Retrieves the session id for the portlet with the specified handle. Note that this will "touch" the session, hence
205: * resetting the time since the last use of the session.
206: *
207: * @param portletHandle the handle of the portlet for which the session id is to be retrieved
208: * @return the session id for the specified portlet, <code>null</code> if there is no session associated with the
209: * portlet or if the session has expired.
210: */
211: public String getSessionIdForPortlet(String portletHandle) {
212: ProducerSessionInformation.SessionIdResult idResult = internalGetSessionIdForPortlet(portletHandle);
213: if (idResult.expired) {
214: return null;
215: }
216:
217: return idResult.id;
218: }
219:
220: public int getNumberOfSessions() {
221: if (portletSessions != null) {
222: return portletSessions.size();
223: } else {
224: return 0;
225: }
226: }
227:
228: /**
229: * @param sessionId
230: * @return the id of the removed session or <code>null</code> if the session had already expired
231: */
232: public String removeSession(String sessionId) {
233: ParameterValidation.throwIllegalArgExceptionIfNull(sessionId,
234: "session id");
235:
236: String portletHandle = sessionId2PortletHandle.get(sessionId);
237: if (portletHandle == null) {
238: throw new IllegalArgumentException("No such session id: '"
239: + sessionId + "'");
240: }
241:
242: return removeSessionForPortlet(portletHandle);
243: }
244:
245: /**
246: * @return a list containing the session ids that were still valid when they were removed and would need to be
247: * released
248: */
249: public List<String> removeSessions() {
250: List<String> idsToRelease = new ArrayList<String>(
251: getNumberOfSessions());
252:
253: // copy to avoid ConcurrentModificationException
254: List<String> handlesCopy = new ArrayList<String>(
255: portletSessions.keySet());
256:
257: for (String handle : handlesCopy) {
258: SessionIdResult result = removeSessionIdForPortlet(handle);
259:
260: // only release sessions that are still valid
261: if (!result.expired) {
262: idsToRelease.add(result.id);
263: }
264: }
265:
266: return idsToRelease;
267: }
268:
269: /**
270: * @param portletHandle
271: * @return the id of the removed session or <code>null</code> if the session had already expired
272: */
273: public String removeSessionForPortlet(String portletHandle) {
274: SessionIdResult result = removeSessionIdForPortlet(portletHandle);
275:
276: return result.expired ? null : result.id;
277: }
278:
279: private SessionIdResult removeSessionIdForPortlet(
280: String portletHandle) {
281: ProducerSessionInformation.SessionIdResult result = internalGetSessionIdForPortlet(portletHandle);
282: final String id = result.id;
283:
284: if (id == null) {
285: throw new IllegalArgumentException(
286: "There is no Session associated with portlet '"
287: + portletHandle + "'");
288: }
289:
290: // if the session is still valid, release it and remove the associated mappings
291: if (!result.expired) {
292: portletSessions.remove(portletHandle);
293: sessionId2PortletHandle.remove(id);
294: if (parent != null) {
295: parent.removeSessionId(id);
296: }
297: }
298:
299: return result;
300: }
301:
302: public void replaceUserCookiesWith(
303: ProducerSessionInformation currentSessionInfo) {
304: if (currentSessionInfo != null
305: && currentSessionInfo.userCookie != null
306: && currentSessionInfo.userCookie.length > 0) {
307: this .userCookie = currentSessionInfo.userCookie;
308: }
309: }
310:
311: /**
312: * Create a String representation of the specified array of Cookies.
313: *
314: * @param cookies the cookies to be output to external form
315: * @return a String representation of the cookies, ready to be sent over the wire.
316: */
317: private String outputToExternalForm(Cookie[] cookies) {
318: if (cookies != null && cookies.length != 0) {
319: StringBuffer sb = new StringBuffer(64);
320: for (int i = 0; i < cookies.length; i++) {
321: sb.append(cookies[i].toExternalForm());
322: if (i != cookies.length - 1) {
323: sb.append(";");
324: }
325: }
326: return sb.toString();
327: }
328: return null;
329: }
330:
331: /**
332: * Purges the expired cookies in the specified array.
333: *
334: * @param cookies the cookies to be purged
335: * @return an array of Cookies containing only still valid cookies
336: */
337: private Cookie[] purgeExpiredCookies(Cookie[] cookies) {
338: List<Cookie> cleanCookies = Tools.toList(cookies);
339:
340: for (Cookie cookie : cookies) {
341: if (cookie.isExpired()) {
342: cleanCookies.remove(cookie);
343: }
344: }
345: return cleanCookies.toArray(new Cookie[cleanCookies.size()]);
346: }
347:
348: private SessionIdResult internalGetSessionIdForPortlet(
349: String portletHandle) {
350: SessionInfo session = getSessionInfoFor(portletHandle);
351: if (session != null) {
352: String id = session.getSessionId();
353: if (!session.isStillValid()) {
354: portletSessions.remove(session.getPortletHandle());
355: sessionId2PortletHandle.remove(session.getSessionId());
356: if (parent != null) {
357: parent.removeSessionId(session.getSessionId());
358: }
359: return new SessionIdResult(id, true);
360: } else {
361: return new SessionIdResult(id, false);
362: }
363: }
364: return new SessionIdResult(null, false);
365: }
366:
367: private SessionInfo getSessionInfoFor(String portletHandle) {
368: ParameterValidation.throwIllegalArgExceptionIfNullOrEmpty(
369: portletHandle, "portlet handle", null);
370:
371: if (portletSessions == null) {
372: return null;
373: }
374:
375: return portletSessions.get(portletHandle);
376: }
377:
378: /**
379: * @return the known session id
380: * @since 2.6
381: */
382: Collection<String> getSessionIds() {
383: return sessionId2PortletHandle.keySet();
384: }
385:
386: /**
387: * @param sessionHandler
388: * @since 2.6
389: */
390: void setParent(SessionHandler sessionHandler) {
391: parent = sessionHandler;
392: }
393:
394: /**
395: * Update the mappings that were associated with the specified original portlet handle after it has been modified as
396: * a result of a clone operation to the specified new handle.
397: *
398: * @param originalHandle
399: * @param newHandle
400: * @since 2.6
401: */
402: public void updateHandleAssociatedInfo(String originalHandle,
403: String newHandle) {
404: ParameterValidation
405: .throwIllegalArgExceptionIfNullOrEmpty(originalHandle,
406: "original handle",
407: "Updating information associated with a portlet handle");
408: ParameterValidation
409: .throwIllegalArgExceptionIfNullOrEmpty(newHandle,
410: "new handle",
411: "Updating information associated with a portlet handle");
412:
413: String sessionId = getSessionIdForPortlet(originalHandle);
414: ProducerSessionInformation.SessionInfo info = getSessionInfoFor(originalHandle);
415: if (sessionId != null && info != null) {
416: portletSessions.put(newHandle, info);
417: portletSessions.remove(originalHandle);
418: sessionId2PortletHandle.put(sessionId, newHandle);
419: log.debug("Updated mapping information for '"
420: + originalHandle + "' to reference '" + newHandle
421: + "' instead.");
422: }
423: }
424:
425: private class SessionInfo {
426: private SessionContext sessionContext;
427: private long lastInvocationTime;
428: private String portletHandle;
429:
430: public SessionInfo(SessionContext sessionContext,
431: String portletHandle) {
432: ParameterValidation.throwIllegalArgExceptionIfNull(
433: sessionContext, "SessionContext");
434: ParameterValidation.throwIllegalArgExceptionIfNullOrEmpty(
435: sessionContext.getSessionID(), "session id",
436: "SessionContext");
437: ParameterValidation.throwIllegalArgExceptionIfNullOrEmpty(
438: portletHandle, "portlet handle", "SessionInfo");
439:
440: this .sessionContext = sessionContext;
441: this .portletHandle = portletHandle;
442: lastInvocationTime = System.currentTimeMillis();
443: }
444:
445: /**
446: * Checks that the session associated with the session context hasn't expired and update the last invocation time
447: *
448: * @return
449: */
450: private boolean isStillValid() {
451: int expires = sessionContext.getExpires();
452: if (expires == WSRPConstants.SESSION_NEVER_EXPIRES) {
453: return true;
454: }
455:
456: long now = System.currentTimeMillis();
457: long secondsSinceLastInvocation = (now - lastInvocationTime) / 1000;
458: lastInvocationTime = now;
459:
460: long diff = expires - secondsSinceLastInvocation;
461: log.debug("Session ID '" + sessionContext.getSessionID()
462: + "' is " + ((diff > 0) ? "" : "not")
463: + " valid (time since last invocation: " + diff
464: + ")");
465: return diff > 0;
466: }
467:
468: private String getSessionId() {
469: return sessionContext.getSessionID();
470: }
471:
472: private String getPortletHandle() {
473: return portletHandle;
474: }
475: }
476:
477: private class SessionIdResult {
478: private String id;
479: private boolean expired;
480:
481: public SessionIdResult(String id, boolean expired) {
482: this.id = id;
483: this.expired = expired;
484: }
485: }
486: }
|