001    // Copyright 2006, 2007, 2008, 2009, 2010, 2011 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.TapestryException;
032    import org.apache.tapestry5.ioc.services.PerThreadValue;
033    import org.apache.tapestry5.model.ComponentModel;
034    import org.apache.tapestry5.runtime.Component;
035    import org.apache.tapestry5.runtime.PageLifecycleAdapter;
036    import org.apache.tapestry5.runtime.PageLifecycleListener;
037    import org.apache.tapestry5.runtime.RenderQueue;
038    import org.apache.tapestry5.services.pageload.ComponentResourceSelector;
039    import org.slf4j.Logger;
040    
041    import java.lang.annotation.Annotation;
042    import java.util.List;
043    import java.util.Locale;
044    import java.util.Map;
045    
046    /**
047     * The bridge between a component and its {@link ComponentPageElement}, that supplies all kinds of
048     * resources to the
049     * component, including access to its parameters, parameter bindings, and persistent field data.
050     */
051    @SuppressWarnings("all")
052    public class InternalComponentResourcesImpl implements InternalComponentResources
053    {
054        private final Page page;
055    
056        private final String completeId;
057    
058        private final String nestedId;
059    
060        private final ComponentModel componentModel;
061    
062        private final ComponentPageElement element;
063    
064        private final Component component;
065    
066        private final ComponentResources containerResources;
067    
068        private final ComponentPageElementResources elementResources;
069    
070        private final boolean mixin;
071    
072        private static final Object[] EMPTY = new Object[0];
073    
074        private static final AnnotationProvider NULL_ANNOTATION_PROVIDER = new NullAnnotationProvider();
075    
076        // Case insensitive map from parameter name to binding
077        private NamedSet<Binding> bindings;
078    
079        // Case insensitive map from parameter name to ParameterConduit, used to support mixins
080        // which need access to the containing component's PC's
081        private NamedSet<ParameterConduit> conduits;
082    
083        private Messages messages;
084    
085        private boolean informalsComputed;
086    
087        private PerThreadValue<Map<String, Object>> renderVariables;
088    
089        private Informal firstInformal;
090    
091        /**
092         * We keep a linked list of informal parameters, which saves us the expense of determining which
093         * bindings are formal
094         * and which are informal. Each Informal points to the next.
095         */
096        private class Informal
097        {
098            private final String name;
099    
100            private final Binding binding;
101    
102            final Informal next;
103    
104            private Informal(String name, Binding binding, Informal next)
105            {
106                this.name = name;
107                this.binding = binding;
108                this.next = next;
109            }
110    
111            void write(MarkupWriter writer)
112            {
113                Object value = binding.get();
114    
115                if (value == null)
116                    return;
117    
118                if (value instanceof Block)
119                    return;
120    
121                // If it's already a String, don't use the TypeCoercer (renderInformalParameters is
122                // a CPU hotspot, as is TypeCoercer.coerce).
123    
124                String valueString = value instanceof String ? (String) value : elementResources
125                        .coerce(value, String.class);
126    
127                writer.attributes(name, valueString);
128            }
129        }
130    
131    
132        private static Worker<ParameterConduit> RESET_PARAMETER_CONDUIT = new Worker<ParameterConduit>()
133        {
134            public void work(ParameterConduit value)
135            {
136                value.reset();
137            }
138        };
139    
140        private static Worker<ParameterConduit> LOAD_PARAMETER_CONDUIT = new Worker<ParameterConduit>()
141        {
142            public void work(ParameterConduit value)
143            {
144                value.load();
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 synchronized Informal firstInformal()
379        {
380            if (!informalsComputed)
381            {
382                for (Map.Entry<String, Binding> e : getInformalParameterBindings().entrySet())
383                {
384                    firstInformal = new Informal(e.getKey(), e.getValue(), firstInformal);
385                }
386    
387                informalsComputed = true;
388            }
389    
390            return firstInformal;
391        }
392    
393        public Component getContainer()
394        {
395            if (containerResources == null)
396                return null;
397    
398            return containerResources.getComponent();
399        }
400    
401        public ComponentResources getContainerResources()
402        {
403            return containerResources;
404        }
405    
406        public Messages getContainerMessages()
407        {
408            return containerResources != null ? containerResources.getMessages() : null;
409        }
410    
411        public Locale getLocale()
412        {
413            return element.getLocale();
414        }
415    
416        public ComponentResourceSelector getResourceSelector()
417        {
418            return element.getResourceSelector();
419        }
420    
421        public synchronized Messages getMessages()
422        {
423            if (messages == null)
424                messages = elementResources.getMessages(componentModel);
425    
426            return messages;
427        }
428    
429        public String getElementName(String defaultElementName)
430        {
431            return element.getElementName(defaultElementName);
432        }
433    
434        public Block getBlock(String blockId)
435        {
436            return element.getBlock(blockId);
437        }
438    
439        public Block getBlockParameter(String parameterName)
440        {
441            return getInformalParameter(parameterName, Block.class);
442        }
443    
444        public Block findBlock(String blockId)
445        {
446            return element.findBlock(blockId);
447        }
448    
449        public Resource getBaseResource()
450        {
451            return componentModel.getBaseResource();
452        }
453    
454        public String getPageName()
455        {
456            return element.getPageName();
457        }
458    
459        public Map<String, Binding> getInformalParameterBindings()
460        {
461            Map<String, Binding> result = CollectionFactory.newMap();
462    
463            for (String name : NamedSet.getNames(bindings))
464            {
465                if (componentModel.getParameterModel(name) != null)
466                    continue;
467    
468                result.put(name, bindings.get(name));
469            }
470    
471            return result;
472        }
473    
474        private synchronized Map<String, Object> getRenderVariables(boolean create)
475        {
476            if (renderVariables == null)
477            {
478                if (!create)
479                    return null;
480    
481                renderVariables = elementResources.createPerThreadValue();
482            }
483    
484            Map<String, Object> result = renderVariables.get();
485    
486            if (result == null && create)
487                result = renderVariables.set(CollectionFactory.newCaseInsensitiveMap());
488    
489            return result;
490        }
491    
492        public Object getRenderVariable(String name)
493        {
494            Map<String, Object> variablesMap = getRenderVariables(false);
495    
496            Object result = InternalUtils.get(variablesMap, name);
497    
498            if (result == null)
499                throw new IllegalArgumentException(StructureMessages.missingRenderVariable(getCompleteId(), name,
500                        variablesMap == null ? null : variablesMap.keySet()));
501    
502            return result;
503        }
504    
505        public void storeRenderVariable(String name, Object value)
506        {
507            assert InternalUtils.isNonBlank(name);
508            assert value != null;
509    
510            Map<String, Object> renderVariables = getRenderVariables(true);
511    
512            renderVariables.put(name, value);
513        }
514    
515        public void postRenderCleanup()
516        {
517            Map<String, Object> variablesMap = getRenderVariables(false);
518    
519            if (variablesMap != null)
520                variablesMap.clear();
521    
522            resetParameterConduits();
523        }
524    
525        public void addPageLifecycleListener(PageLifecycleListener listener)
526        {
527            page.addLifecycleListener(listener);
528        }
529    
530        public void removePageLifecycleListener(PageLifecycleListener listener)
531        {
532            page.removeLifecycleListener(listener);
533        }
534    
535        public void addPageResetListener(PageResetListener listener)
536        {
537            page.addResetListener(listener);
538        }
539    
540    
541    
542        private synchronized void resetParameterConduits()
543        {
544            if (conduits != null)
545            {
546                conduits.eachValue(RESET_PARAMETER_CONDUIT);
547            }
548        }
549    
550        private synchronized void loadParameterConduits()
551        {
552            // Don't need the conduits != null, because this method will only be invoked
553            // when conduits is non-null.
554    
555            conduits.eachValue(LOAD_PARAMETER_CONDUIT);
556        }
557    
558    
559        public synchronized ParameterConduit getParameterConduit(String parameterName)
560        {
561            return NamedSet.get(conduits, parameterName);
562        }
563    
564        public synchronized void setParameterConduit(String parameterName, ParameterConduit conduit)
565        {
566            if (conduits == null)
567            {
568                conduits = NamedSet.create();
569    
570                page.addLifecycleListener(new PageLifecycleAdapter()
571                {
572                    @Override
573                    public void containingPageDidLoad()
574                    {
575                        loadParameterConduits();
576                    }
577                });
578    
579            }
580    
581            conduits.put(parameterName, conduit);
582        }
583    
584    
585        public String getPropertyName(String parameterName)
586        {
587            Binding binding = getBinding(parameterName);
588    
589            if (binding == null)
590            {
591                return null;
592            }
593    
594            if (binding instanceof InternalPropBinding)
595            {
596                return ((InternalPropBinding) binding).getPropertyName();
597            }
598    
599            return null;
600        }
601    
602        /** @since 5.3 */
603        public void render(MarkupWriter writer, RenderQueue queue)
604        {
605            queue.push(element);
606        }
607    }