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