Coverage Report - org.apache.tapestry.Tapestry
 
Classes in this File Line Coverage Branch Coverage Complexity
Tapestry
0%
0/117
0%
0/54
2.667
 
 1  
 // Copyright 2004, 2005 The Apache Software Foundation
 2  
 //
 3  
 // Licensed under the Apache License, Version 2.0 (the "License");
 4  
 // you may not use this file except in compliance with the License.
 5  
 // You may obtain a copy of the License at
 6  
 //
 7  
 //     http://www.apache.org/licenses/LICENSE-2.0
 8  
 //
 9  
 // Unless required by applicable law or agreed to in writing, software
 10  
 // distributed under the License is distributed on an "AS IS" BASIS,
 11  
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12  
 // See the License for the specific language governing permissions and
 13  
 // limitations under the License.
 14  
 
 15  
 package org.apache.tapestry;
 16  
 
 17  
 import org.apache.hivemind.ApplicationRuntimeException;
 18  
 import org.apache.hivemind.Location;
 19  
 import org.apache.tapestry.event.ChangeObserver;
 20  
 import org.apache.tapestry.event.ObservedChangeEvent;
 21  
 import org.apache.tapestry.multipart.IMultipartDecoder;
 22  
 import org.apache.tapestry.spec.IComponentSpecification;
 23  
 import org.apache.tapestry.util.StringSplitter;
 24  
 
 25  
 import java.io.IOException;
 26  
 import java.io.InputStream;
 27  
 import java.text.MessageFormat;
 28  
 import java.util.*;
 29  
 
 30  
 /**
 31  
  * A placeholder for a number of (static) methods that don't belong elsewhere, as well as a global
 32  
  * location for static constants.
 33  
  *
 34  
  * @since 1.0.1
 35  
  * @author Howard Lewis Ship
 36  
  */
 37  
 
 38  
 public final class Tapestry
 39  
 {
 40  
     /**
 41  
      * The name ("direct") of a service that allows stateless behavior for an {@link
 42  
      * org.apache.tapestry.link.DirectLink} component.
 43  
      * <p>
 44  
      * This service rolls back the state of the page but doesn't rewind the the dynamic state of the
 45  
      * page the was the action service does, which is more efficient but less powerful.
 46  
      * <p>
 47  
      * An array of String parameters may be included with the service URL; these will be made
 48  
      * available to the {@link org.apache.tapestry.link.DirectLink} component's listener.
 49  
      */
 50  
 
 51  
     public static final String DIRECT_SERVICE = "direct";
 52  
 
 53  
     /**
 54  
      * Almost identical to the direct service, except specifically for handling
 55  
      * browser level events.
 56  
      *
 57  
      * @since 4.1
 58  
      */
 59  
 
 60  
     public static final String DIRECT_EVENT_SERVICE = "directevent";
 61  
 
 62  
     /**
 63  
      * The name ("external") of a service that a allows {@link IExternalPage} to be selected.
 64  
      * Associated with a {@link org.apache.tapestry.link.ExternalLink} component.
 65  
      * <p>
 66  
      * This service enables {@link IExternalPage}s to be accessed via a URL. External pages may be
 67  
      * booked marked using their URL for future reference.
 68  
      * <p>
 69  
      * An array of Object parameters may be included with the service URL; these will be passed to
 70  
      * the {@link IExternalPage#activateExternalPage(Object[], IRequestCycle)} method.
 71  
      */
 72  
 
 73  
     public static final String EXTERNAL_SERVICE = "external";
 74  
 
 75  
     /**
 76  
      * The name ("page") of a service that allows a new page to be selected. Associated with a
 77  
      * {@link org.apache.tapestry.link.PageLink} component.
 78  
      * <p>
 79  
      * The service requires a single parameter: the name of the target page.
 80  
      */
 81  
 
 82  
     public static final String PAGE_SERVICE = "page";
 83  
 
 84  
     /**
 85  
      * The name ("home") of a service that jumps to the home page. A stand-in for when no service is
 86  
      * provided, which is typically the entrypoint to the application.
 87  
      */
 88  
 
 89  
     public static final String HOME_SERVICE = "home";
 90  
 
 91  
     /**
 92  
      * The name ("restart") of a service that invalidates the session and restarts the application.
 93  
      * Typically used just to recover from an exception.
 94  
      */
 95  
 
 96  
     public static final String RESTART_SERVICE = "restart";
 97  
 
 98  
     /**
 99  
      * The name ("asset") of a service used to access internal assets.
 100  
      */
 101  
 
 102  
     public static final String ASSET_SERVICE = "asset";
 103  
 
 104  
     /**
 105  
      * The name ("reset") of a service used to clear cached template and specification data and
 106  
      * remove all pooled pages. This is only used when debugging as a quick way to clear the out
 107  
      * cached data, to allow updated versions of specifications and templates to be loaded (without
 108  
      * stopping and restarting the servlet container).
 109  
      * <p>
 110  
      * This service is only available if the Java system property
 111  
      * <code>org.apache.tapestry.enable-reset-service</code> is set to <code>true</code>.
 112  
      */
 113  
 
 114  
     public static final String RESET_SERVICE = "reset";
 115  
 
 116  
     /**
 117  
      * Property name used to get the extension used for templates. This may be set in the page or
 118  
      * component specification, or in the page (or component's) immediate container (library or
 119  
      * application specification). Unlike most properties, value isn't inherited all the way up the
 120  
      * chain. The default template extension is "html".
 121  
      *
 122  
      * @since 3.0
 123  
      */
 124  
 
 125  
     public static final String TEMPLATE_EXTENSION_PROPERTY = "org.apache.tapestry.template-extension";
 126  
 
 127  
     /**
 128  
      * The name of an {@link org.apache.tapestry.IRequestCycle} attribute in which the currently
 129  
      * rendering {@link org.apache.tapestry.components.ILinkComponent} is stored. Link components do
 130  
      * not nest.
 131  
      */
 132  
 
 133  
     public static final String LINK_COMPONENT_ATTRIBUTE_NAME = "org.apache.tapestry.active-link-component";
 134  
 
 135  
     /**
 136  
      * Suffix appended to a parameter name to form the name of a property that stores the binding
 137  
      * for the parameter.
 138  
      *
 139  
      * @since 3.0
 140  
      */
 141  
 
 142  
     public static final String PARAMETER_PROPERTY_NAME_SUFFIX = "Binding";
 143  
 
 144  
     /**
 145  
      * Key used to obtain an extension from the application specification. The extension, if it
 146  
      * exists, implements {@link org.apache.tapestry.request.IRequestDecoder}.
 147  
      *
 148  
      * @since 2.2
 149  
      */
 150  
 
 151  
     public static final String REQUEST_DECODER_EXTENSION_NAME = "org.apache.tapestry.request-decoder";
 152  
 
 153  
     /**
 154  
      * Name of optional application extension for the multipart decoder used by the application. The
 155  
      * extension must implement {@link org.apache.tapestry.multipart.IMultipartDecoder} (and is
 156  
      * generally a configured instance of
 157  
      * {@link IMultipartDecoder}).
 158  
      *
 159  
      * @since 3.0
 160  
      */
 161  
 
 162  
     public static final String MULTIPART_DECODER_EXTENSION_NAME = "org.apache.tapestry.multipart-decoder";
 163  
 
 164  
     /**
 165  
      * Method id used to check that {@link IPage#validate(IRequestCycle)} is invoked.
 166  
      *
 167  
      * @see #checkMethodInvocation(Object, String, Object)
 168  
      * @since 3.0
 169  
      */
 170  
 
 171  
     public static final String ABSTRACTPAGE_VALIDATE_METHOD_ID = "AbstractPage.validate()";
 172  
 
 173  
     /**
 174  
      * Method id used to check that {@link IPage#detach()} is invoked.
 175  
      *
 176  
      * @see #checkMethodInvocation(Object, String, Object)
 177  
      * @since 3.0
 178  
      */
 179  
 
 180  
     public static final String ABSTRACTPAGE_DETACH_METHOD_ID = "AbstractPage.detach()";
 181  
 
 182  
     /**
 183  
      * Regular expression defining a simple property name. Used by several different parsers. Simple
 184  
      * property names match Java variable names; a leading letter (or underscore), followed by
 185  
      * letters, numbers and underscores.
 186  
      *
 187  
      * @since 3.0
 188  
      */
 189  
 
 190  
     public static final String SIMPLE_PROPERTY_NAME_PATTERN = "^_?[a-zA-Z]\\w*$";
 191  
 
 192  
     /**
 193  
      * Class name of an {@link ognl.TypeConverter}implementing class to use as a type converter for
 194  
      * {@link org.apache.tapestry.binding.ExpressionBinding}.
 195  
      */
 196  
     public static final String OGNL_TYPE_CONVERTER = "org.apache.tapestry.ognl-type-converter";
 197  
 
 198  
     /**
 199  
      * The version of the framework; this is updated for major releases.
 200  
      */
 201  
 
 202  0
     public static final String VERSION = readVersion();
 203  
 
 204  
     private static final String UNKNOWN_VERSION = "Unknown";
 205  
 
 206  
     /**
 207  
      * Contains strings loaded from TapestryStrings.properties.
 208  
      *
 209  
      * @since 1.0.8
 210  
      */
 211  
 
 212  
     private static ResourceBundle _strings;
 213  
 
 214  
     /**
 215  
      * A {@link Map}that links Locale names (as in {@link Locale#toString()}to {@link Locale}
 216  
      * instances. This prevents needless duplication of Locales.
 217  
      */
 218  
 
 219  0
     private static final Map _localeMap = new HashMap();
 220  
 
 221  
     static
 222  
     {
 223  0
         Locale[] locales = Locale.getAvailableLocales();
 224  0
         for (int i = 0; i < locales.length; i++)
 225  
         {
 226  0
             _localeMap.put(locales[i].toString(), locales[i]);
 227  
         }
 228  
     }
 229  
 
 230  
     /**
 231  
      * Used for tracking if a particular super-class method has been invoked.
 232  
      */
 233  
 
 234  0
     private static final ThreadLocal _invokedMethodIds = new ThreadLocal();
 235  
 
 236  
 
 237  
     /**
 238  
      * Prevent instantiation.
 239  
      */
 240  
 
 241  
     private Tapestry()
 242  0
     {
 243  0
     }
 244  
 
 245  
     /**
 246  
      * Copys all informal {@link IBinding bindings}from a source component to the destination
 247  
      * component. Informal bindings are bindings for informal parameters. This will overwrite
 248  
      * parameters (formal or informal) in the destination component if there is a naming conflict.
 249  
      */
 250  
 
 251  
     public static void copyInformalBindings(IComponent source, IComponent destination)
 252  
     {
 253  0
         Collection names = source.getBindingNames();
 254  
 
 255  0
         if (names == null)
 256  0
             return;
 257  
 
 258  0
         IComponentSpecification specification = source.getSpecification();
 259  0
         Iterator i = names.iterator();
 260  
 
 261  0
         while (i.hasNext())
 262  
         {
 263  0
             String name = (String) i.next();
 264  
 
 265  
             // If not a formal parameter, then copy it over.
 266  
 
 267  0
             if (specification.getParameter(name) == null)
 268  
             {
 269  0
                 IBinding binding = source.getBinding(name);
 270  
 
 271  0
                 destination.setBinding(name, binding);
 272  
             }
 273  0
         }
 274  0
     }
 275  
 
 276  
     /**
 277  
      * Gets the {@link Locale}for the given string, which is the result of
 278  
      * {@link Locale#toString()}. If no such locale is already registered, a new instance is
 279  
      * created, registered and returned.
 280  
      */
 281  
 
 282  
     public static Locale getLocale(String s)
 283  
     {
 284  0
         Locale result = null;
 285  
 
 286  0
         synchronized (_localeMap)
 287  
         {
 288  0
             result = (Locale) _localeMap.get(s);
 289  0
         }
 290  
 
 291  0
         if (result == null)
 292  
         {
 293  0
             StringSplitter splitter = new StringSplitter('_');
 294  0
             String[] terms = splitter.splitToArray(s);
 295  
 
 296  0
             switch (terms.length)
 297  
             {
 298  
                 case 1:
 299  
 
 300  0
                     result = new Locale(terms[0], "");
 301  0
                     break;
 302  
 
 303  
                 case 2:
 304  
 
 305  0
                     result = new Locale(terms[0], terms[1]);
 306  0
                     break;
 307  
 
 308  
                 case 3:
 309  
 
 310  0
                     result = new Locale(terms[0], terms[1], terms[2]);
 311  0
                     break;
 312  
 
 313  
                 default:
 314  
 
 315  0
                     throw new IllegalArgumentException("Unable to convert '" + s + "' to a Locale.");
 316  
             }
 317  
 
 318  0
             synchronized (_localeMap)
 319  
             {
 320  0
                 _localeMap.put(s, result);
 321  0
             }
 322  
 
 323  
         }
 324  
 
 325  0
         return result;
 326  
 
 327  
     }
 328  
 
 329  
     /**
 330  
      * Closes the stream (if not null), ignoring any {@link IOException}thrown.
 331  
      *
 332  
      * @since 1.0.2
 333  
      */
 334  
 
 335  
     public static void close(InputStream stream)
 336  
     {
 337  0
         if (stream != null)
 338  
         {
 339  
             try
 340  
             {
 341  0
                 stream.close();
 342  
             }
 343  0
             catch (IOException ex)
 344  
             {
 345  
                 // Ignore.
 346  0
             }
 347  
         }
 348  0
     }
 349  
 
 350  
     /**
 351  
      * Gets a string from the TapestryStrings resource bundle. The string in the bundle is treated
 352  
      * as a pattern for {@link MessageFormat#format(java.lang.String, java.lang.Object[])}.
 353  
      *
 354  
      * @since 1.0.8
 355  
      */
 356  
 
 357  
     public static String format(String key, Object[] args)
 358  
     {
 359  0
         if (_strings == null)
 360  0
             _strings = ResourceBundle.getBundle("org.apache.tapestry.TapestryStrings");
 361  
 
 362  0
         String pattern = _strings.getString(key);
 363  
 
 364  0
         if (args == null)
 365  0
             return pattern;
 366  
 
 367  0
         return MessageFormat.format(pattern, args);
 368  
     }
 369  
 
 370  
     /**
 371  
      * Convienience method for invoking {@link #format(String, Object[])}.
 372  
      *
 373  
      * @since 3.0
 374  
      */
 375  
 
 376  
     public static String getMessage(String key)
 377  
     {
 378  0
         return format(key, null);
 379  
     }
 380  
 
 381  
     /**
 382  
      * Convienience method for invoking {@link #format(String, Object[])}.
 383  
      *
 384  
      * @since 3.0
 385  
      */
 386  
 
 387  
     public static String format(String key, Object arg)
 388  
     {
 389  0
         return format(key, new Object[]
 390  
           { arg });
 391  
     }
 392  
 
 393  
     /**
 394  
      * Convienience method for invoking {@link #format(String, Object[])}.
 395  
      *
 396  
      * @since 3.0
 397  
      */
 398  
 
 399  
     public static String format(String key, Object arg1, Object arg2)
 400  
     {
 401  0
         return format(key, new Object[]
 402  
           { arg1, arg2 });
 403  
     }
 404  
 
 405  
     /**
 406  
      * Convienience method for invoking {@link #format(String, Object[])}.
 407  
      *
 408  
      * @since 3.0
 409  
      */
 410  
 
 411  
     public static String format(String key, Object arg1, Object arg2, Object arg3)
 412  
     {
 413  0
         return format(key, new Object[]
 414  
           { arg1, arg2, arg3 });
 415  
     }
 416  
 
 417  
     /**
 418  
      * Invoked when the class is initialized to read the current version file.
 419  
      */
 420  
 
 421  
     private static String readVersion()
 422  
     {
 423  0
         Properties props = new Properties();
 424  
 
 425  
         try
 426  
         {
 427  0
             InputStream in = Tapestry.class.getResourceAsStream("version.properties");
 428  
 
 429  0
             if (in == null)
 430  0
                 return UNKNOWN_VERSION;
 431  
 
 432  0
             props.load(in);
 433  
 
 434  0
             in.close();
 435  
 
 436  0
             return props.getProperty("project.version", UNKNOWN_VERSION);
 437  
         }
 438  0
         catch (IOException ex)
 439  
         {
 440  0
             return UNKNOWN_VERSION;
 441  
         }
 442  
 
 443  
     }
 444  
 
 445  
     /**
 446  
      * Returns the size of a collection, or zero if the collection is null.
 447  
      *
 448  
      * @since 2.2
 449  
      */
 450  
 
 451  
     public static int size(Collection c)
 452  
     {
 453  0
         if (c == null)
 454  0
             return 0;
 455  
 
 456  0
         return c.size();
 457  
     }
 458  
 
 459  
     /**
 460  
      * Returns the length of the array, or 0 if the array is null.
 461  
      *
 462  
      * @since 2.2
 463  
      */
 464  
 
 465  
     public static int size(Object[] array)
 466  
     {
 467  0
         if (array == null)
 468  0
             return 0;
 469  
 
 470  0
         return array.length;
 471  
     }
 472  
 
 473  
     /**
 474  
      * Returns true if the Map is null or empty.
 475  
      *
 476  
      * @since 3.0
 477  
      */
 478  
 
 479  
     public static boolean isEmpty(Map map)
 480  
     {
 481  0
         return map == null || map.isEmpty();
 482  
     }
 483  
 
 484  
     /**
 485  
      * Returns true if the Collection is null or empty.
 486  
      *
 487  
      * @since 3.0
 488  
      */
 489  
 
 490  
     public static boolean isEmpty(Collection c)
 491  
     {
 492  0
         return c == null || c.isEmpty();
 493  
     }
 494  
 
 495  
     /**
 496  
      * Converts a {@link Map} to an even-sized array of key/value pairs. This may be useful when
 497  
      * using a Map as service parameters (with {@link org.apache.tapestry.link.DirectLink}.
 498  
      * Assuming the keys and values are simple objects (String, Boolean, Integer, etc.), then the
 499  
      * representation as an array will encode more efficiently (via
 500  
      * {@link org.apache.tapestry.util.io.DataSqueezerImpl} than serializing the Map and its
 501  
      * contents.
 502  
      *
 503  
      * @return the array of keys and values, or null if the input Map is null or empty
 504  
      * @since 2.2
 505  
      */
 506  
 
 507  
     public static Object[] convertMapToArray(Map map)
 508  
     {
 509  0
         if (isEmpty(map))
 510  0
             return null;
 511  
 
 512  0
         Set entries = map.entrySet();
 513  
 
 514  0
         Object[] result = new Object[2 * entries.size()];
 515  0
         int x = 0;
 516  
 
 517  0
         Iterator i = entries.iterator();
 518  0
         while (i.hasNext())
 519  
         {
 520  0
             Map.Entry entry = (Map.Entry) i.next();
 521  
 
 522  0
             result[x++] = entry.getKey();
 523  0
             result[x++] = entry.getValue();
 524  0
         }
 525  
 
 526  0
         return result;
 527  
     }
 528  
 
 529  
     /**
 530  
      * Converts an even-sized array of objects back into a {@link Map}.
 531  
      *
 532  
      * @see #convertMapToArray(Map)
 533  
      * @return a Map, or null if the array is null or empty
 534  
      * @since 2.2
 535  
      */
 536  
 
 537  
     public static Map convertArrayToMap(Object[] array)
 538  
     {
 539  0
         if (array == null || array.length == 0)
 540  0
             return null;
 541  
 
 542  0
         if (array.length % 2 != 0)
 543  0
             throw new IllegalArgumentException(getMessage("Tapestry.even-sized-array"));
 544  
 
 545  0
         Map result = new HashMap();
 546  
 
 547  0
         int x = 0;
 548  0
         while (x < array.length)
 549  
         {
 550  0
             Object key = array[x++];
 551  0
             Object value = array[x++];
 552  
 
 553  0
             result.put(key, value);
 554  0
         }
 555  
 
 556  0
         return result;
 557  
     }
 558  
 
 559  
     /**
 560  
      * Creates an exception indicating the binding value is null.
 561  
      *
 562  
      * @since 3.0
 563  
      */
 564  
 
 565  
     public static BindingException createNullBindingException(IBinding binding)
 566  
     {
 567  0
         return new BindingException(getMessage("null-value-for-binding"), binding);
 568  
     }
 569  
 
 570  
     /** @since 3.0 * */
 571  
 
 572  
     public static ApplicationRuntimeException createNoSuchComponentException(IComponent component,
 573  
                                                                              String id, Location location)
 574  
     {
 575  0
         return new ApplicationRuntimeException(format("no-such-component", component.getExtendedId(), id),
 576  
                                                component, location, null);
 577  
     }
 578  
 
 579  
     /** @since 3.0 * */
 580  
 
 581  
     public static BindingException createRequiredParameterException(IComponent component,
 582  
                                                                     String parameterName)
 583  
     {
 584  0
         return new BindingException(format("required-parameter", parameterName, component.getExtendedId()),
 585  
                                     component, null, component.getBinding(parameterName), null);
 586  
     }
 587  
 
 588  
     /** @since 3.0 * */
 589  
 
 590  
     public static ApplicationRuntimeException createRenderOnlyPropertyException(
 591  
       IComponent component, String propertyName)
 592  
     {
 593  0
         return new ApplicationRuntimeException(format("render-only-property",
 594  
                                                       propertyName,
 595  
                                                       component.getExtendedId()), component, null, null);
 596  
     }
 597  
 
 598  
     /**
 599  
      * Clears the list of method invocations.
 600  
      *
 601  
      * @see #checkMethodInvocation(Object, String, Object)
 602  
      * @since 3.0
 603  
      */
 604  
 
 605  
     public static void clearMethodInvocations()
 606  
     {
 607  0
         _invokedMethodIds.set(null);
 608  0
     }
 609  
 
 610  
     /**
 611  
      * Adds a method invocation to the list of invocations. This is done in a super-class
 612  
      * implementations.
 613  
      *
 614  
      * @see #checkMethodInvocation(Object, String, Object)
 615  
      * @since 3.0
 616  
      */
 617  
 
 618  
     public static void addMethodInvocation(Object methodId)
 619  
     {
 620  0
         List methodIds = (List) _invokedMethodIds.get();
 621  
 
 622  0
         if (methodIds == null)
 623  
         {
 624  0
             methodIds = new ArrayList();
 625  0
             _invokedMethodIds.set(methodIds);
 626  
         }
 627  
 
 628  0
         methodIds.add(methodId);
 629  0
     }
 630  
 
 631  
     /**
 632  
      * Checks to see if a particular method has been invoked. The method is identified by a methodId
 633  
      * (usually a String). The methodName and object are used to create an error message.
 634  
      * <p>
 635  
      * The caller should invoke {@link #clearMethodInvocations()}, then invoke a method on the
 636  
      * object. The super-class implementation should invoke {@link #addMethodInvocation(Object)} to
 637  
      * indicate that it was, in fact, invoked. The caller then invokes this method to validate that
 638  
      * the super-class implementation was invoked.
 639  
      * <p>
 640  
      * The list of method invocations is stored in a {@link ThreadLocal} variable.
 641  
      *
 642  
      * @since 3.0
 643  
      */
 644  
 
 645  
     public static void checkMethodInvocation(Object methodId, String methodName, Object object)
 646  
     {
 647  0
         List methodIds = (List) _invokedMethodIds.get();
 648  
 
 649  0
         if (methodIds != null && methodIds.contains(methodId))
 650  0
             return;
 651  
 
 652  0
         throw new ApplicationRuntimeException(Tapestry.format("Tapestry.missing-method-invocation",
 653  
                                                               object.getClass().getName(),
 654  
                                                               methodName));
 655  
     }
 656  
 
 657  
     /**
 658  
      * Method used by pages and components to send notifications about property changes.
 659  
      *
 660  
      * @param component
 661  
      *            the component containing the property
 662  
      * @param propertyName
 663  
      *            the name of the property which changed
 664  
      * @param newValue
 665  
      *            the new value for the property
 666  
      * @since 3.0
 667  
      */
 668  
     public static void fireObservedChange(IComponent component, String propertyName, Object newValue)
 669  
     {
 670  0
         ChangeObserver observer = component.getPage().getChangeObserver();
 671  
 
 672  0
         if (observer == null)
 673  0
             return;
 674  
 
 675  0
         ObservedChangeEvent event = new ObservedChangeEvent(component, propertyName, newValue);
 676  
 
 677  0
         observer.observeChange(event);
 678  0
     }
 679  
 }