001 // Copyright 2006-2012 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.tapestry5.internal.structure;
016
017 import org.apache.tapestry5.*;
018 import org.apache.tapestry5.func.Worker;
019 import org.apache.tapestry5.internal.InternalComponentResources;
020 import org.apache.tapestry5.internal.bindings.InternalPropBinding;
021 import org.apache.tapestry5.internal.services.Instantiator;
022 import org.apache.tapestry5.internal.transform.ParameterConduit;
023 import org.apache.tapestry5.internal.util.NamedSet;
024 import org.apache.tapestry5.ioc.AnnotationProvider;
025 import org.apache.tapestry5.ioc.Location;
026 import org.apache.tapestry5.ioc.Messages;
027 import org.apache.tapestry5.ioc.Resource;
028 import org.apache.tapestry5.ioc.internal.NullAnnotationProvider;
029 import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
030 import org.apache.tapestry5.ioc.internal.util.InternalUtils;
031 import org.apache.tapestry5.ioc.internal.util.LockSupport;
032 import org.apache.tapestry5.ioc.internal.util.TapestryException;
033 import org.apache.tapestry5.ioc.services.PerThreadValue;
034 import org.apache.tapestry5.model.ComponentModel;
035 import org.apache.tapestry5.runtime.Component;
036 import org.apache.tapestry5.runtime.PageLifecycleCallbackHub;
037 import org.apache.tapestry5.runtime.PageLifecycleListener;
038 import org.apache.tapestry5.runtime.RenderQueue;
039 import org.apache.tapestry5.services.pageload.ComponentResourceSelector;
040 import org.slf4j.Logger;
041
042 import java.lang.annotation.Annotation;
043 import java.util.List;
044 import java.util.Locale;
045 import java.util.Map;
046
047 /**
048 * The bridge between a component and its {@link ComponentPageElement}, that supplies all kinds of
049 * resources to the
050 * component, including access to its parameters, parameter bindings, and persistent field data.
051 */
052 @SuppressWarnings("all")
053 public class InternalComponentResourcesImpl extends LockSupport implements InternalComponentResources
054 {
055 private final Page page;
056
057 private final String completeId;
058
059 private final String nestedId;
060
061 private final ComponentModel componentModel;
062
063 private final ComponentPageElement element;
064
065 private final Component component;
066
067 private final ComponentResources containerResources;
068
069 private final ComponentPageElementResources elementResources;
070
071 private final boolean mixin;
072
073 private static final Object[] EMPTY = new Object[0];
074
075 private static final AnnotationProvider NULL_ANNOTATION_PROVIDER = new NullAnnotationProvider();
076
077 // Map from parameter name to binding. This is mutable but not guarded by the lazy creation lock, as it is only
078 // written to during page load, not at runtime.
079 private NamedSet<Binding> bindings;
080
081 // Maps from parameter name to ParameterConduit, used to support mixins
082 // which need access to the containing component's PC's
083 // Guarded by: LockSupport
084 private NamedSet<ParameterConduit> conduits;
085
086 // Guarded by: LockSupport
087 private Messages messages;
088
089 // Guarded by: LockSupport
090 private boolean informalsComputed;
091
092 // Guarded by: LockSupport
093 private PerThreadValue<Map<String, Object>> renderVariables;
094
095 // Guarded by: LockSupport
096 private Informal firstInformal;
097
098
099 /**
100 * We keep a linked list of informal parameters, which saves us the expense of determining which
101 * bindings are formal
102 * and which are informal. Each Informal points to the next.
103 */
104 private class Informal
105 {
106 private final String name;
107
108 private final Binding binding;
109
110 final Informal next;
111
112 private Informal(String name, Binding binding, Informal next)
113 {
114 this.name = name;
115 this.binding = binding;
116 this.next = next;
117 }
118
119 void write(MarkupWriter writer)
120 {
121 Object value = binding.get();
122
123 if (value == null)
124 return;
125
126 if (value instanceof Block)
127 return;
128
129 // If it's already a String, don't use the TypeCoercer (renderInformalParameters is
130 // a CPU hotspot, as is TypeCoercer.coerce).
131
132 String valueString = value instanceof String ? (String) value : elementResources
133 .coerce(value, String.class);
134
135 writer.attributes(name, valueString);
136 }
137 }
138
139
140 private static Worker<ParameterConduit> RESET_PARAMETER_CONDUIT = new Worker<ParameterConduit>()
141 {
142 public void work(ParameterConduit value)
143 {
144 value.reset();
145 }
146 };
147
148 public InternalComponentResourcesImpl(Page page, ComponentPageElement element,
149 ComponentResources containerResources, ComponentPageElementResources elementResources, String completeId,
150 String nestedId, Instantiator componentInstantiator, boolean mixin)
151 {
152 this.page = page;
153 this.element = element;
154 this.containerResources = containerResources;
155 this.elementResources = elementResources;
156 this.completeId = completeId;
157 this.nestedId = nestedId;
158 this.mixin = mixin;
159
160 componentModel = componentInstantiator.getModel();
161 component = componentInstantiator.newInstance(this);
162 }
163
164 public boolean isMixin()
165 {
166 return mixin;
167 }
168
169 public Location getLocation()
170 {
171 return element.getLocation();
172 }
173
174 public String toString()
175 {
176 return String.format("InternalComponentResources[%s]", getCompleteId());
177 }
178
179 public ComponentModel getComponentModel()
180 {
181 return componentModel;
182 }
183
184 public Component getEmbeddedComponent(String embeddedId)
185 {
186 return element.getEmbeddedElement(embeddedId).getComponent();
187 }
188
189 public Object getFieldChange(String fieldName)
190 {
191 return page.getFieldChange(nestedId, fieldName);
192 }
193
194 public String getId()
195 {
196 return element.getId();
197 }
198
199 public boolean hasFieldChange(String fieldName)
200 {
201 return getFieldChange(fieldName) != null;
202 }
203
204 public Link createEventLink(String eventType, Object... context)
205 {
206 return element.createEventLink(eventType, context);
207 }
208
209 public Link createActionLink(String eventType, boolean forForm, Object... context)
210 {
211 return element.createActionLink(eventType, forForm, context);
212 }
213
214 public Link createFormEventLink(String eventType, Object... context)
215 {
216 return element.createFormEventLink(eventType, context);
217 }
218
219 public Link createPageLink(String pageName, boolean override, Object... context)
220 {
221 return element.createPageLink(pageName, override, context);
222 }
223
224 public Link createPageLink(Class pageClass, boolean override, Object... context)
225 {
226 return element.createPageLink(pageClass, override, context);
227 }
228
229 public void discardPersistentFieldChanges()
230 {
231 page.discardPersistentFieldChanges();
232 }
233
234 public String getElementName()
235 {
236 return getElementName(null);
237 }
238
239 public List<String> getInformalParameterNames()
240 {
241 return InternalUtils.sortedKeys(getInformalParameterBindings());
242 }
243
244 public <T> T getInformalParameter(String name, Class<T> type)
245 {
246 Binding binding = getBinding(name);
247
248 Object value = binding == null ? null : binding.get();
249
250 return elementResources.coerce(value, type);
251 }
252
253 public Block getBody()
254 {
255 return element.getBody();
256 }
257
258 public boolean hasBody()
259 {
260 return element.hasBody();
261 }
262
263 public String getCompleteId()
264 {
265 return completeId;
266 }
267
268 public Component getComponent()
269 {
270 return component;
271 }
272
273 public boolean isBound(String parameterName)
274 {
275 return getBinding(parameterName) != null;
276 }
277
278 public <T extends Annotation> T getParameterAnnotation(String parameterName, Class<T> annotationType)
279 {
280 Binding binding = getBinding(parameterName);
281
282 return binding == null ? null : binding.getAnnotation(annotationType);
283 }
284
285 public boolean isRendering()
286 {
287 return element.isRendering();
288 }
289
290 public boolean triggerEvent(String eventType, Object[] context, ComponentEventCallback handler)
291 {
292 return element.triggerEvent(eventType, defaulted(context), handler);
293 }
294
295 private static Object[] defaulted(Object[] input)
296 {
297 return input == null ? EMPTY : input;
298 }
299
300 public boolean triggerContextEvent(String eventType, EventContext context, ComponentEventCallback callback)
301 {
302 return element.triggerContextEvent(eventType, context, callback);
303 }
304
305 public String getNestedId()
306 {
307 return nestedId;
308 }
309
310 public Component getPage()
311 {
312 return element.getContainingPage().getRootComponent();
313 }
314
315 public boolean isLoaded()
316 {
317 return element.isLoaded();
318 }
319
320 public void persistFieldChange(String fieldName, Object newValue)
321 {
322 try
323 {
324 page.persistFieldChange(this, fieldName, newValue);
325 } catch (Exception ex)
326 {
327 throw new TapestryException(StructureMessages.fieldPersistFailure(getCompleteId(), fieldName, ex),
328 getLocation(), ex);
329 }
330 }
331
332 public void bindParameter(String parameterName, Binding binding)
333 {
334 if (bindings == null)
335 bindings = NamedSet.create();
336
337 bindings.put(parameterName, binding);
338 }
339
340 public Class getBoundType(String parameterName)
341 {
342 Binding binding = getBinding(parameterName);
343
344 return binding == null ? null : binding.getBindingType();
345 }
346
347 public Binding getBinding(String parameterName)
348 {
349 return NamedSet.get(bindings, parameterName);
350 }
351
352 public AnnotationProvider getAnnotationProvider(String parameterName)
353 {
354 Binding binding = getBinding(parameterName);
355
356 return binding == null ? NULL_ANNOTATION_PROVIDER : binding;
357 }
358
359 public Logger getLogger()
360 {
361 return componentModel.getLogger();
362 }
363
364 public Component getMixinByClassName(String mixinClassName)
365 {
366 return element.getMixinByClassName(mixinClassName);
367 }
368
369 public void renderInformalParameters(MarkupWriter writer)
370 {
371 if (bindings == null)
372 return;
373
374 for (Informal i = firstInformal(); i != null; i = i.next)
375 i.write(writer);
376 }
377
378 private Informal firstInformal()
379 {
380 try
381 {
382 acquireReadLock();
383
384 if (!informalsComputed)
385 {
386 computeInformals();
387 }
388
389 return firstInformal;
390 } finally
391 {
392 releaseReadLock();
393 }
394 }
395
396 private void computeInformals()
397 {
398 try
399 {
400 upgradeReadLockToWriteLock();
401
402 if (!informalsComputed)
403 {
404 for (Map.Entry<String, Binding> e : getInformalParameterBindings().entrySet())
405 {
406 firstInformal = new Informal(e.getKey(), e.getValue(), firstInformal);
407 }
408
409 informalsComputed = true;
410 }
411 } finally
412 {
413 downgradeWriteLockToReadLock();
414 }
415 }
416
417 public Component getContainer()
418 {
419 if (containerResources == null)
420 {
421 return null;
422 }
423
424 return containerResources.getComponent();
425 }
426
427 public ComponentResources getContainerResources()
428 {
429 return containerResources;
430 }
431
432 public Messages getContainerMessages()
433 {
434 return containerResources != null ? containerResources.getMessages() : null;
435 }
436
437 public Locale getLocale()
438 {
439 return element.getLocale();
440 }
441
442 public ComponentResourceSelector getResourceSelector()
443 {
444 return element.getResourceSelector();
445 }
446
447 public Messages getMessages()
448 {
449 if (messages == null)
450 {
451 // This kind of lazy loading pattern is acceptable without locking.
452 // Changes to the messages field are atomic; in some race conditions, the call to
453 // getMessages() may occur more than once (but it caches the value anyway).
454 messages = elementResources.getMessages(componentModel);
455 }
456
457 return messages;
458 }
459
460 public String getElementName(String defaultElementName)
461 {
462 return element.getElementName(defaultElementName);
463 }
464
465 public Block getBlock(String blockId)
466 {
467 return element.getBlock(blockId);
468 }
469
470 public Block getBlockParameter(String parameterName)
471 {
472 return getInformalParameter(parameterName, Block.class);
473 }
474
475 public Block findBlock(String blockId)
476 {
477 return element.findBlock(blockId);
478 }
479
480 public Resource getBaseResource()
481 {
482 return componentModel.getBaseResource();
483 }
484
485 public String getPageName()
486 {
487 return element.getPageName();
488 }
489
490 public Map<String, Binding> getInformalParameterBindings()
491 {
492 Map<String, Binding> result = CollectionFactory.newMap();
493
494 for (String name : NamedSet.getNames(bindings))
495 {
496 if (componentModel.getParameterModel(name) != null)
497 continue;
498
499 result.put(name, bindings.get(name));
500 }
501
502 return result;
503 }
504
505 private Map<String, Object> getRenderVariables(boolean create)
506 {
507 try
508 {
509 acquireReadLock();
510
511 if (renderVariables == null)
512 {
513 if (!create)
514 {
515 return null;
516 }
517
518 createRenderVariablesPerThreadValue();
519 }
520
521 Map<String, Object> result = renderVariables.get();
522
523 if (result == null && create)
524 result = renderVariables.set(CollectionFactory.newCaseInsensitiveMap());
525
526 return result;
527 } finally
528 {
529 releaseReadLock();
530 }
531 }
532
533 private void createRenderVariablesPerThreadValue()
534 {
535 try
536 {
537 upgradeReadLockToWriteLock();
538
539 if (renderVariables == null)
540 {
541 renderVariables = elementResources.createPerThreadValue();
542 }
543
544 } finally
545 {
546 downgradeWriteLockToReadLock();
547 }
548 }
549
550 public Object getRenderVariable(String name)
551 {
552 Map<String, Object> variablesMap = getRenderVariables(false);
553
554 Object result = InternalUtils.get(variablesMap, name);
555
556 if (result == null)
557 {
558 throw new IllegalArgumentException(StructureMessages.missingRenderVariable(getCompleteId(), name,
559 variablesMap == null ? null : variablesMap.keySet()));
560 }
561
562 return result;
563 }
564
565 public void storeRenderVariable(String name, Object value)
566 {
567 assert InternalUtils.isNonBlank(name);
568 assert value != null;
569
570 Map<String, Object> renderVariables = getRenderVariables(true);
571
572 renderVariables.put(name, value);
573 }
574
575 public void postRenderCleanup()
576 {
577 Map<String, Object> variablesMap = getRenderVariables(false);
578
579 if (variablesMap != null)
580 variablesMap.clear();
581
582 resetParameterConduits();
583 }
584
585 public void addPageLifecycleListener(PageLifecycleListener listener)
586 {
587 page.addLifecycleListener(listener);
588 }
589
590 public void removePageLifecycleListener(PageLifecycleListener listener)
591 {
592 page.removeLifecycleListener(listener);
593 }
594
595 public void addPageResetListener(PageResetListener listener)
596 {
597 page.addResetListener(listener);
598 }
599
600 private void resetParameterConduits()
601 {
602 try
603 {
604 acquireReadLock();
605
606 if (conduits != null)
607 {
608 conduits.eachValue(RESET_PARAMETER_CONDUIT);
609 }
610 } finally
611 {
612 releaseReadLock();
613 }
614 }
615
616 public ParameterConduit getParameterConduit(String parameterName)
617 {
618 try
619 {
620 acquireReadLock();
621 return NamedSet.get(conduits, parameterName);
622 } finally
623 {
624 releaseReadLock();
625 }
626 }
627
628 public void setParameterConduit(String parameterName, ParameterConduit conduit)
629 {
630 try
631 {
632 acquireReadLock();
633
634 if (conduits == null)
635 {
636 createConduits();
637 }
638
639 conduits.put(parameterName, conduit);
640 } finally
641 {
642 releaseReadLock();
643 }
644 }
645
646 private void createConduits()
647 {
648 try
649 {
650 upgradeReadLockToWriteLock();
651 if (conduits == null)
652 {
653 conduits = NamedSet.create();
654 }
655 } finally
656 {
657 downgradeWriteLockToReadLock();
658 }
659 }
660
661
662 public String getPropertyName(String parameterName)
663 {
664 Binding binding = getBinding(parameterName);
665
666 if (binding == null)
667 {
668 return null;
669 }
670
671 if (binding instanceof InternalPropBinding)
672 {
673 return ((InternalPropBinding) binding).getPropertyName();
674 }
675
676 return null;
677 }
678
679 /**
680 * @since 5.3
681 */
682 public void render(MarkupWriter writer, RenderQueue queue)
683 {
684 queue.push(element);
685 }
686
687 @Override
688 public PageLifecycleCallbackHub getPageLifecycleCallbackHub()
689 {
690 return page;
691 }
692 }