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