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.services.Instantiator;
022import org.apache.tapestry5.internal.transform.ParameterConduit;
023import org.apache.tapestry5.internal.util.NamedSet;
024import org.apache.tapestry5.ioc.AnnotationProvider;
025import org.apache.tapestry5.ioc.Location;
026import org.apache.tapestry5.ioc.Messages;
027import org.apache.tapestry5.ioc.Resource;
028import org.apache.tapestry5.ioc.internal.NullAnnotationProvider;
029import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
030import org.apache.tapestry5.ioc.internal.util.InternalUtils;
031import org.apache.tapestry5.ioc.internal.util.LockSupport;
032import org.apache.tapestry5.ioc.internal.util.TapestryException;
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.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    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}