Save This Page
Home » tiles-2.0.6-src » org.apache » tiles » impl » [javadoc | source]
    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   }

Save This Page
Home » tiles-2.0.6-src » org.apache » tiles » impl » [javadoc | source]