KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > tapestry > services > impl > ComponentTemplateLoaderLogic


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 java.util.HashSet JavaDoc;
18 import java.util.Iterator JavaDoc;
19 import java.util.Map JavaDoc;
20 import java.util.Set JavaDoc;
21
22 import org.apache.commons.logging.Log;
23 import org.apache.hivemind.ApplicationRuntimeException;
24 import org.apache.hivemind.Location;
25 import org.apache.tapestry.BaseComponent;
26 import org.apache.tapestry.IBinding;
27 import org.apache.tapestry.IComponent;
28 import org.apache.tapestry.IRender;
29 import org.apache.tapestry.IRequestCycle;
30 import org.apache.tapestry.ITemplateComponent;
31 import org.apache.tapestry.Tapestry;
32 import org.apache.tapestry.binding.BindingConstants;
33 import org.apache.tapestry.binding.BindingSource;
34 import org.apache.tapestry.binding.BindingUtils;
35 import org.apache.tapestry.binding.LiteralBinding;
36 import org.apache.tapestry.engine.IPageLoader;
37 import org.apache.tapestry.parse.CloseToken;
38 import org.apache.tapestry.parse.ComponentTemplate;
39 import org.apache.tapestry.parse.LocalizationToken;
40 import org.apache.tapestry.parse.OpenToken;
41 import org.apache.tapestry.parse.TemplateToken;
42 import org.apache.tapestry.parse.TextToken;
43 import org.apache.tapestry.parse.TokenType;
44 import org.apache.tapestry.services.TemplateSource;
45 import org.apache.tapestry.spec.IComponentSpecification;
46 import org.apache.tapestry.spec.IContainedComponent;
47 import org.apache.tapestry.spec.IParameterSpecification;
48
49 /**
50  * Contains the logic from {@link org.apache.tapestry.services.impl.ComponentTemplateLoaderImpl},
51  * which creates one of these instances to process the request. This is necessary because the
52  * service must be re-entrant (because templates can contain components that have templates).
53  *
54  * @author Howard Lewis Ship
55  * @since 4.0
56  */

