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.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.LockSupport; 032 import org.apache.tapestry5.ioc.internal.util.TapestryException; 033 import org.apache.tapestry5.ioc.services.PerThreadValue; 034 import org.apache.tapestry5.model.ComponentModel; 035 import org.apache.tapestry5.runtime.Component; 036 import org.apache.tapestry5.runtime.PageLifecycleCallbackHub; 037 import org.apache.tapestry5.runtime.PageLifecycleListener; 038 import org.apache.tapestry5.runtime.RenderQueue; 039 import org.apache.tapestry5.services.pageload.ComponentResourceSelector; 040 import org.slf4j.Logger; 041 042 import java.lang.annotation.Annotation; 043 import java.util.List; 044 import java.util.Locale; 045 import 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") 053 public 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 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 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 if (binding instanceof InternalPropBinding) 672 { 673 return ((InternalPropBinding) binding).getPropertyName(); 674 } 675 676 return null; 677 } 678 679 /** 680 * @since 5.3 681 */ 682 public void render(MarkupWriter writer, RenderQueue queue) 683 { 684 queue.push(element); 685 } 686 687 @Override 688 public PageLifecycleCallbackHub getPageLifecycleCallbackHub() 689 { 690 return page; 691 } 692 }