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