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