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.filter;
016:
017: import java.io.ByteArrayInputStream;
018: import java.io.ObjectInputStream;
019: import java.util.HashMap;
020: import java.util.Map;
021: import org.apache.commons.lang.RandomStringUtils;
022: import org.apache.commons.lang.SerializationUtils;
023: import org.apache.commons.logging.Log;
024: import org.apache.commons.logging.LogFactory;
025: import org.araneaframework.Environment;
026: import org.araneaframework.InputData;
027: import org.araneaframework.OutputData;
028: import org.araneaframework.Path;
029: import org.araneaframework.Relocatable;
030: import org.araneaframework.Service;
031: import org.araneaframework.Relocatable.RelocatableService;
032: import org.araneaframework.core.RelocatableDecorator;
033: import org.araneaframework.core.StandardEnvironment;
034: import org.araneaframework.framework.ThreadContext;
035: import org.araneaframework.framework.TopServiceContext;
036: import org.araneaframework.framework.core.BaseFilterService;
037: import org.araneaframework.http.HttpInputData;
038: import org.araneaframework.http.HttpOutputData;
039: import org.araneaframework.http.ThreadCloningContext;
040: import org.araneaframework.http.util.URLUtil;
041:
042: /**
043: * Filter that clones session threads upon requests and redirects request to cloned session thread. Should
044: * always be configured as the first thread-level filter.
045: *
046: * @author Taimo Peelo (taimo@araneaframework.org)
047: */
048: public class StandardThreadCloningFilterService extends
049: BaseFilterService implements ThreadCloningContext {
050: private static final Log log = LogFactory
051: .getLog(StandardThreadCloningFilterService.class);
052: private Long timeToLive;
053: private boolean initializeChildren = true;
054: private transient byte[] threadSnapshot;
055:
056: public StandardThreadCloningFilterService() {
057: super ();
058: }
059:
060: protected StandardThreadCloningFilterService(Service childService,
061: boolean freshChildren) {
062: /* if children are not fresh (they are clones) they may not be re-inited */
063: this .initializeChildren = freshChildren;
064: /* fresh children need decoration */
065: if (freshChildren)
066: setChildService(childService);
067: else
068: super .setChildService(childService);
069: }
070:
071: public void setChildService(Service childService) {
072: super .setChildService(new RelocatableDecorator(childService));
073: }
074:
075: /**
076: * Sets the time of inactivity after which cloned service may be killed by thread router.
077: * @param timeToLive allowed inactivity time, in milliseconds
078: */
079: public void setTimeToLive(Long timeToLive) {
080: this .timeToLive = timeToLive;
081: }
082:
083: protected void init() throws Exception {
084: if (initializeChildren)
085: super .init();
086: }
087:
088: protected void action(Path path, InputData input, OutputData output)
089: throws Exception {
090: if (cloningRequested(input)) {
091: redirect(cloningAction(path, input, output));
092: } else if (snapshotRequested(input)) {
093: this .threadSnapshot = takeSnapshot((RelocatableService) childService);
094: super .action(path, input, output);
095: } else
096: super .action(path, input, output);
097: this .threadSnapshot = null;
098: }
099:
100: protected String cloningAction(Path path, InputData input,
101: OutputData output) throws Exception {
102: if (log.isDebugEnabled())
103: log.debug("Attempting to clone current thread ('"
104: + getThreadServiceCtx().getCurrentId() + "').");
105:
106: RelocatableService clone = clone((RelocatableService) childService);
107: String cloneServiceId = startClonedThread(clone);
108: clone._getService().action(path, input, output);
109:
110: // return URL where cloned service resides
111: return ((HttpOutputData) getOutputData())
112: .encodeURL((getResponseURL(getRequestURL(),
113: (String) getTopServiceCtx().getCurrentId(),
114: cloneServiceId)));
115: }
116:
117: protected void redirect(String location) throws Exception {
118: ((HttpOutputData) getOutputData()).sendRedirect(location);
119: }
120:
121: protected Environment getChildEnvironment() {
122: return new StandardEnvironment(super .getChildEnvironment(),
123: ThreadCloningContext.class, this );
124: }
125:
126: /**
127: * Clones given {@link org.araneaframework.Relocatable.RelocatableService}.
128: * Clone is created by first serializing and then deserializing given <code>service</code>.
129: * Created clone does not have {@link org.araneaframework.Environment}.
130: * @return clone (without {@link org.araneaframework.Environment}) of given {@link RelocatableService}.
131: */
132: protected RelocatableService clone(RelocatableService service)
133: throws Exception {
134: this .threadSnapshot = takeSnapshot(service);
135: ObjectInputStream ois = new ObjectInputStream(
136: new ByteArrayInputStream(this .threadSnapshot));
137: return (RelocatableService) ois.readObject();
138: }
139:
140: protected byte[] takeSnapshot(RelocatableService service) {
141: Relocatable.Interface relocatable = service._getRelocatable();
142: Environment env = relocatable.getCurrentEnvironment();
143: relocatable.overrideEnvironment(null);
144:
145: byte[] result = SerializationUtils.serialize(service);
146: relocatable.overrideEnvironment(env);
147:
148: return result;
149: }
150:
151: /** Wraps the cloned service in a new StandardThreadCloningFilterService, attaches it to {@link ThreadContext}.
152: * <ul>
153: * <li> created service should not be decorated relocatable again, because <code>clone</code> is relocatable already</li>
154: * <li> new StandardThreadCloningFilterService's childService may not be reinited!</li>
155: * </ul>
156: *
157: * This method is public and part of {@link ThreadCloningContext} interface since Aranea 1.1.
158: *
159: * @return thread id assigned to wrapped service */
160: public String startClonedThread(RelocatableService clone) {
161: StandardThreadCloningFilterService wrappedClone = new StandardThreadCloningFilterService(
162: clone, false);
163: String cloneServiceId = RandomStringUtils.randomAlphabetic(12);
164:
165: if (log.isDebugEnabled())
166: log.debug("Attaching the cloned thread as '"
167: + cloneServiceId + "'.");
168:
169: startThreadService(getThreadServiceCtx(), wrappedClone,
170: cloneServiceId);
171: clone._getRelocatable().overrideEnvironment(
172: wrappedClone.getChildEnvironment());
173:
174: return cloneServiceId;
175: }
176:
177: public byte[] acquireThreadSnapshot() {
178: if (this .threadSnapshot == null && log.isWarnEnabled()) {
179: log
180: .warn("Ineffective call to acquireSerializedThread(), no thread snapshot present. Request is probably missing required parameters.");
181: }
182: return this .threadSnapshot;
183: }
184:
185: private void startThreadService(ThreadContext threadCtx,
186: StandardThreadCloningFilterService cloneService,
187: String cloneServiceId) {
188: if (timeToLive == null)
189: threadCtx.addService(cloneServiceId, cloneService);
190: else
191: threadCtx.addService(cloneServiceId, cloneService,
192: timeToLive);
193: }
194:
195: protected boolean cloningRequested(InputData input)
196: throws Exception {
197: return input.getGlobalData().get(
198: ThreadCloningContext.CLONING_REQUEST_KEY) != null;
199: }
200:
201: protected boolean snapshotRequested(InputData input)
202: throws Exception {
203: return input.getGlobalData().get(
204: ThreadCloningContext.CLONE_ONLY_REQUEST_KEY) != null;
205: }
206:
207: protected String getRequestURL() {
208: return ((HttpInputData) getInputData()).getContainerURL();
209: }
210:
211: protected String getResponseURL(String url, String topServiceId,
212: String threadServiceId) {
213: Map m = new HashMap();
214: m.put(TopServiceContext.TOP_SERVICE_KEY, topServiceId);
215: m.put(ThreadContext.THREAD_SERVICE_KEY, threadServiceId);
216: return URLUtil.parametrizeURI(url, m);
217: }
218:
219: protected ThreadContext getThreadServiceCtx() {
220: return ((ThreadContext) getEnvironment().requireEntry(
221: ThreadContext.class));
222: }
223:
224: protected TopServiceContext getTopServiceCtx() {
225: return ((TopServiceContext) getEnvironment().requireEntry(
226: TopServiceContext.class));
227: }
228: }
|