Coverage Report - org.apache.tapestry.services.impl.ComponentTemplateLoaderLogic
 
Classes in this File Line Coverage Branch Coverage Complexity
ComponentTemplateLoaderLogic
0%
0/126
0%
0/66
4.067
 
 1  
 // Copyright 2004, 2005 The Apache Software Foundation
 2  
 //
 3  
 // Licensed under the Apache License, Version 2.0 (the "License");
 4  
 // you may not use this file except in compliance with the License.
 5  
 // You may obtain a copy of the License at
 6  
 //
 7  
 //     http://www.apache.org/licenses/LICENSE-2.0
 8  
 //
 9  
 // Unless required by applicable law or agreed to in writing, software
 10  
 // distributed under the License is distributed on an "AS IS" BASIS,
 11  
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12  
 // See the License for the specific language governing permissions and
 13  
 // limitations under the License.
 14  
 
 15  
 package org.apache.tapestry.services.impl;
 16  
 
 17  
 import org.apache.commons.logging.Log;
 18  
 import org.apache.hivemind.ApplicationRuntimeException;
 19  
 import org.apache.hivemind.Location;
 20  
 import org.apache.tapestry.*;
 21  
 import org.apache.tapestry.binding.BindingConstants;
 22  
 import org.apache.tapestry.binding.BindingSource;
 23  
 import org.apache.tapestry.binding.LiteralBinding;
 24  
 import org.apache.tapestry.engine.IPageLoader;
 25  
 import org.apache.tapestry.parse.*;
 26  
 import org.apache.tapestry.services.TemplateSource;
 27  
 import org.apache.tapestry.spec.IComponentSpecification;
 28  
 import org.apache.tapestry.spec.IContainedComponent;
 29  
 import org.apache.tapestry.spec.IParameterSpecification;
 30  
 
 31  
 import java.util.HashSet;
 32  
 import java.util.Iterator;
 33  
 import java.util.Map;
 34  
 import java.util.Set;
 35  
 
 36  
 /**
 37  
  * Contains the logic from {@link org.apache.tapestry.services.impl.ComponentTemplateLoaderImpl},
 38  
  * which creates one of these instances to process the request. This is necessary because the
 39  
  * service must be re-entrant (because templates can contain components that have templates).
 40  
  *
 41  
  */
 42  
 public class ComponentTemplateLoaderLogic
 43  
 {
 44  
     private Log _log;
 45  
 
 46  
     private IPageLoader _pageLoader;
 47  
 
 48  
     private IRequestCycle _requestCycle;
 49  
 
 50  
     private ITemplateComponent _loadComponent;
 51  
 
 52  
     private BindingSource _bindingSource;
 53  
 
 54  
     private IComponent[] _stack;
 55  
 
 56  
     private int _stackx;
 57  
 
 58  0
     private IComponent _activeComponent = null;
 59  
 
 60  0
     private Set _seenIds = new HashSet();
 61  
 
 62  
     public ComponentTemplateLoaderLogic(Log log, IPageLoader pageLoader, BindingSource bindingSource)
 63  0
     {
 64  0
         _log = log;
 65  0
         _pageLoader = pageLoader;
 66  0
         _bindingSource = bindingSource;
 67  0
     }
 68  
 
 69  
     public void loadTemplate(IRequestCycle requestCycle, ITemplateComponent loadComponent,
 70  
                              ComponentTemplate template)
 71  
     {
 72  0
         _requestCycle = requestCycle;
 73  0
         _loadComponent = loadComponent;
 74  
 
 75  0
         process(template);
 76  0
     }
 77  
 
 78  
     private void process(ComponentTemplate template)
 79  
     {
 80  0
         int count = template.getTokenCount();
 81  
 
 82  0
         _stack = new IComponent[count];
 83  
 
 84  0
         for (int i = 0; i < count; i++)
 85  
         {
 86  0
             TemplateToken token = template.getToken(i);
 87  
 
 88  0
             TokenType type = token.getType();
 89  
 
 90  0
             if (type == TokenType.TEXT)
 91  
             {
 92  0
                 process((TextToken) token);
 93  0
                 continue;
 94  
             }
 95  
 
 96  0
             if (type == TokenType.OPEN)
 97  
             {
 98  0
                 process((OpenToken) token);
 99  0
                 continue;
 100  
             }
 101  
 
 102  0
             if (type == TokenType.CLOSE)
 103  
             {
 104  0
                 process((CloseToken) token);
 105  0
                 continue;
 106  
             }
 107  
 
 108  0
             if (type == TokenType.LOCALIZATION)
 109  
             {
 110  0
                 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  0
         if (_stackx != 0)
 118  0
             throw new ApplicationRuntimeException(Tapestry.getMessage("BaseComponent.unbalance-open-tags"),
 119  
                                                   _loadComponent, null, null);
 120  
 
 121  0
         checkAllComponentsReferenced();
 122  0
     }
 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  0
         if (_activeComponent == null)
 135  
         {
 136  0
             _loadComponent.addOuter(token);
 137  0
             return;
 138  
         }
 139  
 
 140  0
         if (!_activeComponent.getSpecification().getAllowBody())
 141  0
             throw createBodylessComponentException(_activeComponent);
 142  
 
 143  0
         _activeComponent.addBody(token);
 144  0
     }
 145  
 
 146  
     private void process(OpenToken token)
 147  
     {
 148  0
         String id = token.getId();
 149  0
         IComponent component = null;
 150  0
         String componentType = token.getComponentType();
 151  
 
 152  0
         if (componentType == null)
 153  0
             component = getEmbeddedComponent(id);
 154  
         else
 155  
         {
 156  0
             checkForDuplicateId(id, token.getLocation());
 157  
 
 158  0
             component = createImplicitComponent(id, componentType, token.getLocation());
 159  
         }
 160  
 
 161  
         // Make sure the template contains each component only once.
 162  
 
 163  0
         if (_seenIds.contains(id))
 164  0
             throw new ApplicationRuntimeException(ImplMessages.multipleComponentReferences(_loadComponent,id),
 165  
                                                   _loadComponent, token.getLocation(), null);
 166  
 
 167  0
         _seenIds.add(id);
 168  
 
 169  0
         if (_activeComponent == null)
 170  0
             _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  0
             if (!_activeComponent.getSpecification().getAllowBody())
 177  0
                 throw createBodylessComponentException(_activeComponent);
 178  
 
 179  0
             _activeComponent.addBody(component);
 180  
         }
 181  
 
 182  0
         addTemplateBindings(component, token);
 183  
 
 184  0
         _stack[_stackx++] = _activeComponent;
 185  
 
 186  0
         _activeComponent = component;
 187  0
     }
 188  
 
 189  
     private void checkForDuplicateId(String id, Location location)
 190  
     {
 191  0
         if (id == null)
 192  0
             return;
 193  
 
 194  0
         IContainedComponent cc = _loadComponent.getSpecification().getComponent(id);
 195  
 
 196  0
         if (cc != null)
 197  0
             throw new ApplicationRuntimeException(ImplMessages.dupeComponentId(id, cc),
 198  
                                                   _loadComponent, location, null);
 199  0
     }
 200  
 
 201  
     private IComponent createImplicitComponent(String id, String componentType, Location location)
 202  
     {
 203  0
         return _pageLoader.createImplicitComponent(
 204  
                 _requestCycle,
 205  
                 _loadComponent,
 206  
                 id,
 207  
                 componentType,
 208  
                 location);
 209  
     }
 210  
 
 211  
     private IComponent getEmbeddedComponent(String id)
 212  
     {
 213  0
         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  0
         if (_stackx <= 0)
 222  0
             throw new ApplicationRuntimeException(ImplMessages.unbalancedCloseTags(),
 223  
                                                   _loadComponent, token.getLocation(), null);
 224  
 
 225  
         // Null and forget the top element on the stack.
 226  
 
 227  0
         _stack[_stackx--] = null;
 228  
 
 229  0
         _activeComponent = _stack[_stackx];
 230  0
     }
 231  
 
 232  
     private void process(LocalizationToken token)
 233  
     {
 234  0
         IRender render = new LocalizedStringRender(_loadComponent, token);
 235  
 
 236  0
         if (_activeComponent == null)
 237  0
             _loadComponent.addOuter(render);
 238  
         else
 239  0
             _activeComponent.addBody(render);
 240  0
     }
 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  0
         component.setTemplateTagName(token.getTag());
 251  
 
 252  0
         IComponentSpecification spec = component.getSpecification();
 253  
 
 254  0
         Map attributes = token.getAttributesMap();
 255  
 
 256  0
         if (attributes != null)
 257  
         {
 258  0
             Iterator i = attributes.entrySet().iterator();
 259  
 
 260  0
             while (i.hasNext())
 261  
             {
 262  0
                 Map.Entry entry = (Map.Entry) i.next();
 263  
 
 264  0
                 String attributeName = (String) entry.getKey();
 265  0
                 String value = (String) entry.getValue();
 266  
 
 267  0
                 IParameterSpecification pspec = spec.getParameter(attributeName);
 268  0
                 String parameterName = pspec == null ? attributeName : pspec.getParameterName();
 269  
 
 270  0
                 if (!attributeName.equals(parameterName))
 271  0
                     _log.warn(ImplMessages.usedTemplateParameterAlias(token, attributeName, parameterName));
 272  
 
 273  0
                 String description = ImplMessages.templateParameterName(parameterName);
 274  
 
 275  
                 // Values in a template are always literal, unless prefixed.
 276  
 
 277  0
                 IBinding binding = _bindingSource.createBinding(
 278  
                         _loadComponent,
 279  
                         pspec,
 280  
                         description,
 281  
                         value,
 282  
                         BindingConstants.LITERAL_PREFIX,
 283  
                         token.getLocation());
 284  
 
 285  0
                 addBinding(component, spec, parameterName, binding);
 286  0
             }
 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  0
         if (spec.getParameter(TemplateSource.TEMPLATE_TAG_PARAMETER_NAME) != null
 294  
             && component.getBinding(TemplateSource.TEMPLATE_TAG_PARAMETER_NAME) == null)
 295  
         {
 296  0
             IBinding binding = _bindingSource.createBinding(
 297  
                     component,
 298  
                     TemplateSource.TEMPLATE_TAG_PARAMETER_NAME,
 299  
                     token.getTag(),
 300  
                     BindingConstants.LITERAL_PREFIX,
 301  
                     token.getLocation());
 302  
 
 303  0
             addBinding(component, spec, TemplateSource.TEMPLATE_TAG_PARAMETER_NAME, binding);
 304  
         }
 305  0
     }
 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  0
         boolean valid = validate(component, spec, name, binding);
 321  
 
 322  0
         if (valid)
 323  0
             component.setBinding(name, binding);
 324  0
     }
 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  0
         boolean isLiteral = binding instanceof LiteralBinding;
 333  0
         boolean isBound = component.getBinding(name) != null;
 334  0
         boolean isFormal = spec.getParameter(name) != null;
 335  
 
 336  0
         if (!isFormal)
 337  
         {
 338  0
             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  0
                 if (isLiteral)
 344  0
                     return false;
 345  
 
 346  0
                 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  0
             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  0
                 if (isLiteral)
 359  0
                     return false;
 360  
 
 361  0
                 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  0
         if (isBound)
 373  
         {
 374  
             // Literal bindings in the template that conflict with bound parameters
 375  
             // from the spec are silently ignored.
 376  
 
 377  0
             if (isLiteral)
 378  0
                 return false;
 379  
 
 380  0
             throw new ApplicationRuntimeException(ImplMessages.dupeTemplateBinding(
 381  
                     name,
 382  
                     component,
 383  
                     _loadComponent), component, binding.getLocation(), null);
 384  
         }
 385  
 
 386  0
         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  0
         Map components = _loadComponent.getComponents();
 396  
 
 397  0
         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  0
         if (_seenIds.containsAll(ids))
 403  0
             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  0
         ids = new HashSet(ids);
 409  0
         ids.removeAll(_seenIds);
 410  
 
 411  0
         _log.warn(ImplMessages.missingComponentSpec(_loadComponent, ids));
 412  
 
 413  0
     }
 414  
 
 415  
     private ApplicationRuntimeException createBodylessComponentException(IComponent component)
 416  
     {
 417  0
         return new ApplicationRuntimeException(ImplMessages.bodylessComponent(), component, null, null);
 418  
     }
 419  
 }