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