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 }