001: /**
002: * Copyright 2006 Webmedia Group Ltd.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: **/package org.araneaframework.http.router;
016:
017: import java.io.Serializable;
018: import java.util.Collections;
019: import java.util.HashMap;
020: import java.util.Map;
021: import javax.servlet.http.HttpSession;
022: import org.apache.commons.logging.Log;
023: import org.apache.commons.logging.LogFactory;
024: import org.araneaframework.Environment;
025: import org.araneaframework.InputData;
026: import org.araneaframework.Message;
027: import org.araneaframework.OutputData;
028: import org.araneaframework.Path;
029: import org.araneaframework.Relocatable.RelocatableService;
030: import org.araneaframework.core.BaseService;
031: import org.araneaframework.core.RelocatableDecorator;
032: import org.araneaframework.core.ServiceFactory;
033: import org.araneaframework.core.StandardEnvironment;
034: import org.araneaframework.core.util.ReadWriteLock;
035: import org.araneaframework.core.util.ReaderPreferenceReadWriteLock;
036: import org.araneaframework.http.util.ServletUtil;
037:
038: /**
039: * Associates this service with the HttpSession. If a session does not exist, it is created.
040: * Also handles the invalidation of the session.
041: *
042: * @author Jevgeni Kabanov (ekabanov <i>at</i> araneaframework <i>dot</i> org)
043: * @author Alar Kvell (alar@araneaframework.org)
044: */
045: public class StandardHttpSessionRouterService extends BaseService {
046:
047: private static final long serialVersionUID = 1L;
048:
049: private static final Log log = LogFactory
050: .getLog(StandardHttpSessionRouterService.class);
051:
052: /**
053: * The destroy parameter key in the request.
054: */
055: public static final String DESTROY_SESSION_PARAMETER_KEY = "destroySession";
056:
057: /**
058: * The synchronization parameter key in the request.
059: *
060: * @since 1.1
061: */
062: public static final String SYNC_PARAMETER_KEY = "araSync";
063:
064: /**
065: * The key of the service in the session.
066: */
067: public static final String SESSION_SERVICE_KEY = "sessionService";
068:
069: /**
070: * The key of the synchronization object in the session.
071: *
072: * @since 1.1
073: */
074: public static final String SESSION_SYNC_OBJECT_KEY = "sessionSyncObject";
075:
076: private ServiceFactory serviceFactory;
077:
078: private Map locks = Collections.synchronizedMap(new HashMap());
079:
080: /**
081: * Sets the factory which is used to build the service if one does not exist in the session.
082: */
083: public void setSessionServiceFactory(ServiceFactory factory) {
084: serviceFactory = factory;
085: }
086:
087: /**
088: * Routes an action to the service in the session. If the service does not exist,
089: * it is created.
090: */
091: protected void action(Path path, InputData input, OutputData output)
092: throws Exception {
093: HttpSession sess = ServletUtil.getRequest(input).getSession();
094:
095: boolean destroySession = input.getGlobalData().get(
096: DESTROY_SESSION_PARAMETER_KEY) != null;
097: if (destroySession) {
098: sess.invalidate(); //XXX: Do we neeed to sync this?
099: return;
100: }
101:
102: // Requests are synchronized by default (if "sync" parameter is missing)
103: if (!"false".equals(input.getGlobalData().get(
104: SYNC_PARAMETER_KEY))) {
105: /*
106: * "Synchronized" requests use an additional dummy object for
107: * synchronization, so that only one "synchronized" request is processed
108: * at a time.
109: */
110: synchronized (getOrCreateSessionSyncObject(sess)) {
111: doAction(path, input, output, sess);
112: }
113: } else {
114: doAction(path, input, output, sess);
115: }
116: }
117:
118: /**
119: * @since 1.1
120: */
121: protected void doAction(Path path, InputData input,
122: OutputData output, HttpSession sess) throws Exception {
123: /*
124: * Both "synchronized" and "unsynchronized" requests use the session object
125: * to synchronize critical sections dealing with locking in this method.
126: */
127: synchronized (sess) {
128: ReadWriteLock lock = (ReadWriteLock) locks.get(sess);
129: if (lock == null) {
130: lock = new ReaderPreferenceReadWriteLock();
131: locks.put(sess, lock);
132: }
133: lock.readLock().acquire();
134: }
135:
136: RelocatableService service = getOrCreateSessionService(sess);
137:
138: try {
139: service._getService().action(path, input, output);
140: } finally {
141: ReadWriteLock lock = (ReadWriteLock) locks.get(sess);
142: lock.readLock().release();
143: }
144:
145: synchronized (sess) {
146: ReadWriteLock lock = (ReadWriteLock) locks.get(sess);
147: if (lock.writeLock().attempt(0)) {
148: try {
149: log
150: .info("Propagating session updates to cluster nodes");
151:
152: service._getRelocatable().overrideEnvironment(null);
153:
154: try {
155: sess.setAttribute(SESSION_SERVICE_KEY, service);
156: } catch (IllegalStateException e) {
157: log
158: .warn("Session invalidated before request was finished.");
159: }
160: } finally {
161: locks.remove(sess); // XXX why do we remove the lock? why not do lock.writeLock().release();
162: }
163: }
164: }
165: }
166:
167: public void propagate(Message message, InputData input,
168: OutputData output) { // XXX who uses this method?
169: HttpSession sess = ServletUtil.getRequest(input).getSession();
170:
171: RelocatableService service = getOrCreateSessionService(sess);
172:
173: message.send(null, service);
174:
175: sess.setAttribute(SESSION_SERVICE_KEY, service);
176: }
177:
178: private synchronized Object getOrCreateSessionSyncObject(
179: HttpSession sess) {
180: Object syncObject = sess.getAttribute(SESSION_SYNC_OBJECT_KEY);
181: if (syncObject == null) {
182: syncObject = new Serializable() {
183: };
184: sess.setAttribute(SESSION_SYNC_OBJECT_KEY, syncObject); // XXX why do we store this object in session? why not keep it in a synchronized map?
185: }
186: return syncObject;
187: }
188:
189: private synchronized RelocatableService getOrCreateSessionService(
190: HttpSession sess) {
191: Environment newEnv = new StandardEnvironment(getEnvironment(),
192: HttpSession.class, sess);
193:
194: RelocatableService result = null;
195:
196: if (sess.getAttribute(SESSION_SERVICE_KEY) == null) {
197: log.debug("Created HTTP session '" + sess.getId() + "'");
198: result = new RelocatableDecorator(serviceFactory
199: .buildService(getEnvironment()));
200:
201: result._getComponent().init(getScope(), newEnv);
202: } else {
203: result = (RelocatableService) sess
204: .getAttribute(SESSION_SERVICE_KEY);
205: result._getRelocatable().overrideEnvironment(newEnv);
206: log.debug("Reusing HTTP session '" + sess.getId() + "'");
207: }
208:
209: return result;
210: }
211:
212: }
|