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.services.Instantiator;
022    import org.apache.tapestry5.internal.transform.ParameterConduit;
023    import org.apache.tapestry5.internal.util.NamedSet;
024    import org.apache.tapestry5.ioc.AnnotationProvider;
025    import org.apache.tapestry5.ioc.Location;
026    import org.apache.tapestry5.ioc.Messages;
027    import org.apache.tapestry5.ioc.Resource;
028    import org.apache.tapestry5.ioc.internal.NullAnnotationProvider;
029    import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
030    import org.apache.tapestry5.ioc.internal.util.InternalUtils;
031    import org.apache.tapestry5.ioc.internal.util.LockSupport;
032    import org.apache.tapestry5.ioc.internal.util.TapestryException;
033    import org.apache.tapestry5.ioc.services.PerThreadValue;
034    import org.apache.tapestry5.model.ComponentModel;
035    import org.apache.tapestry5.runtime.Component;
036    import org.apache.tapestry5.runtime.PageLifecycleCallbackHub;
037    import org.apache.tapestry5.runtime.PageLifecycleListener;
038    import org.apache.tapestry5.runtime.RenderQueue;
039    import org.apache.tapestry5.services.pageload.ComponentResourceSelector;
040    import org.slf4j.Logger;
041    
042    import java.lang.annotation.Annotation;
043    import java.util.List;
044    import java.util.Locale;
045    import 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")
053    public 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        public Link createActionLink(String eventType, boolean forForm, Object... context)
210        {
211            return element.createActionLink(eventType, forForm, context);
212        }
213    
214        public Link createFormEventLink(String eventType, Object... context)
215        {
216            return element.createFormEventLink(eventType, context);
217        }
218    
219        public Link createPageLink(String pageName, boolean override, Object... context)
220        {
221            return element.createPageLink(pageName, override, context);
222        }
223    
224        public Link createPageLink(Class pageClass, boolean override, Object... context)
225        {
226            return element.createPageLink(pageClass, override, context);
227        }
228    
229        public void discardPersistentFieldChanges()
230        {
231            page.discardPersistentFieldChanges();
232        }
233    
234        public String getElementName()
235        {
236            return getElementName(null);
237        }
238    
239        public List<String> getInformalParameterNames()
240        {
241            return InternalUtils.sortedKeys(getInformalParameterBindings());
242        }
243    
244        public <T> T getInformalParameter(String name, Class<T> type)
245        {
246            Binding binding = getBinding(name);
247    
248            Object value = binding == null ? null : binding.get();
249    
250            return elementResources.coerce(value, type);
251        }
252    
253        public Block getBody()
254        {
255            return element.getBody();
256        }
257    
258        public boolean hasBody()
259        {
260            return element.hasBody();
261        }
262    
263        public String getCompleteId()
264        {
265            return completeId;
266        }
267    
268        public Component getComponent()
269        {
270            return component;
271        }
272    
273        public boolean isBound(String parameterName)
274        {
275            return getBinding(parameterName) != null;
276        }
277    
278        public <T extends Annotation> T getParameterAnnotation(String parameterName, Class<T> annotationType)
279        {
280            Binding binding = getBinding(parameterName);
281    
282            return binding == null ? null : binding.getAnnotation(annotationType);
283        }
284    
285        public boolean isRendering()
286        {
287            return element.isRendering();
288        }
289    
290        public boolean triggerEvent(String eventType, Object[] context, ComponentEventCallback handler)
291        {
292            return element.triggerEvent(eventType, defaulted(context), handler);
293        }
294    
295        private static Object[] defaulted(Object[] input)
296        {
297            return input == null ? EMPTY : input;
298        }
299    
300        public boolean triggerContextEvent(String eventType, EventContext context, ComponentEventCallback callback)
301        {
302            return element.triggerContextEvent(eventType, context, callback);
303        }
304    
305        public String getNestedId()
306        {
307            return nestedId;
308        }
309    
310        public Component getPage()
311        {
312            return element.getContainingPage().getRootComponent();
313        }
314    
315        public boolean isLoaded()
316        {
317            return element.isLoaded();
318        }
319    
320        public void persistFieldChange(String fieldName, Object newValue)
321        {
322            try
323            {
324                page.persistFieldChange(this, fieldName, newValue);
325            } catch (Exception ex)
326            {
327                throw new TapestryException(StructureMessages.fieldPersistFailure(getCompleteId(), fieldName, ex),
328                        getLocation(), ex);
329            }
330        }
331    
332        public void bindParameter(String parameterName, Binding binding)
333        {
334            if (bindings == null)
335                bindings = NamedSet.create();
336    
337            bindings.put(parameterName, binding);
338        }
339    
340        public Class getBoundType(String parameterName)
341        {
342            Binding binding = getBinding(parameterName);
343    
344            return binding == null ? null : binding.getBindingType();
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            if (binding instanceof InternalPropBinding)
672            {
673                return ((InternalPropBinding) binding).getPropertyName();
674            }
675    
676            return null;
677        }
678    
679        /**
680         * @since 5.3
681         */
682        public void render(MarkupWriter writer, RenderQueue queue)
683        {
684            queue.push(element);
685        }
686    
687        @Override
688        public PageLifecycleCallbackHub getPageLifecycleCallbackHub()
689        {
690            return page;
691        }
692    }