|
|||||||||||||||||||
30 day Evaluation Version distributed via the Maven Jar Repository. Clover is not free. You have 30 days to evaluate it. Please visit http://www.thecortex.net/clover to obtain a licensed version of Clover | |||||||||||||||||||
Source file | Conditionals | Statements | Methods | TOTAL | |||||||||||||||
ComponentTemplateLoaderLogic.java | 83.9% | 89.9% | 93.3% | 88.3% |
|
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;
|
|
18 |
import java.util.Iterator;
|
|
19 |
import java.util.Map;
|
|
20 |
import java.util.Set;
|
|
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 |
|
|
48 |
/**
|
|
49 |
* Contains the logic from {@link org.apache.tapestry.services.impl.ComponentTemplateLoaderImpl},
|
|
50 |
* which creates one of these instances to process the request. This is necessary because the
|
|
51 |
* service must be re-entrant (because templates can contain components that have templates).
|
|
52 |
*
|
|
53 |
* @author Howard Lewis Ship
|
|
54 |
* @since 4.0
|
|
55 |
*/
|
|
56 |
public class ComponentTemplateLoaderLogic |
|
57 |
{ |
|
58 |
private Log _log;
|
|
59 |
|
|
60 |
private IPageLoader _pageLoader;
|
|
61 |
|
|
62 |
private IRequestCycle _requestCycle;
|
|
63 |
|
|
64 |
private ITemplateComponent _loadComponent;
|
|
65 |
|
|
66 |
private BindingSource _bindingSource;
|
|
67 |
|
|
68 |
private IComponent[] _stack;
|
|
69 |
|
|
70 |
private int _stackx; |
|
71 |
|
|
72 |
private IComponent _activeComponent = null; |
|
73 |
|
|
74 |
private Set _seenIds = new HashSet(); |
|
75 |
|
|
76 | 227 |
public ComponentTemplateLoaderLogic(Log log, IPageLoader pageLoader, BindingSource bindingSource)
|
77 |
{ |
|
78 | 227 |
_log = log; |
79 | 227 |
_pageLoader = pageLoader; |
80 | 227 |
_bindingSource = bindingSource; |
81 |
} |
|
82 |
|
|
83 | 227 |
public void loadTemplate(IRequestCycle requestCycle, ITemplateComponent loadComponent, |
84 |
ComponentTemplate template) |
|
85 |
{ |
|
86 | 227 |
_requestCycle = requestCycle; |
87 | 227 |
_loadComponent = loadComponent; |
88 |
|
|
89 | 227 |
process(template); |
90 |
} |
|
91 |
|
|
92 | 227 |
private void process(ComponentTemplate template) |
93 |
{ |
|
94 | 227 |
int count = template.getTokenCount();
|
95 |
|
|
96 | 227 |
_stack = new IComponent[count];
|
97 |
|
|
98 | 227 |
for (int i = 0; i < count; i++) |
99 |
{ |
|
100 | 3668 |
TemplateToken token = template.getToken(i); |
101 |
|
|
102 | 3668 |
TokenType type = token.getType(); |
103 |
|
|
104 | 3668 |
if (type == TokenType.TEXT)
|
105 |
{ |
|
106 | 1620 |
process((TextToken) token); |
107 | 1620 |
continue;
|
108 |
} |
|
109 |
|
|
110 | 2048 |
if (type == TokenType.OPEN)
|
111 |
{ |
|
112 | 1023 |
process((OpenToken) token); |
113 | 1016 |
continue;
|
114 |
} |
|
115 |
|
|
116 | 1025 |
if (type == TokenType.CLOSE)
|
117 |
{ |
|
118 | 1011 |
process((CloseToken) token); |
119 | 1011 |
continue;
|
120 |
} |
|
121 |
|
|
122 | 14 |
if (type == TokenType.LOCALIZATION)
|
123 |
{ |
|
124 | 14 |
process((LocalizationToken) token); |
125 | 14 |
continue;
|
126 |
} |
|
127 |
} |
|
128 |
|
|
129 |
// This is also pretty much unreachable, and the message is kind of out
|
|
130 |
// of date, too.
|
|
131 |
|
|
132 | 220 |
if (_stackx != 0)
|
133 | 0 |
throw new ApplicationRuntimeException(Tapestry |
134 |
.getMessage("BaseComponent.unbalance-open-tags"), _loadComponent, null, null); |
|
135 |
|
|
136 | 220 |
checkAllComponentsReferenced(); |
137 |
} |
|
138 |
|
|
139 |
/**
|
|
140 |
* Adds the token (which implements {@link IRender}) to the active component (using
|
|
141 |
* {@link IComponent#addBody(IRender)}), or to this component
|
|
142 |
* {@link BaseComponent#addOuter(IRender)}.
|
|
143 |
* <p>
|
|
144 |
* A check is made that the active component allows a body.
|
|
145 |
*/
|
|
146 |
|
|
147 | 1620 |
private void process(TextToken token) |
148 |
{ |
|
149 | 1620 |
if (_activeComponent == null) |
150 |
{ |
|
151 | 441 |
_loadComponent.addOuter(token); |
152 | 441 |
return;
|
153 |
} |
|
154 |
|
|
155 | 1179 |
if (!_activeComponent.getSpecification().getAllowBody())
|
156 | 0 |
throw createBodylessComponentException(_activeComponent);
|
157 |
|
|
158 | 1179 |
_activeComponent.addBody(token); |
159 |
} |
|
160 |
|
|
161 | 1023 |
private void process(OpenToken token) |
162 |
{ |
|
163 | 1023 |
String id = token.getId(); |
164 | 1023 |
IComponent component = null;
|
165 | 1023 |
String componentType = token.getComponentType(); |
166 |
|
|
167 | 1023 |
if (componentType == null) |
168 | 413 |
component = getEmbeddedComponent(id); |
169 |
else
|
|
170 |
{ |
|
171 | 610 |
checkForDuplicateId(id, token.getLocation()); |
172 |
|
|
173 | 609 |
component = createImplicitComponent(id, componentType, token.getLocation()); |
174 |
} |
|
175 |
|
|
176 |
// Make sure the template contains each component only once.
|
|
177 |
|
|
178 | 1019 |
if (_seenIds.contains(id))
|
179 | 0 |
throw new ApplicationRuntimeException(ImplMessages.multipleComponentReferences( |
180 |
_loadComponent, |
|
181 |
id), _loadComponent, token.getLocation(), null);
|
|
182 |
|
|
183 | 1019 |
_seenIds.add(id); |
184 |
|
|
185 | 1019 |
if (_activeComponent == null) |
186 | 338 |
_loadComponent.addOuter(component); |
187 |
else
|
|
188 |
{ |
|
189 |
// Note: this code may no longer be reachable (because the
|
|
190 |
// template parser does this check first).
|
|
191 |
|
|
192 | 681 |
if (!_activeComponent.getSpecification().getAllowBody())
|
193 | 0 |
throw createBodylessComponentException(_activeComponent);
|
194 |
|
|
195 | 681 |
_activeComponent.addBody(component); |
196 |
} |
|
197 |
|
|
198 | 1019 |
addTemplateBindings(component, token); |
199 |
|
|
200 | 1016 |
_stack[_stackx++] = _activeComponent; |
201 |
|
|
202 | 1016 |
_activeComponent = component; |
203 |
} |
|
204 |
|
|
205 | 610 |
private void checkForDuplicateId(String id, Location location) |
206 |
{ |
|
207 | 610 |
if (id == null) |
208 | 0 |
return;
|
209 |
|
|
210 | 610 |
IContainedComponent cc = _loadComponent.getSpecification().getComponent(id); |
211 |
|
|
212 | 610 |
if (cc != null) |
213 | 1 |
throw new ApplicationRuntimeException(ImplMessages.dupeComponentId(id, cc), |
214 |
_loadComponent, location, null);
|
|
215 |
} |
|
216 |
|
|
217 | 609 |
private IComponent createImplicitComponent(String id, String componentType, Location location)
|
218 |
{ |
|
219 | 609 |
IComponent result = _pageLoader.createImplicitComponent( |
220 |
_requestCycle, |
|
221 |
_loadComponent, |
|
222 |
id, |
|
223 |
componentType, |
|
224 |
location); |
|
225 |
|
|
226 | 606 |
return result;
|
227 |
} |
|
228 |
|
|
229 | 413 |
private IComponent getEmbeddedComponent(String id)
|
230 |
{ |
|
231 | 413 |
return _loadComponent.getComponent(id);
|
232 |
} |
|
233 |
|
|
234 | 1011 |
private void process(CloseToken token) |
235 |
{ |
|
236 |
// Again, this is pretty much impossible to reach because
|
|
237 |
// the template parser does a great job.
|
|
238 |
|
|
239 | 1011 |
if (_stackx <= 0)
|
240 | 0 |
throw new ApplicationRuntimeException(ImplMessages.unbalancedCloseTags(), |
241 |
_loadComponent, token.getLocation(), null);
|
|
242 |
|
|
243 |
// Null and forget the top element on the stack.
|
|
244 |
|
|
245 | 1011 |
_stack[_stackx--] = null;
|
246 |
|
|
247 | 1011 |
_activeComponent = _stack[_stackx]; |
248 |
} |
|
249 |
|
|
250 | 14 |
private void process(LocalizationToken token) |
251 |
{ |
|
252 | 14 |
IRender render = new LocalizedStringRender(_loadComponent, token);
|
253 |
|
|
254 | 14 |
if (_activeComponent == null) |
255 | 7 |
_loadComponent.addOuter(render); |
256 |
else
|
|
257 | 7 |
_activeComponent.addBody(render); |
258 |
} |
|
259 |
|
|
260 |
/**
|
|
261 |
* Adds bindings based on attributes in the template.
|
|
262 |
*/
|
|
263 |
|
|
264 | 1019 |
private void addTemplateBindings(IComponent component, OpenToken token) |
265 |
{ |
|
266 | 1019 |
IComponentSpecification spec = component.getSpecification(); |
267 |
|
|
268 | 1019 |
Map attributes = token.getAttributesMap(); |
269 |
|
|
270 | 1019 |
if (attributes != null) |
271 |
{ |
|
272 | 539 |
Iterator i = attributes.entrySet().iterator(); |
273 |
|
|
274 | 539 |
while (i.hasNext())
|
275 |
{ |
|
276 | 711 |
Map.Entry entry = (Map.Entry) i.next(); |
277 |
|
|
278 | 711 |
String name = (String) entry.getKey(); |
279 | 711 |
String value = (String) entry.getValue(); |
280 |
|
|
281 | 711 |
String description = ImplMessages.templateParameterName(name); |
282 |
|
|
283 |
// For informal parameters, or formal parameters that don't define a default binding
|
|
284 |
// type,
|
|
285 |
// treat the value as a literal.
|
|
286 |
|
|
287 | 711 |
String defaultBindingType = BindingUtils.getDefaultBindingType( |
288 |
spec, |
|
289 |
name, |
|
290 |
BindingConstants.LITERAL_PREFIX); |
|
291 |
|
|
292 | 711 |
IBinding binding = _bindingSource.createBinding( |
293 |
_loadComponent, |
|
294 |
description, |
|
295 |
value, |
|
296 |
defaultBindingType, |
|
297 |
token.getLocation()); |
|
298 |
|
|
299 | 711 |
addBinding(component, spec, name, binding); |
300 |
} |
|
301 |
} |
|
302 |
|
|
303 |
// if the component defines a templateTag parameter and
|
|
304 |
// there is no established binding for that parameter,
|
|
305 |
// add a static binding carrying the template tag
|
|
306 |
|
|
307 | 1016 |
if (spec.getParameter(TemplateSource.TEMPLATE_TAG_PARAMETER_NAME) != null |
308 |
&& component.getBinding(TemplateSource.TEMPLATE_TAG_PARAMETER_NAME) == null)
|
|
309 |
{ |
|
310 | 6 |
IBinding binding = _bindingSource.createBinding( |
311 |
component, |
|
312 |
TemplateSource.TEMPLATE_TAG_PARAMETER_NAME, |
|
313 |
token.getTag(), |
|
314 |
BindingConstants.LITERAL_PREFIX, |
|
315 |
token.getLocation()); |
|
316 |
|
|
317 | 6 |
addBinding(component, spec, TemplateSource.TEMPLATE_TAG_PARAMETER_NAME, binding); |
318 |
} |
|
319 |
} |
|
320 |
|
|
321 |
/**
|
|
322 |
* Adds an expression binding, checking for errors related to reserved and informal parameters.
|
|
323 |
* <p>
|
|
324 |
* It is an error to specify expression bindings in both the specification and the template.
|
|
325 |
*/
|
|
326 |
|
|
327 | 717 |
private void addBinding(IComponent component, IComponentSpecification spec, String name, |
328 |
IBinding binding) |
|
329 |
{ |
|
330 |
|
|
331 |
// If matches a formal parameter name, allow it to be set
|
|
332 |
// unless there's already a binding.
|
|
333 |
|
|
334 | 717 |
boolean valid = validate(component, spec, name, binding);
|
335 |
|
|
336 | 714 |
if (valid)
|
337 | 684 |
component.setBinding(name, binding); |
338 |
} |
|
339 |
|
|
340 | 717 |
private boolean validate(IComponent component, IComponentSpecification spec, String name, |
341 |
IBinding binding) |
|
342 |
{ |
|
343 |
// TODO: This is ugly! Need a better/smarter way, even if we have to extend BindingSource
|
|
344 |
// to tell us.
|
|
345 |
|
|
346 | 717 |
boolean literal = binding instanceof LiteralBinding; |
347 |
|
|
348 | 717 |
boolean isFormal = (spec.getParameter(name) != null); |
349 |
|
|
350 | 717 |
if (isFormal)
|
351 |
{ |
|
352 | 650 |
if (component.getBinding(name) != null) |
353 |
{ |
|
354 |
// Literal bindings in the template that conflict with bound parameters
|
|
355 |
// from the spec are silently ignored.
|
|
356 |
|
|
357 | 2 |
if (literal)
|
358 | 1 |
return false; |
359 |
|
|
360 | 1 |
throw new ApplicationRuntimeException(ImplMessages.dupeTemplateBinding( |
361 |
name, |
|
362 |
component, |
|
363 |
_loadComponent), component, binding.getLocation(), null);
|
|
364 |
} |
|
365 |
|
|
366 | 648 |
return true; |
367 |
} |
|
368 |
|
|
369 | 67 |
if (!spec.getAllowInformalParameters())
|
370 |
{ |
|
371 |
// Again; if informal parameters are disallowed, ignore literal bindings, as they
|
|
372 |
// are there as placeholders or for WYSIWYG.
|
|
373 |
|
|
374 | 1 |
if (literal)
|
375 | 0 |
return false; |
376 |
|
|
377 | 1 |
throw new ApplicationRuntimeException(ImplMessages.templateBindingForInformalParameter( |
378 |
_loadComponent, |
|
379 |
name, |
|
380 |
component), component, binding.getLocation(), null);
|
|
381 |
} |
|
382 |
|
|
383 |
// If the name is reserved (matches a formal parameter
|
|
384 |
// or reserved name, caselessly), then skip it.
|
|
385 |
|
|
386 | 66 |
if (spec.isReservedParameterName(name))
|
387 |
{ |
|
388 |
// Final case for literals: if they conflict with a reserved name, they are ignored.
|
|
389 |
// Again, there for WYSIWYG.
|
|
390 |
|
|
391 | 30 |
if (literal)
|
392 | 29 |
return false; |
393 |
|
|
394 | 1 |
throw new ApplicationRuntimeException(ImplMessages.templateBindingForReservedParameter( |
395 |
_loadComponent, |
|
396 |
name, |
|
397 |
component), component, binding.getLocation(), null);
|
|
398 |
} |
|
399 |
|
|
400 | 36 |
return true; |
401 |
} |
|
402 |
|
|
403 | 220 |
private void checkAllComponentsReferenced() |
404 |
{ |
|
405 |
// First, contruct a modifiable copy of the ids of all expected components
|
|
406 |
// (that is, components declared in the specification).
|
|
407 |
|
|
408 | 220 |
Map components = _loadComponent.getComponents(); |
409 |
|
|
410 | 220 |
Set ids = components.keySet(); |
411 |
|
|
412 |
// If the seen ids ... ids referenced in the template, matches
|
|
413 |
// all the ids in the specification then we're fine.
|
|
414 |
|
|
415 | 220 |
if (_seenIds.containsAll(ids))
|
416 | 220 |
return;
|
417 |
|
|
418 |
// Create a modifiable copy. Remove the ids that are referenced in
|
|
419 |
// the template. The remainder are worthy of note.
|
|
420 |
|
|
421 | 0 |
ids = new HashSet(ids);
|
422 | 0 |
ids.removeAll(_seenIds); |
423 |
|
|
424 | 0 |
_log.error(ImplMessages.missingComponentSpec(_loadComponent, ids)); |
425 |
|
|
426 |
} |
|
427 |
|
|
428 | 0 |
private ApplicationRuntimeException createBodylessComponentException(IComponent component)
|
429 |
{ |
|
430 | 0 |
return new ApplicationRuntimeException(ImplMessages.bodylessComponent(), component, null, |
431 |
null);
|
|
432 |
} |
|
433 |
} |
|