001// Licensed under the Apache License, Version 2.0 (the "License"); 002// you may not use this file except in compliance with the License. 003// You may obtain a copy of the License at 004// 005// http://www.apache.org/licenses/LICENSE-2.0 006// 007// Unless required by applicable law or agreed to in writing, software 008// distributed under the License is distributed on an "AS IS" BASIS, 009// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 010// See the License for the specific language governing permissions and 011// limitations under the License. 012 013package org.apache.tapestry5.internal.structure; 014 015import org.apache.tapestry5.*; 016import org.apache.tapestry5.func.Worker; 017import org.apache.tapestry5.internal.InternalComponentResources; 018import org.apache.tapestry5.internal.bindings.InternalPropBinding; 019import org.apache.tapestry5.internal.bindings.PropBinding; 020import org.apache.tapestry5.internal.services.Instantiator; 021import org.apache.tapestry5.internal.transform.ParameterConduit; 022import org.apache.tapestry5.internal.util.NamedSet; 023import org.apache.tapestry5.ioc.AnnotationProvider; 024import org.apache.tapestry5.ioc.Location; 025import org.apache.tapestry5.ioc.Messages; 026import org.apache.tapestry5.ioc.Resource; 027import org.apache.tapestry5.ioc.internal.NullAnnotationProvider; 028import org.apache.tapestry5.ioc.internal.util.CollectionFactory; 029import org.apache.tapestry5.ioc.internal.util.InternalUtils; 030import org.apache.tapestry5.ioc.internal.util.LockSupport; 031import org.apache.tapestry5.ioc.internal.util.TapestryException; 032import org.apache.tapestry5.ioc.services.PerThreadValue; 033import org.apache.tapestry5.model.ComponentModel; 034import org.apache.tapestry5.runtime.Component; 035import org.apache.tapestry5.runtime.PageLifecycleCallbackHub; 036import org.apache.tapestry5.runtime.PageLifecycleListener; 037import org.apache.tapestry5.runtime.RenderQueue; 038import org.apache.tapestry5.services.pageload.ComponentResourceSelector; 039import org.slf4j.Logger; 040 041import java.lang.annotation.Annotation; 042import java.lang.reflect.Type; 043import java.util.List; 044import java.util.Locale; 045import 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") 053public 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 210 public Link createFormEventLink(String eventType, Object... context) 211 { 212 return element.createFormEventLink(eventType, context); 213 } 214 215 public void discardPersistentFieldChanges() 216 { 217 page.discardPersistentFieldChanges(); 218 } 219 220 public String getElementName() 221 { 222 return getElementName(null); 223 } 224 225 public List<String> getInformalParameterNames() 226 { 227 return InternalUtils.sortedKeys(getInformalParameterBindings()); 228 } 229 230 public <T> T getInformalParameter(String name, Class<T> type) 231 { 232 Binding binding = getBinding(name); 233 234 Object value = binding == null ? null : binding.get(); 235 236 return elementResources.coerce(value, type); 237 } 238 239 public Block getBody() 240 { 241 return element.getBody(); 242 } 243 244 public boolean hasBody() 245 { 246 return element.hasBody(); 247 } 248 249 public String getCompleteId() 250 { 251 return completeId; 252 } 253 254 public Component getComponent() 255 { 256 return component; 257 } 258 259 public boolean isBound(String parameterName) 260 { 261 return getBinding(parameterName) != null; 262 } 263 264 public <T extends Annotation> T getParameterAnnotation(String parameterName, Class<T> annotationType) 265 { 266 Binding binding = getBinding(parameterName); 267 268 return binding == null ? null : binding.getAnnotation(annotationType); 269 } 270 271 public boolean isRendering() 272 { 273 return element.isRendering(); 274 } 275 276 public boolean triggerEvent(String eventType, Object[] context, ComponentEventCallback handler) 277 { 278 return element.triggerEvent(eventType, defaulted(context), handler); 279 } 280 281 private static Object[] defaulted(Object[] input) 282 { 283 return input == null ? EMPTY : input; 284 } 285 286 public boolean triggerContextEvent(String eventType, EventContext context, ComponentEventCallback callback) 287 { 288 return element.triggerContextEvent(eventType, context, callback); 289 } 290 291 public String getNestedId() 292 { 293 return nestedId; 294 } 295 296 public Component getPage() 297 { 298 return element.getContainingPage().getRootComponent(); 299 } 300 301 public boolean isLoaded() 302 { 303 return element.isLoaded(); 304 } 305 306 public void persistFieldChange(String fieldName, Object newValue) 307 { 308 try 309 { 310 page.persistFieldChange(this, fieldName, newValue); 311 } catch (Exception ex) 312 { 313 throw new TapestryException(StructureMessages.fieldPersistFailure(getCompleteId(), fieldName, ex), 314 getLocation(), ex); 315 } 316 } 317 318 public void bindParameter(String parameterName, Binding binding) 319 { 320 if (bindings == null) 321 bindings = NamedSet.create(); 322 323 bindings.put(parameterName, binding); 324 } 325 326 public Class getBoundType(String parameterName) 327 { 328 Binding binding = getBinding(parameterName); 329 330 return binding == null ? null : binding.getBindingType(); 331 } 332 333 public Type getBoundGenericType(String parameterName) 334 { 335 Binding binding = getBinding(parameterName); 336 Type genericType; 337 if (binding instanceof Binding2) { 338 genericType = ((Binding2) binding).getBindingGenericType(); 339 } else { 340 genericType = binding.getBindingType(); 341 } 342 return genericType; 343 } 344 345 346 public Binding getBinding(String parameterName) 347 { 348 return NamedSet.get(bindings, parameterName); 349 } 350 351 public AnnotationProvider getAnnotationProvider(String parameterName) 352 { 353 Binding binding = getBinding(parameterName); 354 355 return binding == null ? NULL_ANNOTATION_PROVIDER : binding; 356 } 357 358 public Logger getLogger() 359 { 360 return componentModel.getLogger(); 361 } 362 363 public Component getMixinByClassName(String mixinClassName) 364 { 365 return element.getMixinByClassName(mixinClassName); 366 } 367 368 public void renderInformalParameters(MarkupWriter writer) 369 { 370 if (bindings == null) 371 return; 372 373 for (Informal i = firstInformal(); i != null; i = i.next) 374 i.write(writer); 375 } 376 377 private Informal firstInformal() 378 { 379 try 380 { 381 acquireReadLock(); 382 383 if (!informalsComputed) 384 { 385 computeInformals(); 386 } 387 388 return firstInformal; 389 } finally 390 { 391 releaseReadLock(); 392 } 393 } 394 395 private void computeInformals() 396 { 397 try 398 { 399 upgradeReadLockToWriteLock(); 400 401 if (!informalsComputed) 402 { 403 for (Map.Entry<String, Binding> e : getInformalParameterBindings().entrySet()) 404 { 405 firstInformal = new Informal(e.getKey(), e.getValue(), firstInformal); 406 } 407 408 informalsComputed = true; 409 } 410 } finally 411 { 412 downgradeWriteLockToReadLock(); 413 } 414 } 415 416 public Component getContainer() 417 { 418 if (containerResources == null) 419 { 420 return null; 421 } 422 423 return containerResources.getComponent(); 424 } 425 426 public ComponentResources getContainerResources() 427 { 428 return containerResources; 429 } 430 431 public Messages getContainerMessages() 432 { 433 return containerResources != null ? containerResources.getMessages() : null; 434 } 435 436 public Locale getLocale() 437 { 438 return element.getLocale(); 439 } 440 441 public ComponentResourceSelector getResourceSelector() 442 { 443 return element.getResourceSelector(); 444 } 445 446 public Messages getMessages() 447 { 448 if (messages == null) 449 { 450 // This kind of lazy loading pattern is acceptable without locking. 451 // Changes to the messages field are atomic; in some race conditions, the call to 452 // getMessages() may occur more than once (but it caches the value anyway). 453 messages = elementResources.getMessages(componentModel); 454 } 455 456 return messages; 457 } 458 459 public String getElementName(String defaultElementName) 460 { 461 return element.getElementName(defaultElementName); 462 } 463 464 public Block getBlock(String blockId) 465 { 466 return element.getBlock(blockId); 467 } 468 469 public Block getBlockParameter(String parameterName) 470 { 471 return getInformalParameter(parameterName, Block.class); 472 } 473 474 public Block findBlock(String blockId) 475 { 476 return element.findBlock(blockId); 477 } 478 479 public Resource getBaseResource() 480 { 481 return componentModel.getBaseResource(); 482 } 483 484 public String getPageName() 485 { 486 return element.getPageName(); 487 } 488 489 public Map<String, Binding> getInformalParameterBindings() 490 { 491 Map<String, Binding> result = CollectionFactory.newMap(); 492 493 for (String name : NamedSet.getNames(bindings)) 494 { 495 if (componentModel.getParameterModel(name) != null) 496 continue; 497 498 result.put(name, bindings.get(name)); 499 } 500 501 return result; 502 } 503 504 private Map<String, Object> getRenderVariables(boolean create) 505 { 506 try 507 { 508 acquireReadLock(); 509 510 if (renderVariables == null) 511 { 512 if (!create) 513 { 514 return null; 515 } 516 517 createRenderVariablesPerThreadValue(); 518 } 519 520 Map<String, Object> result = renderVariables.get(); 521 522 if (result == null && create) 523 result = renderVariables.set(CollectionFactory.newCaseInsensitiveMap()); 524 525 return result; 526 } finally 527 { 528 releaseReadLock(); 529 } 530 } 531 532 private void createRenderVariablesPerThreadValue() 533 { 534 try 535 { 536 upgradeReadLockToWriteLock(); 537 538 if (renderVariables == null) 539 { 540 renderVariables = elementResources.createPerThreadValue(); 541 } 542 543 } finally 544 { 545 downgradeWriteLockToReadLock(); 546 } 547 } 548 549 public Object getRenderVariable(String name) 550 { 551 Map<String, Object> variablesMap = getRenderVariables(false); 552 553 Object result = InternalUtils.get(variablesMap, name); 554 555 if (result == null) 556 { 557 throw new IllegalArgumentException(StructureMessages.missingRenderVariable(getCompleteId(), name, 558 variablesMap == null ? null : variablesMap.keySet())); 559 } 560 561 return result; 562 } 563 564 public void storeRenderVariable(String name, Object value) 565 { 566 assert InternalUtils.isNonBlank(name); 567 assert value != null; 568 569 Map<String, Object> renderVariables = getRenderVariables(true); 570 571 renderVariables.put(name, value); 572 } 573 574 public void postRenderCleanup() 575 { 576 Map<String, Object> variablesMap = getRenderVariables(false); 577 578 if (variablesMap != null) 579 variablesMap.clear(); 580 581 resetParameterConduits(); 582 } 583 584 public void addPageLifecycleListener(PageLifecycleListener listener) 585 { 586 page.addLifecycleListener(listener); 587 } 588 589 public void removePageLifecycleListener(PageLifecycleListener listener) 590 { 591 page.removeLifecycleListener(listener); 592 } 593 594 public void addPageResetListener(PageResetListener listener) 595 { 596 page.addResetListener(listener); 597 } 598 599 private void resetParameterConduits() 600 { 601 try 602 { 603 acquireReadLock(); 604 605 if (conduits != null) 606 { 607 conduits.eachValue(RESET_PARAMETER_CONDUIT); 608 } 609 } finally 610 { 611 releaseReadLock(); 612 } 613 } 614 615 public ParameterConduit getParameterConduit(String parameterName) 616 { 617 try 618 { 619 acquireReadLock(); 620 return NamedSet.get(conduits, parameterName); 621 } finally 622 { 623 releaseReadLock(); 624 } 625 } 626 627 public void setParameterConduit(String parameterName, ParameterConduit conduit) 628 { 629 try 630 { 631 acquireReadLock(); 632 633 if (conduits == null) 634 { 635 createConduits(); 636 } 637 638 conduits.put(parameterName, conduit); 639 } finally 640 { 641 releaseReadLock(); 642 } 643 } 644 645 private void createConduits() 646 { 647 try 648 { 649 upgradeReadLockToWriteLock(); 650 if (conduits == null) 651 { 652 conduits = NamedSet.create(); 653 } 654 } finally 655 { 656 downgradeWriteLockToReadLock(); 657 } 658 } 659 660 661 public String getPropertyName(String parameterName) 662 { 663 Binding binding = getBinding(parameterName); 664 665 if (binding == null) 666 { 667 return null; 668 } 669 670 // TAP5-1718: we need the full prop binding expression, not just the (final) property name 671 if (binding instanceof PropBinding) 672 { 673 return ((PropBinding) binding).getExpression(); 674 } 675 676 if (binding instanceof InternalPropBinding) 677 { 678 return ((InternalPropBinding) binding).getPropertyName(); 679 } 680 681 return null; 682 } 683 684 /** 685 * @since 5.3 686 */ 687 public void render(MarkupWriter writer, RenderQueue queue) 688 { 689 queue.push(element); 690 } 691 692 public PageLifecycleCallbackHub getPageLifecycleCallbackHub() 693 { 694 return page; 695 } 696}