    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}