001 package org.apache.myfaces.tobago.component; 002 003 /* 004 * Licensed to the Apache Software Foundation (ASF) under one or more 005 * contributor license agreements. See the NOTICE file distributed with 006 * this work for additional information regarding copyright ownership. 007 * The ASF licenses this file to You under the Apache License, Version 2.0 008 * (the "License"); you may not use this file except in compliance with 009 * the License. You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, software 014 * distributed under the License is distributed on an "AS IS" BASIS, 015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 016 * See the License for the specific language governing permissions and 017 * limitations under the License. 018 */ 019 020 import org.slf4j.Logger; 021 import org.slf4j.LoggerFactory; 022 import org.apache.myfaces.tobago.ajax.AjaxUtils; 023 import org.apache.myfaces.tobago.compat.FacesUtils; 024 import org.apache.myfaces.tobago.compat.InvokeOnComponent; 025 import org.apache.myfaces.tobago.context.ClientProperties; 026 import org.apache.myfaces.tobago.context.TobagoFacesContext; 027 import org.apache.myfaces.tobago.internal.ajax.AjaxInternalUtils; 028 import org.apache.myfaces.tobago.internal.ajax.AjaxResponseRenderer; 029 import org.apache.myfaces.tobago.internal.component.AbstractUIPage; 030 import org.apache.myfaces.tobago.util.ApplyRequestValuesCallback; 031 import org.apache.myfaces.tobago.util.ComponentUtils; 032 import org.apache.myfaces.tobago.util.ProcessValidationsCallback; 033 import org.apache.myfaces.tobago.util.TobagoCallback; 034 import org.apache.myfaces.tobago.util.UpdateModelValuesCallback; 035 import org.apache.myfaces.tobago.util.VariableResolverUtils; 036 037 import javax.faces.FacesException; 038 import javax.faces.component.ContextCallback; 039 import javax.faces.component.UIComponent; 040 import javax.faces.context.FacesContext; 041 import javax.faces.event.AbortProcessingException; 042 import javax.faces.event.FacesEvent; 043 import javax.faces.event.PhaseId; 044 import java.io.IOException; 045 import java.util.ArrayList; 046 import java.util.ConcurrentModificationException; 047 import java.util.List; 048 import java.util.ListIterator; 049 import java.util.Locale; 050 import java.util.Map; 051 052 public class UIViewRoot extends javax.faces.component.UIViewRoot implements InvokeOnComponent { 053 054 private static final Logger LOG = LoggerFactory.getLogger(UIViewRoot.class); 055 056 private static final TobagoCallback APPLY_REQUEST_VALUES_CALLBACK = new ApplyRequestValuesCallback(); 057 private static final ContextCallback PROCESS_VALIDATION_CALLBACK = new ProcessValidationsCallback(); 058 private static final ContextCallback UPDATE_MODEL_VALUES_CALLBACK = new UpdateModelValuesCallback(); 059 060 private List<FacesEvent> events; 061 062 // XXX check if TOBAGO-811 is still an issue 063 // private int nextUniqueId; 064 065 @Override 066 public void setLocale(Locale locale) { 067 super.setLocale(locale); 068 ClientProperties clientProperties = VariableResolverUtils.resolveClientProperties(getFacesContext()); 069 clientProperties.setLocale(locale); 070 } 071 072 /* 073 // XXX check if TOBAGO-811 is still an issue 074 075 @Override 076 public Object saveState(FacesContext facesContext) { 077 if (FacesVersion.supports12()) { 078 return super.saveState(facesContext); 079 } else { 080 Object[] state = new Object[2]; 081 state[0] = super.saveState(facesContext); 082 state[1] = nextUniqueId; 083 return state; 084 } 085 } 086 087 @Override 088 public void restoreState(FacesContext facesContext, Object o) { 089 if (FacesVersion.supports12()) { 090 super.restoreState(facesContext, o); 091 } else { 092 Object[] state = (Object[]) o; 093 super.restoreState(facesContext, state[0]); 094 nextUniqueId = (Integer) state[1]; 095 } 096 } 097 098 @Override 099 public String createUniqueId() { 100 if (FacesVersion.supports12()) { 101 return super.createUniqueId(); 102 } else { 103 ExternalContext extCtx = FacesContext.getCurrentInstance().getExternalContext(); 104 return extCtx.encodeNamespace(UNIQUE_ID_PREFIX + nextUniqueId++); 105 } 106 } 107 */ 108 109 // XXX begin of JSF 2.0 like code 110 111 public void broadcastEventsForPhase(FacesContext context, PhaseId phaseId) { 112 broadcastForPhase(phaseId); 113 if (context.getRenderResponse() || context.getResponseComplete()) { 114 clearEvents(); 115 } 116 } 117 118 // ----------------------------------------------------------------------------- 119 // ----------------------------------------------------------------------------- 120 // 121 // The following code is copied from myfaces implementation! 122 // In suns jsf-api 1.1.01 are the events not cleared if renderResponse is true 123 // after processUpdates, seems to be a bug. This is fixed at least in 124 // Nightly Snapshot from 15.08.2005, but not in stable yet. 125 // Events are private member of UIViewRoot, so we have to copy anny code 126 // accessing them. 127 // 128 // TODO: remove if fixed in stable release! In 1.1_02 this seems to be fixed. 129 130 public void queueEvent(FacesEvent event) { 131 if (event == null) { 132 throw new NullPointerException("event"); 133 } 134 if (events == null) { 135 events = new ArrayList<FacesEvent>(); 136 } 137 events.add(event); 138 } 139 140 141 private void broadcastForPhase(PhaseId phaseId) { 142 if (events == null) { 143 return; 144 } 145 146 boolean abort = false; 147 148 int phaseIdOrdinal = phaseId.getOrdinal(); 149 for (ListIterator<FacesEvent> listiterator = events.listIterator(); listiterator.hasNext();) { 150 FacesEvent event = listiterator.next(); 151 int ordinal = event.getPhaseId().getOrdinal(); 152 if (ordinal == PhaseId.ANY_PHASE.getOrdinal() || ordinal == phaseIdOrdinal) { 153 UIComponent source = event.getComponent(); 154 try { 155 source.broadcast(event); 156 } catch (FacesException e) { 157 Throwable fe = e; 158 while (fe != null) { 159 if (fe instanceof AbortProcessingException) { 160 if (LOG.isTraceEnabled()) { 161 LOG.trace("AbortProcessingException caught!"); 162 } 163 // abort event processing 164 // Page 3-30 of JSF 1.1 spec: "Throw an AbortProcessingException, to tell the JSF implementation 165 // that no further broadcast of this event, or any further events, should take place." 166 abort = true; 167 break; 168 } 169 fe = fe.getCause(); 170 } 171 if (!abort) { 172 throw e; 173 } else { 174 break; 175 } 176 } finally { 177 178 try { 179 listiterator.remove(); 180 } catch (ConcurrentModificationException cme) { 181 int eventIndex = listiterator.previousIndex(); 182 events.remove(eventIndex); 183 //listiterator = events.listIterator(); 184 } 185 } 186 } 187 } 188 189 if (abort) { 190 // TODO: abort processing of any event of any phase or just of any event of the current phase??? 191 clearEvents(); 192 } 193 } 194 195 196 private void clearEvents() { 197 events = null; 198 } 199 200 201 @Override 202 public void processDecodes(FacesContext context) { 203 if (context == null) { 204 throw new NullPointerException("context"); 205 } 206 Map<String, UIComponent> ajaxComponents = AjaxInternalUtils.parseAndStoreComponents(context); 207 if (ajaxComponents != null) { 208 // first decode the page 209 AbstractUIPage page = ComponentUtils.findPage(context); 210 page.decode(context); 211 page.markSubmittedForm(context); 212 if (context instanceof TobagoFacesContext) { 213 ((TobagoFacesContext) context).setAjax(true); 214 } 215 // decode the action if actionComponent not inside one of the ajaxComponents 216 // otherwise it is decoded there 217 decodeActionComponent(context, page, ajaxComponents); 218 219 // and all ajax components 220 for (Map.Entry<String, UIComponent> entry : ajaxComponents.entrySet()) { 221 if (context instanceof TobagoFacesContext) { 222 ((TobagoFacesContext) context).setAjaxComponentId(entry.getKey()); 223 } 224 invokeOnComponent(context, entry.getKey(), APPLY_REQUEST_VALUES_CALLBACK); 225 } 226 } else { 227 super.processDecodes(context); 228 } 229 broadcastForPhase(PhaseId.APPLY_REQUEST_VALUES); 230 if (context.getRenderResponse() || context.getResponseComplete()) { 231 clearEvents(); 232 } 233 } 234 235 private void decodeActionComponent(FacesContext facesContext, AbstractUIPage page, Map<String, 236 UIComponent> ajaxComponents) { 237 String actionId = page.getActionId(); 238 UIComponent actionComponent = null; 239 if (actionId != null) { 240 actionComponent = findComponent(actionId); 241 } 242 if (actionComponent == null) { 243 return; 244 } 245 for (UIComponent ajaxComponent : ajaxComponents.values()) { 246 UIComponent component = actionComponent; 247 while (component != null) { 248 if (component == ajaxComponent) { 249 return; 250 } 251 component = component.getParent(); 252 } 253 } 254 invokeOnComponent(facesContext, actionId, APPLY_REQUEST_VALUES_CALLBACK); 255 } 256 257 258 @Override 259 public void processValidators(FacesContext context) { 260 if (context == null) { 261 throw new NullPointerException("context"); 262 } 263 264 Map<String, UIComponent> ajaxComponents = AjaxInternalUtils.getAjaxComponents(context); 265 if (ajaxComponents != null) { 266 for (Map.Entry<String, UIComponent> entry : ajaxComponents.entrySet()) { 267 if (context instanceof TobagoFacesContext) { 268 ((TobagoFacesContext) context).setAjaxComponentId(entry.getKey()); 269 } 270 invokeOnComponent(context, entry.getKey(), PROCESS_VALIDATION_CALLBACK); 271 } 272 } else { 273 super.processValidators(context); 274 } 275 broadcastForPhase(PhaseId.PROCESS_VALIDATIONS); 276 if (context.getRenderResponse() || context.getResponseComplete()) { 277 clearEvents(); 278 } 279 } 280 281 @Override 282 public void processUpdates(FacesContext context) { 283 if (context == null) { 284 throw new NullPointerException("context"); 285 } 286 Map<String, UIComponent> ajaxComponents = AjaxInternalUtils.getAjaxComponents(context); 287 if (ajaxComponents != null) { 288 for (Map.Entry<String, UIComponent> entry : ajaxComponents.entrySet()) { 289 invokeOnComponent(context, entry.getKey(), UPDATE_MODEL_VALUES_CALLBACK); 290 } 291 } else { 292 super.processUpdates(context); 293 } 294 broadcastForPhase(PhaseId.UPDATE_MODEL_VALUES); 295 if (context.getRenderResponse() || context.getResponseComplete()) { 296 clearEvents(); 297 } 298 } 299 300 @Override 301 public void processApplication(FacesContext context) { 302 if (context == null) { 303 throw new NullPointerException("context"); 304 } 305 broadcastForPhase(PhaseId.INVOKE_APPLICATION); 306 if (context.getRenderResponse() || context.getResponseComplete()) { 307 clearEvents(); 308 } 309 } 310 311 // XXX end of JSF 2.0 like code 312 313 @Override 314 public boolean getRendersChildren() { 315 if (AjaxUtils.isAjaxRequest(FacesContext.getCurrentInstance())) { 316 return true; 317 } else { 318 return super.getRendersChildren(); 319 } 320 } 321 322 @Override 323 public void encodeChildren(FacesContext context) throws IOException { 324 if (AjaxUtils.isAjaxRequest(context)) { 325 new AjaxResponseRenderer().renderResponse(context); 326 327 } else { 328 super.encodeChildren(context); 329 } 330 } 331 332 // todo: after removing jsf 1.1: @Override 333 public boolean invokeOnComponent(FacesContext context, String clientId, ContextCallback callback) 334 throws FacesException { 335 return FacesUtils.invokeOnComponent(context, this, clientId, callback); 336 } 337 }