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