View Javadoc
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  
20  package org.apache.myfaces.tobago.internal.component;
21  
22  import org.apache.myfaces.tobago.ajax.AjaxUtils;
23  import org.apache.myfaces.tobago.component.Visual;
24  import org.apache.myfaces.tobago.internal.ajax.AjaxInternalUtils;
25  import org.apache.myfaces.tobago.internal.ajax.AjaxResponseRenderer;
26  import org.apache.myfaces.tobago.internal.layout.LayoutUtils;
27  import org.apache.myfaces.tobago.internal.util.FacesContextUtils;
28  import org.apache.myfaces.tobago.util.ApplyRequestValuesCallback;
29  import org.apache.myfaces.tobago.util.ComponentUtils;
30  import org.apache.myfaces.tobago.util.DebugUtils;
31  import org.apache.myfaces.tobago.util.FacesVersion;
32  import org.apache.myfaces.tobago.util.ProcessValidationsCallback;
33  import org.apache.myfaces.tobago.util.TobagoCallback;
34  import org.apache.myfaces.tobago.util.UpdateModelValuesCallback;
35  import org.slf4j.Logger;
36  import org.slf4j.LoggerFactory;
37  
38  import javax.faces.component.ContextCallback;
39  import javax.faces.component.UIComponent;
40  import javax.faces.component.UIViewRoot;
41  import javax.faces.context.FacesContext;
42  import java.io.IOException;
43  import java.util.Iterator;
44  import java.util.Map;
45  
46  public abstract class AbstractUIPage extends AbstractUIForm implements Visual {
47  
48    private static final Logger LOG = LoggerFactory.getLogger(AbstractUIPage.class);
49  
50    public static final String COMPONENT_TYPE = "org.apache.myfaces.tobago.Page";
51  
52    public static final String FORM_ACCEPT_CHARSET = "utf-8";
53  
54    private static final TobagoCallback APPLY_REQUEST_VALUES_CALLBACK = new ApplyRequestValuesCallback();
55    private static final ContextCallback PROCESS_VALIDATION_CALLBACK = new ProcessValidationsCallback();
56    private static final ContextCallback UPDATE_MODEL_VALUES_CALLBACK = new UpdateModelValuesCallback();
57  
58    private String formId;
59  
60    @Override
61    public boolean getRendersChildren() {
62      return true;
63    }
64  
65    public String getFormId(final FacesContext facesContext) {
66      if (formId == null) {
67        formId = getClientId(facesContext) + ComponentUtils.SUB_SEPARATOR + "form";
68      }
69      return formId;
70    }
71  
72    @Override
73    public void encodeBegin(final FacesContext facesContext) throws IOException {
74      if (!AjaxUtils.isAjaxRequest(facesContext)) {
75        super.encodeBegin(facesContext);
76        final UIComponent layoutManager = LayoutUtils.getLayoutManager(this);
77        if (layoutManager != null) {
78          layoutManager.encodeBegin(facesContext);
79        }
80      }
81    }
82  
83    @Override
84    public void encodeChildren(final FacesContext facesContext) throws IOException {
85      if (AjaxUtils.isAjaxRequest(facesContext)) {
86        new AjaxResponseRenderer().renderResponse(facesContext);
87      } else {
88        final UIComponent layoutManager = LayoutUtils.getLayoutManager(this);
89        if (layoutManager != null) {
90          layoutManager.encodeChildren(facesContext);
91        } else {
92          super.encodeChildren(facesContext);
93        }
94      }
95    }
96  
97    @Override
98    public void encodeEnd(final FacesContext facesContext) throws IOException {
99      if (!AjaxUtils.isAjaxRequest(facesContext)) {
100       final UIComponent layoutManager = LayoutUtils.getLayoutManager(this);
101       if (layoutManager != null) {
102         layoutManager.encodeEnd(facesContext);
103       }
104       super.encodeEnd(facesContext);
105     }
106     if (LOG.isTraceEnabled()) {
107       LOG.trace(DebugUtils.toString(this.getParent(), 0));
108     }
109   }
110 
111   private void processDecodes0(final FacesContext facesContext) {
112 
113     decode(facesContext);
114 
115     markSubmittedForm(facesContext);
116 
117     // invoke processDecodes() on children
118     for (final Iterator kids = getFacetsAndChildren(); kids.hasNext();) {
119       final UIComponent kid = (UIComponent) kids.next();
120       kid.processDecodes(facesContext);
121     }
122   }
123 
124   @Override
125   public void processDecodes(final FacesContext context) {
126     if (context == null) {
127       throw new NullPointerException("context");
128     }
129     final Map<String, UIComponent> ajaxComponents = AjaxInternalUtils.parseAndStoreComponents(context);
130     if (ajaxComponents != null) {
131       // first decode the page
132       final AbstractUIPage page = ComponentUtils.findPage(context);
133       page.decode(context);
134       page.markSubmittedForm(context);
135       FacesContextUtils.setAjax(context, true);
136 
137       // decode the action if actionComponent not inside one of the ajaxComponents
138       // otherwise it is decoded there
139       decodeActionComponent(context, page, ajaxComponents);
140 
141       // and all ajax components
142       for (final Map.Entry<String, UIComponent> entry : ajaxComponents.entrySet()) {
143         FacesContextUtils.setAjaxComponentId(context, entry.getKey());
144         invokeOnComponent(context, entry.getKey(), APPLY_REQUEST_VALUES_CALLBACK);
145       }
146     } else {
147       processDecodes0(context);
148     }
149   }
150 
151   private void decodeActionComponent(
152       final FacesContext facesContext, final AbstractUIPage page, final Map<String,
153       UIComponent> ajaxComponents) {
154     final String actionId = page.getActionId();
155     UIComponent actionComponent = null;
156     if (actionId != null) {
157       actionComponent = findComponent(actionId);
158       if (actionComponent == null && FacesVersion.supports20() && FacesVersion.isMyfaces()) {
159         final String bugActionId = actionId.replaceAll(":\\d+:", ":");
160         try {
161           actionComponent = findComponent(bugActionId);
162           //LOG.info("command = \"" + actionComponent + "\"", new Exception());
163         } catch (final Exception e) {
164           // ignore
165         }
166       }
167     }
168     if (actionComponent == null) {
169       return;
170     }
171     for (final UIComponent ajaxComponent : ajaxComponents.values()) {
172       UIComponent component = actionComponent;
173       while (component != null) {
174         if (component == ajaxComponent) {
175           return;
176         }
177         component = component.getParent();
178       }
179     }
180     invokeOnComponent(facesContext, actionId, APPLY_REQUEST_VALUES_CALLBACK);
181   }
182 
183 
184   @Override
185   public void processValidators(final FacesContext context) {
186     if (context == null) {
187       throw new NullPointerException("context");
188     }
189 
190     final Map<String, UIComponent> ajaxComponents = AjaxInternalUtils.getAjaxComponents(context);
191     if (ajaxComponents != null) {
192       for (final Map.Entry<String, UIComponent> entry : ajaxComponents.entrySet()) {
193         FacesContextUtils.setAjaxComponentId(context, entry.getKey());
194         invokeOnComponent(context, entry.getKey(), PROCESS_VALIDATION_CALLBACK);
195       }
196     } else {
197       super.processValidators(context);
198     }
199   }
200 
201   @Override
202   public void processUpdates(final FacesContext context) {
203     if (context == null) {
204       throw new NullPointerException("context");
205     }
206     final Map<String, UIComponent> ajaxComponents = AjaxInternalUtils.getAjaxComponents(context);
207     if (ajaxComponents != null) {
208       for (final Map.Entry<String, UIComponent> entry : ajaxComponents.entrySet()) {
209         invokeOnComponent(context, entry.getKey(), UPDATE_MODEL_VALUES_CALLBACK);
210       }
211     } else {
212       super.processUpdates(context);
213     }
214   }
215 
216   public void markSubmittedForm(final FacesContext facesContext) {
217     // find the form of the action command and set submitted to it and all
218     // children
219 
220     final UIViewRoot viewRoot = facesContext.getViewRoot();
221 
222     // reset old submitted state
223     setSubmitted(false);
224 
225     String sourceId = facesContext.getExternalContext().getRequestParameterMap().get("javax.faces.source");
226     UIComponent command = null;
227     if (sourceId != null) {
228       if (LOG.isDebugEnabled()) {
229         LOG.debug("sourceId = '" + sourceId + "'");
230       }
231       command = viewRoot.findComponent(sourceId);
232     } else {
233       LOG.warn("No sourceId found!");
234     }
235 
236     // TODO: remove this if block if proven this never happens anymore
237     if (command == null
238         && sourceId != null && sourceId.matches(".*:\\d+:.*")) {
239       // If currentActionId component was inside a sheet the id contains the
240       // rowIndex and is therefore not found here.
241       // We do not need the row here because we want just to find the
242       // related form, so removing the rowIndex will help here.
243       sourceId = sourceId.replaceAll(":\\d+:", ":");
244       try {
245         command = viewRoot.findComponent(sourceId);
246         //LOG.info("command = \"" + command + "\"", new Exception());
247       } catch (final Exception e) {
248         // ignore
249       }
250     }
251 
252     if (LOG.isTraceEnabled()) {
253       LOG.trace(sourceId);
254       LOG.trace("command:{}", command);
255       LOG.trace(DebugUtils.toString(viewRoot, 0));
256     }
257 
258     if (command != null) {
259       final AbstractUIForm form = ComponentUtils.findForm(command);
260       form.setSubmitted(true);
261 
262       if (LOG.isTraceEnabled()) {
263         LOG.trace("form:{}", form);
264         LOG.trace(form.getClientId(facesContext));
265       }
266     } else {
267       if (LOG.isDebugEnabled()) {
268         LOG.debug("Illegal actionId! Render response...");
269       }
270       facesContext.renderResponse();
271     }
272   }
273 
274   /** @deprecated XXX delete me */
275   @Deprecated
276   private String getActionId() {
277     LOG.warn("XXX should not be called, because of AJAX cleanup...");
278     return null;
279   }
280 }