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 }