001 // Copyright 2004, 2005 The Apache Software Foundation 002 // 003 // Licensed under the Apache License, Version 2.0 (the "License"); 004 // you may not use this file except in compliance with the License. 005 // You may obtain a copy of the License at 006 // 007 // http://www.apache.org/licenses/LICENSE-2.0 008 // 009 // Unless required by applicable law or agreed to in writing, software 010 // distributed under the License is distributed on an "AS IS" BASIS, 011 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 012 // See the License for the specific language governing permissions and 013 // limitations under the License. 014 015 package org.apache.tapestry; 016 017 import java.io.IOException; 018 import java.io.InputStream; 019 import java.text.MessageFormat; 020 import java.util.ArrayList; 021 import java.util.Collection; 022 import java.util.HashMap; 023 import java.util.Iterator; 024 import java.util.List; 025 import java.util.Locale; 026 import java.util.Map; 027 import java.util.Properties; 028 import java.util.ResourceBundle; 029 import java.util.Set; 030 031 import org.apache.hivemind.ApplicationRuntimeException; 032 import org.apache.hivemind.HiveMind; 033 import org.apache.hivemind.Location; 034 import org.apache.hivemind.service.ClassFabUtils; 035 import org.apache.tapestry.event.ChangeObserver; 036 import org.apache.tapestry.event.ObservedChangeEvent; 037 import org.apache.tapestry.services.ServiceConstants; 038 import org.apache.tapestry.spec.IComponentSpecification; 039 import org.apache.tapestry.util.StringSplitter; 040 041 /** 042 * A placeholder for a number of (static) methods that don't belong elsewhere, as well as a global 043 * location for static constants. 044 * 045 * @since 1.0.1 046 * @author Howard Lewis Ship 047 */ 048 049 public final class Tapestry 050 { 051 /** 052 * The name ("action") of a service that allows behavior to be associated with an 053 * {@link IAction} component, such as {@link org.apache.tapestry.link.ActionLink }or 054 * {@link org.apache.tapestry.form.Form}. 055 * <p> 056 * This service is used with actions that are tied to the dynamic state of the page, and which 057 * require a rewind of the page. 058 */ 059 060 public final static String ACTION_SERVICE = "action"; 061 062 /** 063 * The name ("direct") of a service that allows stateless behavior for an {@link 064 * org.apache.tapestry.link.DirectLink} component. 065 * <p> 066 * This service rolls back the state of the page but doesn't rewind the the dynamic state of the 067 * page the was the action service does, which is more efficient but less powerful. 068 * <p> 069 * An array of String parameters may be included with the service URL; these will be made 070 * available to the {@link org.apache.tapestry.link.DirectLink} component's listener. 071 */ 072 073 public final static String DIRECT_SERVICE = "direct"; 074 075 /** 076 * The name ("external") of a service that a allows {@link IExternalPage} to be selected. 077 * Associated with a {@link org.apache.tapestry.link.ExternalLink} component. 078 * <p> 079 * This service enables {@link IExternalPage}s to be accessed via a URL. External pages may be 080 * booked marked using their URL for future reference. 081 * <p> 082 * An array of Object parameters may be included with the service URL; these will be passed to 083 * the {@link IExternalPage#activateExternalPage(Object[], IRequestCycle)} method. 084 */ 085 086 public final static String EXTERNAL_SERVICE = "external"; 087 088 /** 089 * The name ("page") of a service that allows a new page to be selected. Associated with a 090 * {@link org.apache.tapestry.link.PageLink} component. 091 * <p> 092 * The service requires a single parameter: the name of the target page. 093 */ 094 095 public final static String PAGE_SERVICE = "page"; 096 097 /** 098 * The name ("home") of a service that jumps to the home page. A stand-in for when no service is 099 * provided, which is typically the entrypoint to the application. 100 */ 101 102 public final static String HOME_SERVICE = "home"; 103 104 /** 105 * The name ("restart") of a service that invalidates the session and restarts the application. 106 * Typically used just to recover from an exception. 107 */ 108 109 public static final String RESTART_SERVICE = "restart"; 110 111 /** 112 * The name ("asset") of a service used to access internal assets. 113 */ 114 115 public static final String ASSET_SERVICE = "asset"; 116 117 /** 118 * The name ("reset") of a service used to clear cached template and specification data and 119 * remove all pooled pages. This is only used when debugging as a quick way to clear the out 120 * cached data, to allow updated versions of specifications and templates to be loaded (without 121 * stopping and restarting the servlet container). 122 * <p> 123 * This service is only available if the Java system property 124 * <code>org.apache.tapestry.enable-reset-service</code> is set to <code>true</code>. 125 */ 126 127 public static final String RESET_SERVICE = "reset"; 128 129 /** 130 * Query parameter that identfies the service for the request. 131 * 132 * @since 1.0.3 133 * @deprecated To be removed in 4.1. Use 134 * {@link org.apache.tapestry.services.ServiceConstants#SERVICE} instead. 135 */ 136 137 public static final String SERVICE_QUERY_PARAMETER_NAME = ServiceConstants.SERVICE; 138 139 /** 140 * The query parameter for application specific parameters to the service (this is used with the 141 * direct service). Each of these values is encoded with 142 * {@link java.net.URLEncoder#encode(String)} before being added to the URL. Multiple values are 143 * handle by repeatedly establishing key/value pairs (this is a change from behavior in 2.1 and 144 * earlier). 145 * 146 * @since 1.0.3 147 * @deprecated To be removed in 4.1. Use 148 * {@link org.apache.tapestry.services.ServiceConstants#PARAMETER} instead. 149 */ 150 151 public static final String PARAMETERS_QUERY_PARAMETER_NAME = ServiceConstants.PARAMETER; 152 153 /** 154 * Property name used to get the extension used for templates. This may be set in the page or 155 * component specification, or in the page (or component's) immediate container (library or 156 * application specification). Unlike most properties, value isn't inherited all the way up the 157 * chain. The default template extension is "html". 158 * 159 * @since 3.0 160 */ 161 162 public static final String TEMPLATE_EXTENSION_PROPERTY = "org.apache.tapestry.template-extension"; 163 164 /** 165 * The name of an {@link org.apache.tapestry.IRequestCycle} attribute in which the currently 166 * rendering {@link org.apache.tapestry.components.ILinkComponent} is stored. Link components do 167 * not nest. 168 */ 169 170 public static final String LINK_COMPONENT_ATTRIBUTE_NAME = "org.apache.tapestry.active-link-component"; 171 172 /** 173 * Suffix appended to a parameter name to form the name of a property that stores the binding 174 * for the parameter. 175 * 176 * @since 3.0 177 */ 178 179 public static final String PARAMETER_PROPERTY_NAME_SUFFIX = "Binding"; 180 181 /** 182 * Key used to obtain an extension from the application specification. The extension, if it 183 * exists, implements {@link org.apache.tapestry.request.IRequestDecoder}. 184 * 185 * @since 2.2 186 */ 187 188 public static final String REQUEST_DECODER_EXTENSION_NAME = "org.apache.tapestry.request-decoder"; 189 190 /** 191 * Name of optional application extension for the multipart decoder used by the application. The 192 * extension must implement {@link org.apache.tapestry.multipart.IMultipartDecoder} (and is 193 * generally a configured instance of 194 * {@link org.apache.tapestry.multipart.DefaultMultipartDecoder}). 195 * 196 * @since 3.0 197 */ 198 199 public static final String MULTIPART_DECODER_EXTENSION_NAME = "org.apache.tapestry.multipart-decoder"; 200 201 /** 202 * Method id used to check that {@link IPage#validate(IRequestCycle)} is invoked. 203 * 204 * @see #checkMethodInvocation(Object, String, Object) 205 * @since 3.0 206 */ 207 208 public static final String ABSTRACTPAGE_VALIDATE_METHOD_ID = "AbstractPage.validate()"; 209 210 /** 211 * Method id used to check that {@link IPage#detach()} is invoked. 212 * 213 * @see #checkMethodInvocation(Object, String, Object) 214 * @since 3.0 215 */ 216 217 public static final String ABSTRACTPAGE_DETACH_METHOD_ID = "AbstractPage.detach()"; 218 219 /** 220 * Regular expression defining a simple property name. Used by several different parsers. Simple 221 * property names match Java variable names; a leading letter (or underscore), followed by 222 * letters, numbers and underscores. 223 * 224 * @since 3.0 225 */ 226 227 public static final String SIMPLE_PROPERTY_NAME_PATTERN = "^_?[a-zA-Z]\\w*$"; 228 229 /** 230 * Name of an application extension used as a factory for 231 * {@link org.apache.tapestry.engine.IMonitor}instances. The extension must implement 232 * {@link org.apache.tapestry.engine.IMonitorFactory}. 233 * 234 * @since 3.0 235 */ 236 237 public static final String MONITOR_FACTORY_EXTENSION_NAME = "org.apache.tapestry.monitor-factory"; 238 239 /** 240 * Class name of an {@link ognl.TypeConverter}implementing class to use as a type converter for 241 * {@link org.apache.tapestry.binding.ExpressionBinding} 242 */ 243 public static final String OGNL_TYPE_CONVERTER = "org.apache.tapestry.ognl-type-converter"; 244 245 /** 246 * Prevent instantiation. 247 */ 248 249 private Tapestry() 250 { 251 } 252 253 /** 254 * The version of the framework; this is updated for major releases. 255 */ 256 257 public static final String VERSION = readVersion(); 258 259 /** 260 * Contains strings loaded from TapestryStrings.properties. 261 * 262 * @since 1.0.8 263 */ 264 265 private static ResourceBundle _strings; 266 267 /** 268 * A {@link Map}that links Locale names (as in {@link Locale#toString()}to {@link Locale} 269 * instances. This prevents needless duplication of Locales. 270 */ 271 272 private static final Map _localeMap = new HashMap(); 273 274 static 275 { 276 Locale[] locales = Locale.getAvailableLocales(); 277 for (int i = 0; i < locales.length; i++) 278 { 279 _localeMap.put(locales[i].toString(), locales[i]); 280 } 281 } 282 283 /** 284 * Used for tracking if a particular super-class method has been invoked. 285 */ 286 287 private static final ThreadLocal _invokedMethodIds = new ThreadLocal(); 288 289 /** 290 * Copys all informal {@link IBinding bindings}from a source component to the destination 291 * component. Informal bindings are bindings for informal parameters. This will overwrite 292 * parameters (formal or informal) in the destination component if there is a naming conflict. 293 */ 294 295 public static void copyInformalBindings(IComponent source, IComponent destination) 296 { 297 Collection names = source.getBindingNames(); 298 299 if (names == null) 300 return; 301 302 IComponentSpecification specification = source.getSpecification(); 303 Iterator i = names.iterator(); 304 305 while (i.hasNext()) 306 { 307 String name = (String) i.next(); 308 309 // If not a formal parameter, then copy it over. 310 311 if (specification.getParameter(name) == null) 312 { 313 IBinding binding = source.getBinding(name); 314 315 destination.setBinding(name, binding); 316 } 317 } 318 } 319 320 /** 321 * Gets the {@link Locale}for the given string, which is the result of 322 * {@link Locale#toString()}. If no such locale is already registered, a new instance is 323 * created, registered and returned. 324 */ 325 326 public static Locale getLocale(String s) 327 { 328 Locale result = null; 329 330 synchronized (_localeMap) 331 { 332 result = (Locale) _localeMap.get(s); 333 } 334 335 if (result == null) 336 { 337 StringSplitter splitter = new StringSplitter('_'); 338 String[] terms = splitter.splitToArray(s); 339 340 switch (terms.length) 341 { 342 case 1: 343 344 result = new Locale(terms[0], ""); 345 break; 346 347 case 2: 348 349 result = new Locale(terms[0], terms[1]); 350 break; 351 352 case 3: 353 354 result = new Locale(terms[0], terms[1], terms[2]); 355 break; 356 357 default: 358 359 throw new IllegalArgumentException("Unable to convert '" + s + "' to a Locale."); 360 } 361 362 synchronized (_localeMap) 363 { 364 _localeMap.put(s, result); 365 } 366 367 } 368 369 return result; 370 371 } 372 373 /** 374 * Closes the stream (if not null), ignoring any {@link IOException}thrown. 375 * 376 * @since 1.0.2 377 */ 378 379 public static void close(InputStream stream) 380 { 381 if (stream != null) 382 { 383 try 384 { 385 stream.close(); 386 } 387 catch (IOException ex) 388 { 389 // Ignore. 390 } 391 } 392 } 393 394 /** 395 * Gets a string from the TapestryStrings resource bundle. The string in the bundle is treated 396 * as a pattern for {@link MessageFormat#format(java.lang.String, java.lang.Object[])}. 397 * 398 * @since 1.0.8 399 */ 400 401 public static String format(String key, Object[] args) 402 { 403 if (_strings == null) 404 _strings = ResourceBundle.getBundle("org.apache.tapestry.TapestryStrings"); 405 406 String pattern = _strings.getString(key); 407 408 if (args == null) 409 return pattern; 410 411 return MessageFormat.format(pattern, args); 412 } 413 414 /** 415 * Convienience method for invoking {@link #format(String, Object[])}. 416 * 417 * @since 3.0 418 */ 419 420 public static String getMessage(String key) 421 { 422 return format(key, null); 423 } 424 425 /** 426 * Convienience method for invoking {@link #format(String, Object[])}. 427 * 428 * @since 3.0 429 */ 430 431 public static String format(String key, Object arg) 432 { 433 return format(key, new Object[] 434 { arg }); 435 } 436 437 /** 438 * Convienience method for invoking {@link #format(String, Object[])}. 439 * 440 * @since 3.0 441 */ 442 443 public static String format(String key, Object arg1, Object arg2) 444 { 445 return format(key, new Object[] 446 { arg1, arg2 }); 447 } 448 449 /** 450 * Convienience method for invoking {@link #format(String, Object[])}. 451 * 452 * @since 3.0 453 */ 454 455 public static String format(String key, Object arg1, Object arg2, Object arg3) 456 { 457 return format(key, new Object[] 458 { arg1, arg2, arg3 }); 459 } 460 461 private static final String UNKNOWN_VERSION = "Unknown"; 462 463 /** 464 * Invoked when the class is initialized to read the current version file. 465 */ 466 467 private static final String readVersion() 468 { 469 Properties props = new Properties(); 470 471 try 472 { 473 InputStream in = Tapestry.class.getResourceAsStream("version.properties"); 474 475 if (in == null) 476 return UNKNOWN_VERSION; 477 478 props.load(in); 479 480 in.close(); 481 482 return props.getProperty("project.version", UNKNOWN_VERSION); 483 } 484 catch (IOException ex) 485 { 486 return UNKNOWN_VERSION; 487 } 488 489 } 490 491 /** 492 * Returns the size of a collection, or zero if the collection is null. 493 * 494 * @since 2.2 495 */ 496 497 public static int size(Collection c) 498 { 499 if (c == null) 500 return 0; 501 502 return c.size(); 503 } 504 505 /** 506 * Returns the length of the array, or 0 if the array is null. 507 * 508 * @since 2.2 509 */ 510 511 public static int size(Object[] array) 512 { 513 if (array == null) 514 return 0; 515 516 return array.length; 517 } 518 519 /** 520 * Returns true if the Map is null or empty. 521 * 522 * @since 3.0 523 */ 524 525 public static boolean isEmpty(Map map) 526 { 527 return map == null || map.isEmpty(); 528 } 529 530 /** 531 * Returns true if the Collection is null or empty. 532 * 533 * @since 3.0 534 */ 535 536 public static boolean isEmpty(Collection c) 537 { 538 return c == null || c.isEmpty(); 539 } 540 541 /** 542 * Converts a {@link Map} to an even-sized array of key/value pairs. This may be useful when 543 * using a Map as service parameters (with {@link org.apache.tapestry.link.DirectLink}. 544 * Assuming the keys and values are simple objects (String, Boolean, Integer, etc.), then the 545 * representation as an array will encode more efficiently (via 546 * {@link org.apache.tapestry.util.io.DataSqueezerImpl} than serializing the Map and its 547 * contents. 548 * 549 * @return the array of keys and values, or null if the input Map is null or empty 550 * @since 2.2 551 */ 552 553 public static Object[] convertMapToArray(Map map) 554 { 555 if (isEmpty(map)) 556 return null; 557 558 Set entries = map.entrySet(); 559 560 Object[] result = new Object[2 * entries.size()]; 561 int x = 0; 562 563 Iterator i = entries.iterator(); 564 while (i.hasNext()) 565 { 566 Map.Entry entry = (Map.Entry) i.next(); 567 568 result[x++] = entry.getKey(); 569 result[x++] = entry.getValue(); 570 } 571 572 return result; 573 } 574 575 /** 576 * Converts an even-sized array of objects back into a {@link Map}. 577 * 578 * @see #convertMapToArray(Map) 579 * @return a Map, or null if the array is null or empty 580 * @since 2.2 581 */ 582 583 public static Map convertArrayToMap(Object[] array) 584 { 585 if (array == null || array.length == 0) 586 return null; 587 588 if (array.length % 2 != 0) 589 throw new IllegalArgumentException(getMessage("Tapestry.even-sized-array")); 590 591 Map result = new HashMap(); 592 593 int x = 0; 594 while (x < array.length) 595 { 596 Object key = array[x++]; 597 Object value = array[x++]; 598 599 result.put(key, value); 600 } 601 602 return result; 603 } 604 605 /** 606 * Given a Class, creates a presentable name for the class, even if the class is a scalar type 607 * or Array type. 608 * 609 * @since 3.0 610 * @deprecated To be removed in 4.1. 611 */ 612 613 public static String getClassName(Class subject) 614 { 615 return ClassFabUtils.getJavaClassName(subject); 616 } 617 618 /** 619 * Creates an exception indicating the binding value is null. 620 * 621 * @since 3.0 622 */ 623 624 public static BindingException createNullBindingException(IBinding binding) 625 { 626 return new BindingException(getMessage("null-value-for-binding"), binding); 627 } 628 629 /** @since 3.0 * */ 630 631 public static ApplicationRuntimeException createNoSuchComponentException(IComponent component, 632 String id, Location location) 633 { 634 return new ApplicationRuntimeException(format("no-such-component", component 635 .getExtendedId(), id), component, location, null); 636 } 637 638 /** @since 3.0 * */ 639 640 public static BindingException createRequiredParameterException(IComponent component, 641 String parameterName) 642 { 643 return new BindingException(format("required-parameter", parameterName, component 644 .getExtendedId()), component, null, component.getBinding(parameterName), null); 645 } 646 647 /** @since 3.0 * */ 648 649 public static ApplicationRuntimeException createRenderOnlyPropertyException( 650 IComponent component, String propertyName) 651 { 652 return new ApplicationRuntimeException(format( 653 "render-only-property", 654 propertyName, 655 component.getExtendedId()), component, null, null); 656 } 657 658 /** 659 * Clears the list of method invocations. 660 * 661 * @see #checkMethodInvocation(Object, String, Object) 662 * @since 3.0 663 */ 664 665 public static void clearMethodInvocations() 666 { 667 _invokedMethodIds.set(null); 668 } 669 670 /** 671 * Adds a method invocation to the list of invocations. This is done in a super-class 672 * implementations. 673 * 674 * @see #checkMethodInvocation(Object, String, Object) 675 * @since 3.0 676 */ 677 678 public static void addMethodInvocation(Object methodId) 679 { 680 List methodIds = (List) _invokedMethodIds.get(); 681 682 if (methodIds == null) 683 { 684 methodIds = new ArrayList(); 685 _invokedMethodIds.set(methodIds); 686 } 687 688 methodIds.add(methodId); 689 } 690 691 /** 692 * Checks to see if a particular method has been invoked. The method is identified by a methodId 693 * (usually a String). The methodName and object are used to create an error message. 694 * <p> 695 * The caller should invoke {@link #clearMethodInvocations()}, then invoke a method on the 696 * object. The super-class implementation should invoke {@link #addMethodInvocation(Object)} to 697 * indicate that it was, in fact, invoked. The caller then invokes this method to validate that 698 * the super-class implementation was invoked. 699 * <p> 700 * The list of method invocations is stored in a {@link ThreadLocal} variable. 701 * 702 * @since 3.0 703 */ 704 705 public static void checkMethodInvocation(Object methodId, String methodName, Object object) 706 { 707 List methodIds = (List) _invokedMethodIds.get(); 708 709 if (methodIds != null && methodIds.contains(methodId)) 710 return; 711 712 throw new ApplicationRuntimeException(Tapestry.format( 713 "Tapestry.missing-method-invocation", 714 object.getClass().getName(), 715 methodName)); 716 } 717 718 /** 719 * Method used by pages and components to send notifications about property changes. 720 * 721 * @param component 722 * the component containing the property 723 * @param propertyName 724 * the name of the property which changed 725 * @param newValue 726 * the new value for the property 727 * @since 3.0 728 */ 729 public static void fireObservedChange(IComponent component, String propertyName, Object newValue) 730 { 731 ChangeObserver observer = component.getPage().getChangeObserver(); 732 733 if (observer == null) 734 return; 735 736 ObservedChangeEvent event = new ObservedChangeEvent(component, propertyName, newValue); 737 738 observer.observeChange(event); 739 } 740 741 /** 742 * Returns true if the input is null or contains only whitespace. 743 * <p> 744 * Note: Yes, you'd think we'd use <code>StringUtils</code>, but with the change in names and 745 * behavior between releases, it is smarter to just implement our own little method! 746 * 747 * @since 3.0 748 * @deprecated To be removed in Tapestry 4.1. Use {@link HiveMind#isBlank(java.lang.String)} 749 * instead. 750 */ 751 752 public static boolean isBlank(String input) 753 { 754 return HiveMind.isBlank(input); 755 } 756 757 /** 758 * Returns true if the input is not null and not empty (or only whitespace). 759 * 760 * @since 3.0 761 * @deprecated To be removed in Tapestry 4.1. Use {@link HiveMind#isNonBlank(java.lang.String)} 762 * instead. 763 */ 764 765 public static boolean isNonBlank(String input) 766 { 767 return HiveMind.isNonBlank(input); 768 } 769 }