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