57 public class ComponentTemplateLoaderLogic
58 {
59     private Log _log;
60
61     private IPageLoader _pageLoader;
62
63     private IRequestCycle _requestCycle;
64
65     private ITemplateComponent _loadComponent;
66
67     private BindingSource _bindingSource;
68
69     private IComponent[] _stack;
70
71     private int _stackx;
72
73     private IComponent _activeComponent = null;
74
75     private Set JavaDoc _seenIds = new HashSet JavaDoc();
76
77     public ComponentTemplateLoaderLogic(Log log, IPageLoader pageLoader, BindingSource bindingSource)
78     {
79         _log = log;
80         _pageLoader = pageLoader;
81         _bindingSource = bindingSource;
82     }
83
84     public void loadTemplate(IRequestCycle requestCycle, ITemplateComponent loadComponent,
85             ComponentTemplate template)
86     {
87         _requestCycle = requestCycle;
88         _loadComponent = loadComponent;
89
90         process(template);
91     }
92
93     private void process(ComponentTemplate template)
94     {
95         int count = template.getTokenCount();
96
97         _stack = new IComponent[count];
98
99         for (int i = 0; i < count; i++)
100         {
101             TemplateToken token = template.getToken(i);
102
103             TokenType type = token.getType();
104
105             if (type == TokenType.TEXT)
106             {
107                 process((TextToken) token);
108                 continue;
109             }
110
111             if (type == TokenType.OPEN)
112             {
113                 process((OpenToken) token);
114                 continue;
115             }
116
117             if (type == TokenType.CLOSE)
118             {
119                 process((CloseToken) token);
120                 continue;
121             }
122
123             if (type == TokenType.LOCALIZATION)
124             {
125                 process((LocalizationToken) token);
126                 continue;
127             }
128         }
129
130         // This is also pretty much unreachable, and the message is kind of out
131
// of date, too.
132

133         if (_stackx != 0)
134             throw new ApplicationRuntimeException(Tapestry
135                     .getMessage("BaseComponent.unbalance-open-tags"), _loadComponent, null, null);
136
137         checkAllComponentsReferenced();
138     }
139
140     /**
141      * Adds the token (which implements {@link IRender}) to the active component (using
142      * {@link IComponent#addBody(IRender)}), or to this component
143      * {@link BaseComponent#addOuter(IRender)}.
144      * <p>
145      * A check is made that the active component allows a body.
146      */

147
148     private void process(TextToken token)
149     {
150         if (_activeComponent == null)
151         {
152             _loadComponent.addOuter(token);
153             return;
154         }
155
156         if (!_activeComponent.getSpecification().getAllowBody())
157             throw createBodylessComponentException(_activeComponent);
158
159         _activeComponent.addBody(token);
160     }
161
162     private void process(OpenToken token)
163     {
164         String JavaDoc id = token.getId();
165         IComponent component = null;
166         String JavaDoc componentType = token.getComponentType();
167
168         if (componentType == null)
169             component = getEmbeddedComponent(id);
170         else
171         {
172             checkForDuplicateId(id, token.getLocation());
173
174             component = createImplicitComponent(id, componentType, token.getLocation());
175         }
176
177         // Make sure the template contains each component only once.
178

179         if (_seenIds.contains(id))
180             throw new ApplicationRuntimeException(ImplMessages.multipleComponentReferences(
181                     _loadComponent,
182                     id), _loadComponent, token.getLocation(), null);
183
184         _seenIds.add(id);
185
186         if (_activeComponent == null)
187             _loadComponent.addOuter(component);
188         else
189         {
190             // Note: this code may no longer be reachable (because the
191
// template parser does this check first).
192

193             if (!_activeComponent.getSpecification().getAllowBody())
194                 throw createBodylessComponentException(_activeComponent);
195
196             _activeComponent.addBody(component);
197         }
198
199         addTemplateBindings(component, token);
200
201         _stack[_stackx++] = _activeComponent;
202
203         _activeComponent = component;
204     }
205
206     private void checkForDuplicateId(String JavaDoc id, Location location)
207     {
208         if (id == null)
209             return;
210
211         IContainedComponent cc = _loadComponent.getSpecification().getComponent(id);
212
213         if (cc != null)
214             throw new ApplicationRuntimeException(ImplMessages.dupeComponentId(id, cc),
215                     _loadComponent, location, null);
216     }
217
218     private IComponent createImplicitComponent(String JavaDoc id, String JavaDoc componentType, Location location)
219     {
220         IComponent result = _pageLoader.createImplicitComponent(
221                 _requestCycle,
222                 _loadComponent,
223                 id,
224                 componentType,
225                 location);
226
227         return result;
228     }
229
230     private IComponent getEmbeddedComponent(String JavaDoc id)
231     {
232         return _loadComponent.getComponent(id);
233     }
234
235     private void process(CloseToken token)
236     {
237         // Again, this is pretty much impossible to reach because
238
// the template parser does a great job.
239

240         if (_stackx <= 0)
241             throw new ApplicationRuntimeException(ImplMessages.unbalancedCloseTags(),
242                     _loadComponent, token.getLocation(), null);
243
244         // Null and forget the top element on the stack.
245

246         _stack[_stackx--] = null;
247
248         _activeComponent = _stack[_stackx];
249     }
250
251     private void process(LocalizationToken token)
252     {
253         IRender render = new LocalizedStringRender(_loadComponent, token);
254
255         if (_activeComponent == null)
256             _loadComponent.addOuter(render);
257         else
258             _activeComponent.addBody(render);
259     }
260
261     /**
262      * Adds bindings based on attributes in the template.
263      */

264
265     void addTemplateBindings(IComponent component, OpenToken token)
266     {
267         IComponentSpecification spec = component.getSpecification();
268
269         Map JavaDoc attributes = token.getAttributesMap();
270
271         if (attributes != null)
272         {
273             Iterator JavaDoc i = attributes.entrySet().iterator();
274
275             while (i.hasNext())
276             {
277                 Map.Entry JavaDoc entry = (Map.Entry JavaDoc) i.next();
278
279                 String JavaDoc attributeName = (String JavaDoc) entry.getKey();
280                 String JavaDoc value = (String JavaDoc) entry.getValue();
281
282                 IParameterSpecification pspec = spec.getParameter(attributeName);
283                 String JavaDoc parameterName = pspec == null ? attributeName : pspec.getParameterName();
284
285                 if (!attributeName.equals(parameterName))
286                     _log.error(ImplMessages.usedTemplateParameterAlias(
287                             token,
288                             attributeName,
289                             parameterName));
290
291                 String JavaDoc description = ImplMessages.templateParameterName(parameterName);
292
293                 // For informal parameters, or formal parameters that don't define a default binding
294
// type,
295
// treat the value as a literal.
296

297                 String JavaDoc defaultBindingType = BindingUtils.getDefaultBindingType(
298                         spec,
299                         parameterName,
300                         BindingConstants.LITERAL_PREFIX);
301
302                 IBinding binding = _bindingSource.createBinding(
303                         _loadComponent,
304                         description,
305                         value,
306                         defaultBindingType,
307                         token.getLocation());
308
309                 addBinding(component, spec, parameterName, binding);
310             }
311         }
312
313         // if the component defines a templateTag parameter and
314
// there is no established binding for that parameter,
315
// add a static binding carrying the template tag
316

317         if (spec.getParameter(TemplateSource.TEMPLATE_TAG_PARAMETER_NAME) != null
318                 && component.getBinding(TemplateSource.TEMPLATE_TAG_PARAMETER_NAME) == null)
319         {
320             IBinding binding = _bindingSource.createBinding(
321                     component,
322                     TemplateSource.TEMPLATE_TAG_PARAMETER_NAME,
323                     token.getTag(),
324                     BindingConstants.LITERAL_PREFIX,
325                     token.getLocation());
326
327             addBinding(component, spec, TemplateSource.TEMPLATE_TAG_PARAMETER_NAME, binding);
328         }
329     }
330
331     /**
332      * Adds an expression binding, checking for errors related to reserved and informal parameters.
333      * <p>
334      * It is an error to specify expression bindings in both the specification and the template.
335      */

336
337     private void addBinding(IComponent component, IComponentSpecification spec, String JavaDoc name,
338             IBinding binding)
339     {
340
341         // If matches a formal parameter name, allow it to be set
342
// unless there's already a binding.
343

344         boolean valid = validate(component, spec, name, binding);
345
346         if (valid)
347             component.setBinding(name, binding);
348     }
349
350     private boolean validate(IComponent component, IComponentSpecification spec, String JavaDoc name,
351             IBinding binding)
352     {
353         // TODO: This is ugly! Need a better/smarter way, even if we have to extend BindingSource
354
// to tell us.
355

356         boolean literal = binding instanceof LiteralBinding;
357
358         boolean isFormal = (spec.getParameter(name) != null);
359
360         if (isFormal)
361         {
362             if (component.getBinding(name) != null)
363             {
364                 // Literal bindings in the template that conflict with bound parameters
365
// from the spec are silently ignored.
366

367                 if (literal)
368                     return false;
369
370                 throw new ApplicationRuntimeException(ImplMessages.dupeTemplateBinding(
371                         name,
372                         component,
373                         _loadComponent), component, binding.getLocation(), null);
374             }
375
376             return true;
377         }
378
379         if (!spec.getAllowInformalParameters())
380         {
381             // Again; if informal parameters are disallowed, ignore literal bindings, as they
382
// are there as placeholders or for WYSIWYG.
383

384             if (literal)
385                 return false;
386
387             throw new ApplicationRuntimeException(ImplMessages.templateBindingForInformalParameter(
388                     _loadComponent,
389                     name,
390                     component), component, binding.getLocation(), null);
391         }
392
393         // If the name is reserved (matches a formal parameter
394
// or reserved name, caselessly), then skip it.
395

396         if (spec.isReservedParameterName(name))
397         {
398             // Final case for literals: if they conflict with a reserved name, they are ignored.
399
// Again, there for WYSIWYG.
400

401             if (literal)
402                 return false;
403
404             throw new ApplicationRuntimeException(ImplMessages.templateBindingForReservedParameter(
405                     _loadComponent,
406                     name,
407                     component), component, binding.getLocation(), null);
408         }
409
410         return true;
411     }
412
413     private void checkAllComponentsReferenced()
414     {
415         // First, contruct a modifiable copy of the ids of all expected components
416
// (that is, components declared in the specification).
417

418         Map JavaDoc components = _loadComponent.getComponents();
419
420         Set JavaDoc ids = components.keySet();
421
422         // If the seen ids ... ids referenced in the template, matches
423
// all the ids in the specification then we're fine.
424

425         if (_seenIds.containsAll(ids))
426             return;
427
428         // Create a modifiable copy. Remove the ids that are referenced in
429
// the template. The remainder are worthy of note.
430

431         ids = new HashSet JavaDoc(ids);
432         ids.removeAll(_seenIds);
433
434         _log.error(ImplMessages.missingComponentSpec(_loadComponent, ids));
435
436     }
437
438     private ApplicationRuntimeException createBodylessComponentException(IComponent component)
439     {
440         return new ApplicationRuntimeException(ImplMessages.bodylessComponent(), component, null,
441                 null);
442     }
443 }
Popular Tags