    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
    015    package org.apache.tapestry5.internal.structure;
    016
    017    import org.apache.tapestry5.*;
    018    import org.apache.tapestry5.func.Worker;
    019    import org.apache.tapestry5.internal.InternalComponentResources;
    020    import org.apache.tapestry5.internal.bindings.InternalPropBinding;
    021    import org.apache.tapestry5.internal.bindings.PropBinding;
    022    import org.apache.tapestry5.internal.services.Instantiator;
    023    import org.apache.tapestry5.internal.transform.ParameterConduit;
    024    import org.apache.tapestry5.internal.util.NamedSet;
    025    import org.apache.tapestry5.ioc.AnnotationProvider;
    026    import org.apache.tapestry5.ioc.Location;
    027    import org.apache.tapestry5.ioc.Messages;
    028    import org.apache.tapestry5.ioc.Resource;
    029    import org.apache.tapestry5.ioc.internal.NullAnnotationProvider;
    030    import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
    031    import org.apache.tapestry5.ioc.internal.util.InternalUtils;
    032    import org.apache.tapestry5.ioc.internal.util.LockSupport;
    033    import org.apache.tapestry5.ioc.internal.util.TapestryException;
    034    import org.apache.tapestry5.ioc.services.PerThreadValue;
    035    import org.apache.tapestry5.model.ComponentModel;
    036    import org.apache.tapestry5.runtime.Component;
    037    import org.apache.tapestry5.runtime.PageLifecycleCallbackHub;
    038    import org.apache.tapestry5.runtime.PageLifecycleListener;
    039    import org.apache.tapestry5.runtime.RenderQueue;
    040    import org.apache.tapestry5.services.pageload.ComponentResourceSelector;
    041    import org.slf4j.Logger;
    042
    043    import java.lang.annotation.Annotation;
    044    import java.util.List;
    045    import java.util.Locale;
    046    import 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")
    054    public 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    }