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 static org.apache.myfaces.tobago.TobagoConstants.ATTR_APPLICATION_ICON;
021    import org.apache.commons.collections.KeyValue;
022    import org.apache.commons.collections.list.SetUniqueList;
023    import org.apache.commons.collections.set.ListOrderedSet;
024    import org.apache.commons.logging.Log;
025    import org.apache.commons.logging.LogFactory;
026    
027    import static org.apache.myfaces.tobago.TobagoConstants.ATTR_FOCUS_ID;
028    import static org.apache.myfaces.tobago.TobagoConstants.ATTR_HEIGHT;
029    import static org.apache.myfaces.tobago.TobagoConstants.ATTR_STATE;
030    import static org.apache.myfaces.tobago.TobagoConstants.ATTR_WIDTH;
031    import static org.apache.myfaces.tobago.TobagoConstants.SUBCOMPONENT_SEP;
032    import org.apache.myfaces.tobago.model.PageState;
033    import org.apache.myfaces.tobago.model.PageStateImpl;
034    import org.apache.myfaces.tobago.webapp.TobagoMultipartFormdataRequest;
035    
036    import javax.faces.application.FacesMessage;
037    import javax.faces.component.UIComponent;
038    import javax.faces.context.FacesContext;
039    import javax.faces.el.ValueBinding;
040    import javax.servlet.ServletRequest;
041    import javax.servlet.http.HttpServletRequestWrapper;
042    import java.io.IOException;
043    import java.util.ArrayList;
044    import java.util.Iterator;
045    import java.util.List;
046    import java.util.Set;
047    import java.util.StringTokenizer;
048    
049    public class UIPage extends UIForm {
050    
051      private static final Log LOG = LogFactory.getLog(UIPage.class);
052    
053      public static final String COMPONENT_TYPE = "org.apache.myfaces.tobago.Page";
054    
055      public static final String DEFAULT_STYLE = "style/style.css";
056    
057      private static final int DEFAULT_WIDTH = 1024;
058    
059      private static final int DEFAULT_HEIGHT = 768;
060    
061      private String formId;
062    
063      private String focusId;
064    
065      private String actionId;
066    
067      private String defaultActionId;
068    
069      private List<KeyValue> postfields;
070    
071      private SetUniqueList scriptFiles;
072    
073      private Set<String> scriptBlocks;
074    
075      private Set<String> styleFiles;
076    
077      private Set<String> styleBlocks;
078    
079      private Set<String> onloadScripts;
080    
081      private Set<String> onunloadScripts;
082    
083      private Set<String> onexitScripts;
084    
085      private Set<String> onsubmitScripts;
086    
087      private List<UIPopup> popups;
088    
089      private Integer width;
090    
091      private Integer height;
092    
093      private String applicationIcon;
094    
095      @SuppressWarnings({"unchecked"})
096      public UIPage() {
097        scriptFiles = SetUniqueList.decorate(new ArrayList());
098        scriptBlocks = new ListOrderedSet();
099        styleFiles = new ListOrderedSet();
100        styleFiles.add(DEFAULT_STYLE);
101        styleBlocks = new ListOrderedSet();
102        onloadScripts = new ListOrderedSet();
103        onunloadScripts = new ListOrderedSet();
104        onexitScripts = new ListOrderedSet();
105        onsubmitScripts = new ListOrderedSet();
106        popups = new ArrayList<UIPopup>();
107      }
108    
109      @Override
110      public void encodeBegin(FacesContext facesContext) throws IOException {
111        // TODO change this should be renamed to DimensionUtils.prepare!!!
112        UILayout.getLayout(this).layoutBegin(facesContext, this);
113        super.encodeBegin(facesContext);
114      }
115    
116    
117      @Override
118      public void encodeChildren(FacesContext context) throws IOException {
119      }
120    
121      public String getFormId(FacesContext facesContext) {
122        if (formId == null) {
123          formId = getClientId(facesContext)
124              + SUBCOMPONENT_SEP + "form";
125        }
126        return formId;
127      }
128    
129      @Override
130      public void processDecodes(FacesContext facesContext) {
131    
132        checkTobagoRequest(facesContext);
133    
134        decode(facesContext);
135    
136        clearScriptsAndPopups();
137    
138        markSubmittedForm(facesContext);
139    
140        // invoke processDecodes() on children
141        for (Iterator kids = getFacetsAndChildren(); kids.hasNext();) {
142          UIComponent kid = (UIComponent) kids.next();
143          kid.processDecodes(facesContext);
144        }
145      }
146    
147      public void markSubmittedForm(FacesContext facesContext) {
148        // find the form of the action command and set submitted to it and all
149        // children
150    
151        // reset old submitted state
152        setSubmitted(false);
153    
154        String currentActionId = getActionId();
155        if (LOG.isDebugEnabled()) {
156          LOG.debug("actionId = '" + currentActionId + "'");
157        }
158    
159        UIComponent command = null;
160        try {
161          command = findComponent(currentActionId);
162        } catch (Exception e) {/* ignore */}
163    
164        // TODO: remove this if block if prooved this never happens anymore
165        if (command == null
166            && currentActionId != null && currentActionId.matches(".*:\\d+:.*")) {
167          // If currentActionId component was inside a sheet the id contains the
168          // rowindex and is therefore not found here.
169          // We do not need the row here because we want just to find the
170          // related form, so removing the rowindex will help here.
171          currentActionId = currentActionId.replaceAll(":\\d+:", ":");
172          try {
173            command = findComponent(currentActionId);
174            LOG.info("command = \"" + command + "\"", new Exception());
175          } catch (Exception e) {/* ignore */}
176        }
177    
178        if (LOG.isTraceEnabled()) {
179          LOG.trace(currentActionId);
180          LOG.trace(command);
181          LOG.trace(ComponentUtil.toString(facesContext.getViewRoot(), 0));
182        }
183    
184        if (command != null) {
185          UIForm form = ComponentUtil.findForm(command);
186          form.setSubmitted(true);
187    
188          if (LOG.isTraceEnabled()) {
189            LOG.trace(form);
190            LOG.trace(form.getClientId(facesContext));
191          }
192        } else {
193          if (LOG.isDebugEnabled()) {
194            LOG.debug("Illegal actionId! Rerender the view.");
195          }
196          facesContext.renderResponse();
197        }
198      }
199    
200      private void clearScriptsAndPopups() {
201        // clear script Set's
202        getOnloadScripts().clear();
203        getOnunloadScripts().clear();
204        getOnexitScripts().clear();
205        getScriptBlocks().clear();
206        getPopups().clear();
207      }
208    
209      private void checkTobagoRequest(FacesContext facesContext) {
210        // multipart/form-data must use TobagoMultipartFormdataRequest
211        String contentType = (String) facesContext.getExternalContext()
212            .getRequestHeaderMap().get("content-type");
213        if (contentType != null && contentType.startsWith("multipart/form-data")) {
214          Object request = facesContext.getExternalContext().getRequest();
215          boolean okay = false;
216          if (request instanceof TobagoMultipartFormdataRequest) {
217            okay = true;
218          } else if (request instanceof HttpServletRequestWrapper) {
219            ServletRequest wrappedRequest
220                = ((HttpServletRequestWrapper) request).getRequest();
221            if (wrappedRequest instanceof TobagoMultipartFormdataRequest) {
222              okay = true;
223            }
224          }
225          // TODO PortletRequest ??
226          if (!okay) {
227            LOG.error("Can't process multipart/form-data without TobagoRequest. "
228                + "Please check the web.xml and define a TobagoMultipartFormdataFilter. "
229                + "See documentation for <tc:file>");
230            facesContext.addMessage(null, new FacesMessage("An error has occured!"));
231          }
232        }
233      }
234    
235      public List<KeyValue> getPostfields() {
236        if (postfields == null) {
237          postfields = new ArrayList<KeyValue>();
238        }
239        return postfields;
240      }
241    
242      @Override
243      public void processUpdates(FacesContext context) {
244        super.processUpdates(context);
245        updatePageState(context);
246      }
247    
248      public void updatePageState(FacesContext facesContext) {
249        PageState state = getPageState(facesContext);
250        decodePageState(facesContext, state);
251      }
252    
253      @SuppressWarnings({"unchecked"})
254      private void decodePageState(FacesContext facesContext, PageState pageState) {
255        String name;
256        String value = null;
257        try {
258          name = getClientId(facesContext)
259                  + SUBCOMPONENT_SEP + "form-clientDimension";
260          value = (String) facesContext.getExternalContext()
261                  .getRequestParameterMap().get(name);
262            if (value != null) {
263              StringTokenizer tokenizer = new StringTokenizer(value, ";");
264              int width = Integer.parseInt(tokenizer.nextToken());
265              int height = Integer.parseInt(tokenizer.nextToken());
266              if (pageState != null) {
267                pageState.setClientWidth(width);
268                pageState.setClientHeight(height);
269              }
270              facesContext.getExternalContext().getRequestMap().put("tobago-page-clientDimension-with", width);
271              facesContext.getExternalContext().getRequestMap().put("tobago-page-clientDimension-height", height);
272            }
273        } catch (Exception e) {
274          LOG.error("Error in decoding state: value='" + value + "'", e);
275        }
276      }
277    
278      public PageState getPageState(FacesContext facesContext) {
279        ValueBinding stateBinding = getValueBinding(ATTR_STATE);
280        if (stateBinding != null) {
281          PageState state = (PageState) stateBinding.getValue(facesContext);
282          if (state == null) {
283            state = new PageStateImpl();
284            stateBinding.setValue(facesContext, state);
285          }
286          return state;
287        } else {
288          return null;
289        }
290      }
291    
292    // ///////////////////////////////////////////// bean getter + setter
293    
294      public String getFocusId() {
295        if (focusId != null) {
296          return focusId;
297        }
298        ValueBinding vb = getValueBinding(ATTR_FOCUS_ID);
299        if (vb != null) {
300          return (String) vb.getValue(getFacesContext());
301        } else {
302          return null;
303        }
304      }
305    
306      public void setFocusId(String focusId) {
307        this.focusId = focusId;
308      }
309    
310      public String getActionId() {
311        return actionId;
312      }
313    
314      public void setActionId(String actionId) {
315        this.actionId = actionId;
316      }
317    
318      public String getDefaultActionId() {
319        return defaultActionId;
320      }
321    
322      public void setDefaultActionId(String defaultActionId) {
323        this.defaultActionId = defaultActionId;
324      }
325    
326      @SuppressWarnings({"unchecked"})
327      public List<String> getScriptFiles() {
328        return scriptFiles;
329      }
330    
331      public Set<String> getScriptBlocks() {
332        return scriptBlocks;
333      }
334    
335      public Set<String> getStyleFiles() {
336        return styleFiles;
337      }
338    
339      public Set<String> getStyleBlocks() {
340        return styleBlocks;
341      }
342    
343      public Set<String> getOnloadScripts() {
344        return onloadScripts;
345      }
346    
347      public Set<String> getOnunloadScripts() {
348        return onunloadScripts;
349      }
350    
351      public Set<String> getOnexitScripts() {
352        return onexitScripts;
353      }
354    
355      public Set<String> getOnsubmitScripts() {
356        return onsubmitScripts;
357      }
358    
359      public List<UIPopup> getPopups() {
360        return popups;
361      }
362    
363      public Integer getWidth() {
364        if (width != null) {
365          return width;
366        }
367        ValueBinding vb = getValueBinding(ATTR_WIDTH);
368        if (vb != null) {
369          return (Integer) vb.getValue(getFacesContext());
370        } else {
371          Integer requestWidth =
372              (Integer) FacesContext.getCurrentInstance().getExternalContext().
373                  getRequestMap().get("tobago-page-clientDimension-with");
374          if (requestWidth != null) {
375            return requestWidth;
376          } else {
377            return DEFAULT_WIDTH;
378          }
379        }
380      }
381    
382      public void setWidth(Integer width) {
383        this.width = width;
384      }
385    
386      public Integer getHeight() {
387        if (height != null) {
388          return height;
389        }
390        ValueBinding vb = getValueBinding(ATTR_HEIGHT);
391        if (vb != null) {
392          return (Integer) vb.getValue(getFacesContext());
393        } else {
394          Integer requestHeight =
395              (Integer) FacesContext.getCurrentInstance().getExternalContext().
396                  getRequestMap().get("tobago-page-clientDimension-height");
397          if (requestHeight != null) {
398            return requestHeight;
399          } else {
400            return DEFAULT_HEIGHT;
401          }
402        }
403      }
404    
405      public void setHeight(Integer height) {
406        this.height = height;
407      }
408    
409      public String getApplicationIcon() {
410        if (applicationIcon != null) {
411          return applicationIcon;
412        }
413        ValueBinding vb = getValueBinding(ATTR_APPLICATION_ICON);
414        if (vb != null) {
415          return (String) vb.getValue(getFacesContext());
416        } else {
417          return null;
418        }
419      }
420    
421      public void setApplicationIcon(String applicationIcon) {
422        this.applicationIcon = applicationIcon;
423      }
424    
425      public void restoreState(FacesContext context, Object state) {
426        Object[] values = (Object[]) state;
427        super.restoreState(context, values[0]);
428        this.width = (Integer) values[1];
429        this.height = (Integer) values[2];
430        this.focusId = (String) values[3];
431        this.applicationIcon = (String) values[4];
432      }
433    
434      public Object saveState(FacesContext context) {
435        Object[] values = new Object[5];
436        values[0] = super.saveState(context);
437        values[1] = width;
438        values[2] = height;
439        values[3] = focusId;
440        values[4] = applicationIcon;
441        return values;
442      }
443    }