    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}