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