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 javax.faces.context.ExternalContext;
27
28 import javax.faces.context.FacesContext;
29 import javax.faces.el.ValueBinding;
30 import javax.faces.event.AbortProcessingException;
31 import javax.faces.event.FacesEvent;
32 import javax.faces.event.PhaseId;
33
34 /**
35 * Creates a JSF View, which is a container that holds all of the
36 * components that are part of the view.
37 * <p>
38 * Unless otherwise specified, all attributes accept static values or EL expressions.
39 * </p>
40 * See Javadoc of <a href="http://java.sun.com/j2ee/javaserverfaces/1.1_01/docs/api/index.html">JSF Specification</a>
41 *
42 * @JSFComponent
43 * name = "f:view"
44 * bodyContent = "JSP"
45 * tagClass = "org.apache.myfaces.taglib.core.ViewTag"
46 *
47 * @JSFJspProperty name = "binding" returnType = "java.lang.String" tagExcluded = "true"
48 *
49 * @author Manfred Geiler (latest modification by $Author: lu4242 $)
50 * @version $Revision: 949070 $ $Date: 2010-05-27 21:22:03 -0500 (Thu, 27 May 2010) $
51 */
52 public class UIViewRoot extends UIComponentBase
53 {
54 public static final String COMPONENT_TYPE = "javax.faces.ViewRoot";
55 public static final String COMPONENT_FAMILY = "javax.faces.ViewRoot";
56
57 public static final String UNIQUE_ID_PREFIX = "_id";
58
59 private static final int ANY_PHASE_ORDINAL = PhaseId.ANY_PHASE.getOrdinal();
60
61 /**
62 * The counter which will ensure a unique component id
63 * for every component instance in the tree that doesn't have
64 * an id attribute set.
65 */
66 private long _uniqueIdCounter = 0;
67
68 private String _renderKitId = null;
69 private String _viewId = null;
70 private Locale _locale = null;
71 private List _events = null;
72
73 public String getViewId()
74 {
75 return _viewId;
76 }
77
78 public void setViewId(String viewId)
79 {
80 // It really doesn't make much sense to allow null here.
81 // However the TCK does not check for it, and sun's implementation
82 // allows it so here we allow it too.
83 _viewId = viewId;
84 }
85
86 public void queueEvent(FacesEvent event)
87 {
88 if (event == null)
89 {
90 throw new NullPointerException("event");
91 }
92 if (_events == null)
93 {
94 _events = new ArrayList();
95 }
96 _events.add(event);
97 }
98
99 private void _broadcastForPhase(PhaseId phaseId)
100 {
101 if (_events == null) return;
102
103 boolean abort = false;
104
105 int phaseIdOrdinal = phaseId.getOrdinal();
106 for (ListIterator listiterator = _events.listIterator(); listiterator.hasNext();)
107 {
108 FacesEvent event = (FacesEvent) listiterator.next();
109 int ordinal = event.getPhaseId().getOrdinal();
110 if (ordinal == ANY_PHASE_ORDINAL || ordinal == phaseIdOrdinal)
111 {
112 UIComponent source = event.getComponent();
113 try
114 {
115 source.broadcast(event);
116 }
117 catch (AbortProcessingException e)
118 {
119 // abort event processing
120 // Page 3-30 of JSF 1.1 spec: "Throw an AbortProcessingException, to tell the JSF implementation
121 // that no further broadcast of this event, or any further events, should take place."
122 abort = true;
123 break;
124 }
125 finally
126 {
127 try
128 {
129 listiterator.remove();
130 }
131 catch(ConcurrentModificationException cme)
132 {
133 int eventIndex = listiterator.previousIndex();
134 _events.remove(eventIndex);
135 listiterator = _events.listIterator();
136 }
137 }
138 }
139 }
140
141 if (abort)
142 {
143 // TODO: abort processing of any event of any phase or just of any event of the current phase???
144 clearEvents();
145 }
146 }
147
148
149 private void clearEvents()
150 {
151 _events = null;
152 }
153
154
155 public void processDecodes(FacesContext context)
156 {
157 if (context == null) throw new NullPointerException("context");
158 super.processDecodes(context);
159 _broadcastForPhase(PhaseId.APPLY_REQUEST_VALUES);
160 if (context.getRenderResponse() || context.getResponseComplete())
161 {
162 clearEvents();
163 }
164 }
165
166 public void processValidators(FacesContext context)
167 {
168 if (context == null) throw new NullPointerException("context");
169 super.processValidators(context);
170 _broadcastForPhase(PhaseId.PROCESS_VALIDATIONS);
171 if (context.getRenderResponse() || context.getResponseComplete())
172 {
173 clearEvents();
174 }
175 }
176
177 public void processUpdates(FacesContext context)
178 {
179 if (context == null) throw new NullPointerException("context");
180 super.processUpdates(context);
181 _broadcastForPhase(PhaseId.UPDATE_MODEL_VALUES);
182 if (context.getRenderResponse() || context.getResponseComplete())
183 {
184 clearEvents();
185 }
186 }
187
188 public void processApplication(FacesContext context)
189 {
190 if (context == null) throw new NullPointerException("context");
191 _broadcastForPhase(PhaseId.INVOKE_APPLICATION);
192 if (context.getRenderResponse() || context.getResponseComplete())
193 {
194 clearEvents();
195 }
196 }
197
198 public void encodeBegin(FacesContext context)
199 throws java.io.IOException
200 {
201 clearEvents();
202 super.encodeBegin(context);
203 }
204
205 /**
206 * Provides a unique id for this component instance.
207 */
208 public String createUniqueId()
209 {
210 ExternalContext extCtx = getFacesContext().getExternalContext();
211 return extCtx.encodeNamespace(UNIQUE_ID_PREFIX + _uniqueIdCounter++);
212 }
213
214 /**
215 * The locale of this view. Default: the default locale from the configuration file.
216 *
217 * @JSFProperty
218 */
219 public Locale getLocale()
220 {
221 if (_locale != null) return _locale;
222 ValueBinding vb = getValueBinding("locale");
223 FacesContext facesContext = getFacesContext();
224 if (vb == null)
225 {
226 return facesContext.getApplication().getViewHandler().calculateLocale(facesContext);
227 }
228 Object locale = vb.getValue(facesContext);
229 if (locale == null)
230 {
231 return facesContext.getApplication().getViewHandler().calculateLocale(facesContext);
232 }
233 if (locale instanceof Locale)
234 {
235 return (Locale)locale;
236 }
237 else if (locale instanceof String)
238 {
239 return getLocale((String)locale);
240 }
241 else
242 {
243 throw new IllegalArgumentException("locale binding"); //TODO: not specified!?
244 }
245 }
246
247 public void setLocale(Locale locale)
248 {
249 _locale = locale;
250 }
251
252 /**
253 * Create Locale from String representation.
254 *
255 * http://java.sun.com/j2se/1.4.2/docs/api/java/util/Locale.html
256 *
257 * @param locale locale representation in String.
258 * @return Locale instance
259 */
260 private static Locale getLocale(String locale){
261 int cnt = 0;
262 int pos = 0;
263 int prev = 0;
264
265 // store locale variation.
266 // ex. "ja_JP_POSIX"
267 // lv[0] : language(ja)
268 // lv[1] : country(JP)
269 // lv[2] : variant(POSIX)
270 String[] lv = new String[3];
271 Locale l=null;
272
273 while((pos=locale.indexOf('_',prev))!=-1){
274 lv[cnt++] = locale.substring(prev,pos);
275 prev = pos + 1;
276 }
277
278 lv[cnt++] = locale.substring(prev,locale.length());
279
280 switch(cnt){
281 case 1:
282 // create Locale from language.
283 l = new Locale(lv[0]);
284 break;
285 case 2:
286 // create Locale from language and country.
287 l = new Locale(lv[0],lv[1]);
288 break;
289 case 3:
290 // create Locale from language, country and variant.
291 l = new Locale(lv[0], lv[1], lv[2]);
292 break;
293 }
294 return l;
295 }
296
297 public String getFamily()
298 {
299 return COMPONENT_FAMILY;
300 }
301
302 /**
303 * Defines what renderkit should be used to render this view.
304 * <p>
305 * Note that in JSF1.1 this property cannot be set via the f:view tag
306 * (this is possible in JSF1.2).
307 */
308 public String getRenderKitId()
309 {
310 if (_renderKitId != null) return _renderKitId;
311 ValueBinding vb = getValueBinding("renderKitId");
312 return vb != null ? _ComponentUtils.getStringValue(getFacesContext(), vb) : null; //DEFAULT_RENDERKITID
313 }
314
315 public void setRenderKitId(String renderKitId)
316 {
317 _renderKitId = renderKitId;
318 }
319
320 /**
321 * DO NOT USE.
322 * <p>
323 * Although this class extends a base-class that defines a read/write
324 * rendered property, it makes no sense for this particular subclass to
325 * support it. Yes, this is broken OO design: direct all complaints to
326 * the JSF spec group.
327 * <p>
328 * Ideally this method would throw an UnsupportedOperationException when
329 * invoked. Unfortunately, the JSF TCK calls this method, so instead we
330 * just pass on the call to the default inherited implementation.
331 *
332 * @JSFProperty tagExcluded="true"
333 */
334 public void setRendered(boolean state)
335 {
336 super.setRendered(state);
337 }
338
339 public boolean isRendered()
340 {
341 // Should just return true. But due to damned faulty TCK, we cannot do that.
342 return super.isRendered();
343 }
344
345 /**
346 * DO NOT USE.
347 * <p>
348 * Although this class extends a base-class that defines a read/write
349 * id property, it makes no sense for this particular subclass to support
350 * it. The tag library does not export this property for use, but there
351 * is no way to "undeclare" a java method. Yes, this is broken OO design:
352 * direct all complaints to the JSF spec group.
353 * <p>
354 * This property should be disabled (ie throw an exception if invoked).
355 * However there are currently several places that call this method
356 * (eg during restoreState) so it just does the normal thing for the
357 * moment. TODO: fix callers then make this throw an exception.
358 *
359 * @JSFProperty tagExcluded="true"
360 */
361 public void setId(String id)
362 {
363 // throw new UnsupportedOperationException();
364
365 // Leave enabled for now. Things like the TreeStructureManager call this,
366 // even though they probably should not.
367 super.setId(id);
368 }
369
370 public String getId()
371 {
372 // Should just return null. But as setId passes the method on, do same here.
373 return super.getId();
374 }
375
376 /**
377 * DO NOT USE.
378 * <p>
379 * As this component has no "id" property, it has no clientId property either.
380 * <p>
381 * Ideally, this method would just return null when called (or even throw an
382 * exception) Unforunately, the TCK invokes it so we just invoke the interited
383 * implementation instead.
384 */
385 public String getClientId(FacesContext context)
386 {
387 // Should return null, but cannot due to flawed TCK tests.
388 return super.getClientId(context);
389 }
390
391 public Object saveState(FacesContext context)
392 {
393 Object values[] = new Object[5];
394 values[0] = super.saveState(context);
395 values[1] = _locale;
396 values[2] = _renderKitId;
397 values[3] = _viewId;
398 values[4] = new Long(_uniqueIdCounter);
399 return values;
400 }
401
402 public void restoreState(FacesContext context, Object state)
403 {
404 Object values[] = (Object[])state;
405 super.restoreState(context, values[0]);
406 _locale = (Locale)values[1];
407 _renderKitId = (String)values[2];
408 _viewId = (String)values[3];
409 _uniqueIdCounter = values[4]==null?0:((Long)values[4]).longValue();
410 }
411 }