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.commons.AnnotationProvider;
017import org.apache.tapestry5.commons.Location;
018import org.apache.tapestry5.commons.Messages;
019import org.apache.tapestry5.commons.Resource;
020import org.apache.tapestry5.commons.internal.NullAnnotationProvider;
021import org.apache.tapestry5.commons.internal.util.LockSupport;
022import org.apache.tapestry5.commons.internal.util.TapestryException;
023import org.apache.tapestry5.commons.util.CollectionFactory;
024import org.apache.tapestry5.func.Worker;
025import org.apache.tapestry5.http.Link;
026import org.apache.tapestry5.internal.InternalComponentResources;
027import org.apache.tapestry5.internal.bindings.InternalPropBinding;
028import org.apache.tapestry5.internal.bindings.PropBinding;
029import org.apache.tapestry5.internal.services.Instantiator;
030import org.apache.tapestry5.internal.transform.ParameterConduit;
031import org.apache.tapestry5.internal.util.NamedSet;
032import org.apache.tapestry5.ioc.internal.util.InternalUtils;
033import org.apache.tapestry5.ioc.services.PerThreadValue;
034import org.apache.tapestry5.model.ComponentModel;
035import org.apache.tapestry5.runtime.Component;
036import org.apache.tapestry5.runtime.PageLifecycleCallbackHub;
037import org.apache.tapestry5.runtime.PageLifecycleListener;
038import org.apache.tapestry5.runtime.RenderQueue;
039import org.apache.tapestry5.services.pageload.ComponentResourceSelector;
040import org.slf4j.Logger;
041
042import java.lang.annotation.Annotation;
043import java.lang.reflect.Type;
044import java.util.List;
045import java.util.Locale;
046import 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")
054public 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
211    public Link createFormEventLink(String eventType, Object... context)
212    {
213        return element.createFormEventLink(eventType, context);
214    }
215
216    public void discardPersistentFieldChanges()
217    {
218        page.discardPersistentFieldChanges();
219    }
220
221    public String getElementName()
222    {
223        return getElementName(null);
224    }
225
226    public List<String> getInformalParameterNames()
227    {
228        return InternalUtils.sortedKeys(getInformalParameterBindings());
229    }
230
231    public <T> T getInformalParameter(String name, Class<T> type)
232    {
233        Binding binding = getBinding(name);
234
235        Object value = binding == null ? null : binding.get();
236
237        return elementResources.coerce(value, type);
238    }
239
240    public Block getBody()
241    {
242        return element.getBody();
243    }
244
245    public boolean hasBody()
246    {
247        return element.hasBody();
248    }
249
250    public String getCompleteId()
251    {
252        return completeId;
253    }
254
255    public Component getComponent()
256    {
257        return component;
258    }
259
260    public boolean isBound(String parameterName)
261    {
262        return getBinding(parameterName) != null;
263    }
264
265    public <T extends Annotation> T getParameterAnnotation(String parameterName, Class<T> annotationType)
266    {
267        Binding binding = getBinding(parameterName);
268
269        return binding == null ? null : binding.getAnnotation(annotationType);
270    }
271
272    public boolean isRendering()
273    {
274        return element.isRendering();
275    }
276
277    public boolean triggerEvent(String eventType, Object[] context, ComponentEventCallback handler)
278    {
279        return element.triggerEvent(eventType, defaulted(context), handler);
280    }
281
282    private static Object[] defaulted(Object[] input)
283    {
284        return input == null ? EMPTY : input;
285    }
286
287    public boolean triggerContextEvent(String eventType, EventContext context, ComponentEventCallback callback)
288    {
289        return element.triggerContextEvent(eventType, context, callback);
290    }
291
292    public String getNestedId()
293    {
294        return nestedId;
295    }
296
297    public Component getPage()
298    {
299        return element.getContainingPage().getRootComponent();
300    }
301
302    public boolean isLoaded()
303    {
304        return element.isLoaded();
305    }
306
307    public void persistFieldChange(String fieldName, Object newValue)
308    {
309        try
310        {
311            page.persistFieldChange(this, fieldName, newValue);
312        } catch (Exception ex)
313        {
314            throw new TapestryException(StructureMessages.fieldPersistFailure(getCompleteId(), fieldName, ex),
315                    getLocation(), ex);
316        }
317    }
318
319    public void bindParameter(String parameterName, Binding binding)
320    {
321        if (bindings == null)
322            bindings = NamedSet.create();
323
324        bindings.put(parameterName, binding);
325    }
326
327    public Class getBoundType(String parameterName)
328    {
329        Binding binding = getBinding(parameterName);
330
331        return binding == null ? null : binding.getBindingType();
332    }
333    
334    public Type getBoundGenericType(String parameterName)
335    {
336        Binding binding = getBinding(parameterName);
337        Type genericType;
338        if (binding instanceof Binding2) {
339            genericType = ((Binding2) binding).getBindingGenericType();
340        } else {
341            genericType = binding.getBindingType();
342        }
343        return genericType;
344    }
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        // TAP5-1718: we need the full prop binding expression, not just the (final) property name
672        if (binding instanceof PropBinding) 
673        {
674            return ((PropBinding) binding).getExpression();
675        }
676        
677        if (binding instanceof InternalPropBinding)
678        {
679            return ((InternalPropBinding) binding).getPropertyName();
680        }
681
682        return null;
683    }
684
685    /**
686     * @since 5.3
687     */
688    public void render(MarkupWriter writer, RenderQueue queue)
689    {
690        queue.push(element);
691    }
692
693    public PageLifecycleCallbackHub getPageLifecycleCallbackHub()
694    {
695        return page;
696    }
697}