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.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    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}