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 015package org.apache.tapestry5.internal.structure; 016 017import org.apache.tapestry5.*; 018import org.apache.tapestry5.func.Worker; 019import org.apache.tapestry5.internal.InternalComponentResources; 020import org.apache.tapestry5.internal.bindings.InternalPropBinding; 021import org.apache.tapestry5.internal.bindings.PropBinding; 022import org.apache.tapestry5.internal.services.Instantiator; 023import org.apache.tapestry5.internal.transform.ParameterConduit; 024import org.apache.tapestry5.internal.util.NamedSet; 025import org.apache.tapestry5.ioc.AnnotationProvider; 026import org.apache.tapestry5.ioc.Location; 027import org.apache.tapestry5.ioc.Messages; 028import org.apache.tapestry5.ioc.Resource; 029import org.apache.tapestry5.ioc.internal.NullAnnotationProvider; 030import org.apache.tapestry5.ioc.internal.util.CollectionFactory; 031import org.apache.tapestry5.ioc.internal.util.InternalUtils; 032import org.apache.tapestry5.ioc.internal.util.LockSupport; 033import org.apache.tapestry5.ioc.internal.util.TapestryException; 034import org.apache.tapestry5.ioc.services.PerThreadValue; 035import org.apache.tapestry5.model.ComponentModel; 036import org.apache.tapestry5.runtime.Component; 037import org.apache.tapestry5.runtime.PageLifecycleCallbackHub; 038import org.apache.tapestry5.runtime.PageLifecycleListener; 039import org.apache.tapestry5.runtime.RenderQueue; 040import org.apache.tapestry5.services.pageload.ComponentResourceSelector; 041import org.slf4j.Logger; 042 043import java.lang.annotation.Annotation; 044import java.lang.reflect.Type; 045import java.util.List; 046import java.util.Locale; 047import java.util.Map; 048 049/** 050 * The bridge between a component and its {@link ComponentPageElement}, that supplies all kinds of 051 * resources to the 052 * component, including access to its parameters, parameter bindings, and persistent field data. 053 */ 054@SuppressWarnings("all") 055public class InternalComponentResourcesImpl extends LockSupport implements InternalComponentResources 056{ 057 private final Page page; 058 059 private final String completeId; 060 061 private final String nestedId; 062 063 private final ComponentModel componentModel; 064 065 private final ComponentPageElement element; 066 067 private final Component component; 068 069 private final ComponentResources containerResources; 070 071 private final ComponentPageElementResources elementResources; 072 073 private final boolean mixin; 074 075 private static final Object[] EMPTY = new Object[0]; 076 077 private static final AnnotationProvider NULL_ANNOTATION_PROVIDER = new NullAnnotationProvider(); 078 079 // Map from parameter name to binding. This is mutable but not guarded by the lazy creation lock, as it is only 080 // written to during page load, not at runtime. 081 private NamedSet<Binding> bindings; 082 083 // Maps from parameter name to ParameterConduit, used to support mixins 084 // which need access to the containing component's PC's 085 // Guarded by: LockSupport 086 private NamedSet<ParameterConduit> conduits; 087 088 // Guarded by: LockSupport 089 private Messages messages; 090 091 // Guarded by: LockSupport 092 private boolean informalsComputed; 093 094 // Guarded by: LockSupport 095 private PerThreadValue<Map<String, Object>> renderVariables; 096 097 // Guarded by: LockSupport 098 private Informal firstInformal; 099 100 101 /** 102 * We keep a linked list of informal parameters, which saves us the expense of determining which 103 * bindings are formal 104 * and which are informal. Each Informal points to the next. 105 */ 106 private class Informal 107 { 108 private final String name; 109 110 private final Binding binding; 111 112 final Informal next; 113 114 private Informal(String name, Binding binding, Informal next) 115 { 116 this.name = name; 117 this.binding = binding; 118 this.next = next; 119 } 120 121 void write(MarkupWriter writer) 122 { 123 Object value = binding.get(); 124 125 if (value == null) 126 return; 127 128 if (value instanceof Block) 129 return; 130 131 // If it's already a String, don't use the TypeCoercer (renderInformalParameters is 132 // a CPU hotspot, as is TypeCoercer.coerce). 133 134 String valueString = value instanceof String ? (String) value : elementResources 135 .coerce(value, String.class); 136 137 writer.attributes(name, valueString); 138 } 139 } 140 141 142 private static Worker<ParameterConduit> RESET_PARAMETER_CONDUIT = new Worker<ParameterConduit>() 143 { 144 public void work(ParameterConduit value) 145 { 146 value.reset(); 147 } 148 }; 149 150 public InternalComponentResourcesImpl(Page page, ComponentPageElement element, 151 ComponentResources containerResources, ComponentPageElementResources elementResources, String completeId, 152 String nestedId, Instantiator componentInstantiator, boolean mixin) 153 { 154 this.page = page; 155 this.element = element; 156 this.containerResources = containerResources; 157 this.elementResources = elementResources; 158 this.completeId = completeId; 159 this.nestedId = nestedId; 160 this.mixin = mixin; 161 162 componentModel = componentInstantiator.getModel(); 163 component = componentInstantiator.newInstance(this); 164 } 165 166 public boolean isMixin() 167 { 168 return mixin; 169 } 170 171 public Location getLocation() 172 { 173 return element.getLocation(); 174 } 175 176 public String toString() 177 { 178 return String.format("InternalComponentResources[%s]", getCompleteId()); 179 } 180 181 public ComponentModel getComponentModel() 182 { 183 return componentModel; 184 } 185 186 public Component getEmbeddedComponent(String embeddedId) 187 { 188 return element.getEmbeddedElement(embeddedId).getComponent(); 189 } 190 191 public Object getFieldChange(String fieldName) 192 { 193 return page.getFieldChange(nestedId, fieldName); 194 } 195 196 public String getId() 197 { 198 return element.getId(); 199 } 200 201 public boolean hasFieldChange(String fieldName) 202 { 203 return getFieldChange(fieldName) != null; 204 } 205 206 public Link createEventLink(String eventType, Object... context) 207 { 208 return element.createEventLink(eventType, context); 209 } 210 211 public Link createActionLink(String eventType, boolean forForm, Object... context) 212 { 213 return element.createActionLink(eventType, forForm, context); 214 } 215 216 public Link createFormEventLink(String eventType, Object... context) 217 { 218 return element.createFormEventLink(eventType, context); 219 } 220 221 public Link createPageLink(String pageName, boolean override, Object... context) 222 { 223 return element.createPageLink(pageName, override, context); 224 } 225 226 public Link createPageLink(Class pageClass, boolean override, Object... context) 227 { 228 return element.createPageLink(pageClass, override, context); 229 } 230 231 public void discardPersistentFieldChanges() 232 { 233 page.discardPersistentFieldChanges(); 234 } 235 236 public String getElementName() 237 { 238 return getElementName(null); 239 } 240 241 public List<String> getInformalParameterNames() 242 { 243 return InternalUtils.sortedKeys(getInformalParameterBindings()); 244 } 245 246 public <T> T getInformalParameter(String name, Class<T> type) 247 { 248 Binding binding = getBinding(name); 249 250 Object value = binding == null ? null : binding.get(); 251 252 return elementResources.coerce(value, type); 253 } 254 255 public Block getBody() 256 { 257 return element.getBody(); 258 } 259 260 public boolean hasBody() 261 { 262 return element.hasBody(); 263 } 264 265 public String getCompleteId() 266 { 267 return completeId; 268 } 269 270 public Component getComponent() 271 { 272 return component; 273 } 274 275 public boolean isBound(String parameterName) 276 { 277 return getBinding(parameterName) != null; 278 } 279 280 public <T extends Annotation> T getParameterAnnotation(String parameterName, Class<T> annotationType) 281 { 282 Binding binding = getBinding(parameterName); 283 284 return binding == null ? null : binding.getAnnotation(annotationType); 285 } 286 287 public boolean isRendering() 288 { 289 return element.isRendering(); 290 } 291 292 public boolean triggerEvent(String eventType, Object[] context, ComponentEventCallback handler) 293 { 294 return element.triggerEvent(eventType, defaulted(context), handler); 295 } 296 297 private static Object[] defaulted(Object[] input) 298 { 299 return input == null ? EMPTY : input; 300 } 301 302 public boolean triggerContextEvent(String eventType, EventContext context, ComponentEventCallback callback) 303 { 304 return element.triggerContextEvent(eventType, context, callback); 305 } 306 307 public String getNestedId() 308 { 309 return nestedId; 310 } 311 312 public Component getPage() 313 { 314 return element.getContainingPage().getRootComponent(); 315 } 316 317 public boolean isLoaded() 318 { 319 return element.isLoaded(); 320 } 321 322 public void persistFieldChange(String fieldName, Object newValue) 323 { 324 try 325 { 326 page.persistFieldChange(this, fieldName, newValue); 327 } catch (Exception ex) 328 { 329 throw new TapestryException(StructureMessages.fieldPersistFailure(getCompleteId(), fieldName, ex), 330 getLocation(), ex); 331 } 332 } 333 334 public void bindParameter(String parameterName, Binding binding) 335 { 336 if (bindings == null) 337 bindings = NamedSet.create(); 338 339 bindings.put(parameterName, binding); 340 } 341 342 public Class getBoundType(String parameterName) 343 { 344 Binding binding = getBinding(parameterName); 345 346 return binding == null ? null : binding.getBindingType(); 347 } 348 349 public Type getBoundGenericType(String parameterName) 350 { 351 Binding binding = getBinding(parameterName); 352 Type genericType; 353 if (binding instanceof Binding2) { 354 genericType = ((Binding2) binding).getBindingGenericType(); 355 } else { 356 genericType = binding.getBindingType(); 357 } 358 return genericType; 359 } 360 361 362 public Binding getBinding(String parameterName) 363 { 364 return NamedSet.get(bindings, parameterName); 365 } 366 367 public AnnotationProvider getAnnotationProvider(String parameterName) 368 { 369 Binding binding = getBinding(parameterName); 370 371 return binding == null ? NULL_ANNOTATION_PROVIDER : binding; 372 } 373 374 public Logger getLogger() 375 { 376 return componentModel.getLogger(); 377 } 378 379 public Component getMixinByClassName(String mixinClassName) 380 { 381 return element.getMixinByClassName(mixinClassName); 382 } 383 384 public void renderInformalParameters(MarkupWriter writer) 385 { 386 if (bindings == null) 387 return; 388 389 for (Informal i = firstInformal(); i != null; i = i.next) 390 i.write(writer); 391 } 392 393 private Informal firstInformal() 394 { 395 try 396 { 397 acquireReadLock(); 398 399 if (!informalsComputed) 400 { 401 computeInformals(); 402 } 403 404 return firstInformal; 405 } finally 406 { 407 releaseReadLock(); 408 } 409 } 410 411 private void computeInformals() 412 { 413 try 414 { 415 upgradeReadLockToWriteLock(); 416 417 if (!informalsComputed) 418 { 419 for (Map.Entry<String, Binding> e : getInformalParameterBindings().entrySet()) 420 { 421 firstInformal = new Informal(e.getKey(), e.getValue(), firstInformal); 422 } 423 424 informalsComputed = true; 425 } 426 } finally 427 { 428 downgradeWriteLockToReadLock(); 429 } 430 } 431 432 public Component getContainer() 433 { 434 if (containerResources == null) 435 { 436 return null; 437 } 438 439 return containerResources.getComponent(); 440 } 441 442 public ComponentResources getContainerResources() 443 { 444 return containerResources; 445 } 446 447 public Messages getContainerMessages() 448 { 449 return containerResources != null ? containerResources.getMessages() : null; 450 } 451 452 public Locale getLocale() 453 { 454 return element.getLocale(); 455 } 456 457 public ComponentResourceSelector getResourceSelector() 458 { 459 return element.getResourceSelector(); 460 } 461 462 public Messages getMessages() 463 { 464 if (messages == null) 465 { 466 // This kind of lazy loading pattern is acceptable without locking. 467 // Changes to the messages field are atomic; in some race conditions, the call to 468 // getMessages() may occur more than once (but it caches the value anyway). 469 messages = elementResources.getMessages(componentModel); 470 } 471 472 return messages; 473 } 474 475 public String getElementName(String defaultElementName) 476 { 477 return element.getElementName(defaultElementName); 478 } 479 480 public Block getBlock(String blockId) 481 { 482 return element.getBlock(blockId); 483 } 484 485 public Block getBlockParameter(String parameterName) 486 { 487 return getInformalParameter(parameterName, Block.class); 488 } 489 490 public Block findBlock(String blockId) 491 { 492 return element.findBlock(blockId); 493 } 494 495 public Resource getBaseResource() 496 { 497 return componentModel.getBaseResource(); 498 } 499 500 public String getPageName() 501 { 502 return element.getPageName(); 503 } 504 505 public Map<String, Binding> getInformalParameterBindings() 506 { 507 Map<String, Binding> result = CollectionFactory.newMap(); 508 509 for (String name : NamedSet.getNames(bindings)) 510 { 511 if (componentModel.getParameterModel(name) != null) 512 continue; 513 514 result.put(name, bindings.get(name)); 515 } 516 517 return result; 518 } 519 520 private Map<String, Object> getRenderVariables(boolean create) 521 { 522 try 523 { 524 acquireReadLock(); 525 526 if (renderVariables == null) 527 { 528 if (!create) 529 { 530 return null; 531 } 532 533 createRenderVariablesPerThreadValue(); 534 } 535 536 Map<String, Object> result = renderVariables.get(); 537 538 if (result == null && create) 539 result = renderVariables.set(CollectionFactory.newCaseInsensitiveMap()); 540 541 return result; 542 } finally 543 { 544 releaseReadLock(); 545 } 546 } 547 548 private void createRenderVariablesPerThreadValue() 549 { 550 try 551 { 552 upgradeReadLockToWriteLock(); 553 554 if (renderVariables == null) 555 { 556 renderVariables = elementResources.createPerThreadValue(); 557 } 558 559 } finally 560 { 561 downgradeWriteLockToReadLock(); 562 } 563 } 564 565 public Object getRenderVariable(String name) 566 { 567 Map<String, Object> variablesMap = getRenderVariables(false); 568 569 Object result = InternalUtils.get(variablesMap, name); 570 571 if (result == null) 572 { 573 throw new IllegalArgumentException(StructureMessages.missingRenderVariable(getCompleteId(), name, 574 variablesMap == null ? null : variablesMap.keySet())); 575 } 576 577 return result; 578 } 579 580 public void storeRenderVariable(String name, Object value) 581 { 582 assert InternalUtils.isNonBlank(name); 583 assert value != null; 584 585 Map<String, Object> renderVariables = getRenderVariables(true); 586 587 renderVariables.put(name, value); 588 } 589 590 public void postRenderCleanup() 591 { 592 Map<String, Object> variablesMap = getRenderVariables(false); 593 594 if (variablesMap != null) 595 variablesMap.clear(); 596 597 resetParameterConduits(); 598 } 599 600 public void addPageLifecycleListener(PageLifecycleListener listener) 601 { 602 page.addLifecycleListener(listener); 603 } 604 605 public void removePageLifecycleListener(PageLifecycleListener listener) 606 { 607 page.removeLifecycleListener(listener); 608 } 609 610 public void addPageResetListener(PageResetListener listener) 611 { 612 page.addResetListener(listener); 613 } 614 615 private void resetParameterConduits() 616 { 617 try 618 { 619 acquireReadLock(); 620 621 if (conduits != null) 622 { 623 conduits.eachValue(RESET_PARAMETER_CONDUIT); 624 } 625 } finally 626 { 627 releaseReadLock(); 628 } 629 } 630 631 public ParameterConduit getParameterConduit(String parameterName) 632 { 633 try 634 { 635 acquireReadLock(); 636 return NamedSet.get(conduits, parameterName); 637 } finally 638 { 639 releaseReadLock(); 640 } 641 } 642 643 public void setParameterConduit(String parameterName, ParameterConduit conduit) 644 { 645 try 646 { 647 acquireReadLock(); 648 649 if (conduits == null) 650 { 651 createConduits(); 652 } 653 654 conduits.put(parameterName, conduit); 655 } finally 656 { 657 releaseReadLock(); 658 } 659 } 660 661 private void createConduits() 662 { 663 try 664 { 665 upgradeReadLockToWriteLock(); 666 if (conduits == null) 667 { 668 conduits = NamedSet.create(); 669 } 670 } finally 671 { 672 downgradeWriteLockToReadLock(); 673 } 674 } 675 676 677 public String getPropertyName(String parameterName) 678 { 679 Binding binding = getBinding(parameterName); 680 681 if (binding == null) 682 { 683 return null; 684 } 685 686 // TAP5-1718: we need the full prop binding expression, not just the (final) property name 687 if (binding instanceof PropBinding) 688 { 689 return ((PropBinding) binding).getExpression(); 690 } 691 692 if (binding instanceof InternalPropBinding) 693 { 694 return ((InternalPropBinding) binding).getPropertyName(); 695 } 696 697 return null; 698 } 699 700 /** 701 * @since 5.3 702 */ 703 public void render(MarkupWriter writer, RenderQueue queue) 704 { 705 queue.push(element); 706 } 707 708 public PageLifecycleCallbackHub getPageLifecycleCallbackHub() 709 { 710 return page; 711 } 712}