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    }