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  package javax.faces.component;
20  
21  import java.util.ArrayList;
22  import java.util.ConcurrentModificationException;
23  import java.util.List;
24  import java.util.ListIterator;
25  import java.util.Locale;
26  import java.util.logging.Level;
27  import java.util.logging.Logger;
28  
29  import javax.el.MethodExpression;
30  import javax.el.ValueExpression;
31  import javax.faces.FactoryFinder;
32  import javax.faces.context.ExternalContext;
33  import javax.faces.context.FacesContext;
34  import javax.faces.event.AbortProcessingException;
35  import javax.faces.event.FacesEvent;
36  import javax.faces.event.PhaseEvent;
37  import javax.faces.event.PhaseId;
38  import javax.faces.event.PhaseListener;
39  import javax.faces.lifecycle.Lifecycle;
40  import javax.faces.lifecycle.LifecycleFactory;
41  import javax.faces.webapp.FacesServlet;
42  
43  import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFComponent;
44  import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFJspProperty;
45  import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFProperty;
46  
47  /**
48   * Creates a JSF View, which is a container that holds all of the components
49   * that are part of the view.
50   * <p>
51   * Unless otherwise specified, all attributes accept static values or EL
52   * expressions.
53   * </p>
54   * <p>
55   * See the javadoc for this class in the <a
56   * href="http://java.sun.com/j2ee/javaserverfaces/1.2/docs/api/index.html">JSF
57   * Specification</a> for further details.
58   * </p>
59   */
60  @JSFComponent(name="f:view", bodyContent="JSP", tagClass="org.apache.myfaces.taglib.core.ViewTag")
61  @JSFJspProperty(name="binding", returnType="java.lang.String", tagExcluded=true)
62  public class UIViewRoot extends UIComponentBase
63  {
64      public static final String COMPONENT_TYPE = "javax.faces.ViewRoot";
65      public static final String COMPONENT_FAMILY = "javax.faces.ViewRoot";
66  
67      public static final String UNIQUE_ID_PREFIX = "j_id";
68      private static final int ANY_PHASE_ORDINAL = PhaseId.ANY_PHASE.getOrdinal();
69  
70      private final Logger logger = Logger.getLogger(UIViewRoot.class.getName());
71  
72      /**
73       * The counter which will ensure a unique component id for every component instance in the tree that
74       * doesn't have an id attribute set.
75       */
76      private long _uniqueIdCounter = 0;
77  
78      private Locale _locale;
79      private String _renderKitId;
80      private String _viewId;
81  
82      // todo: is it right to save the state of _events and _phaseListeners?
83      private List<FacesEvent> _events;
84      private List<PhaseListener> _phaseListeners;
85  
86      private MethodExpression _beforePhaseListener;
87      private MethodExpression _afterPhaseListener;
88  
89      private transient Lifecycle _lifecycle = null;
90  
91      private interface Processor
92      {
93          void process();
94      }
95  
96      /**
97       * Construct an instance of the UIViewRoot.
98       */
99      public UIViewRoot()
100     {
101         setRendererType(null);
102     }
103 
104     public void queueEvent(FacesEvent event)
105     {
106         checkNull(event, "event");
107         if (_events == null)
108         {
109             _events = new ArrayList<FacesEvent>();
110         }
111         _events.add(event);
112     }
113 
114     public void processDecodes(final FacesContext context)
115     {
116         checkNull(context, "context");
117         process(context, PhaseId.APPLY_REQUEST_VALUES, new Processor()
118         {
119             public void process()
120             {
121                 UIViewRoot.super.processDecodes(context);
122             }
123         }, true);
124     }
125 
126     public void processValidators(final FacesContext context)
127     {
128         checkNull(context, "context");
129         process(context, PhaseId.PROCESS_VALIDATIONS, new Processor()
130         {
131             public void process()
132             {
133                 UIViewRoot.super.processValidators(context);
134             }
135         }, true);
136     }
137 
138     public void processUpdates(final FacesContext context)
139     {
140         checkNull(context, "context");
141         process(context, PhaseId.UPDATE_MODEL_VALUES, new Processor()
142         {
143             public void process()
144             {
145                 UIViewRoot.super.processUpdates(context);
146             }
147         }, true);
148     }
149 
150     public void processApplication(final FacesContext context)
151     {
152         checkNull(context, "context");
153         process(context, PhaseId.INVOKE_APPLICATION, null, true);
154     }
155 
156     public void encodeBegin(FacesContext context) throws java.io.IOException
157     {
158         checkNull(context, "context");
159 
160         boolean skipPhase = false;
161 
162         try
163         {
164             skipPhase = notifyListeners(context, PhaseId.RENDER_RESPONSE,
165                     getBeforePhaseListener(), true);
166         }
167         catch (Exception e)
168         {
169             // following the spec we have to swallow the exception
170             logger.log(Level.SEVERE,
171                     "Exception while processing phase listener: "
172                             + e.getMessage(), e);
173         }
174 
175         if (!skipPhase)
176         {
177             super.encodeBegin(context);
178         }
179     }
180 
181     public void encodeEnd(FacesContext context) throws java.io.IOException
182     {
183         checkNull(context, "context");
184         super.encodeEnd(context);
185         try
186         {
187             notifyListeners(context, PhaseId.RENDER_RESPONSE,
188                     getAfterPhaseListener(), false);
189         }
190         catch (Exception e)
191         {
192             // following the spec we have to swallow the exception
193             logger.log(Level.SEVERE,
194                     "Exception while processing phase listener: "
195                             + e.getMessage(), e);
196         }
197     }
198 
199     /**
200      * Provides a unique id for this component instance.
201      */
202     public String createUniqueId()
203     {
204         StringBuilder bld = __getSharedStringBuilder();
205         return bld.append(UNIQUE_ID_PREFIX).append(
206                 _uniqueIdCounter++).toString();
207     }
208 
209     /**
210      * The locale for this view.
211      * <p>
212      * Defaults to the default locale specified in the faces configuration file.
213      * </p>
214      */
215     @JSFProperty
216     public Locale getLocale()
217     {
218         if (_locale != null)
219         {
220             return _locale;
221         }
222         ValueExpression expression = getValueExpression("locale");
223         if (expression != null)
224         {
225             return (Locale) expression.getValue(getFacesContext()
226                     .getELContext());
227         }
228         else
229         {
230             Object locale = getFacesContext().getApplication().getViewHandler()
231                     .calculateLocale(getFacesContext());
232 
233             if (locale instanceof Locale)
234             {
235                 return (Locale) locale;
236             }
237             else if (locale instanceof String)
238             {
239                 return stringToLocale((String) locale);
240             }
241         }
242 
243         return getFacesContext().getApplication().getViewHandler()
244                 .calculateLocale(getFacesContext());
245     }
246 
247     public void setLocale(Locale locale)
248     {
249         this._locale = locale;
250     }
251 
252     private boolean process(FacesContext context, PhaseId phaseId,
253             Processor processor, boolean broadcast)
254     {
255         if (!notifyListeners(context, phaseId, getBeforePhaseListener(), true))
256         {
257             if (processor != null)
258                 processor.process();
259 
260             if (broadcast)
261             {
262                 _broadcastForPhase(phaseId);
263             }
264         }
265         if (context.getRenderResponse() || context.getResponseComplete())
266         {
267             clearEvents();
268         }
269         return notifyListeners(context, phaseId, getAfterPhaseListener(), false);
270     }
271 
272     /**
273      * Invoke view-specific phase listeners, plus an optional EL MethodExpression.
274      * <p>
275      * JSF1.2 adds the ability for PhaseListener objects to be added to a UIViewRoot instance,
276      * and for "beforePhaseListener" and "afterPhaseListener" EL expressions to be defined
277      * on the viewroot. This method is expected to be called at appropriate times, and will
278      * then execute the relevant listener callbacks.
279      * <p>
280      * Parameter "listener" may be null. If not null, then it is an EL expression pointing
281      * to a user method that will be invoked.
282      * <p>
283      * Note that the global PhaseListeners are invoked via the Lifecycle implementation, not
284      * from this method here.
285      */
286     private boolean notifyListeners(FacesContext context, PhaseId phaseId,
287             MethodExpression listener, boolean beforePhase)
288     {
289         boolean skipPhase = false;
290 
291         if (listener != null
292                 || (_phaseListeners != null && !_phaseListeners.isEmpty()))
293         {
294             PhaseEvent event = createEvent(context, phaseId);
295 
296             if (listener != null)
297             {
298                 listener.invoke(context.getELContext(), new Object[]
299                 { event });
300                 skipPhase = context.getResponseComplete()
301                         || context.getRenderResponse();
302             }
303 
304             if (_phaseListeners != null && !_phaseListeners.isEmpty())
305             {
306                 for (PhaseListener phaseListener : _phaseListeners)
307                 {
308                     PhaseId listenerPhaseId = phaseListener.getPhaseId();
309                     if (phaseId.equals(listenerPhaseId)
310                             || PhaseId.ANY_PHASE.equals(listenerPhaseId))
311                     {
312                         if (beforePhase)
313                         {
314                             phaseListener.beforePhase(event);
315                         }
316                         else
317                         {
318                             phaseListener.afterPhase(event);
319                         }
320                         skipPhase = context.getResponseComplete()
321                                 || context.getRenderResponse();
322                     }
323                 }
324             }
325         }
326 
327         return skipPhase;
328     }
329 
330     private PhaseEvent createEvent(FacesContext context, PhaseId phaseId)
331     {
332         if (_lifecycle == null)
333         {
334             LifecycleFactory factory = (LifecycleFactory) FactoryFinder
335                     .getFactory(FactoryFinder.LIFECYCLE_FACTORY);
336             String id = context.getExternalContext().getInitParameter(
337                     FacesServlet.LIFECYCLE_ID_ATTR);
338             if (id == null)
339             {
340                 id = LifecycleFactory.DEFAULT_LIFECYCLE;
341             }
342             _lifecycle = factory.getLifecycle(id);
343         }
344         return new PhaseEvent(context, phaseId, _lifecycle);
345     }
346 
347     private void _broadcastForPhase(PhaseId phaseId)
348     {
349         if (_events == null)
350         {
351             return;
352         }
353 
354         boolean abort = false;
355 
356         int phaseIdOrdinal = phaseId.getOrdinal();
357         for (ListIterator<FacesEvent> listiterator = _events.listIterator(); listiterator
358                 .hasNext();)
359         {
360             FacesEvent event = listiterator.next();
361             int ordinal = event.getPhaseId().getOrdinal();
362             if (ordinal == ANY_PHASE_ORDINAL || ordinal == phaseIdOrdinal)
363             {
364                 UIComponent source = event.getComponent();
365                 try
366                 {
367                     source.broadcast(event);
368                 }
369                 catch (AbortProcessingException e)
370                 {
371                     // abort event processing
372                     // Page 3-30 of JSF 1.1 spec: "Throw an
373                     // AbortProcessingException, to tell the JSF implementation
374                     // that no further broadcast of this event, or any further
375                     // events, should take place."
376                     abort = true;
377                     break;
378                 }
379                 finally
380                 {
381                     try
382                     {
383                         listiterator.remove();
384                     }
385                     catch (ConcurrentModificationException cme)
386                     {
387                         int eventIndex = listiterator.previousIndex();
388                         _events.remove(eventIndex);
389                         listiterator = _events.listIterator();
390                     }
391                 }
392             }
393         }
394 
395         if (abort)
396         {
397             // TODO: abort processing of any event of any phase or just of any
398             // event of the current phase???
399             clearEvents();
400         }
401     }
402 
403     private void clearEvents()
404     {
405         _events = null;
406     }
407 
408     private void checkNull(Object value, String valueLabel)
409     {
410         if (value == null)
411         {
412             throw new NullPointerException(valueLabel + " is null");
413         }
414     }
415 
416     private Locale stringToLocale(String localeStr)
417     {
418         // locale expr: \[a-z]{2}((-|_)[A-Z]{2})?
419 
420         if (localeStr.contains("_") || localeStr.contains("-"))
421         {
422             if (localeStr.length() == 2)
423             {
424                 // localeStr is the lang
425                 return new Locale(localeStr);
426             }
427         }
428         else
429         {
430             if (localeStr.length() == 5)
431             {
432                 String lang = localeStr.substring(0, 1);
433                 String country = localeStr.substring(3, 4);
434                 return new Locale(lang, country);
435             }
436         }
437 
438         return Locale.getDefault();
439     }
440 
441     /**
442      * Defines what renderkit should be used to render this view.
443      */
444     @JSFProperty
445     public String getRenderKitId()
446     {
447         if (_renderKitId != null)
448         {
449             return _renderKitId;
450         }
451         ValueExpression expression = getValueExpression("renderKitId");
452         if (expression != null)
453         {
454             return (String) expression.getValue(getFacesContext()
455                     .getELContext());
456         }
457         return null;
458     }
459 
460     public void setRenderKitId(String renderKitId)
461     {
462         this._renderKitId = renderKitId;
463     }
464 
465     /**
466      * DO NOT USE.
467      * <p>
468      * This inherited property is disabled. Although this class extends a base-class that
469      * defines a read/write rendered property, this particular subclass does not
470      * support setting it. Yes, this is broken OO design: direct all complaints
471      * to the JSF spec group.
472      */
473     @Override
474     @JSFProperty(tagExcluded=true)
475     public void setRendered(boolean state)
476     {
477         //Call parent method due to TCK problems
478         super.setRendered(state);
479         //throw new UnsupportedOperationException();
480     }
481 
482     @Override
483     public boolean isRendered()
484     {
485         //Call parent method due to TCK problems
486         return super.isRendered();
487     }
488 
489     /**
490      * DO NOT USE.
491      * <p>
492      * Although this class extends a base-class that defines a read/write id
493      * property, it makes no sense for this particular subclass to support it.
494      * The tag library does not export this property for use, but there is no
495      * way to "undeclare" a java method. Yes, this is broken OO design: direct
496      * all complaints to the JSF spec group.
497      * <p>
498      * This property should be disabled (ie throw an exception if invoked).
499      * However there are currently several places that call this method (eg
500      * during restoreState) so it just does the normal thing for the moment.
501      * TODO: fix callers then make this throw an exception.
502      * 
503      * @JSFProperty tagExcluded="true"
504      */
505     public void setId(String id)
506     {
507         // throw new UnsupportedOperationException();
508 
509         // Leave enabled for now. Things like the TreeStructureManager call this,
510         // even though they probably should not.
511         super.setId(id);
512     }
513 
514     public String getId()
515     {
516         // Should just return null. But as setId passes the method on, do same here.
517         return super.getId();
518     }
519 
520     /**
521      * DO NOT USE.
522      * <p>
523      * As this component has no "id" property, it has no clientId property either.
524      */
525     public String getClientId(FacesContext context)
526     {
527         return super.getClientId(context);
528         //Call parent method due to TCK problems
529         //return null;
530     }
531 
532     /**
533      * A unique identifier for the "template" from which this view was generated.
534      * <p>
535      * Typically this is the filesystem path to the template file, but the exact
536      * details are the responsibility of the current ViewHandler implementation.
537      */
538     @JSFProperty(tagExcluded = true)
539     public String getViewId()
540     {
541         return _viewId;
542     }
543 
544     public void setViewId(String viewId)
545     {
546         // It really doesn't make much sense to allow null here.
547         // However the TCK does not check for it, and sun's implementation
548         // allows it so here we allow it too.
549         this._viewId = viewId;
550     }
551 
552     /**
553      * Adds a The phaseListeners attached to ViewRoot.
554      */
555     public void addPhaseListener(PhaseListener phaseListener)
556     {
557         if (phaseListener == null)
558             throw new NullPointerException("phaseListener");
559         if (_phaseListeners == null)
560             _phaseListeners = new ArrayList<PhaseListener>();
561 
562         _phaseListeners.add(phaseListener);
563     }
564 
565     /**
566      * Removes a The phaseListeners attached to ViewRoot.
567      */
568     public void removePhaseListener(PhaseListener phaseListener)
569     {
570         if (phaseListener == null || _phaseListeners == null)
571             return;
572 
573         _phaseListeners.remove(phaseListener);
574     }
575 
576     /**
577      * MethodBinding pointing to a method that takes a
578      * javax.faces.event.PhaseEvent and returns void,
579      * called before every phase except for restore view.
580      * 
581      * @return the new beforePhaseListener value
582      */
583     @JSFProperty(stateHolder = true, returnSignature = "void", methodSignature = "javax.faces.event.PhaseEvent", jspName = "beforePhase")
584     public MethodExpression getBeforePhaseListener()
585     {
586         if (_beforePhaseListener != null)
587         {
588             return _beforePhaseListener;
589         }
590         ValueExpression expression = getValueExpression("beforePhaseListener");
591         if (expression != null)
592         {
593             return (MethodExpression) expression.getValue(getFacesContext()
594                     .getELContext());
595         }
596         return null;
597     }
598 
599     /**
600      * Sets
601      * 
602      * @param beforePhaseListener
603      *            the new beforePhaseListener value
604      */
605     public void setBeforePhaseListener(MethodExpression beforePhaseListener)
606     {
607         this._beforePhaseListener = beforePhaseListener;
608     }
609 
610     /**
611      * MethodBinding pointing to a method that takes a
612      * javax.faces.event.PhaseEvent and returns void,
613      * called after every phase except for restore view.
614      * 
615      * @return the new afterPhaseListener value
616      */
617     @JSFProperty(stateHolder = true, returnSignature = "void", methodSignature = "javax.faces.event.PhaseEvent", jspName = "afterPhase")
618     public MethodExpression getAfterPhaseListener()
619     {
620         if (_afterPhaseListener != null)
621         {
622             return _afterPhaseListener;
623         }
624         ValueExpression expression = getValueExpression("afterPhaseListener");
625         if (expression != null)
626         {
627             return (MethodExpression) expression.getValue(getFacesContext()
628                     .getELContext());
629         }
630         return null;
631     }
632 
633     /**
634      * Sets
635      * 
636      * @param afterPhaseListener
637      *            the new afterPhaseListener value
638      */
639     public void setAfterPhaseListener(MethodExpression afterPhaseListener)
640     {
641         this._afterPhaseListener = afterPhaseListener;
642     }
643 
644     @Override
645     public Object saveState(FacesContext facesContext)
646     {
647         Object[] values = new Object[8];
648         values[0] = super.saveState(facesContext);
649         values[1] = _locale;
650         values[2] = _renderKitId;
651         values[3] = _viewId;
652         values[4] = _uniqueIdCounter;
653         values[5] = saveAttachedState(facesContext, _phaseListeners);
654         values[6] = saveAttachedState(facesContext, _beforePhaseListener);
655         values[7] = saveAttachedState(facesContext, _afterPhaseListener);
656 
657         return values;
658     }
659 
660     @Override
661     public void restoreState(FacesContext facesContext, Object state)
662     {
663         Object[] values = (Object[]) state;
664         super.restoreState(facesContext, values[0]);
665         _locale = (Locale) values[1];
666         _renderKitId = (String) values[2];
667         _viewId = (String) values[3];
668         _uniqueIdCounter = (Long) values[4];
669         _phaseListeners = (List) restoreAttachedState(facesContext, values[5]);
670         _beforePhaseListener = (MethodExpression) restoreAttachedState(
671                 facesContext, values[6]);
672         _afterPhaseListener = (MethodExpression) restoreAttachedState(
673                 facesContext, values[7]);
674     }
675 
676     @Override
677     public String getFamily()
678     {
679         return COMPONENT_FAMILY;
680     }
681 }