001// Licensed under the Apache License, Version 2.0 (the "License");
002// you may not use this file except in compliance with the License.
003// You may obtain a copy of the License at
004//
005// http://www.apache.org/licenses/LICENSE-2.0
006//
007// Unless required by applicable law or agreed to in writing, software
008// distributed under the License is distributed on an "AS IS" BASIS,
009// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
010// See the License for the specific language governing permissions and
011// limitations under the License.
012
013package org.apache.tapestry5.internal.structure;
014
015import org.apache.tapestry5.*;
016import org.apache.tapestry5.func.Worker;
017import org.apache.tapestry5.internal.InternalComponentResources;
018import org.apache.tapestry5.internal.bindings.InternalPropBinding;
019import org.apache.tapestry5.internal.bindings.PropBinding;
020import org.apache.tapestry5.internal.services.Instantiator;
021import org.apache.tapestry5.internal.transform.ParameterConduit;
022import org.apache.tapestry5.internal.util.NamedSet;
023import org.apache.tapestry5.ioc.AnnotationProvider;
024import org.apache.tapestry5.ioc.Location;
025import org.apache.tapestry5.ioc.Messages;
026import org.apache.tapestry5.ioc.Resource;
027import org.apache.tapestry5.ioc.internal.NullAnnotationProvider;
028import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
029import org.apache.tapestry5.ioc.internal.util.InternalUtils;
030import org.apache.tapestry5.ioc.internal.util.LockSupport;
031import org.apache.tapestry5.ioc.internal.util.TapestryException;
032import org.apache.tapestry5.ioc.services.PerThreadValue;
033import org.apache.tapestry5.model.ComponentModel;
034import org.apache.tapestry5.runtime.Component;
035import org.apache.tapestry5.runtime.PageLifecycleCallbackHub;
036import org.apache.tapestry5.runtime.PageLifecycleListener;
037import org.apache.tapestry5.runtime.RenderQueue;
038import org.apache.tapestry5.services.pageload.ComponentResourceSelector;
039import org.slf4j.Logger;
040
041import java.lang.annotation.Annotation;
042import java.lang.reflect.Type;
043import java.util.List;
044import java.util.Locale;
045import 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")
053public 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
210    public Link createFormEventLink(String eventType, Object... context)
211    {
212        return element.createFormEventLink(eventType, context);
213    }
214
215    public void discardPersistentFieldChanges()
216    {
217        page.discardPersistentFieldChanges();
218    }
219
220    public String getElementName()
221    {
222        return getElementName(null);
223    }
224
225    public List<String> getInformalParameterNames()
226    {
227        return InternalUtils.sortedKeys(getInformalParameterBindings());
228    }
229
230    public <T> T getInformalParameter(String name, Class<T> type)
231    {
232        Binding binding = getBinding(name);
233
234        Object value = binding == null ? null : binding.get();
235
236        return elementResources.coerce(value, type);
237    }
238
239    public Block getBody()
240    {
241        return element.getBody();
242    }
243
244    public boolean hasBody()
245    {
246        return element.hasBody();
247    }
248
249    public String getCompleteId()
250    {
251        return completeId;
252    }
253
254    public Component getComponent()
255    {
256        return component;
257    }
258
259    public boolean isBound(String parameterName)
260    {
261        return getBinding(parameterName) != null;
262    }
263
264    public <T extends Annotation> T getParameterAnnotation(String parameterName, Class<T> annotationType)
265    {
266        Binding binding = getBinding(parameterName);
267
268        return binding == null ? null : binding.getAnnotation(annotationType);
269    }
270
271    public boolean isRendering()
272    {
273        return element.isRendering();
274    }
275
276    public boolean triggerEvent(String eventType, Object[] context, ComponentEventCallback handler)
277    {
278        return element.triggerEvent(eventType, defaulted(context), handler);
279    }
280
281    private static Object[] defaulted(Object[] input)
282    {
283        return input == null ? EMPTY : input;
284    }
285
286    public boolean triggerContextEvent(String eventType, EventContext context, ComponentEventCallback callback)
287    {
288        return element.triggerContextEvent(eventType, context, callback);
289    }
290
291    public String getNestedId()
292    {
293        return nestedId;
294    }
295
296    public Component getPage()
297    {
298        return element.getContainingPage().getRootComponent();
299    }
300
301    public boolean isLoaded()
302    {
303        return element.isLoaded();
304    }
305
306    public void persistFieldChange(String fieldName, Object newValue)
307    {
308        try
309        {
310            page.persistFieldChange(this, fieldName, newValue);
311        } catch (Exception ex)
312        {
313            throw new TapestryException(StructureMessages.fieldPersistFailure(getCompleteId(), fieldName, ex),
314                    getLocation(), ex);
315        }
316    }
317
318    public void bindParameter(String parameterName, Binding binding)
319    {
320        if (bindings == null)
321            bindings = NamedSet.create();
322
323        bindings.put(parameterName, binding);
324    }
325
326    public Class getBoundType(String parameterName)
327    {
328        Binding binding = getBinding(parameterName);
329
330        return binding == null ? null : binding.getBindingType();
331    }
332    
333    public Type getBoundGenericType(String parameterName)
334    {
335        Binding binding = getBinding(parameterName);
336        Type genericType;
337        if (binding instanceof Binding2) {
338            genericType = ((Binding2) binding).getBindingGenericType();
339        } else {
340            genericType = binding.getBindingType();
341        }
342        return genericType;
343    }
344    
345
346    public Binding getBinding(String parameterName)
347    {
348        return NamedSet.get(bindings, parameterName);
349    }
350
351    public AnnotationProvider getAnnotationProvider(String parameterName)
352    {
353        Binding binding = getBinding(parameterName);
354
355        return binding == null ? NULL_ANNOTATION_PROVIDER : binding;
356    }
357
358    public Logger getLogger()
359    {
360        return componentModel.getLogger();
361    }
362
363    public Component getMixinByClassName(String mixinClassName)
364    {
365        return element.getMixinByClassName(mixinClassName);
366    }
367
368    public void renderInformalParameters(MarkupWriter writer)
369    {
370        if (bindings == null)
371            return;
372
373        for (Informal i = firstInformal(); i != null; i = i.next)
374            i.write(writer);
375    }
376
377    private Informal firstInformal()
378    {
379        try
380        {
381            acquireReadLock();
382
383            if (!informalsComputed)
384            {
385                computeInformals();
386            }
387
388            return firstInformal;
389        } finally
390        {
391            releaseReadLock();
392        }
393    }
394
395    private void computeInformals()
396    {
397        try
398        {
399            upgradeReadLockToWriteLock();
400
401            if (!informalsComputed)
402            {
403                for (Map.Entry<String, Binding> e : getInformalParameterBindings().entrySet())
404                {
405                    firstInformal = new Informal(e.getKey(), e.getValue(), firstInformal);
406                }
407
408                informalsComputed = true;
409            }
410        } finally
411        {
412            downgradeWriteLockToReadLock();
413        }
414    }
415
416    public Component getContainer()
417    {
418        if (containerResources == null)
419        {
420            return null;
421        }
422
423        return containerResources.getComponent();
424    }
425
426    public ComponentResources getContainerResources()
427    {
428        return containerResources;
429    }
430
431    public Messages getContainerMessages()
432    {
433        return containerResources != null ? containerResources.getMessages() : null;
434    }
435
436    public Locale getLocale()
437    {
438        return element.getLocale();
439    }
440
441    public ComponentResourceSelector getResourceSelector()
442    {
443        return element.getResourceSelector();
444    }
445
446    public Messages getMessages()
447    {
448        if (messages == null)
449        {
450            // This kind of lazy loading pattern is acceptable without locking.
451            // Changes to the messages field are atomic; in some race conditions, the call to
452            // getMessages() may occur more than once (but it caches the value anyway).
453            messages = elementResources.getMessages(componentModel);
454        }
455
456        return messages;
457    }
458
459    public String getElementName(String defaultElementName)
460    {
461        return element.getElementName(defaultElementName);
462    }
463
464    public Block getBlock(String blockId)
465    {
466        return element.getBlock(blockId);
467    }
468
469    public Block getBlockParameter(String parameterName)
470    {
471        return getInformalParameter(parameterName, Block.class);
472    }
473
474    public Block findBlock(String blockId)
475    {
476        return element.findBlock(blockId);
477    }
478
479    public Resource getBaseResource()
480    {
481        return componentModel.getBaseResource();
482    }
483
484    public String getPageName()
485    {
486        return element.getPageName();
487    }
488
489    public Map<String, Binding> getInformalParameterBindings()
490    {
491        Map<String, Binding> result = CollectionFactory.newMap();
492
493        for (String name : NamedSet.getNames(bindings))
494        {
495            if (componentModel.getParameterModel(name) != null)
496                continue;
497
498            result.put(name, bindings.get(name));
499        }
500
501        return result;
502    }
503
504    private Map<String, Object> getRenderVariables(boolean create)
505    {
506        try
507        {
508            acquireReadLock();
509
510            if (renderVariables == null)
511            {
512                if (!create)
513                {
514                    return null;
515                }
516
517                createRenderVariablesPerThreadValue();
518            }
519
520            Map<String, Object> result = renderVariables.get();
521
522            if (result == null && create)
523                result = renderVariables.set(CollectionFactory.newCaseInsensitiveMap());
524
525            return result;
526        } finally
527        {
528            releaseReadLock();
529        }
530    }
531
532    private void createRenderVariablesPerThreadValue()
533    {
534        try
535        {
536            upgradeReadLockToWriteLock();
537
538            if (renderVariables == null)
539            {
540                renderVariables = elementResources.createPerThreadValue();
541            }
542
543        } finally
544        {
545            downgradeWriteLockToReadLock();
546        }
547    }
548
549    public Object getRenderVariable(String name)
550    {
551        Map<String, Object> variablesMap = getRenderVariables(false);
552
553        Object result = InternalUtils.get(variablesMap, name);
554
555        if (result == null)
556        {
557            throw new IllegalArgumentException(StructureMessages.missingRenderVariable(getCompleteId(), name,
558                    variablesMap == null ? null : variablesMap.keySet()));
559        }
560
561        return result;
562    }
563
564    public void storeRenderVariable(String name, Object value)
565    {
566        assert InternalUtils.isNonBlank(name);
567        assert value != null;
568
569        Map<String, Object> renderVariables = getRenderVariables(true);
570
571        renderVariables.put(name, value);
572    }
573
574    public void postRenderCleanup()
575    {
576        Map<String, Object> variablesMap = getRenderVariables(false);
577
578        if (variablesMap != null)
579            variablesMap.clear();
580
581        resetParameterConduits();
582    }
583
584    public void addPageLifecycleListener(PageLifecycleListener listener)
585    {
586        page.addLifecycleListener(listener);
587    }
588
589    public void removePageLifecycleListener(PageLifecycleListener listener)
590    {
591        page.removeLifecycleListener(listener);
592    }
593
594    public void addPageResetListener(PageResetListener listener)
595    {
596        page.addResetListener(listener);
597    }
598
599    private void resetParameterConduits()
600    {
601        try
602        {
603            acquireReadLock();
604
605            if (conduits != null)
606            {
607                conduits.eachValue(RESET_PARAMETER_CONDUIT);
608            }
609        } finally
610        {
611            releaseReadLock();
612        }
613    }
614
615    public ParameterConduit getParameterConduit(String parameterName)
616    {
617        try
618        {
619            acquireReadLock();
620            return NamedSet.get(conduits, parameterName);
621        } finally
622        {
623            releaseReadLock();
624        }
625    }
626
627    public void setParameterConduit(String parameterName, ParameterConduit conduit)
628    {
629        try
630        {
631            acquireReadLock();
632
633            if (conduits == null)
634            {
635                createConduits();
636            }
637
638            conduits.put(parameterName, conduit);
639        } finally
640        {
641            releaseReadLock();
642        }
643    }
644
645    private void createConduits()
646    {
647        try
648        {
649            upgradeReadLockToWriteLock();
650            if (conduits == null)
651            {
652                conduits = NamedSet.create();
653            }
654        } finally
655        {
656            downgradeWriteLockToReadLock();
657        }
658    }
659
660
661    public String getPropertyName(String parameterName)
662    {
663        Binding binding = getBinding(parameterName);
664
665        if (binding == null)
666        {
667            return null;
668        }
669
670        // TAP5-1718: we need the full prop binding expression, not just the (final) property name
671        if (binding instanceof PropBinding) 
672        {
673            return ((PropBinding) binding).getExpression();
674        }
675        
676        if (binding instanceof InternalPropBinding)
677        {
678            return ((InternalPropBinding) binding).getPropertyName();
679        }
680
681        return null;
682    }
683
684    /**
685     * @since 5.3
686     */
687    public void render(MarkupWriter writer, RenderQueue queue)
688    {
689        queue.push(element);
690    }
691
692    public PageLifecycleCallbackHub getPageLifecycleCallbackHub()
693    {
694        return page;
695    }
696}