001 // Copyright 2006, 2007, 2008, 2009, 2010, 2011 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.TapestryException; 032 import org.apache.tapestry5.ioc.services.PerThreadValue; 033 import org.apache.tapestry5.model.ComponentModel; 034 import org.apache.tapestry5.runtime.Component; 035 import org.apache.tapestry5.runtime.PageLifecycleAdapter; 036 import org.apache.tapestry5.runtime.PageLifecycleListener; 037 import org.apache.tapestry5.runtime.RenderQueue; 038 import org.apache.tapestry5.services.pageload.ComponentResourceSelector; 039 import org.slf4j.Logger; 040 041 import java.lang.annotation.Annotation; 042 import java.util.List; 043 import java.util.Locale; 044 import java.util.Map; 045 046 /** 047 * The bridge between a component and its {@link ComponentPageElement}, that supplies all kinds of 048 * resources to the 049 * component, including access to its parameters, parameter bindings, and persistent field data. 050 */ 051 @SuppressWarnings("all") 052 public class InternalComponentResourcesImpl implements InternalComponentResources 053 { 054 private final Page page; 055 056 private final String completeId; 057 058 private final String nestedId; 059 060 private final ComponentModel componentModel; 061 062 private final ComponentPageElement element; 063 064 private final Component component; 065 066 private final ComponentResources containerResources; 067 068 private final ComponentPageElementResources elementResources; 069 070 private final boolean mixin; 071 072 private static final Object[] EMPTY = new Object[0]; 073 074 private static final AnnotationProvider NULL_ANNOTATION_PROVIDER = new NullAnnotationProvider(); 075 076 // Case insensitive map from parameter name to binding 077 private NamedSet<Binding> bindings; 078 079 // Case insensitive map from parameter name to ParameterConduit, used to support mixins 080 // which need access to the containing component's PC's 081 private NamedSet<ParameterConduit> conduits; 082 083 private Messages messages; 084 085 private boolean informalsComputed; 086 087 private PerThreadValue<Map<String, Object>> renderVariables; 088 089 private Informal firstInformal; 090 091 /** 092 * We keep a linked list of informal parameters, which saves us the expense of determining which 093 * bindings are formal 094 * and which are informal. Each Informal points to the next. 095 */ 096 private class Informal 097 { 098 private final String name; 099 100 private final Binding binding; 101 102 final Informal next; 103 104 private Informal(String name, Binding binding, Informal next) 105 { 106 this.name = name; 107 this.binding = binding; 108 this.next = next; 109 } 110 111 void write(MarkupWriter writer) 112 { 113 Object value = binding.get(); 114 115 if (value == null) 116 return; 117 118 if (value instanceof Block) 119 return; 120 121 // If it's already a String, don't use the TypeCoercer (renderInformalParameters is 122 // a CPU hotspot, as is TypeCoercer.coerce). 123 124 String valueString = value instanceof String ? (String) value : elementResources 125 .coerce(value, String.class); 126 127 writer.attributes(name, valueString); 128 } 129 } 130 131 132 private static Worker<ParameterConduit> RESET_PARAMETER_CONDUIT = new Worker<ParameterConduit>() 133 { 134 public void work(ParameterConduit value) 135 { 136 value.reset(); 137 } 138 }; 139 140 private static Worker<ParameterConduit> LOAD_PARAMETER_CONDUIT = new Worker<ParameterConduit>() 141 { 142 public void work(ParameterConduit value) 143 { 144 value.load(); 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 synchronized Informal firstInformal() 379 { 380 if (!informalsComputed) 381 { 382 for (Map.Entry<String, Binding> e : getInformalParameterBindings().entrySet()) 383 { 384 firstInformal = new Informal(e.getKey(), e.getValue(), firstInformal); 385 } 386 387 informalsComputed = true; 388 } 389 390 return firstInformal; 391 } 392 393 public Component getContainer() 394 { 395 if (containerResources == null) 396 return null; 397 398 return containerResources.getComponent(); 399 } 400 401 public ComponentResources getContainerResources() 402 { 403 return containerResources; 404 } 405 406 public Messages getContainerMessages() 407 { 408 return containerResources != null ? containerResources.getMessages() : null; 409 } 410 411 public Locale getLocale() 412 { 413 return element.getLocale(); 414 } 415 416 public ComponentResourceSelector getResourceSelector() 417 { 418 return element.getResourceSelector(); 419 } 420 421 public synchronized Messages getMessages() 422 { 423 if (messages == null) 424 messages = elementResources.getMessages(componentModel); 425 426 return messages; 427 } 428 429 public String getElementName(String defaultElementName) 430 { 431 return element.getElementName(defaultElementName); 432 } 433 434 public Block getBlock(String blockId) 435 { 436 return element.getBlock(blockId); 437 } 438 439 public Block getBlockParameter(String parameterName) 440 { 441 return getInformalParameter(parameterName, Block.class); 442 } 443 444 public Block findBlock(String blockId) 445 { 446 return element.findBlock(blockId); 447 } 448 449 public Resource getBaseResource() 450 { 451 return componentModel.getBaseResource(); 452 } 453 454 public String getPageName() 455 { 456 return element.getPageName(); 457 } 458 459 public Map<String, Binding> getInformalParameterBindings() 460 { 461 Map<String, Binding> result = CollectionFactory.newMap(); 462 463 for (String name : NamedSet.getNames(bindings)) 464 { 465 if (componentModel.getParameterModel(name) != null) 466 continue; 467 468 result.put(name, bindings.get(name)); 469 } 470 471 return result; 472 } 473 474 private synchronized Map<String, Object> getRenderVariables(boolean create) 475 { 476 if (renderVariables == null) 477 { 478 if (!create) 479 return null; 480 481 renderVariables = elementResources.createPerThreadValue(); 482 } 483 484 Map<String, Object> result = renderVariables.get(); 485 486 if (result == null && create) 487 result = renderVariables.set(CollectionFactory.newCaseInsensitiveMap()); 488 489 return result; 490 } 491 492 public Object getRenderVariable(String name) 493 { 494 Map<String, Object> variablesMap = getRenderVariables(false); 495 496 Object result = InternalUtils.get(variablesMap, name); 497 498 if (result == null) 499 throw new IllegalArgumentException(StructureMessages.missingRenderVariable(getCompleteId(), name, 500 variablesMap == null ? null : variablesMap.keySet())); 501 502 return result; 503 } 504 505 public void storeRenderVariable(String name, Object value) 506 { 507 assert InternalUtils.isNonBlank(name); 508 assert value != null; 509 510 Map<String, Object> renderVariables = getRenderVariables(true); 511 512 renderVariables.put(name, value); 513 } 514 515 public void postRenderCleanup() 516 { 517 Map<String, Object> variablesMap = getRenderVariables(false); 518 519 if (variablesMap != null) 520 variablesMap.clear(); 521 522 resetParameterConduits(); 523 } 524 525 public void addPageLifecycleListener(PageLifecycleListener listener) 526 { 527 page.addLifecycleListener(listener); 528 } 529 530 public void removePageLifecycleListener(PageLifecycleListener listener) 531 { 532 page.removeLifecycleListener(listener); 533 } 534 535 public void addPageResetListener(PageResetListener listener) 536 { 537 page.addResetListener(listener); 538 } 539 540 541 542 private synchronized void resetParameterConduits() 543 { 544 if (conduits != null) 545 { 546 conduits.eachValue(RESET_PARAMETER_CONDUIT); 547 } 548 } 549 550 private synchronized void loadParameterConduits() 551 { 552 // Don't need the conduits != null, because this method will only be invoked 553 // when conduits is non-null. 554 555 conduits.eachValue(LOAD_PARAMETER_CONDUIT); 556 } 557 558 559 public synchronized ParameterConduit getParameterConduit(String parameterName) 560 { 561 return NamedSet.get(conduits, parameterName); 562 } 563 564 public synchronized void setParameterConduit(String parameterName, ParameterConduit conduit) 565 { 566 if (conduits == null) 567 { 568 conduits = NamedSet.create(); 569 570 page.addLifecycleListener(new PageLifecycleAdapter() 571 { 572 @Override 573 public void containingPageDidLoad() 574 { 575 loadParameterConduits(); 576 } 577 }); 578 579 } 580 581 conduits.put(parameterName, conduit); 582 } 583 584 585 public String getPropertyName(String parameterName) 586 { 587 Binding binding = getBinding(parameterName); 588 589 if (binding == null) 590 { 591 return null; 592 } 593 594 if (binding instanceof InternalPropBinding) 595 { 596 return ((InternalPropBinding) binding).getPropertyName(); 597 } 598 599 return null; 600 } 601 602 /** @since 5.3 */ 603 public void render(MarkupWriter writer, RenderQueue queue) 604 { 605 queue.push(element); 606 } 607 }