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