This project has retired. For details please refer to its Attic page.
BasicTilesContainer xref
View Javadoc

1   /*
2    * $Id: BasicTilesContainer.java 797765 2009-07-25 13:20:26Z apetrelli $
3    *
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   * http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  package org.apache.tiles.impl;
22  
23  import java.io.IOException;
24  import java.io.Writer;
25  import java.util.ArrayList;
26  import java.util.List;
27  import java.util.Map;
28  import java.util.StringTokenizer;
29  
30  import org.apache.tiles.ArrayStack;
31  import org.apache.tiles.Attribute;
32  import org.apache.tiles.AttributeContext;
33  import org.apache.tiles.BasicAttributeContext;
34  import org.apache.tiles.Definition;
35  import org.apache.tiles.TilesApplicationContext;
36  import org.apache.tiles.TilesContainer;
37  import org.apache.tiles.context.TilesRequestContext;
38  import org.apache.tiles.context.TilesRequestContextFactory;
39  import org.apache.tiles.definition.DefinitionsFactory;
40  import org.apache.tiles.definition.DefinitionsFactoryException;
41  import org.apache.tiles.definition.NoSuchDefinitionException;
42  import org.apache.tiles.evaluator.AttributeEvaluator;
43  import org.apache.tiles.evaluator.AttributeEvaluatorFactory;
44  import org.apache.tiles.evaluator.AttributeEvaluatorFactoryAware;
45  import org.apache.tiles.preparer.NoSuchPreparerException;
46  import org.apache.tiles.preparer.PreparerFactory;
47  import org.apache.tiles.preparer.ViewPreparer;
48  import org.apache.tiles.renderer.AttributeRenderer;
49  import org.apache.tiles.renderer.RendererFactory;
50  import org.slf4j.Logger;
51  import org.slf4j.LoggerFactory;
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: 797765 $ $Date: 2009-07-25 15:20:26 +0200 (sab, 25 lug 2009) $
61   */
62  public class BasicTilesContainer implements TilesContainer,
63          AttributeEvaluatorFactoryAware {
64  
65      /***
66       * Constant representing the configuration parameter used to define the
67       * tiles definition resources.
68       *
69       * @deprecated Use
70       * {@link org.apache.tiles.definition.DefinitionsFactory#DEFINITIONS_CONFIG}.
71       */
72      public static final String DEFINITIONS_CONFIG = "org.apache.tiles.impl.BasicTilesContainer.DEFINITIONS_CONFIG";
73  
74      /***
75       * Compatibility constant.
76       *
77       * @deprecated use {@link #DEFINITIONS_CONFIG} to avoid namespace collisions.
78       */
79      private static final String LEGACY_DEFINITIONS_CONFIG = "definitions-config";
80  
81      /***
82       * Name used to store attribute context stack.
83       */
84      private static final String ATTRIBUTE_CONTEXT_STACK =
85          "org.apache.tiles.AttributeContext.STACK";
86  
87      /***
88       * Log instance for all BasicTilesContainer
89       * instances.
90       */
91      private final Logger log = LoggerFactory
92              .getLogger(BasicTilesContainer.class);
93  
94      /***
95       * The Tiles application context object.
96       */
97      private TilesApplicationContext context;
98  
99      /***
100      * The definitions factory.
101      */
102     private DefinitionsFactory definitionsFactory;
103 
104     /***
105      * The preparer factory.
106      */
107     private PreparerFactory preparerFactory;
108 
109     /***
110      * The renderer factory.
111      */
112     private RendererFactory rendererFactory;
113 
114     /***
115      * The attribute evaluator.
116      */
117     private AttributeEvaluatorFactory attributeEvaluatorFactory;
118 
119     /***
120      * The Tiles request context factory.
121      */
122     private TilesRequestContextFactory contextFactory;
123 
124     /***
125      * Initialization flag. If set, this container cannot be changed.
126      */
127     private boolean initialized = false;
128 
129     /***
130      * Initialize the Container with the given configuration.
131      *
132      * @param initParameters application context for this container
133      * @throws IllegalStateException If the container has been already
134      * initialized.
135      * @throws DefinitionsFactoryException If something goes wrong during
136      * initialization.
137      */
138     public void init(Map<String, String> initParameters) {
139         checkInit();
140         initialized = true;
141 
142         if (rendererFactory == null) {
143             throw new IllegalStateException("RendererFactory not specified");
144         }
145         if (preparerFactory == null) {
146             throw new IllegalStateException("PreparerFactory not specified");
147         }
148         if (definitionsFactory == null) {
149             throw new IllegalStateException("DefinitionsFactory not specified");
150         }
151         if (attributeEvaluatorFactory == null) {
152             throw new IllegalStateException("AttributeEvaluatorFactory not specified");
153         }
154         if (contextFactory == null) {
155             throw new IllegalStateException("TilesContextFactory not specified");
156         }
157         if (context == null) {
158             throw new IllegalStateException("TilesApplicationContext not specified");
159         }
160     }
161 
162     /*** {@inheritDoc} */
163     public AttributeContext startContext(Object... requestItems) {
164         TilesRequestContext tilesContext = getRequestContext(requestItems);
165         return startContext(tilesContext);
166     }
167 
168     /*** {@inheritDoc} */
169     public void endContext(Object... requestItems) {
170         TilesRequestContext tilesContext = getRequestContext(requestItems);
171         endContext(tilesContext);
172     }
173 
174     /*** {@inheritDoc} */
175     public void renderContext(Object... requestItems) {
176         TilesRequestContext request = getRequestContext(requestItems);
177         AttributeContext attributeContext = getAttributeContext(request);
178 
179         render(request, attributeContext);
180     }
181 
182     /***
183      * Returns the Tiles application context used by this container.
184      *
185      * @return the application context for this container.
186      */
187     public TilesApplicationContext getApplicationContext() {
188         return context;
189     }
190 
191     /***
192      * Sets the Tiles application context to use.
193      *
194      * @param context The Tiles application context.
195      */
196     public void setApplicationContext(TilesApplicationContext context) {
197         this.context = context;
198     }
199 
200     /*** {@inheritDoc} */
201     public AttributeContext getAttributeContext(Object... requestItems) {
202         TilesRequestContext tilesContext = getRequestContext(requestItems);
203         return getAttributeContext(tilesContext);
204 
205     }
206 
207     /***
208      * Returns the context factory.
209      *
210      * @return Always <code>null</code>.
211      * @deprecated Do not use it, it returns <code>null</code>. Use
212      * {@link #getRequestContextFactory()}.
213      */
214     @Deprecated
215     public org.apache.tiles.context.TilesContextFactory getContextFactory() {
216         return null;
217     }
218 
219     /***
220      * Returns the request context factory.
221      *
222      * @return The request context factory.
223      * @since 2.1.1
224      */
225     protected TilesRequestContextFactory getRequestContextFactory() {
226         return contextFactory;
227     }
228 
229     /***
230      * Sets the context factory.
231      *
232      * @param contextFactory The context factory.
233      * @deprecated Use
234      * {@link #setRequestContextFactory(TilesRequestContextFactory)}.
235      */
236     public void setContextFactory(org.apache.tiles.context.TilesContextFactory contextFactory) {
237         // Does nothing
238     }
239 
240     /***
241      * Sets the request context factory.
242      *
243      * @param contextFactory The context factory.
244      * @since 2.1.1
245      */
246     public void setRequestContextFactory(TilesRequestContextFactory contextFactory) {
247         checkInit();
248         this.contextFactory = contextFactory;
249     }
250 
251     /***
252      * Returns the definitions factory.
253      *
254      * @return The definitions factory used by this container.
255      */
256     public DefinitionsFactory getDefinitionsFactory() {
257         return definitionsFactory;
258     }
259 
260     /***
261      * Set the definitions factory. This method first ensures
262      * that the container has not yet been initialized.
263      *
264      * @param definitionsFactory the definitions factory for this instance.
265      */
266     public void setDefinitionsFactory(DefinitionsFactory definitionsFactory) {
267         checkInit();
268         this.definitionsFactory = definitionsFactory;
269     }
270 
271     /***
272      * Returns the preparer factory used by this container.
273      *
274      * @return return the preparerInstance factory used by this container.
275      */
276     public PreparerFactory getPreparerFactory() {
277         return preparerFactory;
278     }
279 
280     /***
281      * Set the preparerInstance factory.  This method first ensures
282      * that the container has not yet been initialized.
283      *
284      * @param preparerFactory the preparerInstance factory for this conainer.
285      */
286     public void setPreparerFactory(PreparerFactory preparerFactory) {
287         this.preparerFactory = preparerFactory;
288     }
289 
290     /***
291      * Sets the renderer instance factory.
292      *
293      * @param rendererFactory the renderer instance factory for this container.
294      * @since 2.1.0
295      */
296     public void setRendererFactory(RendererFactory rendererFactory) {
297         this.rendererFactory = rendererFactory;
298     }
299 
300     /*** {@inheritDoc} */
301     public void setAttributeEvaluatorFactory(
302             AttributeEvaluatorFactory attributeEvaluatorFactory) {
303         this.attributeEvaluatorFactory = attributeEvaluatorFactory;
304     }
305 
306     /*** {@inheritDoc} */
307     public void prepare(String preparer, Object... requestItems) {
308         TilesRequestContext requestContext = getRequestContextFactory().createRequestContext(
309             getApplicationContext(),
310             requestItems
311         );
312         prepare(requestContext, preparer, false);
313     }
314 
315     /*** {@inheritDoc} */
316     public void render(String definitionName, Object... requestItems) {
317         TilesRequestContext requestContext = getRequestContextFactory().createRequestContext(
318             getApplicationContext(),
319             requestItems
320         );
321         render(requestContext, definitionName);
322     }
323 
324     /*** {@inheritDoc} */
325     @Deprecated
326     public void render(Attribute attr, Writer writer, Object... requestItems)
327         throws IOException {
328         render(attr, requestItems);
329     }
330 
331     /*** {@inheritDoc} */
332     public void render(Attribute attr, Object... requestItems)
333         throws IOException {
334         TilesRequestContext requestContext = getRequestContextFactory()
335                 .createRequestContext(getApplicationContext(), requestItems);
336         render(attr, requestContext);
337     }
338 
339     /*** {@inheritDoc} */
340     public Object evaluate(Attribute attribute, Object... requestItems) {
341         TilesRequestContext request = getRequestContextFactory()
342                 .createRequestContext(context, requestItems);
343         AttributeEvaluator evaluator = attributeEvaluatorFactory
344                 .getAttributeEvaluator(attribute);
345         return evaluator.evaluate(attribute, request);
346     }
347 
348     /*** {@inheritDoc} */
349     public boolean isValidDefinition(String definitionName, Object... requestItems) {
350         return isValidDefinition(getRequestContext(requestItems), definitionName);
351     }
352 
353     /***
354      * Returns a definition specifying its name.
355      *
356      * @param definitionName The name of the definition to find.
357      * @param request The request context.
358      * @return The definition, if found.
359      * @throws DefinitionsFactoryException If the definitions factory throws an
360      * exception.
361      */
362     protected Definition getDefinition(String definitionName,
363             TilesRequestContext request) {
364         Definition definition =
365             definitionsFactory.getDefinition(definitionName, request);
366         return definition;
367     }
368 
369     /***
370      * Derive the resource string from the initialization parameters.
371      * If no parameter {@link #DEFINITIONS_CONFIG} is available, attempts
372      * to retrieve {@link #LEGACY_DEFINITIONS_CONFIG}.  If niether are
373      * available, returns "/WEB-INF/tiles.xml".
374      *
375      * @return resource string to be parsed.
376      */
377     protected String getResourceString() {
378         return getResourceString(context.getInitParams());
379     }
380 
381     /***
382      * Derive the resource string from the initialization parameters.
383      * If no parameter {@link #DEFINITIONS_CONFIG} is available, attempts
384      * to retrieve {@link #LEGACY_DEFINITIONS_CONFIG}.  If niether are
385      * available, returns "/WEB-INF/tiles.xml".
386      *
387      * @param parms The initialization parameters.
388      * @return resource string to be parsed.
389      */
390     protected String getResourceString(Map<String, String> parms) {
391         String resourceStr = parms.get(DEFINITIONS_CONFIG);
392         if (resourceStr == null) {
393             resourceStr = parms.get(LEGACY_DEFINITIONS_CONFIG);
394         }
395         if (resourceStr == null) {
396             resourceStr = "/WEB-INF/tiles.xml";
397         }
398         return resourceStr;
399     }
400 
401     /***
402      * Parse the resourceString into a list of resource paths
403      * which can be loaded by the application context.
404      *
405      * @param resourceString comma seperated resources
406      * @return parsed resources
407      */
408     protected List<String> getResourceNames(String resourceString) {
409         StringTokenizer tokenizer = new StringTokenizer(resourceString, ",");
410         List<String> filenames = new ArrayList<String>(tokenizer.countTokens());
411         while (tokenizer.hasMoreTokens()) {
412             filenames.add(tokenizer.nextToken().trim());
413         }
414         return filenames;
415     }
416 
417     /***
418      * Determine whether or not the container has been
419      * initialized. Utility method used for methods which
420      * can not be invoked after the container has been
421      * started.
422      *
423      * @throws IllegalStateException if the container has already been initialized.
424      */
425     protected void checkInit() {
426         if (initialized) {
427             throw new IllegalStateException("Container allready initialized");
428         }
429     }
430 
431     /***
432      * Initializes a definitions factory.
433      *
434      * @param definitionsFactory The factory to initialize.
435      * @param resourceString The string containing a comma-separated-list of
436      * resources.
437      * @param initParameters A map containing the initialization parameters.
438      * @throws DefinitionsFactoryException If something goes wrong.
439      * @deprecated Do not use, the Definitions Factory should be initialized by
440      * the Tiles Container Factory.
441      */
442     @Deprecated
443     protected void initializeDefinitionsFactory(
444             DefinitionsFactory definitionsFactory, String resourceString,
445             Map<String, String> initParameters) {
446         if (rendererFactory == null) {
447             throw new IllegalStateException("No RendererFactory found");
448         }
449 
450         definitionsFactory.init(initParameters);
451 
452         if (log.isInfoEnabled()) {
453             log.info("Tiles2 container initialization complete.");
454         }
455     }
456 
457     /***
458      * Returns the context stack.
459      *
460      * @param tilesContext The Tiles context object to use.
461      * @return The needed stack of contexts.
462      * @since 2.0.6
463      */
464     @SuppressWarnings("unchecked")
465     protected ArrayStack<AttributeContext> getContextStack(TilesRequestContext tilesContext) {
466         ArrayStack<AttributeContext> contextStack =
467             (ArrayStack<AttributeContext>) tilesContext
468                 .getRequestScope().get(ATTRIBUTE_CONTEXT_STACK);
469         if (contextStack == null) {
470             contextStack = new ArrayStack<AttributeContext>();
471             tilesContext.getRequestScope().put(ATTRIBUTE_CONTEXT_STACK,
472                     contextStack);
473         }
474 
475         return contextStack;
476     }
477 
478     /***
479      * Pushes a context object in the stack.
480      *
481      * @param context The context to push.
482      * @param tilesContext The Tiles context object to use.
483      * @since 2.0.6
484      */
485     protected void pushContext(AttributeContext context,
486             TilesRequestContext tilesContext) {
487         ArrayStack<AttributeContext> contextStack = getContextStack(tilesContext);
488         contextStack.push(context);
489     }
490 
491     /***
492      * Pops a context object out of the stack.
493      *
494      * @param tilesContext The Tiles context object to use.
495      * @return The popped context object.
496      * @since 2.0.6
497      */
498     protected AttributeContext popContext(TilesRequestContext tilesContext) {
499         ArrayStack<AttributeContext> contextStack = getContextStack(tilesContext);
500         return contextStack.pop();
501     }
502 
503     /***
504      * Get attribute context from request.
505      *
506      * @param tilesContext current Tiles application context.
507      * @return BasicAttributeContext or null if context is not found.
508      * @since 2.0.6
509      */
510     protected AttributeContext getContext(TilesRequestContext tilesContext) {
511         ArrayStack<AttributeContext> contextStack = getContextStack(tilesContext);
512         if (!contextStack.isEmpty()) {
513             return contextStack.peek();
514         } else {
515             return null;
516         }
517     }
518 
519     /***
520      * Returns the current attribute context.
521      *
522      * @param tilesContext The request context to use.
523      * @return The current attribute context.
524      */
525     private AttributeContext getAttributeContext(TilesRequestContext tilesContext) {
526         AttributeContext context = getContext(tilesContext);
527         if (context == null) {
528             context = new BasicAttributeContext();
529             pushContext(context, tilesContext);
530         }
531         return context;
532     }
533 
534     /***
535      * Creates a Tiles request context from request items.
536      *
537      * @param requestItems The request items.
538      * @return The created Tiles request context.
539      */
540     private TilesRequestContext getRequestContext(Object... requestItems) {
541         return getRequestContextFactory().createRequestContext(
542                 getApplicationContext(), requestItems);
543     }
544 
545     /***
546      * Starts an attribute context inside the container.
547      *
548      * @param tilesContext The request context to use.
549      * @return The newly created attribute context.
550      */
551     private AttributeContext startContext(TilesRequestContext tilesContext) {
552         AttributeContext context = new BasicAttributeContext();
553         ArrayStack<AttributeContext>  stack = getContextStack(tilesContext);
554         if (!stack.isEmpty()) {
555             AttributeContext parent = stack.peek();
556             context.inheritCascadedAttributes(parent);
557         }
558         stack.push(context);
559         return context;
560     }
561 
562     /***
563      * Releases and removes a previously created attribute context.
564      *
565      * @param tilesContext The request context to use.
566      */
567     private void endContext(TilesRequestContext tilesContext) {
568         popContext(tilesContext);
569     }
570 
571     /***
572      * Execute a preparer.
573      *
574      * @param context The request context.
575      * @param preparerName The name of the preparer.
576      * @param ignoreMissing If <code>true</code> if the preparer is not found,
577      * it ignores the problem.
578      * @throws NoSuchPreparerException If the preparer is not found (and
579      * <code>ignoreMissing</code> is not set) or if the preparer itself threw an
580      * exception.
581      */
582     private void prepare(TilesRequestContext context, String preparerName, boolean ignoreMissing) {
583 
584         if (log.isDebugEnabled()) {
585             log.debug("Prepare request received for '" + preparerName);
586         }
587 
588         ViewPreparer preparer = preparerFactory.getPreparer(preparerName, context);
589         if (preparer == null && ignoreMissing) {
590             return;
591         }
592 
593         if (preparer == null) {
594             throw new NoSuchPreparerException("Preparer '" + preparerName + " not found");
595         }
596 
597         AttributeContext attributeContext = getContext(context);
598 
599         preparer.execute(context, attributeContext);
600     }
601 
602     /***
603      * Renders the definition with specified name.
604      *
605      * @param request The request context.
606      * @param definitionName The name of the definition to render.
607      * @throws NoSuchDefinitionException If the definition has not been found.
608      * @throws DefinitionsFactoryException If something goes wrong when
609      * obtaining the definition.
610      * @since 2.1.3
611      */
612     protected void render(TilesRequestContext request, String definitionName) {
613 
614         if (log.isDebugEnabled()) {
615             log.debug("Render request recieved for definition '" + definitionName + "'");
616         }
617 
618         Definition definition = getDefinition(definitionName, request);
619 
620         if (definition == null) {
621             if (log.isWarnEnabled()) {
622                 String message = "Unable to find the definition '" + definitionName + "'";
623                 log.warn(message);
624             }
625             throw new NoSuchDefinitionException(definitionName);
626         }
627         render(request, definition);
628     }
629 
630     /***
631      * Renders the specified definition.
632      * @param request The request context.
633      * @param definition The definition to render.
634      * @since 2.1.3
635      */
636     protected void render(TilesRequestContext request, Definition definition) {
637         AttributeContext originalContext = getAttributeContext(request);
638         BasicAttributeContext subContext = new BasicAttributeContext(originalContext);
639         subContext.inherit(definition);
640 
641         pushContext(subContext, request);
642 
643         try {
644             render(request, subContext);
645         } finally {
646             popContext(request);
647         }
648     }
649 
650     /***
651      * Renders an attribute.
652      *
653      * @param attr The attribute to render.
654      * @param requestContext The Tiles request context.
655      * @throws IOException If something goes wrong during rendering.
656      */
657     private void render(Attribute attr, TilesRequestContext requestContext)
658             throws IOException {
659         if (attr == null) {
660             throw new CannotRenderException("Cannot render a null attribute");
661         }
662 
663         AttributeRenderer renderer = rendererFactory.getRenderer(attr
664                 .getRenderer());
665         if (renderer == null) {
666             throw new CannotRenderException(
667                     "Cannot render an attribute with renderer name "
668                             + attr.getRenderer());
669         }
670         renderer.render(attr, requestContext);
671     }
672 
673     /***
674      * Renders the specified attribute context.
675      *
676      * @param request The request context.
677      * @param attributeContext The context to render.
678      * @throws InvalidTemplateException If the template is not valid.
679      * @throws CannotRenderException If something goes wrong during rendering.
680      * @since 2.1.3
681      */
682     protected void render(TilesRequestContext request,
683             AttributeContext attributeContext) {
684 
685         try {
686             if (attributeContext.getPreparer() != null) {
687                 prepare(request, attributeContext.getPreparer(), true);
688             }
689 
690             render(attributeContext.getTemplateAttribute(), request);
691         } catch (IOException e) {
692             throw new CannotRenderException(e.getMessage(), e);
693         }
694     }
695 
696     /***
697      * Checks if a string is a valid definition name.
698      *
699      * @param context The request context.
700      * @param definitionName The name of the definition to find.
701      * @return <code>true</code> if <code>definitionName</code> is a valid
702      * definition name.
703      */
704     private boolean isValidDefinition(TilesRequestContext context, String definitionName) {
705         try {
706             Definition definition = getDefinition(definitionName, context);
707             return definition != null;
708         } catch (NoSuchDefinitionException nsde) {
709             return false;
710         } catch (DefinitionsFactoryException e) {
711             // TODO, is this the right thing to do?
712             return false;
713         }
714     }
715 }