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 }