This project has retired. For details please refer to its
Attic page.
BasicTilesContainer xref
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 package org.apache.tiles.impl;
22
23 import org.apache.commons.logging.Log;
24 import org.apache.commons.logging.LogFactory;
25 import org.apache.tiles.Attribute;
26 import org.apache.tiles.AttributeContext;
27 import org.apache.tiles.Definition;
28 import org.apache.tiles.TilesApplicationContext;
29 import org.apache.tiles.TilesContainer;
30 import org.apache.tiles.TilesException;
31 import org.apache.tiles.Attribute.AttributeType;
32 import org.apache.tiles.context.BasicAttributeContext;
33 import org.apache.tiles.context.TilesContextFactory;
34 import org.apache.tiles.context.TilesRequestContext;
35 import org.apache.tiles.definition.DefinitionsFactory;
36 import org.apache.tiles.definition.DefinitionsFactoryException;
37 import org.apache.tiles.definition.NoSuchDefinitionException;
38 import org.apache.tiles.preparer.NoSuchPreparerException;
39 import org.apache.tiles.preparer.PreparerFactory;
40 import org.apache.tiles.preparer.ViewPreparer;
41
42 import java.io.IOException;
43 import java.io.Writer;
44 import java.net.URL;
45 import java.util.ArrayList;
46 import java.util.Iterator;
47 import java.util.List;
48 import java.util.Map;
49 import java.util.Set;
50 import java.util.Stack;
51 import java.util.StringTokenizer;
52
53 /***
54 * Basic implementation of the tiles container interface.
55 * In most cases, this container will be customized by
56 * injecting customized services, not necessarily by
57 * override the container
58 *
59 * @since 2.0
60 * @version $Rev: 619574 $ $Date: 2008-02-07 20:09:33 +0100 (Thu, 07 Feb 2008) $
61 */
62 public class BasicTilesContainer implements TilesContainer {
63
64 /***
65 * Constant representing the configuration parameter
66 * used to define the tiles definition resources.
67 */
68 public static final String DEFINITIONS_CONFIG = "org.apache.tiles.impl.BasicTilesContainer.DEFINITIONS_CONFIG";
69
70 /***
71 * Compatibility constant.
72 *
73 * @deprecated use {@link #DEFINITIONS_CONFIG} to avoid namespace collisions.
74 */
75 private static final String LEGACY_DEFINITIONS_CONFIG = "definitions-config";
76
77 /***
78 * Name used to store attribute context stack.
79 */
80 private static final String ATTRIBUTE_CONTEXT_STACK =
81 "org.apache.tiles.AttributeContext.STACK";
82
83 /***
84 * Log instance for all BasicTilesContainer
85 * instances.
86 */
87 private static final Log LOG =
88 LogFactory.getLog(BasicTilesContainer.class);
89
90 /***
91 * The Tiles application context object.
92 */
93 private TilesApplicationContext context;
94
95 /***
96 * The definitions factory.
97 */
98 private DefinitionsFactory definitionsFactory;
99
100 /***
101 * The preparer factory.
102 */
103 private PreparerFactory preparerFactory;
104
105 /***
106 * The Tiles context factory.
107 */
108 private TilesContextFactory contextFactory;
109
110 /***
111 * Initialization flag. If set, this container cannot be changed.
112 */
113 private boolean initialized = false;
114
115 /***
116 * Initialize the Container with the given configuration.
117 *
118 * @param initParameters application context for this container
119 * @throws TilesException If something goes wrong during initialization.
120 */
121 public void init(Map<String, String> initParameters) throws TilesException {
122 checkInit();
123 initialized = true;
124 if (LOG.isInfoEnabled()) {
125 LOG.info("Initializing Tiles2 container. . .");
126 }
127
128
129
130 initializeDefinitionsFactory(definitionsFactory, getResourceString(),
131 initParameters);
132 }
133
134 /*** {@inheritDoc} */
135 public AttributeContext startContext(Object... requestItems) {
136 TilesRequestContext tilesContext = getRequestContext(requestItems);
137 return startContext(tilesContext);
138 }
139
140 /*** {@inheritDoc} */
141 public void endContext(Object... requestItems) {
142 TilesRequestContext tilesContext = getRequestContext(requestItems);
143 endContext(tilesContext);
144 }
145
146 /***
147 * Returns the Tiles application context used by this container.
148 *
149 * @return the application context for this container.
150 */
151 public TilesApplicationContext getApplicationContext() {
152 return context;
153 }
154
155 /***
156 * Sets the Tiles application context to use.
157 *
158 * @param context The Tiles application context.
159 */
160 public void setApplicationContext(TilesApplicationContext context) {
161 this.context = context;
162 }
163
164 /*** {@inheritDoc} */
165 public AttributeContext getAttributeContext(Object... requestItems) {
166 TilesRequestContext tilesContext = getRequestContext(requestItems);
167 return getAttributeContext(tilesContext);
168
169 }
170
171 /***
172 * Returns the context factory.
173 *
174 * @return The context factory.
175 */
176 public TilesContextFactory getContextFactory() {
177 return contextFactory;
178 }
179
180 /***
181 * Sets the context factory.
182 *
183 * @param contextFactory The context factory.
184 */
185 public void setContextFactory(TilesContextFactory contextFactory) {
186 checkInit();
187 this.contextFactory = contextFactory;
188 }
189
190 /***
191 * Returns the definitions factory.
192 *
193 * @return The definitions factory used by this container.
194 */
195 public DefinitionsFactory getDefinitionsFactory() {
196 return definitionsFactory;
197 }
198
199 /***
200 * Set the definitions factory. This method first ensures
201 * that the container has not yet been initialized.
202 *
203 * @param definitionsFactory the definitions factory for this instance.
204 */
205 public void setDefinitionsFactory(DefinitionsFactory definitionsFactory) {
206 checkInit();
207 this.definitionsFactory = definitionsFactory;
208 }
209
210 /***
211 * Returns the preparer factory used by this container.
212 *
213 * @return return the preparerInstance factory used by this container.
214 */
215 public PreparerFactory getPreparerFactory() {
216 return preparerFactory;
217 }
218
219 /***
220 * Set the preparerInstance factory. This method first ensures
221 * that the container has not yet been initialized.
222 *
223 * @param preparerFactory the preparerInstance factory for this conainer.
224 */
225 public void setPreparerFactory(PreparerFactory preparerFactory) {
226 this.preparerFactory = preparerFactory;
227 }
228
229 /*** {@inheritDoc} */
230 public void prepare(String preparer, Object... requestItems)
231 throws TilesException {
232 TilesRequestContext requestContext = getContextFactory().createRequestContext(
233 getApplicationContext(),
234 requestItems
235 );
236 prepare(requestContext, preparer, false);
237 }
238
239 /*** {@inheritDoc} */
240 public void render(String definitionName, Object... requestItems)
241 throws TilesException {
242 TilesRequestContext requestContext = getContextFactory().createRequestContext(
243 getApplicationContext(),
244 requestItems
245 );
246 render(requestContext, definitionName);
247 }
248
249 /*** {@inheritDoc} */
250 public void render(Attribute attr, Writer writer, Object... requestItems)
251 throws TilesException, IOException {
252 TilesRequestContext request = getRequestContext(requestItems);
253
254 if (attr == null) {
255 throw new TilesException("Cannot render a null attribute");
256 }
257
258 if (!isPermitted(request, attr.getRoles())) {
259 if (LOG.isDebugEnabled()) {
260 LOG.debug("Access to attribute denied. User not in role '"
261 + attr.getRoles() + "'");
262 }
263 return;
264 }
265
266 AttributeType type = attr.getType();
267 if (type == null) {
268 type = calculateType(attr, request);
269 attr.setType(type);
270 }
271
272 switch (type) {
273 case OBJECT:
274 throw new TilesException(
275 "Cannot insert an attribute of 'object' type");
276 case STRING:
277 writer.write(attr.getValue().toString());
278 break;
279 case DEFINITION:
280 render(request, attr.getValue().toString());
281 break;
282 case TEMPLATE:
283 request.dispatch(attr.getValue().toString());
284 break;
285 default:
286 throw new TilesException(
287 "Unrecognized type for attribute value "
288 + attr.getValue());
289 }
290 }
291
292 /*** {@inheritDoc} */
293 public boolean isValidDefinition(String definitionName, Object... requestItems) {
294 return isValidDefinition(getRequestContext(requestItems), definitionName);
295 }
296
297 /***
298 * Returns a definition specifying its name.
299 *
300 * @param definitionName The name of the definition to find.
301 * @param request The request context.
302 * @return The definition, if found.
303 * @throws DefinitionsFactoryException If the definitions factory throws an
304 * exception.
305 */
306 protected Definition getDefinition(String definitionName,
307 TilesRequestContext request) throws DefinitionsFactoryException {
308 Definition definition =
309 definitionsFactory.getDefinition(definitionName, request);
310 return definition;
311 }
312
313 /***
314 * Derive the resource string from the initialization parameters.
315 * If no parameter {@link #DEFINITIONS_CONFIG} is available, attempts
316 * to retrieve {@link #LEGACY_DEFINITIONS_CONFIG}. If niether are
317 * available, returns "/WEB-INF/tiles.xml".
318 *
319 * @return resource string to be parsed.
320 */
321 protected String getResourceString() {
322 return getResourceString(context.getInitParams());
323 }
324
325 /***
326 * Derive the resource string from the initialization parameters.
327 * If no parameter {@link #DEFINITIONS_CONFIG} is available, attempts
328 * to retrieve {@link #LEGACY_DEFINITIONS_CONFIG}. If niether are
329 * available, returns "/WEB-INF/tiles.xml".
330 *
331 * @param parms The initialization parameters.
332 * @return resource string to be parsed.
333 */
334 protected String getResourceString(Map<String, String> parms) {
335 String resourceStr = parms.get(DEFINITIONS_CONFIG);
336 if (resourceStr == null) {
337 resourceStr = parms.get(LEGACY_DEFINITIONS_CONFIG);
338 }
339 if (resourceStr == null) {
340 resourceStr = "/WEB-INF/tiles.xml";
341 }
342 return resourceStr;
343 }
344
345 /***
346 * Parse the resourceString into a list of resource paths
347 * which can be loaded by the application context.
348 *
349 * @param resourceString comma seperated resources
350 * @return parsed resources
351 */
352 protected List<String> getResourceNames(String resourceString) {
353 StringTokenizer tokenizer = new StringTokenizer(resourceString, ",");
354 List<String> filenames = new ArrayList<String>(tokenizer.countTokens());
355 while (tokenizer.hasMoreTokens()) {
356 filenames.add(tokenizer.nextToken().trim());
357 }
358 return filenames;
359 }
360
361 /***
362 * Determine whether or not the container has been
363 * initialized. Utility method used for methods which
364 * can not be invoked after the container has been
365 * started.
366 *
367 * @throws IllegalStateException if the container has already been initialized.
368 */
369 protected void checkInit() {
370 if (initialized) {
371 throw new IllegalStateException("Container allready initialized");
372 }
373 }
374
375 /***
376 * Initializes a definitions factory.
377 *
378 * @param definitionsFactory The factory to initialize.
379 * @param resourceString The string containing a comma-separated-list of
380 * resources.
381 * @param initParameters A map containing the initialization parameters.
382 * @throws TilesException If something goes wrong.
383 */
384 protected void initializeDefinitionsFactory(
385 DefinitionsFactory definitionsFactory, String resourceString,
386 Map<String, String> initParameters) throws TilesException {
387 List<String> resources = getResourceNames(resourceString);
388
389 try {
390 for (String resource : resources) {
391 URL resourceUrl = context.getResource(resource);
392 if (resourceUrl != null) {
393 if (LOG.isDebugEnabled()) {
394 LOG.debug("Adding resource '" + resourceUrl + "' to definitions factory.");
395 }
396 definitionsFactory.addSource(resourceUrl);
397 } else {
398 LOG.warn("Unable to find configured definition '" + resource + "'");
399 }
400 }
401 } catch (IOException e) {
402 throw new DefinitionsFactoryException("Unable to parse definitions from "
403 + resourceString, e);
404 }
405
406 definitionsFactory.init(initParameters);
407
408 if (LOG.isInfoEnabled()) {
409 LOG.info("Tiles2 container initialization complete.");
410 }
411 }
412
413 /***
414 * Returns the context stack.
415 *
416 * @param tilesContext The Tiles context object to use.
417 * @return The needed stack of contexts.
418 * @since 2.0.6
419 */
420 @SuppressWarnings("unchecked")
421 protected Stack<AttributeContext> getContextStack(TilesRequestContext tilesContext) {
422 Stack<AttributeContext> contextStack =
423 (Stack<AttributeContext>) tilesContext
424 .getRequestScope().get(ATTRIBUTE_CONTEXT_STACK);
425 if (contextStack == null) {
426 contextStack = new Stack<AttributeContext>();
427 tilesContext.getRequestScope().put(ATTRIBUTE_CONTEXT_STACK,
428 contextStack);
429 }
430
431 return contextStack;
432 }
433
434 /***
435 * Pushes a context object in the stack.
436 *
437 * @param context The context to push.
438 * @param tilesContext The Tiles context object to use.
439 * @since 2.0.6
440 */
441 protected void pushContext(AttributeContext context,
442 TilesRequestContext tilesContext) {
443 Stack<AttributeContext> contextStack = getContextStack(tilesContext);
444 contextStack.push(context);
445 }
446
447 /***
448 * Pops a context object out of the stack.
449 *
450 * @param tilesContext The Tiles context object to use.
451 * @return The popped context object.
452 * @since 2.0.6
453 */
454 protected AttributeContext popContext(TilesRequestContext tilesContext) {
455 Stack<AttributeContext> contextStack = getContextStack(tilesContext);
456 return contextStack.pop();
457 }
458
459 /***
460 * Get attribute context from request.
461 *
462 * @param tilesContext current Tiles application context.
463 * @return BasicAttributeContext or null if context is not found or an
464 * jspException is present in the request.
465 * @since 2.0.6
466 */
467 protected AttributeContext getContext(TilesRequestContext tilesContext) {
468 Stack<AttributeContext> contextStack = getContextStack(tilesContext);
469 if (!contextStack.isEmpty()) {
470 return contextStack.peek();
471 } else {
472 return null;
473 }
474 }
475
476 /***
477 * Returns the current attribute context.
478 *
479 * @param tilesContext The request context to use.
480 * @return The current attribute context.
481 */
482 private AttributeContext getAttributeContext(TilesRequestContext tilesContext) {
483 AttributeContext context = getContext(tilesContext);
484 if (context == null) {
485 context = new BasicAttributeContext();
486 pushContext(context, tilesContext);
487 }
488 return context;
489 }
490
491 /***
492 * Creates a Tiles request context from request items.
493 *
494 * @param requestItems The request items.
495 * @return The created Tiles request context.
496 */
497 private TilesRequestContext getRequestContext(Object... requestItems) {
498 return getContextFactory().createRequestContext(
499 getApplicationContext(),
500 requestItems
501 );
502 }
503
504 /***
505 * Starts an attribute context inside the container.
506 *
507 * @param tilesContext The request context to use.
508 * @return The newly created attribute context.
509 */
510 private AttributeContext startContext(TilesRequestContext tilesContext) {
511 AttributeContext context = new BasicAttributeContext();
512 pushContext(context, tilesContext);
513 return context;
514 }
515
516 /***
517 * Releases and removes a previously created attribute context.
518 *
519 * @param tilesContext The request context to use.
520 */
521 private void endContext(TilesRequestContext tilesContext) {
522 popContext(tilesContext);
523 }
524
525 /***
526 * Execute a preparer.
527 *
528 * @param context The request context.
529 * @param preparerName The name of the preparer.
530 * @param ignoreMissing If <code>true</code> if the preparer is not found,
531 * it ignores the problem.
532 * @throws TilesException If the preparer is not found (and
533 * <code>ignoreMissing</code> is not set) or if the preparer itself threw an
534 * exception.
535 */
536 private void prepare(TilesRequestContext context, String preparerName, boolean ignoreMissing)
537 throws TilesException {
538
539 if (LOG.isDebugEnabled()) {
540 LOG.debug("Prepare request received for '" + preparerName);
541 }
542
543 ViewPreparer preparer = preparerFactory.getPreparer(preparerName, context);
544 if (preparer == null && ignoreMissing) {
545 return;
546 }
547
548 if (preparer == null) {
549 throw new NoSuchPreparerException("Preparer '" + preparerName + " not found");
550 }
551
552 AttributeContext attributeContext = getContext(context);
553
554 preparer.execute(context, attributeContext);
555 }
556
557 /***
558 * Renders the specified definition.
559 *
560 * @param request The request context.
561 * @param definitionName The name of the definition to render.
562 * @throws TilesException If something goes wrong during rendering.
563 */
564 private void render(TilesRequestContext request, String definitionName)
565 throws TilesException {
566
567 if (LOG.isDebugEnabled()) {
568 LOG.debug("Render request recieved for definition '" + definitionName + "'");
569 }
570
571 Definition definition = getDefinition(definitionName, request);
572
573 if (definition == null) {
574 if (LOG.isWarnEnabled()) {
575 String message = "Unable to find the definition '" + definitionName + "'";
576 LOG.warn(message);
577 }
578 throw new NoSuchDefinitionException(definitionName);
579 }
580
581 if (!isPermitted(request, definition.getRoles())) {
582 if (LOG.isDebugEnabled()) {
583 LOG.debug("Access to definition '" + definitionName
584 + "' denied. User not in role '"
585 + definition.getRoles());
586 }
587 return;
588 }
589
590 AttributeContext originalContext = getAttributeContext(request);
591 BasicAttributeContext subContext = new BasicAttributeContext(originalContext);
592 subContext.addMissing(definition.getAttributes());
593 pushContext(subContext, request);
594
595 try {
596 if (definition.getPreparer() != null) {
597 prepare(request, definition.getPreparer(), true);
598 }
599
600 String dispatchPath = definition.getTemplate();
601
602 if (LOG.isDebugEnabled()) {
603 LOG.debug("Dispatching to definition path '"
604 + definition.getTemplate() + " '");
605 }
606 request.dispatch(dispatchPath);
607
608
609 } catch (TilesException e) {
610 throw e;
611 } catch (Exception e) {
612 LOG.error("Error rendering tile", e);
613
614 throw new TilesException(e.getMessage(), e);
615 } finally {
616 popContext(request);
617 }
618 }
619
620 /***
621 * Calculates the type of an attribute.
622 *
623 * @param attr The attribute to check.
624 * @param request The request object.
625 * @return The calculated attribute type.
626 * @throws TilesException If the type is not recognized.
627 */
628 private AttributeType calculateType(Attribute attr,
629 TilesRequestContext request) throws TilesException {
630 AttributeType type;
631 Object valueContent = attr.getValue();
632 if (valueContent instanceof String) {
633 String valueString = (String) valueContent;
634 if (isValidDefinition(request, valueString)) {
635 type = AttributeType.DEFINITION;
636 } else if (valueString.startsWith("/")) {
637 type = AttributeType.TEMPLATE;
638 } else {
639 type = AttributeType.STRING;
640 }
641 } else {
642 type = AttributeType.OBJECT;
643 }
644
645 return type;
646 }
647
648 /***
649 * Checks if the current user is in one of the comma-separated roles
650 * specified in the <code>role</code> parameter.
651 *
652 * @param request The request context.
653 * @param roles The list of roles.
654 * @return <code>true</code> if the current user is in one of those roles.
655 */
656 private boolean isPermitted(TilesRequestContext request, Set<String> roles) {
657 if (roles == null || roles.isEmpty()) {
658 return true;
659 }
660
661 boolean retValue = false;
662
663 for (Iterator<String> roleIt = roles.iterator(); roleIt.hasNext()
664 && !retValue;) {
665 retValue = request.isUserInRole(roleIt.next());
666 }
667
668 return retValue;
669 }
670
671 /***
672 * Checks if a string is a valid definition name.
673 *
674 * @param context The request context.
675 * @param definitionName The name of the definition to find.
676 * @return <code>true</code> if <code>definitionName</code> is a valid
677 * definition name.
678 */
679 private boolean isValidDefinition(TilesRequestContext context, String definitionName) {
680 try {
681 Definition definition = getDefinition(definitionName, context);
682 return definition != null;
683 } catch (NoSuchDefinitionException nsde) {
684 return false;
685 } catch (DefinitionsFactoryException e) {
686
687 return false;
688 }
689 }
690 }