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