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.Attributes;
24  import org.apache.myfaces.tobago.component.ComponentTypes;
25  import org.apache.myfaces.tobago.component.DeprecatedDimension;
26  import org.apache.myfaces.tobago.component.Facets;
27  import org.apache.myfaces.tobago.component.OnComponentPopulated;
28  import org.apache.myfaces.tobago.component.RendererTypes;
29  import org.apache.myfaces.tobago.internal.ajax.AjaxInternalUtils;
30  import org.apache.myfaces.tobago.internal.ajax.AjaxResponseRenderer;
31  import org.apache.myfaces.tobago.internal.layout.LayoutUtils;
32  import org.apache.myfaces.tobago.internal.util.Deprecation;
33  import org.apache.myfaces.tobago.internal.util.FacesContextUtils;
34  import org.apache.myfaces.tobago.internal.webapp.TobagoMultipartFormdataRequest;
35  import org.apache.myfaces.tobago.layout.Box;
36  import org.apache.myfaces.tobago.layout.LayoutComponent;
37  import org.apache.myfaces.tobago.layout.LayoutContainer;
38  import org.apache.myfaces.tobago.layout.LayoutManager;
39  import org.apache.myfaces.tobago.layout.Measure;
40  import org.apache.myfaces.tobago.model.PageState;
41  import org.apache.myfaces.tobago.model.PageStateImpl;
42  import org.apache.myfaces.tobago.util.ApplyRequestValuesCallback;
43  import org.apache.myfaces.tobago.util.ComponentUtils;
44  import org.apache.myfaces.tobago.util.CreateComponentUtils;
45  import org.apache.myfaces.tobago.util.DebugUtils;
46  import org.apache.myfaces.tobago.util.FacesVersion;
47  import org.apache.myfaces.tobago.util.ProcessValidationsCallback;
48  import org.apache.myfaces.tobago.util.TobagoCallback;
49  import org.apache.myfaces.tobago.util.UpdateModelValuesCallback;
50  import org.slf4j.Logger;
51  import org.slf4j.LoggerFactory;
52  
53  import javax.el.ELContext;
54  import javax.el.ValueExpression;
55  import javax.faces.FacesException;
56  import javax.faces.application.FacesMessage;
57  import javax.faces.component.ContextCallback;
58  import javax.faces.component.UIComponent;
59  import javax.faces.component.UIViewRoot;
60  import javax.faces.context.FacesContext;
61  import javax.servlet.ServletRequest;
62  import javax.servlet.http.HttpServletRequestWrapper;
63  import java.io.IOException;
64  import java.util.Iterator;
65  import java.util.List;
66  import java.util.Map;
67  
68  public abstract class AbstractUIPage extends AbstractUIForm
69      implements OnComponentPopulated, LayoutContainer, DeprecatedDimension {
70  
71    private static final Logger LOG = LoggerFactory.getLogger(AbstractUIPage.class);
72  
73    public static final String COMPONENT_TYPE = "org.apache.myfaces.tobago.Page";
74  
75    public static final String FORM_ACCEPT_CHARSET = "utf-8";
76  
77    private static final TobagoCallback APPLY_REQUEST_VALUES_CALLBACK = new ApplyRequestValuesCallback();
78    private static final ContextCallback PROCESS_VALIDATION_CALLBACK = new ProcessValidationsCallback();
79    private static final ContextCallback UPDATE_MODEL_VALUES_CALLBACK = new UpdateModelValuesCallback();
80  
81    private String formId;
82  
83    private String actionId;
84  
85    private Box actionPosition;
86  
87    @Override
88    public boolean getRendersChildren() {
89      return true;
90    }
91  
92    public String getFormId(FacesContext facesContext) {
93      if (formId == null) {
94        formId = getClientId(facesContext) + ComponentUtils.SUB_SEPARATOR + "form";
95      }
96      return formId;
97    }
98  
99    @Override
100   public void encodeBegin(FacesContext facesContext) throws IOException {
101     if (!AjaxUtils.isAjaxRequest(facesContext)) {
102       super.encodeBegin(facesContext);
103       ((AbstractUILayoutBase) getLayoutManager()).encodeBegin(facesContext);
104     }
105   }
106 
107   @Override
108   public void encodeChildren(FacesContext facesContext) throws IOException {
109     if (AjaxUtils.isAjaxRequest(facesContext)) {
110       new AjaxResponseRenderer().renderResponse(facesContext);
111     } else {
112       ((AbstractUILayoutBase) getLayoutManager()).encodeChildren(facesContext);
113     }
114   }
115 
116   @Override
117   public void encodeEnd(FacesContext facesContext) throws IOException {
118     if (!AjaxUtils.isAjaxRequest(facesContext)) {
119       ((AbstractUILayoutBase) getLayoutManager()).encodeEnd(facesContext);
120       super.encodeEnd(facesContext);
121     }
122   }
123 
124   private void processDecodes0(FacesContext facesContext) {
125 
126     checkTobagoRequest(facesContext);
127 
128     decode(facesContext);
129 
130     markSubmittedForm(facesContext);
131 
132     // invoke processDecodes() on children
133     for (final Iterator kids = getFacetsAndChildren(); kids.hasNext();) {
134       final UIComponent kid = (UIComponent) kids.next();
135       kid.processDecodes(facesContext);
136     }
137   }
138 
139   @Override
140   public void processDecodes(FacesContext context) {
141     if (context == null) {
142       throw new NullPointerException("context");
143     }
144     Map<String, UIComponent> ajaxComponents = AjaxInternalUtils.parseAndStoreComponents(context);
145     if (ajaxComponents != null) {
146       // first decode the page
147       AbstractUIPage page = ComponentUtils.findPage(context);
148       page.decode(context);
149       page.markSubmittedForm(context);
150       FacesContextUtils.setAjax(context, true);
151 
152       // decode the action if actionComponent not inside one of the ajaxComponents
153       // otherwise it is decoded there
154       decodeActionComponent(context, page, ajaxComponents);
155 
156       // and all ajax components
157       for (Map.Entry<String, UIComponent> entry : ajaxComponents.entrySet()) {
158         FacesContextUtils.setAjaxComponentId(context, entry.getKey());
159         invokeOnComponent(context, entry.getKey(), APPLY_REQUEST_VALUES_CALLBACK);
160       }
161     } else {
162       processDecodes0(context);
163     }
164   }
165 
166   private void decodeActionComponent(
167       FacesContext facesContext, AbstractUIPage page, Map<String,
168       UIComponent> ajaxComponents) {
169     String actionId = page.getActionId();
170     UIComponent actionComponent = null;
171     if (actionId != null) {
172       actionComponent = findComponent(actionId);
173       if (actionComponent == null && FacesVersion.supports20() && FacesVersion.isMyfaces()) {
174         String bugActionId = actionId.replaceAll(":\\d+:", ":");
175         try {
176           actionComponent = findComponent(bugActionId);
177           //LOG.info("command = \"" + actionComponent + "\"", new Exception());
178         } catch (Exception e) {
179           // ignore
180         }
181       }
182     }
183     if (actionComponent == null) {
184       return;
185     }
186     for (UIComponent ajaxComponent : ajaxComponents.values()) {
187       UIComponent component = actionComponent;
188       while (component != null) {
189         if (component == ajaxComponent) {
190           return;
191         }
192         component = component.getParent();
193       }
194     }
195     invokeOnComponent(facesContext, actionId, APPLY_REQUEST_VALUES_CALLBACK);
196   }
197 
198 
199   @Override
200   public void processValidators(FacesContext context) {
201     if (context == null) {
202       throw new NullPointerException("context");
203     }
204 
205     Map<String, UIComponent> ajaxComponents = AjaxInternalUtils.getAjaxComponents(context);
206     if (ajaxComponents != null) {
207       for (Map.Entry<String, UIComponent> entry : ajaxComponents.entrySet()) {
208         FacesContextUtils.setAjaxComponentId(context, entry.getKey());
209         invokeOnComponent(context, entry.getKey(), PROCESS_VALIDATION_CALLBACK);
210       }
211     } else {
212       super.processValidators(context);
213     }
214   }
215 
216   @Override
217   public void processUpdates(FacesContext context) {
218     if (context == null) {
219       throw new NullPointerException("context");
220     }
221     Map<String, UIComponent> ajaxComponents = AjaxInternalUtils.getAjaxComponents(context);
222     if (ajaxComponents != null) {
223       for (Map.Entry<String, UIComponent> entry : ajaxComponents.entrySet()) {
224         invokeOnComponent(context, entry.getKey(), UPDATE_MODEL_VALUES_CALLBACK);
225       }
226     } else {
227       super.processUpdates(context);
228     }
229   }
230 
231 
232   @Override
233   public boolean invokeOnComponent(FacesContext context, String clientId, ContextCallback callback)
234       throws FacesException {
235     return ComponentUtils.invokeOnComponent(context, this, clientId, callback);
236   }
237 
238 
239   public void markSubmittedForm(FacesContext facesContext) {
240     // find the form of the action command and set submitted to it and all
241     // children
242 
243     // reset old submitted state
244     setSubmitted(false);
245 
246     String currentActionId = getActionId();
247     if (LOG.isDebugEnabled()) {
248       LOG.debug("actionId = '" + currentActionId + "'");
249     }
250 
251     final UIViewRoot viewRoot = facesContext.getViewRoot();
252     UIComponent command = viewRoot.findComponent(currentActionId);
253 
254     // TODO: remove this if block if proven this never happens anymore
255     if (command == null
256         && currentActionId != null && currentActionId.matches(".*:\\d+:.*")) {
257       // If currentActionId component was inside a sheet the id contains the
258       // rowIndex and is therefore not found here.
259       // We do not need the row here because we want just to find the
260       // related form, so removing the rowIndex will help here.
261       currentActionId = currentActionId.replaceAll(":\\d+:", ":");
262       try {
263         command = viewRoot.findComponent(currentActionId);
264         //LOG.info("command = \"" + command + "\"", new Exception());
265       } catch (Exception e) {
266         // ignore
267       }
268     }
269 
270     if (LOG.isTraceEnabled()) {
271       LOG.trace(currentActionId);
272       LOG.trace("command:{}", command);
273       LOG.trace(DebugUtils.toString(viewRoot, 0));
274     }
275 
276     if (command != null) {
277       AbstractUIForm form = ComponentUtils.findForm(command);
278       form.setSubmitted(true);
279 
280       if (LOG.isTraceEnabled()) {
281         LOG.trace("form:{}", form);
282         LOG.trace(form.getClientId(facesContext));
283       }
284     } else {
285       if (LOG.isDebugEnabled()) {
286         LOG.debug("Illegal actionId! Rerender the view.");
287       }
288       facesContext.renderResponse();
289     }
290   }
291 
292   private void checkTobagoRequest(FacesContext facesContext) {
293     // multipart/form-data must use TobagoMultipartFormdataRequest
294     String contentType = facesContext.getExternalContext().getRequestHeaderMap().get("content-type");
295     if (contentType != null && contentType.startsWith("multipart/form-data")) {
296       Object request = facesContext.getExternalContext().getRequest();
297       boolean okay = false;
298       if (request instanceof TobagoMultipartFormdataRequest) {
299         okay = true;
300       } else if (request instanceof HttpServletRequestWrapper) {
301         ServletRequest wrappedRequest = ((HttpServletRequestWrapper) request).getRequest();
302         if (wrappedRequest instanceof TobagoMultipartFormdataRequest) {
303           okay = true;
304         }
305       }
306       // TODO PortletRequest ??
307       if (!okay) {
308         LOG.error("Can't process multipart/form-data without TobagoRequest. "
309             + "Please check the web.xml and define a TobagoMultipartFormdataFilter. "
310             + "See documentation for <tc:file>");
311         facesContext.addMessage(null, new FacesMessage("An error has occurred!"));
312       }
313     }
314   }
315 
316   /**
317    * @deprecated PageState is deprecated since 1.5.0
318    */
319   @Deprecated
320   public void updatePageState(FacesContext facesContext) {
321   }
322 
323   /**
324    * @deprecated PageState is deprecated since 1.5.0
325    */
326   @Deprecated
327   public PageState getPageState(FacesContext facesContext) {
328     final ValueExpression expression = getValueExpression(Attributes.STATE);
329     if (expression != null) {
330       final ELContext elContext = facesContext.getELContext();
331       PageState state = (PageState) expression.getValue(elContext);
332       if (state == null) {
333         state = new PageStateImpl();
334         expression.setValue(elContext, state);
335       }
336       return state;
337     } else {
338       return null;
339     }
340   }
341 
342   public String getActionId() {
343     return actionId;
344   }
345 
346   public void setActionId(String actionId) {
347     this.actionId = actionId;
348   }
349 
350   public Box getActionPosition() {
351     return actionPosition;
352   }
353 
354   public void setActionPosition(Box actionPosition) {
355     this.actionPosition = actionPosition;
356   }
357 
358   /**
359    * @deprecated since 1.5.7 and 2.0.0
360    */
361   public String getDefaultActionId() {
362     Deprecation.LOG.error("The default action handling has been changed!");
363     return null;
364   }
365 
366   /**
367    * @deprecated since 1.5.7 and 2.0.0
368    */
369   public void setDefaultActionId(String defaultActionId) {
370     Deprecation.LOG.error("The default action handling has been changed!");
371   }
372 
373   public void onComponentPopulated(FacesContext facesContext, UIComponent parent) {
374     if (getLayoutManager() == null) {
375       setLayoutManager(CreateComponentUtils.createAndInitLayout(
376           facesContext, ComponentTypes.GRID_LAYOUT, RendererTypes.GRID_LAYOUT, parent));
377     }
378   }
379 
380   public List<LayoutComponent> getComponents() {
381     return LayoutUtils.findLayoutChildren(this);
382   }
383 
384   public LayoutManager getLayoutManager() {
385     return (LayoutManager) getFacet(Facets.LAYOUT);
386   }
387 
388   public void setLayoutManager(LayoutManager layoutManager) {
389     getFacets().put(Facets.LAYOUT, (AbstractUILayoutBase) layoutManager);
390   }
391 
392   public boolean isLayoutChildren() {
393     return isRendered();
394   }
395 
396   public abstract Measure getWidth();
397 
398   public abstract Measure getHeight();
399 }