001// Copyright 2004, 2005 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.tapestry.services.impl; 016 017import java.util.HashSet; 018import java.util.Iterator; 019import java.util.Map; 020import java.util.Set; 021 022import org.apache.commons.logging.Log; 023import org.apache.hivemind.ApplicationRuntimeException; 024import org.apache.hivemind.Location; 025import org.apache.tapestry.IBinding; 026import org.apache.tapestry.IComponent; 027import org.apache.tapestry.IRender; 028import org.apache.tapestry.IRequestCycle; 029import org.apache.tapestry.ITemplateComponent; 030import org.apache.tapestry.Tapestry; 031import org.apache.tapestry.binding.BindingConstants; 032import org.apache.tapestry.binding.BindingSource; 033import org.apache.tapestry.binding.LiteralBinding; 034import org.apache.tapestry.engine.IPageLoader; 035import org.apache.tapestry.parse.CloseToken; 036import org.apache.tapestry.parse.ComponentTemplate; 037import org.apache.tapestry.parse.LocalizationToken; 038import org.apache.tapestry.parse.OpenToken; 039import org.apache.tapestry.parse.TemplateToken; 040import org.apache.tapestry.parse.TextToken; 041import org.apache.tapestry.parse.TokenType; 042import org.apache.tapestry.services.TemplateSource; 043import org.apache.tapestry.spec.IComponentSpecification; 044import org.apache.tapestry.spec.IContainedComponent; 045import org.apache.tapestry.spec.IParameterSpecification; 046 047/** 048 * Contains the logic from {@link org.apache.tapestry.services.impl.ComponentTemplateLoaderImpl}, 049 * which creates one of these instances to process the request. This is necessary because the 050 * service must be re-entrant (because templates can contain components that have templates). 051 * 052 * @author Howard Lewis Ship 053 * @since 4.0 054 */ 055public class ComponentTemplateLoaderLogic 056{ 057 private Log _log; 058 059 private IPageLoader _pageLoader; 060 061 private IRequestCycle _requestCycle; 062 063 private ITemplateComponent _loadComponent; 064 065 private BindingSource _bindingSource; 066 067 private IComponent[] _stack; 068 069 private int _stackx; 070 071 private IComponent _activeComponent = null; 072 073 private Set _seenIds = new HashSet(); 074 075 public ComponentTemplateLoaderLogic(Log log, IPageLoader pageLoader, BindingSource bindingSource) 076 { 077 _log = log; 078 _pageLoader = pageLoader; 079 _bindingSource = bindingSource; 080 } 081 082 public void loadTemplate(IRequestCycle requestCycle, ITemplateComponent loadComponent, 083 ComponentTemplate template) 084 { 085 _requestCycle = requestCycle; 086 _loadComponent = loadComponent; 087 088 process(template); 089 } 090 091 private void process(ComponentTemplate template) 092 { 093 int count = template.getTokenCount(); 094 095 _stack = new IComponent[count]; 096 097 for (int i = 0; i < count; i++) 098 { 099 TemplateToken token = template.getToken(i); 100 101 TokenType type = token.getType(); 102 103 if (type == TokenType.TEXT) 104 { 105 process((TextToken) token); 106 continue; 107 } 108 109 if (type == TokenType.OPEN) 110 { 111 process((OpenToken) token); 112 continue; 113 } 114 115 if (type == TokenType.CLOSE) 116 { 117 process((CloseToken) token); 118 continue; 119 } 120 121 if (type == TokenType.LOCALIZATION) 122 { 123 process((LocalizationToken) token); 124 continue; 125 } 126 } 127 128 // This is also pretty much unreachable, and the message is kind of out 129 // of date, too. 130 131 if (_stackx != 0) 132 throw new ApplicationRuntimeException(Tapestry 133 .getMessage("BaseComponent.unbalance-open-tags"), _loadComponent, null, null); 134 135 checkAllComponentsReferenced(); 136 } 137 138 /** 139 * Adds the token (which implements {@link IRender}) to the active component (using 140 * {@link IComponent#addBody(IRender)}), or to this component 141 * {@link BaseComponent#addOuter(IRender)}. 142 * <p> 143 * A check is made that the active component allows a body. 144 */ 145 146 private void process(TextToken token) 147 { 148 if (_activeComponent == null) 149 { 150 _loadComponent.addOuter(token); 151 return; 152 } 153 154 if (!_activeComponent.getSpecification().getAllowBody()) 155 throw createBodylessComponentException(_activeComponent); 156 157 _activeComponent.addBody(token); 158 } 159 160 private void process(OpenToken token) 161 { 162 String id = token.getId(); 163 IComponent component = null; 164 String componentType = token.getComponentType(); 165 166 if (componentType == null) 167 component = getEmbeddedComponent(id); 168 else 169 { 170 checkForDuplicateId(id, token.getLocation()); 171 172 component = createImplicitComponent(id, componentType, token.getLocation()); 173 } 174 175 // Make sure the template contains each component only once. 176 177 if (_seenIds.contains(id)) 178 throw new ApplicationRuntimeException(ImplMessages.multipleComponentReferences( 179 _loadComponent, 180 id), _loadComponent, token.getLocation(), null); 181 182 _seenIds.add(id); 183 184 if (_activeComponent == null) 185 _loadComponent.addOuter(component); 186 else 187 { 188 // Note: this code may no longer be reachable (because the 189 // template parser does this check first). 190 191 if (!_activeComponent.getSpecification().getAllowBody()) 192 throw createBodylessComponentException(_activeComponent); 193 194 _activeComponent.addBody(component); 195 } 196 197 addTemplateBindings(component, token); 198 199 _stack[_stackx++] = _activeComponent; 200 201 _activeComponent = component; 202 } 203 204 private void checkForDuplicateId(String id, Location location) 205 { 206 if (id == null) 207 return; 208 209 IContainedComponent cc = _loadComponent.getSpecification().getComponent(id); 210 211 if (cc != null) 212 throw new ApplicationRuntimeException(ImplMessages.dupeComponentId(id, cc), 213 _loadComponent, location, null); 214 } 215 216 private IComponent createImplicitComponent(String id, String componentType, Location location) 217 { 218 IComponent result = _pageLoader.createImplicitComponent( 219 _requestCycle, 220 _loadComponent, 221 id, 222 componentType, 223 location); 224 225 return result; 226 } 227 228 private IComponent getEmbeddedComponent(String id) 229 { 230 return _loadComponent.getComponent(id); 231 } 232 233 private void process(CloseToken token) 234 { 235 // Again, this is pretty much impossible to reach because 236 // the template parser does a great job. 237 238 if (_stackx <= 0) 239 throw new ApplicationRuntimeException(ImplMessages.unbalancedCloseTags(), 240 _loadComponent, token.getLocation(), null); 241 242 // Null and forget the top element on the stack. 243 244 _stack[_stackx--] = null; 245 246 _activeComponent = _stack[_stackx]; 247 } 248 249 private void process(LocalizationToken token) 250 { 251 IRender render = new LocalizedStringRender(_loadComponent, token); 252 253 if (_activeComponent == null) 254 _loadComponent.addOuter(render); 255 else 256 _activeComponent.addBody(render); 257 } 258 259 /** 260 * Adds bindings based on attributes in the template. 261 */ 262 263 void addTemplateBindings(IComponent component, OpenToken token) 264 { 265 IComponentSpecification spec = component.getSpecification(); 266 267 Map attributes = token.getAttributesMap(); 268 269 if (attributes != null) 270 { 271 Iterator i = attributes.entrySet().iterator(); 272 273 while (i.hasNext()) 274 { 275 Map.Entry entry = (Map.Entry) i.next(); 276 277 String attributeName = (String) entry.getKey(); 278 String value = (String) entry.getValue(); 279 280 IParameterSpecification pspec = spec.getParameter(attributeName); 281 String parameterName = pspec == null ? attributeName : pspec.getParameterName(); 282 283 if (!attributeName.equals(parameterName)) 284 _log.warn(ImplMessages.usedTemplateParameterAlias( 285 token, 286 attributeName, 287 parameterName)); 288 289 String description = ImplMessages.templateParameterName(parameterName); 290 291 // Values in a template are always literal, unless prefixed. 292 293 IBinding binding = _bindingSource.createBinding( 294 _loadComponent, 295 description, 296 value, 297 BindingConstants.LITERAL_PREFIX, 298 token.getLocation()); 299 300 addBinding(component, spec, parameterName, binding); 301 } 302 } 303 304 // if the component defines a templateTag parameter and 305 // there is no established binding for that parameter, 306 // add a static binding carrying the template tag 307 308 if (spec.getParameter(TemplateSource.TEMPLATE_TAG_PARAMETER_NAME) != null 309 && component.getBinding(TemplateSource.TEMPLATE_TAG_PARAMETER_NAME) == null) 310 { 311 IBinding binding = _bindingSource.createBinding( 312 component, 313 TemplateSource.TEMPLATE_TAG_PARAMETER_NAME, 314 token.getTag(), 315 BindingConstants.LITERAL_PREFIX, 316 token.getLocation()); 317 318 addBinding(component, spec, TemplateSource.TEMPLATE_TAG_PARAMETER_NAME, binding); 319 } 320 } 321 322 /** 323 * Adds an expression binding, checking for errors related to reserved and informal parameters. 324 * <p> 325 * It is an error to specify expression bindings in both the specification and the template. 326 */ 327 328 private void addBinding(IComponent component, IComponentSpecification spec, String name, 329 IBinding binding) 330 { 331 332 // If matches a formal parameter name, allow it to be set 333 // unless there's already a binding. 334 335 boolean valid = validate(component, spec, name, binding); 336 337 if (valid) 338 component.setBinding(name, binding); 339 } 340 341 private boolean validate(IComponent component, IComponentSpecification spec, String name, 342 IBinding binding) 343 { 344 // TODO: This is ugly! Need a better/smarter way, even if we have to extend BindingSource 345 // to tell us. 346 347 boolean isLiteral = binding instanceof LiteralBinding; 348 boolean isBound = component.getBinding(name) != null; 349 boolean isFormal = spec.getParameter(name) != null; 350 351 if (!isFormal) 352 { 353 if (!spec.getAllowInformalParameters()) 354 { 355 // Again; if informal parameters are disallowed, ignore literal bindings, as they 356 // are there as placeholders or for WYSIWYG. 357 358 if (isLiteral) 359 return false; 360 361 throw new ApplicationRuntimeException(ImplMessages 362 .templateBindingForInformalParameter(_loadComponent, name, component), 363 component, binding.getLocation(), null); 364 } 365 366 // If the name is reserved (matches a formal parameter 367 // or reserved name, caselessly), then skip it. 368 369 if (spec.isReservedParameterName(name)) 370 { 371 // Final case for literals: if they conflict with a reserved name, they are ignored. 372 // Again, there for WYSIWYG. 373 374 if (isLiteral) 375 return false; 376 377 throw new ApplicationRuntimeException(ImplMessages 378 .templateBindingForReservedParameter(_loadComponent, name, component), 379 component, binding.getLocation(), null); 380 } 381 } 382 383 // So, at this point it doesn't matter if the parameter is a formal parameter or 384 // an informal parameter. The binding (if any) in the specification takes precendence 385 // over the template. Literal bindings that conflict are considered to be there for WYSIWYG 386 // purposes. Non-literal bindings that conflict with a specification binding are an 387 // error. 388 389 if (isBound) 390 { 391 // Literal bindings in the template that conflict with bound parameters 392 // from the spec are silently ignored. 393 394 if (isLiteral) 395 return false; 396 397 throw new ApplicationRuntimeException(ImplMessages.dupeTemplateBinding( 398 name, 399 component, 400 _loadComponent), component, binding.getLocation(), null); 401 } 402 403 return true; 404 405 } 406 407 private void checkAllComponentsReferenced() 408 { 409 // First, contruct a modifiable copy of the ids of all expected components 410 // (that is, components declared in the specification). 411 412 Map components = _loadComponent.getComponents(); 413 414 Set ids = components.keySet(); 415 416 // If the seen ids ... ids referenced in the template, matches 417 // all the ids in the specification then we're fine. 418 419 if (_seenIds.containsAll(ids)) 420 return; 421 422 // Create a modifiable copy. Remove the ids that are referenced in 423 // the template. The remainder are worthy of note. 424 425 ids = new HashSet(ids); 426 ids.removeAll(_seenIds); 427 428 _log.warn(ImplMessages.missingComponentSpec(_loadComponent, ids)); 429 430 } 431 432 private ApplicationRuntimeException createBodylessComponentException(IComponent component) 433 { 434 return new ApplicationRuntimeException(ImplMessages.bodylessComponent(), component, null, 435 null); 436 } 437}