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

1   /*
2    * $Id: BasicTilesContainer.java 619574 2008-02-07 19:09:33Z 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 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         //Everything is now initialized.  We will populate
129         // our definitions
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: // should not happen
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             // tiles exception so that it doesn't need to be rethrown.
609         } catch (TilesException e) {
610             throw e;
611         } catch (Exception e) {
612             LOG.error("Error rendering tile", e);
613             // TODO it would be nice to make the preparerInstance throw a more specific
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             // TODO, is this the right thing to do?
687             return false;
688         }
689     }
690 }