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) {
163          // ignore
164        }
165    
166        // TODO: remove this if block if prooven this never happens anymore
167        if (command == null
168            && currentActionId != null && currentActionId.matches(".*:\\d+:.*")) {
169          // If currentActionId component was inside a sheet the id contains the
170          // rowindex and is therefore not found here.
171          // We do not need the row here because we want just to find the
172          // related form, so removing the rowindex will help here.
173          currentActionId = currentActionId.replaceAll(":\\d+:", ":");
174          try {
175            command = findComponent(currentActionId);
176            LOG.info("command = \"" + command + "\"", new Exception());
177          } catch (Exception e) {
178            // ignore
179          }
180        }
181    
182        if (LOG.isTraceEnabled()) {
183          LOG.trace(currentActionId);
184          LOG.trace(command);
185          LOG.trace(ComponentUtil.toString(facesContext.getViewRoot(), 0));
186        }
187    
188        if (command != null) {
189          UIForm form = ComponentUtil.findForm(command);
190          form.setSubmitted(true);
191    
192          if (LOG.isTraceEnabled()) {
193            LOG.trace(form);
194            LOG.trace(form.getClientId(facesContext));
195          }
196        } else {
197          if (LOG.isDebugEnabled()) {
198            LOG.debug("Illegal actionId! Rerender the view.");
199          }
200          facesContext.renderResponse();
201        }
202      }
203    
204      private void clearScriptsAndPopups() {
205        // clear script Set's
206        getOnloadScripts().clear();
207        getOnunloadScripts().clear();
208        getOnexitScripts().clear();
209        getScriptBlocks().clear();
210        getPopups().clear();
211      }
212    
213      private void checkTobagoRequest(FacesContext facesContext) {
214        // multipart/form-data must use TobagoMultipartFormdataRequest
215        String contentType = (String) facesContext.getExternalContext()
216            .getRequestHeaderMap().get("content-type");
217        if (contentType != null && contentType.startsWith("multipart/form-data")) {
218          Object request = facesContext.getExternalContext().getRequest();
219          boolean okay = false;
220          if (request instanceof TobagoMultipartFormdataRequest) {
221            okay = true;
222          } else if (request instanceof HttpServletRequestWrapper) {
223            ServletRequest wrappedRequest
224                = ((HttpServletRequestWrapper) request).getRequest();
225            if (wrappedRequest instanceof TobagoMultipartFormdataRequest) {
226              okay = true;
227            }
228          }
229          // TODO PortletRequest ??
230          if (!okay) {
231            LOG.error("Can't process multipart/form-data without TobagoRequest. "
232                + "Please check the web.xml and define a TobagoMultipartFormdataFilter. "
233                + "See documentation for <tc:file>");
234            facesContext.addMessage(null, new FacesMessage("An error has occured!"));
235          }
236        }
237      }
238    
239      public List<KeyValue> getPostfields() {
240        if (postfields == null) {
241          postfields = new ArrayList<KeyValue>();
242        }
243        return postfields;
244      }
245    
246      @Override
247      public void processUpdates(FacesContext context) {
248        super.processUpdates(context);
249        updatePageState(context);
250      }
251    
252      public void updatePageState(FacesContext facesContext) {
253        PageState state = getPageState(facesContext);
254        decodePageState(facesContext, state);
255      }
256    
257      @SuppressWarnings({"unchecked"})
258      private void decodePageState(FacesContext facesContext, PageState pageState) {
259        String name;
260        String value = null;
261        try {
262          name = getClientId(facesContext)
263                  + SUBCOMPONENT_SEP + "form-clientDimension";
264          value = (String) facesContext.getExternalContext()
265                  .getRequestParameterMap().get(name);
266            if (value != null) {
267              StringTokenizer tokenizer = new StringTokenizer(value, ";");
268              int width = Integer.parseInt(tokenizer.nextToken());
269              int height = Integer.parseInt(tokenizer.nextToken());
270              if (pageState != null) {
271                pageState.setClientWidth(width);
272                pageState.setClientHeight(height);
273              }
274              facesContext.getExternalContext().getRequestMap().put("tobago-page-clientDimension-width", width);
275              facesContext.getExternalContext().getRequestMap().put("tobago-page-clientDimension-height", height);
276            }
277        } catch (Exception e) {
278          LOG.error("Error in decoding state: value='" + value + "'", e);
279        }
280      }
281    
282      public PageState getPageState(FacesContext facesContext) {
283        ValueBinding stateBinding = getValueBinding(ATTR_STATE);
284        if (stateBinding != null) {
285          PageState state = (PageState) stateBinding.getValue(facesContext);
286          if (state == null) {
287            state = new PageStateImpl();
288            stateBinding.setValue(facesContext, state);
289          }
290          return state;
291        } else {
292          return null;
293        }
294      }
295    
296    // ///////////////////////////////////////////// bean getter + setter
297    
298      public String getFocusId() {
299        if (focusId != null) {
300          return focusId;
301        }
302        ValueBinding vb = getValueBinding(ATTR_FOCUS_ID);
303        if (vb != null) {
304          return (String) vb.getValue(getFacesContext());
305        } else {
306          return null;
307        }
308      }
309    
310      public void setFocusId(String focusId) {
311        this.focusId = focusId;
312      }
313    
314      public String getActionId() {
315        return actionId;
316      }
317    
318      public void setActionId(String actionId) {
319        this.actionId = actionId;
320      }
321    
322      public String getDefaultActionId() {
323        return defaultActionId;
324      }
325    
326      public void setDefaultActionId(String defaultActionId) {
327        this.defaultActionId = defaultActionId;
328      }
329    
330      @SuppressWarnings({"unchecked"})
331      public List<String> getScriptFiles() {
332        return scriptFiles;
333      }
334    
335      public Set<String> getScriptBlocks() {
336        return scriptBlocks;
337      }
338    
339      public Set<String> getStyleFiles() {
340        return styleFiles;
341      }
342    
343      public Set<String> getStyleBlocks() {
344        return styleBlocks;
345      }
346    
347      public Set<String> getOnloadScripts() {
348        return onloadScripts;
349      }
350    
351      public Set<String> getOnunloadScripts() {
352        return onunloadScripts;
353      }
354    
355      public Set<String> getOnexitScripts() {
356        return onexitScripts;
357      }
358    
359      public Set<String> getOnsubmitScripts() {
360        return onsubmitScripts;
361      }
362    
363      public List<UIPopup> getPopups() {
364        return popups;
365      }
366    
367      public Integer getWidth() {
368        if (width != null) {
369          return width;
370        }
371        ValueBinding vb = getValueBinding(ATTR_WIDTH);
372        if (vb != null) {
373          return (Integer) vb.getValue(getFacesContext());
374        } else {
375          Integer requestWidth =
376              (Integer) FacesContext.getCurrentInstance().getExternalContext().
377                  getRequestMap().get("tobago-page-clientDimension-width");
378          if (requestWidth != null) {
379            return requestWidth;
380          } else {
381            return DEFAULT_WIDTH;
382          }
383        }
384      }
385    
386      public void setWidth(Integer width) {
387        this.width = width;
388      }
389    
390      public Integer getHeight() {
391        if (height != null) {
392          return height;
393        }
394        ValueBinding vb = getValueBinding(ATTR_HEIGHT);
395        if (vb != null) {
396          return (Integer) vb.getValue(getFacesContext());
397        } else {
398          Integer requestHeight =
399              (Integer) FacesContext.getCurrentInstance().getExternalContext().
400                  getRequestMap().get("tobago-page-clientDimension-height");
401          if (requestHeight != null) {
402            return requestHeight;
403          } else {
404            return DEFAULT_HEIGHT;
405          }
406        }
407      }
408    
409      public void setHeight(Integer height) {
410        this.height = height;
411      }
412    
413      public String getApplicationIcon() {
414        if (applicationIcon != null) {
415          return applicationIcon;
416        }
417        ValueBinding vb = getValueBinding(ATTR_APPLICATION_ICON);
418        if (vb != null) {
419          return (String) vb.getValue(getFacesContext());
420        } else {
421          return null;
422        }
423      }
424    
425      public void setApplicationIcon(String applicationIcon) {
426        this.applicationIcon = applicationIcon;
427      }
428    
429      public void restoreState(FacesContext context, Object state) {
430        Object[] values = (Object[]) state;
431        super.restoreState(context, values[0]);
432        this.width = (Integer) values[1];
433        this.height = (Integer) values[2];
434        this.focusId = (String) values[3];
435        this.applicationIcon = (String) values[4];
436      }
437    
438      public Object saveState(FacesContext context) {
439        Object[] values = new Object[5];
440        values[0] = super.saveState(context);
441        values[1] = width;
442        values[2] = height;
443        values[3] = focusId;
444        values[4] = applicationIcon;
445        return values;
446      }
447    }