1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19 package org.apache.myfaces.orchestra.lib.jsf;
20
21 import java.util.Map;
22
23 import javax.faces.FacesException;
24 import javax.faces.context.ExternalContext;
25 import javax.faces.context.FacesContext;
26
27 import org.apache.commons.logging.Log;
28 import org.apache.commons.logging.LogFactory;
29 import org.apache.myfaces.orchestra.CoreConfig;
30 import org.apache.myfaces.orchestra.conversation.ConversationContext;
31 import org.apache.myfaces.orchestra.conversation.ConversationManager;
32
33 /**
34 * RequestHandler that ensures that only one thread is processing
35 * each ConversationContext at a time.
36 *
37 * @since 1.1
38 */
39 class ContextLockRequestHandler implements RequestHandler
40 {
41 private Log log = LogFactory.getLog(ContextLockRequestHandler.class);
42 private ConversationContext context;
43 private boolean lockAcquired = false;
44
45 public void init(FacesContext facesContext) throws FacesException
46 {
47 if (getSerializeRequests(facesContext))
48 {
49 // Fetch the ConversationManager instance for the current HttpSession.
50 //
51 // We do not want to create a ConversationManager instance if one does not exist; that would force an
52 // HttpSession to be created in webapps where Orchestra usage only occurs in a small part of the webapp;
53 // if the user doesn't visit that part of the app we should not force a session and ConversationManager
54 // to be created until they do need it.
55 //
56 // We also should avoid creating an HttpSession unless one exists (and creating a ConversationManager
57 // instance requires a session). This is particularly useful for applications that have cookies turned
58 // off (ie use a jsessionid value encoded in the url). In this case, weblets requests will not have
59 // the jsessionid but do trigger the creation of a FacesContext, and therefore run this code. If we
60 // create a session here, then we will create a separate session for each and every weblets resource
61 // request - and they will live until the webapp session timeout expires. Bad. Very bad.
62 //
63 // Note that if the request being processed includes any code that uses FacesContext.responseWriter
64 // then that invokes the ConversationRequestParameterProvider. And that always writes out a contextId
65 // which in turn requires creating a ConversationManager. But there are value requests that run the
66 //
67 // Note that ConversationManager.getInstance requires the FrameworkAdapter to be initialized.
68 ConversationManager manager = ConversationManager.getInstance(false);
69 if (manager != null)
70 {
71 // Fetch a context for this request if one already exists, and lock it
72 // so that concurrent requests that affect this context block until
73 // this request is complete. Not doing so can cause races for resources
74 // within the current context, such as beans or PersistenceContexts.
75 //
76 // But if the request did not explicitly specify a contextId then we
77 // do NOT create a new context at this point. Doing so would create
78 // contexts for things like Weblet resource requests, and that context
79 // would then just hang around unused until it times out!
80 //
81 // Note that a request that does not explicitly specify a contextId
82 // might have one created for it later in the request, eg when an
83 // orchestra-scoped bean is accessed. However this is not a race
84 // condition because nothing else can refer to that newly created
85 // id until the response for this request has been sent back to the
86 // client browser.
87 context = manager.getCurrentRootConversationContext();
88 if (context != null)
89 {
90 try
91 {
92 if (log.isDebugEnabled())
93 {
94 log.debug("Locking context " + context.getId());
95 }
96 context.lockInterruptablyForCurrentThread();
97 lockAcquired = true;
98 }
99 catch(InterruptedException e)
100 {
101 throw new FacesException(e);
102 }
103 }
104 else
105 {
106 if (log.isDebugEnabled())
107 {
108 log.debug("No conversation context specified for this request");
109 }
110 }
111 }
112 else
113 {
114 if (log.isDebugEnabled())
115 {
116 log.debug("No conversation manager exists for this request");
117 }
118 }
119 }
120 }
121
122 public void deinit() throws FacesException
123 {
124 if (context != null)
125 {
126 if (lockAcquired == true)
127 {
128 if (log.isDebugEnabled())
129 {
130 log.debug("Unlocking context " + context.getId());
131 }
132 context.unlockForCurrentThread();
133 }
134 else
135 {
136 if (log.isDebugEnabled())
137 {
138 log.debug("Odd situation: lock never acquired. Perhaps InterruptedException occurred"
139 + " while waiting to get the context lock?");
140 }
141 }
142 }
143 }
144
145 private boolean getSerializeRequests(FacesContext facesContext)
146 {
147 ExternalContext ec = facesContext.getExternalContext();
148
149 // Check for deprecated setting via the OrchestraServletFilter.
150 Map reqScope = ec.getRequestMap();
151 Boolean serializeRequests = (Boolean) reqScope.get(CoreConfig.SERIALIZE_REQUESTS);
152 if (serializeRequests != null)
153 {
154 return serializeRequests.booleanValue();
155 }
156
157 // Check for the normal global init param; true unless "false" is specified
158 String value = ec.getInitParameter(CoreConfig.SERIALIZE_REQUESTS);
159 return !"false".equals(value); // NON-NLS
160 }
161 }
162