1 /*
2 * $Id: Util.java,v 1.216.4.4 2008/04/30 01:14:00 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 // Util.java
42
43 package com.sun.faces.util;
44
45 import com.sun.faces.RIConstants;
46
47 import javax.el.ELResolver;
48 import javax.el.ValueExpression;
49 import javax.faces.FacesException;
50 import javax.faces.application.Application;
51 import javax.faces.application.StateManager;
52 import javax.faces.application.ViewHandler;
53 import javax.faces.component.UIComponent;
54 import javax.faces.component.UIViewRoot;
55 import javax.faces.context.ExternalContext;
56 import javax.faces.context.FacesContext;
57 import javax.faces.convert.Converter;
58 import javax.faces.event.AbortProcessingException;
59 import java.beans.FeatureDescriptor;
60 import java.lang.reflect.Method;
61 import java.util.Iterator;
62 import java.util.Locale;
63 import java.util.Map;
64 import java.util.logging.Level;
65 import java.util.logging.Logger;
66 import java.util.regex.Pattern;
67
68 /**
69 * <B>Util</B> is a class ...
70 * <p/>
71 * <B>Lifetime And Scope</B> <P>
72 *
73 * @version $Id: Util.java,v 1.216.4.4 2008/04/30 01:14:00 rlubke Exp $
74 */
75
76 public class Util {
77
78
79 // Log instance for this class
80 private static final Logger LOGGER = FacesLogger.APPLICATION.getLogger();
81
82 // README - make sure to add the message identifier constant
83 // (ex: Util.CONVERSION_ERROR_MESSAGE_ID) and the number of substitution
84 // parameters to test/com/sun/faces/util/TestUtil_messages (see comment there).
85
86 // Loggers
87 public static final String RENDERKIT_LOGGER = ".renderkit";
88 public static final String TAGLIB_LOGGER = ".taglib";
89 public static final String APPLICATION_LOGGER = ".application";
90 public static final String CONTEXT_LOGGER = ".context";
91 public static final String CONFIG_LOGGER = ".config";
92 public static final String LIFECYCLE_LOGGER = ".lifecycle";
93 public static final String TIMING_LOGGER = ".timing";
94
95 /**
96 * Flag that, when true, enables special behavior in the RI to enable
97 * unit testing.
98 */
99 private static boolean unitTestModeEnabled = false;
100
101 /**
102 * Flag that enables/disables the core TLV.
103 */
104 private static boolean coreTLVEnabled = true;
105
106 /**
107 * Flag that enables/disables the html TLV.
108 */
109 private static boolean htmlTLVEnabled = true;
110
111 private static final Map<String,Pattern> patternCache =
112 new LRUMap<String,Pattern>(15);
113
114 //
115 // Instance Variables
116 //
117
118 // Attribute Instance Variables
119
120 // Relationship Instance Variables
121
122 //
123 // Constructors and Initializers
124 //
125
126 private Util() {
127 throw new IllegalStateException();
128 }
129
130 //
131 // Class methods
132 //
133
134 /**
135 * <p>Factory method for creating the varius JSF listener
136 * instances that may be referenced by <code>type</code>
137 * or <code>binding</code>.</p>
138 * <p>If <code>binding</code> is not <code>null</code>
139 * and the evaluation result is not <code>null</code> return
140 * that instance. Otherwise try to instantiate an instances
141 * based on <code>type</code>.</p>
142 *
143 * @param type the <code>Listener</code> type
144 * @param binding a <code>ValueExpression</code> which resolves
145 * to a <code>Listener</code> instance
146 * @return a <code>Listener</code> instance based off the provided
147 * <code>type</code> and <binding>
148 */
149 public static Object getListenerInstance(ValueExpression type,
150 ValueExpression binding) {
151
152 FacesContext faces = FacesContext.getCurrentInstance();
153 Object instance = null;
154 if (faces == null) {
155 return null;
156 }
157 if (binding != null) {
158 instance = binding.getValue(faces.getELContext());
159 }
160 if (instance == null && type != null) {
161 try {
162 instance = ReflectionUtils.newInstance(((String) type.getValue(faces.getELContext())));
163 } catch (Exception e) {
164 throw new AbortProcessingException(e.getMessage(), e);
165 }
166
167 if (binding != null) {
168 binding.setValue(faces.getELContext(), instance);
169 }
170 }
171
172 return instance;
173
174 }
175
176 public static void setUnitTestModeEnabled(boolean enabled) {
177 unitTestModeEnabled = enabled;
178 }
179
180 public static boolean isUnitTestModeEnabled() {
181 return unitTestModeEnabled;
182 }
183
184 public static void setCoreTLVActive(boolean active) {
185 coreTLVEnabled = active;
186 }
187
188 public static boolean isCoreTLVActive() {
189 return coreTLVEnabled;
190 }
191
192 public static void setHtmlTLVActive(boolean active) {
193 htmlTLVEnabled = active;
194 }
195
196 public static boolean isHtmlTLVActive() {
197 return htmlTLVEnabled;
198 }
199
200
201 public static Class loadClass(String name,
202 Object fallbackClass)
203 throws ClassNotFoundException {
204 ClassLoader loader = Util.getCurrentLoader(fallbackClass);
205 // Where to begin...
206 // JDK 6 introduced CR 6434149 where one couldn't pass
207 // in a literal for an array type ([Ljava.lang.String) and
208 // get the Class representation using ClassLoader.loadClass().
209 // It was recommended to use Class.forName(String, boolean, ClassLoader)
210 // for all ClassLoading requests.
211 // HOWEVER, when trying to eliminate the need for .groovy extensions
212 // being specified in the faces-config.xml for Groovy-based artifacts,
213 // by using a an adapter to the GroovyScriptEngine, I found that the class
214 // instance was cached somewhere, so that no matter what change I made,
215 // Class.forName() always returned the same instance. I haven't been
216 // able to determine why this happens in the appserver environment
217 // as the same adapter in a standalone program works as one might expect.
218 // So, for now, if the classname starts with '[', then use Class.forName()
219 // to avoid CR 643419 and for all other cases, use ClassLoader.loadClass().
220 if (name.charAt(0) == '[') {
221 return Class.forName(name, true, loader);
222 } else {
223 return loader.loadClass(name);
224 }
225 }
226
227
228 public static ClassLoader getCurrentLoader(Object fallbackClass) {
229 ClassLoader loader =
230 Thread.currentThread().getContextClassLoader();
231 if (loader == null) {
232 loader = fallbackClass.getClass().getClassLoader();
233 }
234 return loader;
235 }
236
237
238 public static void notNull(String varname, Object var) {
239
240 if (var == null) {
241 throw new NullPointerException(
242 MessageUtils.getExceptionMessageString(
243 MessageUtils.NULL_PARAMETERS_ERROR_MESSAGE_ID, varname));
244 }
245
246 }
247
248
249 /**
250 * @param context the <code>FacesContext</code> for the current request
251 * @return the Locale from the UIViewRoot, the the value of Locale.getDefault()
252 */
253 public static Locale getLocaleFromContextOrSystem(FacesContext context) {
254 Locale result, temp = Locale.getDefault();
255 UIViewRoot root;
256 result = temp;
257 if (null != context) {
258 if (null != (root = context.getViewRoot())) {
259 if (null == (result = root.getLocale())) {
260 result = temp;
261 }
262 }
263 }
264 return result;
265 }
266
267
268 public static Converter getConverterForClass(Class converterClass,
269 FacesContext context) {
270 if (converterClass == null) {
271 return null;
272 }
273 try {
274 Application application = context.getApplication();
275 return (application.createConverter(converterClass));
276 } catch (Exception e) {
277 return (null);
278 }
279 }
280
281
282 public static Converter getConverterForIdentifer(String converterId,
283 FacesContext context) {
284 if (converterId == null) {
285 return null;
286 }
287 try {
288 Application application = context.getApplication();
289 return (application.createConverter(converterId));
290 } catch (Exception e) {
291 return (null);
292 }
293 }
294
295
296 public static StateManager getStateManager(FacesContext context)
297 throws FacesException {
298 return (context.getApplication().getStateManager());
299 }
300
301
302 public static ViewHandler getViewHandler(FacesContext context)
303 throws FacesException {
304 // Get Application instance
305 Application application = context.getApplication();
306 assert (application != null);
307
308 // Get the ViewHandler
309 ViewHandler viewHandler = application.getViewHandler();
310 assert (viewHandler != null);
311
312 return viewHandler;
313 }
314
315
316 public static boolean componentIsDisabled(UIComponent component) {
317
318 return (Boolean.valueOf(String.valueOf(component.getAttributes().get("disabled"))));
319
320 }
321
322
323 public static boolean componentIsDisabledOrReadonly(UIComponent component) {
324 return Boolean.valueOf(String.valueOf(component.getAttributes().get("disabled"))) || Boolean.valueOf(String.valueOf(component.getAttributes().get("readonly")));
325 }
326
327
328
329
330 // W3C XML specification refers to IETF RFC 1766 for language code
331 // structure, therefore the value for the xml:lang attribute should
332 // be in the form of language or language-country or
333 // language-country-variant.
334
335 public static Locale getLocaleFromString(String localeStr)
336 throws IllegalArgumentException {
337 // length must be at least 2.
338 if (null == localeStr || localeStr.length() < 2) {
339 throw new IllegalArgumentException("Illegal locale String: " +
340 localeStr);
341 }
342
343 Locale result = null;
344 String lang = null;
345 String country = null;
346 String variant = null;
347 char[] seps = {
348 '-',
349 '_'
350 };
351 int i = 0;
352 int j = 0;
353 int inputLength = localeStr.length();
354
355 // to have a language, the length must be >= 2
356 if ((inputLength >= 2) &&
357 ((i = indexOfSet(localeStr, seps, 0)) == -1)) {
358 // we have only Language, no country or variant
359 if (inputLength != 2) {
360 throw new
361 IllegalArgumentException("Illegal locale String: " +
362 localeStr);
363 }
364 lang = localeStr.toLowerCase();
365 }
366
367 // we have a separator, it must be either '-' or '_'
368 if (i != -1) {
369 lang = localeStr.substring(0, i);
370 // look for the country sep.
371 // to have a country, the length must be >= 5
372 if ((inputLength >= 5) &&
373 ((j = indexOfSet(localeStr, seps, i + 1)) == -1)) {
374 // no further separators, length must be 5
375 if (inputLength != 5) {
376 throw new
377 IllegalArgumentException("Illegal locale String: " +
378 localeStr);
379 }
380 country = localeStr.substring(i + 1);
381 }
382 if (j != -1) {
383 country = localeStr.substring(i + 1, j);
384 // if we have enough separators for language, locale,
385 // and variant, the length must be >= 8.
386 if (inputLength >= 8) {
387 variant = localeStr.substring(j + 1);
388 } else {
389 throw new
390 IllegalArgumentException("Illegal locale String: " +
391 localeStr);
392 }
393 }
394 }
395 if (variant != null && country != null && lang != null) {
396 result = new Locale(lang, country, variant);
397 } else if (lang != null && country != null) {
398 result = new Locale(lang, country);
399 } else if (lang != null) {
400 result = new Locale(lang, "");
401 }
402 return result;
403 }
404
405
406 /**
407 * @param str local string
408 * @param set the substring
409 * @param fromIndex starting index
410 * @return starting at <code>fromIndex</code>, the index of the
411 * first occurrence of any substring from <code>set</code> in
412 * <code>toSearch</code>, or -1 if no such match is found
413 */
414 public static int indexOfSet(String str, char[] set, int fromIndex) {
415 int result = -1;
416 for (int i = fromIndex, len = str.length(); i < len; i++) {
417 for (int j = 0, innerLen = set.length; j < innerLen; j++) {
418 if (str.charAt(i) == set[j]) {
419 result = i;
420 break;
421 }
422 }
423 if (result != -1) {
424 break;
425 }
426 }
427 return result;
428 }
429
430
431 public static void parameterNonNull(Object param) throws FacesException {
432 if (null == param) {
433 throw new FacesException(
434 MessageUtils.getExceptionMessageString(MessageUtils.NULL_PARAMETERS_ERROR_MESSAGE_ID, "param"));
435 }
436 }
437
438
439 public static void parameterNonEmpty(String param) throws FacesException {
440 if (null == param || 0 == param.length()) {
441 throw new FacesException(MessageUtils.getExceptionMessageString(MessageUtils.EMPTY_PARAMETER_ID));
442 }
443 }
444
445 /**
446 * <p>Leverage the Throwable.getStackTrace() method to produce a String
447 * version of the stack trace, with a "\n" before each line.</p>
448 *
449 * @param e the Throwable to obtain the stacktrace from
450 *
451 * @return the String representation ofthe stack trace obtained by calling
452 * getStackTrace() on the passed in exception. If null is passed
453 * in, we return the empty String.
454 */
455 public static String getStackTraceString(Throwable e) {
456 if (null == e) {
457 return "";
458 }
459
460 StackTraceElement[] stacks = e.getStackTrace();
461 StringBuffer sb = new StringBuffer();
462 for (int i = 0; i < stacks.length; i++) {
463 sb.append(stacks[i].toString()).append('\n');
464 }
465 return sb.toString();
466 }
467
468 /**
469 * <p>PRECONDITION: argument <code>response</code> is non-null and
470 * has a method called <code>getContentType</code> that takes no
471 * arguments and returns a String, with no side-effects.</p>
472 *
473 * <p>This method allows us to get the contentType in both the
474 * servlet and portlet cases, without introducing a compile-time
475 * dependency on the portlet api.</p>
476 *
477 * @param response the current response
478 * @return the content type of the response
479 */
480 public static String getContentTypeFromResponse(Object response) {
481 String result = null;
482 if (null != response) {
483
484 try {
485 Method method = ReflectionUtils.lookupMethod(
486 response.getClass(),
487 "getContentType",
488 RIConstants.EMPTY_CLASS_ARGS
489 );
490 if (null != method) {
491 Object obj =
492 method.invoke(response, RIConstants.EMPTY_METH_ARGS);
493 if (null != obj) {
494 result = obj.toString();
495 }
496 }
497 } catch (Exception e) {
498 throw new FacesException(e);
499 }
500 }
501 return result;
502 }
503
504 public static boolean prefixViewTraversal(FacesContext context,
505 UIComponent root,
506 TreeTraversalCallback action)
507 throws FacesException {
508 boolean keepGoing;
509 if (keepGoing = action.takeActionOnNode(context, root)) {
510 Iterator<UIComponent> kids = root.getFacetsAndChildren();
511 while (kids.hasNext() && keepGoing) {
512 keepGoing = prefixViewTraversal(context,
513 kids.next(),
514 action);
515 }
516 }
517 return keepGoing;
518 }
519
520 public static interface TreeTraversalCallback {
521 public boolean takeActionOnNode(FacesContext context,
522 UIComponent curNode) throws FacesException;
523 }
524
525 public static FeatureDescriptor getFeatureDescriptor(String name, String
526 displayName, String desc, boolean expert, boolean hidden,
527 boolean preferred, Object type, Boolean designTime) {
528
529 FeatureDescriptor fd = new FeatureDescriptor();
530 fd.setName(name);
531 fd.setDisplayName(displayName);
532 fd.setShortDescription(desc);
533 fd.setExpert(expert);
534 fd.setHidden(hidden);
535 fd.setPreferred(preferred);
536 fd.setValue(ELResolver.TYPE, type);
537 fd.setValue(ELResolver.RESOLVABLE_AT_DESIGN_TIME, designTime);
538 return fd;
539 }
540
541
542 /**
543 * <p>A slightly more efficient version of
544 * <code>String.split()</code> which caches
545 * the <code>Pattern</code>s in an LRUMap instead of
546 * creating a new <code>Pattern</code> on each
547 * invocation.</p>
548 * @param toSplit the string to split
549 * @param regex the regex used for splitting
550 * @return the result of <code>Pattern.spit(String, int)</code>
551 */
552 public synchronized static String[] split(String toSplit, String regex) {
553 Pattern pattern = patternCache.get(regex);
554 if (pattern == null) {
555 pattern = Pattern.compile(regex);
556 patternCache.put(regex, pattern);
557 }
558 return pattern.split(toSplit, 0);
559 }
560
561
562 /**
563 * <p>Returns the URL pattern of the
564 * {@link javax.faces.webapp.FacesServlet} that
565 * is executing the current request. If there are multiple
566 * URL patterns, the value returned by
567 * <code>HttpServletRequest.getServletPath()</code> and
568 * <code>HttpServletRequest.getPathInfo()</code> is
569 * used to determine which mapping to return.</p>
570 * If no mapping can be determined, it most likely means
571 * that this particular request wasn't dispatched through
572 * the {@link javax.faces.webapp.FacesServlet}.
573 *
574 * @param context the {@link FacesContext} of the current request
575 *
576 * @return the URL pattern of the {@link javax.faces.webapp.FacesServlet}
577 * or <code>null</code> if no mapping can be determined
578 *
579 * @throws NullPointerException if <code>context</code> is null
580 */
581 public static String getFacesMapping(FacesContext context) {
582
583 if (context == null) {
584 String message = MessageUtils.getExceptionMessageString
585 (MessageUtils.NULL_PARAMETERS_ERROR_MESSAGE_ID, "context");
586 throw new NullPointerException(message);
587 }
588
589 // Check for a previously stored mapping
590 ExternalContext extContext = context.getExternalContext();
591 String mapping =
592 (String) RequestStateManager.get(context, RequestStateManager.INVOCATION_PATH);
593
594 if (mapping == null) {
595
596 // first check for javax.servlet.forward.servlet_path
597 // and javax.servlet.forward.path_info for non-null
598 // values. if either is non-null, use this
599 // information to generate determine the mapping.
600
601 String servletPath = extContext.getRequestServletPath();
602 String pathInfo = extContext.getRequestPathInfo();
603
604 mapping = getMappingForRequest(servletPath, pathInfo);
605 if (mapping == null) {
606 if (LOGGER.isLoggable(Level.FINE)) {
607 LOGGER.log(Level.FINE,
608 "jsf.faces_servlet_mapping_cannot_be_determined_error",
609 new Object[]{servletPath});
610 }
611 }
612 }
613
614 // if the FacesServlet is mapped to /* throw an
615 // Exception in order to prevent an endless
616 // RequestDispatcher loop
617 //if ("/*".equals(mapping)) {
618 // throw new FacesException(MessageUtils.getExceptionMessageString(
619 // MessageUtils.FACES_SERVLET_MAPPING_INCORRECT_ID));
620 //}
621
622 if (mapping != null) {
623 RequestStateManager.set(context,
624 RequestStateManager.INVOCATION_PATH,
625 mapping);
626 }
627 if (LOGGER.isLoggable(Level.FINE)) {
628 LOGGER.log(Level.FINE,
629 "URL pattern of the FacesServlet executing the current request "
630 + mapping);
631 }
632 return mapping;
633 }
634
635 /**
636 * <p>Return the appropriate {@link javax.faces.webapp.FacesServlet} mapping
637 * based on the servlet path of the current request.</p>
638 *
639 * @param servletPath the servlet path of the request
640 * @param pathInfo the path info of the request
641 *
642 * @return the appropriate mapping based on the current request
643 *
644 * @see javax.servlet.http.HttpServletRequest#getServletPath()
645 */
646 private static String getMappingForRequest(String servletPath, String pathInfo) {
647
648 if (servletPath == null) {
649 return null;
650 }
651 if (LOGGER.isLoggable(Level.FINE)) {
652 LOGGER.log(Level.FINE, "servletPath " + servletPath);
653 LOGGER.log(Level.FINE, "pathInfo " + pathInfo);
654 }
655 // If the path returned by HttpServletRequest.getServletPath()
656 // returns a zero-length String, then the FacesServlet has
657 // been mapped to '/*'.
658 if (servletPath.length() == 0) {
659 return "/*";
660 }
661
662 // presence of path info means we were invoked
663 // using a prefix path mapping
664 if (pathInfo != null) {
665 return servletPath;
666 } else if (servletPath.indexOf('.') < 0) {
667 // if pathInfo is null and no '.' is present, assume the
668 // FacesServlet was invoked using prefix path but without
669 // any pathInfo - i.e. GET /contextroot/faces or
670 // GET /contextroot/faces/
671 return servletPath;
672 } else {
673 // Servlet invoked using extension mapping
674 return servletPath.substring(servletPath.lastIndexOf('.'));
675 }
676 }
677
678
679 /**
680 * <p>Returns true if the provided <code>url-mapping</code> is
681 * a prefix path mapping (starts with <code>/</code>).</p>
682 *
683 * @param mapping a <code>url-pattern</code>
684 * @return true if the mapping starts with <code>/</code>
685 */
686 public static boolean isPrefixMapped(String mapping) {
687 return (mapping.charAt(0) == '/');
688 }
689
690
691 } // end of class Util