1 /*
2 * $Id: ApplicationAssociate.java,v 1.49.4.5 2008/06/11 18:03:03 rlubke Exp $
3 */
4
5 /*
6 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
7 *
8 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
9 *
10 * The contents of this file are subject to the terms of either the GNU
11 * General Public License Version 2 only ("GPL") or the Common Development
12 * and Distribution License("CDDL") (collectively, the "License"). You
13 * may not use this file except in compliance with the License. You can obtain
14 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
15 * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
16 * language governing permissions and limitations under the License.
17 *
18 * When distributing the software, include this License Header Notice in each
19 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
20 * Sun designates this particular file as subject to the "Classpath" exception
21 * as provided by Sun in the GPL Version 2 section of the License file that
22 * accompanied this code. If applicable, add the following below the License
23 * Header, with the fields enclosed by brackets [] replaced by your own
24 * identifying information: "Portions Copyrighted [year]
25 * [name of copyright owner]"
26 *
27 * Contributor(s):
28 *
29 * If you wish your version of this file to be governed by only the CDDL or
30 * only the GPL Version 2, indicate your decision by adding "[Contributor]
31 * elects to include this software in this distribution under the [CDDL or GPL
32 * Version 2] license." If you don't indicate a single choice of license, a
33 * recipient has the option to distribute your version of this file under
34 * either the CDDL, the GPL Version 2 or to extend the choice of license to
35 * its licensees as provided above. However, if you add GPL Version 2 code
36 * and therefore, elected the GPL Version 2 license, then the option applies
37 * only if the new code is made subject to such option by the copyright
38 * holder.
39 */
40
41 package com.sun.faces.application;
42
43 import java.util.ArrayList;
44 import java.util.Collections;
45 import java.util.Comparator;
46 import java.util.HashMap;
47 import java.util.List;
48 import java.util.Locale;
49 import java.util.Map;
50 import java.util.ResourceBundle;
51 import java.util.TreeSet;
52
53 import javax.el.CompositeELResolver;
54 import javax.el.ELResolver;
55 import javax.el.ExpressionFactory;
56 import javax.faces.component.UIViewRoot;
57 import javax.faces.context.ExternalContext;
58 import javax.faces.context.FacesContext;
59 import javax.faces.el.PropertyResolver;
60 import javax.faces.el.VariableResolver;
61 import javax.servlet.ServletContext;
62
63 import com.sun.faces.RIConstants;
64 import com.sun.faces.config.WebConfiguration;
65 import static com.sun.faces.config.WebConfiguration.BooleanWebContextInitParameter;
66 import com.sun.faces.scripting.GroovyHelper;
67 import com.sun.faces.mgbean.BeanManager;
68 import com.sun.faces.spi.InjectionProvider;
69 import com.sun.faces.spi.InjectionProviderFactory;
70 import com.sun.faces.util.MessageUtils;
71
72 /**
73 * <p>Break out the things that are associated with the Application, but
74 * need to be present even when the user has replaced the Application
75 * instance.</p>
76 * <p/>
77 * <p>For example: the user replaces ApplicationFactory, and wants to
78 * intercept calls to createValueExpression() and createMethodExpression() for
79 * certain kinds of expressions, but allow the existing application to
80 * handle the rest.</p>
81 */
82
83 public class ApplicationAssociate {
84
85
86 private static final String APPLICATION_IMPL_ATTR_NAME =
87 RIConstants.FACES_PREFIX + "ApplicationImpl";
88
89 private ApplicationImpl app = null;
90
91
92
93
94 /**
95 * Overall Map containing <code>from-view-id</code> key and
96 * <code>ArrayList</code> of <code>ConfigNavigationCase</code>
97 * objects for that key; The <code>from-view-id</code> strings in
98 * this map will be stored as specified in the configuration file -
99 * some of them will have a trailing asterisk "*" signifying wild
100 * card, and some may be specified as an asterisk "*".
101 */
102 private Map<String, List<ConfigNavigationCase>> caseListMap = null;
103
104 /**
105 * The List that contains all view identifier strings ending in an
106 * asterisk "*". The entries are stored without the trailing
107 * asterisk.
108 */
109 private TreeSet<String> wildcardMatchList = null;
110
111 // Flag indicating that a response has been rendered.
112 private boolean responseRendered = false;
113
114 private static final String ASSOCIATE_KEY = RIConstants.FACES_PREFIX +
115 "ApplicationAssociate";
116
117 private static ThreadLocal<ApplicationAssociate> instance =
118 new ThreadLocal<ApplicationAssociate>() {
119 protected ApplicationAssociate initialValue() {
120 return (null);
121 }
122 };
123
124 private List<ELResolver> elResolversFromFacesConfig = null;
125
126 @SuppressWarnings("deprecation")
127 private VariableResolver legacyVRChainHead = null;
128
129 @SuppressWarnings("deprecation")
130 private PropertyResolver legacyPRChainHead = null;
131 private ExpressionFactory expressionFactory = null;
132
133 @SuppressWarnings("deprecation")
134 private PropertyResolver legacyPropertyResolver = null;
135
136 @SuppressWarnings("deprecation")
137 private VariableResolver legacyVariableResolver = null;
138 private CompositeELResolver facesELResolverForJsp = null;
139
140 private InjectionProvider injectionProvider;
141
142 private String contextName;
143 private boolean requestServiced;
144
145 private BeanManager beanManager;
146 private GroovyHelper groovyHelper;
147 private boolean devModeEnabled;
148
149 private PropertyEditorHelper propertyEditorHelper;
150
151 public ApplicationAssociate(ApplicationImpl appImpl) {
152 app = appImpl;
153
154 propertyEditorHelper = new PropertyEditorHelper(appImpl);
155
156 FacesContext ctx = FacesContext.getCurrentInstance();
157 if (ctx == null) {
158 throw new IllegalStateException(
159 MessageUtils.getExceptionMessageString(
160 MessageUtils.APPLICATION_ASSOCIATE_CTOR_WRONG_CALLSTACK_ID));
161 }
162 ExternalContext externalContext = ctx.getExternalContext();
163 if (null != externalContext.getApplicationMap().get(ASSOCIATE_KEY)) {
164 throw new IllegalStateException(
165 MessageUtils.getExceptionMessageString(
166 MessageUtils.APPLICATION_ASSOCIATE_EXISTS_ID));
167 }
168 externalContext.getApplicationMap().put(APPLICATION_IMPL_ATTR_NAME,
169 appImpl);
170 externalContext.getApplicationMap().put(ASSOCIATE_KEY, this);
171 //noinspection CollectionWithoutInitialCapacity
172 caseListMap = new HashMap<String, List<ConfigNavigationCase>>();
173 wildcardMatchList = new TreeSet<String>(new SortIt());
174 injectionProvider = InjectionProviderFactory.createInstance(externalContext);
175 WebConfiguration webConfig = WebConfiguration.getInstance(externalContext);
176 beanManager = new BeanManager(injectionProvider,
177 webConfig.isOptionEnabled(
178 BooleanWebContextInitParameter.EnableLazyBeanValidation));
179 groovyHelper = GroovyHelper.getCurrentInstance();
180 devModeEnabled = webConfig.isOptionEnabled(BooleanWebContextInitParameter.DevelopmentMode);
181 }
182
183 public static ApplicationAssociate getInstance(ExternalContext
184 externalContext) {
185 if (externalContext == null) {
186 return null;
187 }
188 Map applicationMap = externalContext.getApplicationMap();
189 return ((ApplicationAssociate)
190 applicationMap.get(ASSOCIATE_KEY));
191 }
192
193 public static ApplicationAssociate getInstance(ServletContext context) {
194 if (context == null) {
195 return null;
196 }
197 return (ApplicationAssociate) context.getAttribute(ASSOCIATE_KEY);
198 }
199
200 public static void setCurrentInstance(ApplicationAssociate associate) {
201
202 if (associate == null) {
203 instance.remove();
204 } else {
205 instance.set(associate);
206 }
207
208 }
209
210 public static ApplicationAssociate getCurrentInstance() {
211
212 ApplicationAssociate associate = instance.get();
213 if (associate == null) {
214 // Fallback to ExternalContext lookup
215 FacesContext fc = FacesContext.getCurrentInstance();
216 if (fc != null) {
217 ExternalContext extContext = fc.getExternalContext();
218 if (extContext != null) {
219 return ApplicationAssociate.getInstance(extContext);
220 }
221 }
222 }
223
224 return associate;
225
226 }
227
228 public static void clearInstance(ExternalContext
229 externalContext) {
230 Map applicationMap = externalContext.getApplicationMap();
231 ApplicationAssociate me = (ApplicationAssociate) applicationMap.get(ASSOCIATE_KEY);
232 if (null != me) {
233 if (null != me.resourceBundles) {
234 me.resourceBundles.clear();
235 }
236 }
237 applicationMap.remove(ASSOCIATE_KEY);
238 }
239
240
241 public BeanManager getBeanManager() {
242 return beanManager;
243 }
244
245 public GroovyHelper getGroovyHelper() {
246 return groovyHelper;
247 }
248
249 public boolean isDevModeEnabled() {
250 return devModeEnabled;
251 }
252
253 /**
254 * Obtain the PropertyEditorHelper instance for this app.
255 *
256 * @return
257 */
258 public PropertyEditorHelper getPropertyEditorHelper() {
259 return propertyEditorHelper;
260 }
261
262 /**
263 * This method is called by <code>ConfigureListener</code> and will
264 * contain any <code>VariableResolvers</code> defined within
265 * faces-config configuration files.
266 *
267 * @param resolver VariableResolver
268 */
269 @SuppressWarnings("deprecation")
270 public void setLegacyVRChainHead(VariableResolver resolver) {
271 this.legacyVRChainHead = resolver;
272 }
273
274 @SuppressWarnings("deprecation")
275 public VariableResolver getLegacyVRChainHead() {
276 return legacyVRChainHead;
277 }
278
279 /**
280 * This method is called by <code>ConfigureListener</code> and will
281 * contain any <code>PropertyResolvers</code> defined within
282 * faces-config configuration files.
283 *
284 * @param resolver PropertyResolver
285 */
286 @SuppressWarnings("deprecation")
287 public void setLegacyPRChainHead(PropertyResolver resolver) {
288 this.legacyPRChainHead = resolver;
289 }
290
291 @SuppressWarnings("deprecation")
292 public PropertyResolver getLegacyPRChainHead() {
293 return legacyPRChainHead;
294 }
295
296 public CompositeELResolver getFacesELResolverForJsp() {
297 return facesELResolverForJsp;
298 }
299
300 public void setFacesELResolverForJsp(CompositeELResolver celr) {
301 facesELResolverForJsp = celr;
302 }
303
304 public void setELResolversFromFacesConfig(List<ELResolver> resolvers) {
305 this.elResolversFromFacesConfig = resolvers;
306 }
307
308 public List<ELResolver> getELResolversFromFacesConfig() {
309 return elResolversFromFacesConfig;
310 }
311
312 public void setExpressionFactory(ExpressionFactory expressionFactory) {
313 this.expressionFactory = expressionFactory;
314 }
315
316 public ExpressionFactory getExpressionFactory() {
317 return this.expressionFactory;
318 }
319
320 public List<ELResolver> getApplicationELResolvers() {
321 return app.getApplicationELResolvers();
322 }
323
324 public InjectionProvider getInjectionProvider() {
325 return injectionProvider;
326 }
327
328 public void setContextName(String contextName) {
329 this.contextName = contextName;
330 }
331
332 public String getContextName() {
333 return contextName;
334 }
335
336 /**
337 * Maintains the PropertyResolver called through
338 * Application.setPropertyResolver()
339 * @param resolver PropertyResolver
340 */
341 @SuppressWarnings("deprecation")
342 public void setLegacyPropertyResolver(PropertyResolver resolver) {
343 this.legacyPropertyResolver = resolver;
344 }
345
346 /**
347 * @return the PropertyResolver called through
348 * Application.getPropertyResolver()
349 */
350 @SuppressWarnings("deprecation")
351 public PropertyResolver getLegacyPropertyResolver() {
352 return legacyPropertyResolver;
353 }
354
355 /**
356 * Maintains the PropertyResolver called through
357 * Application.setVariableResolver()
358 * @param resolver VariableResolver
359 */
360 @SuppressWarnings("deprecation")
361 public void setLegacyVariableResolver(VariableResolver resolver) {
362 this.legacyVariableResolver = resolver;
363 }
364
365 /**
366 * @return the VariableResolver called through
367 * Application.getVariableResolver()
368 */
369 @SuppressWarnings("deprecation")
370 public VariableResolver getLegacyVariableResolver() {
371 return legacyVariableResolver;
372 }
373
374
375 /**
376 * Called by application code to indicate we've processed the
377 * first request to the application.
378 */
379 public void setRequestServiced() {
380 this.requestServiced = true;
381 }
382
383 /**
384 * @return <code>true</code> if we've processed a request, otherwise
385 * <code>false</code>
386 */
387 public boolean hasRequestBeenServiced() {
388 return requestServiced;
389 }
390
391
392 /**
393 * Add a navigation case to the internal case list. If a case list
394 * does not already exist in the case list map containing this case
395 * (identified by <code>from-view-id</code>), start a new list,
396 * add the case to it, and store the list in the case list map.
397 * If a case list already exists, see if a case entry exists in the list
398 * with a matching <code>from-view-id</code><code>from-action</code>
399 * <code>from-outcome</code> combination. If there is suach an entry,
400 * overwrite it with this new case. Otherwise, add the case to the list.
401 *
402 * @param navigationCase the navigation case containing navigation
403 * mapping information from the configuration file.
404 */
405 public void addNavigationCase(ConfigNavigationCase navigationCase) {
406
407 String fromViewId = navigationCase.getFromViewId();
408 List<ConfigNavigationCase> caseList = caseListMap.get(fromViewId);
409 if (caseList == null) {
410 //noinspection CollectionWithoutInitialCapacity
411 caseList = new ArrayList<ConfigNavigationCase>();
412 caseList.add(navigationCase);
413 caseListMap.put(fromViewId, caseList);
414 } else {
415 String key = navigationCase.getKey();
416 boolean foundIt = false;
417 for (int i = 0; i < caseList.size(); i++) {
418 ConfigNavigationCase navCase = caseList.get(i);
419 // if there already is a case existing for the
420 // fromviewid/fromaction.fromoutcome combination,
421 // replace it ... (last one wins).
422 //
423 if (key.equals(navCase.getKey())) {
424 caseList.set(i, navigationCase);
425 foundIt = true;
426 break;
427 }
428 }
429 if (!foundIt) {
430 caseList.add(navigationCase);
431 }
432 }
433 if (fromViewId.endsWith("*")) {
434 fromViewId =
435 fromViewId.substring(0, fromViewId.lastIndexOf('*'));
436 wildcardMatchList.add(fromViewId);
437 }
438
439 }
440
441
442 /**
443 * Return a <code>Map</code> of navigation mappings loaded from
444 * the configuration system. The key for the returned <code>Map</code>
445 * is <code>from-view-id</code>, and the value is a <code>List</code>
446 * of navigation cases.
447 *
448 * @return Map the map of navigation mappings.
449 */
450 public Map<String, List<ConfigNavigationCase>> getNavigationCaseListMappings() {
451 if (caseListMap == null) {
452 return Collections.emptyMap();
453 }
454 return caseListMap;
455 }
456
457
458 /**
459 * Return all navigation mappings whose <code>from-view-id</code>
460 * contained a trailing "*".
461 *
462 * @return <code>TreeSet</code> The navigation mappings sorted in
463 * descending order.
464 */
465 public TreeSet<String> getNavigationWildCardList() {
466 return wildcardMatchList;
467 }
468
469 public ResourceBundle getResourceBundle(FacesContext context,
470 String var) {
471 ApplicationResourceBundle bundle = resourceBundles.get(var);
472 if (bundle == null) {
473 return null;
474 }
475 UIViewRoot root;
476 // Start out with the default locale
477 Locale locale;
478 Locale defaultLocale = Locale.getDefault();
479 locale = defaultLocale;
480 // See if this FacesContext has a ViewRoot
481 if (null != (root = context.getViewRoot())) {
482 // If so, ask it for its Locale
483 if (null == (locale = root.getLocale())) {
484 // If the ViewRoot has no Locale, fall back to the default.
485 locale = defaultLocale;
486 }
487 }
488 assert (null != locale);
489 //ResourceBundleBean bean = resourceBundles.get(var);
490 return bundle.getResourceBundle(locale);
491
492 }
493
494 /**
495 * keys: <var> element from faces-config<p>
496 * <p/>
497 * values: ResourceBundleBean instances.
498 */
499
500 @SuppressWarnings({"CollectionWithoutInitialCapacity"})
501 Map<String, ApplicationResourceBundle> resourceBundles =
502 new HashMap<String, ApplicationResourceBundle>();
503
504 public void addResourceBundle(String var, ApplicationResourceBundle bundle) {
505 resourceBundles.put(var, bundle);
506 }
507
508 public Map<String, ApplicationResourceBundle> getResourceBundles() {
509 return resourceBundles;
510 }
511
512 // This is called by ViewHandlerImpl.renderView().
513 void responseRendered() {
514 responseRendered = true;
515 }
516
517 boolean isResponseRendered() {
518 return responseRendered;
519 }
520
521
522 /**
523 * This Comparator class will help sort the <code>ConfigNavigationCase</code> objects
524 * based on their <code>fromViewId</code> properties in descending order -
525 * largest string to smallest string.
526 */
527 static class SortIt implements Comparator<String> {
528
529 public int compare(String fromViewId1, String fromViewId2) {
530 return -(fromViewId1.compareTo(fromViewId2));
531 }
532 }
533
534 }