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 log.debug("No conversation context specified for this request"); 107 } 108 } 109 else 110 { 111 log.debug("No conversation manager exists for this request"); 112 } 113 } 114 } 115 116 public void deinit() throws FacesException 117 { 118 if (context != null) 119 { 120 if (lockAcquired == true) 121 { 122 if (log.isDebugEnabled()) 123 { 124 log.debug("Unlocking context " + context.getId()); 125 } 126 context.unlockForCurrentThread(); 127 } 128 else 129 { 130 log.debug( 131 "Odd situation: lock never acquired. Perhaps InterruptedException occurred" 132 + " while waiting to get the context lock?"); 133 } 134 } 135 } 136 137 private boolean getSerializeRequests(FacesContext facesContext) 138 { 139 ExternalContext ec = facesContext.getExternalContext(); 140 141 // Check for deprecated setting via the OrchestraServletFilter. 142 Map reqScope = ec.getRequestMap(); 143 Boolean serializeRequests = (Boolean) reqScope.get(CoreConfig.SERIALIZE_REQUESTS); 144 if (serializeRequests != null) 145 { 146 return serializeRequests.booleanValue(); 147 } 148 149 // Check for the normal global init param; true unless "false" is specified 150 String value = ec.getInitParameter(CoreConfig.SERIALIZE_REQUESTS); 151 return !"false".equals(value); // NON-NLS 152 } 153 } 154