From 6691cd09ec239764f678a445ecf2020e27e638b1 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Wed, 13 May 2009 14:22:03 +0000 Subject: [PATCH] Velocity/FreeMarker/TilesViewResolver only return a view if the target resource exists now git-svn-id: https://src.springframework.org/svn/spring-framework/trunk@1171 50f2f4bb-b051-0410-bef5-90022cba6387 --- .../view/AbstractCachingViewResolver.java | 5 +- .../servlet/view/AbstractUrlBasedView.java | 12 +++- .../servlet/view/UrlBasedViewResolver.java | 17 +++-- .../view/freemarker/FreeMarkerConfig.java | 10 +-- .../view/freemarker/FreeMarkerConfigurer.java | 6 +- .../view/freemarker/FreeMarkerView.java | 18 +++-- .../freemarker/FreeMarkerViewResolver.java | 22 +++--- .../servlet/view/tiles2/TilesConfigurer.java | 12 ++-- .../web/servlet/view/tiles2/TilesView.java | 9 ++- .../view/tiles2/TilesViewResolver.java | 54 +++++++++++++++ .../view/velocity/VelocityLayoutView.java | 14 ++-- .../servlet/view/velocity/VelocityView.java | 30 +++++---- .../view/velocity/VelocityViewResolver.java | 21 +++--- .../view/freemarker/FreeMarkerViewTests.java | 65 ++++++++++++++---- .../view/velocity/TestVelocityEngine.java | 3 +- .../view/velocity/VelocityViewTests.java | 67 +++---------------- 16 files changed, 211 insertions(+), 154 deletions(-) create mode 100644 org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/view/tiles2/TilesViewResolver.java diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/view/AbstractCachingViewResolver.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/view/AbstractCachingViewResolver.java index b906f5bd142..0ed7464c48b 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/view/AbstractCachingViewResolver.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/view/AbstractCachingViewResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 the original author or authors. + * Copyright 2002-2009 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -66,7 +66,6 @@ public abstract class AbstractCachingViewResolver extends WebApplicationObjectSu public View resolveViewName(String viewName, Locale locale) throws Exception { if (!isCache()) { - logger.warn("View caching is SWITCHED OFF -- DEVELOPMENT SETTING ONLY: This can severely impair performance"); return createView(viewName, locale); } else { @@ -112,7 +111,7 @@ public abstract class AbstractCachingViewResolver extends WebApplicationObjectSu } else { Object cacheKey = getCacheKey(viewName, locale); - Object cachedView = null; + Object cachedView; synchronized (this.viewCache) { cachedView = this.viewCache.remove(cacheKey); } diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/view/AbstractUrlBasedView.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/view/AbstractUrlBasedView.java index 8c98feeab9b..7ed61933ebf 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/view/AbstractUrlBasedView.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/view/AbstractUrlBasedView.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 the original author or authors. + * Copyright 2002-2009 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -75,6 +75,16 @@ public abstract class AbstractUrlBasedView extends AbstractView implements Initi return true; } + /** + * Check whether the underlying resource that the configured URL points to + * actually exists. + * @return true if the resource exists (or is assumed to exist); + * false if we know that it does not exist + * @throws Exception if the resource exists but is invalid (e.g. could not be parsed) + */ + public boolean checkResource() throws Exception { + return true; + } @Override public String toString() { diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/view/UrlBasedViewResolver.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/view/UrlBasedViewResolver.java index ddc0831afa5..c7674a69301 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/view/UrlBasedViewResolver.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/view/UrlBasedViewResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 the original author or authors. + * Copyright 2002-2009 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -61,9 +61,12 @@ import org.springframework.web.servlet.View; *

Note: This class does not support localized resolution, i.e. resolving * a symbolic view name to different resources depending on the current locale. * - *

Note: When chaining ViewResolvers, a UrlBasedViewResolver always needs - * to be last, as it will attempt to resolve any view name, no matter whether - * the underlying resource actually exists. + *

Note: When chaining ViewResolvers, a UrlBasedViewResolver will check whether + * the {@link AbstractUrlBasedView#checkResource specified resource actually exists}. + * However, with {@link InternalResourceView}, it is not generally possible to + * determine the existence of the target resource upfront. In such a scenario, + * a UrlBasedViewResolver will always return View for any given view name; + * as a consequence, it should be configured as the last ViewResolver in the chain. * * @author Juergen Hoeller * @author Rob Harrop @@ -370,8 +373,7 @@ public class UrlBasedViewResolver extends AbstractCachingViewResolver implements // Check for special "redirect:" prefix. if (viewName.startsWith(REDIRECT_URL_PREFIX)) { String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length()); - return new RedirectView( - redirectUrl, isRedirectContextRelative(), isRedirectHttp10Compatible()); + return new RedirectView(redirectUrl, isRedirectContextRelative(), isRedirectHttp10Compatible()); } // Check for special "forward:" prefix. if (viewName.startsWith(FORWARD_URL_PREFIX)) { @@ -415,7 +417,8 @@ public class UrlBasedViewResolver extends AbstractCachingViewResolver implements @Override protected View loadView(String viewName, Locale locale) throws Exception { AbstractUrlBasedView view = buildView(viewName); - return (View) getApplicationContext().getAutowireCapableBeanFactory().initializeBean(view, viewName); + View result = (View) getApplicationContext().getAutowireCapableBeanFactory().initializeBean(view, viewName); + return (view.checkResource() ? result : null); } /** diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/view/freemarker/FreeMarkerConfig.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/view/freemarker/FreeMarkerConfig.java index da75f55f105..595a3bea3f3 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/view/freemarker/FreeMarkerConfig.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/view/freemarker/FreeMarkerConfig.java @@ -1,12 +1,12 @@ /* - * Copyright 2002-2005 the original author or authors. - * + * Copyright 2002-2009 the original author or authors. + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -22,7 +22,7 @@ import freemarker.ext.jsp.TaglibFactory; /** * Interface to be implemented by objects that configure and manage a * FreeMarker Configuration object in a web environment. Detected and - * used by FreeMarkerView. + * used by {@link FreeMarkerView}. * * @author Darren Davison * @author Rob Harrop diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/view/freemarker/FreeMarkerConfigurer.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/view/freemarker/FreeMarkerConfigurer.java index d09afb5d0d8..a3397391de9 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/view/freemarker/FreeMarkerConfigurer.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/view/freemarker/FreeMarkerConfigurer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2006 the original author or authors. + * Copyright 2002-2009 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,10 +18,10 @@ package org.springframework.web.servlet.view.freemarker; import java.io.IOException; import java.util.List; - import javax.servlet.ServletContext; import freemarker.cache.ClassTemplateLoader; +import freemarker.cache.TemplateLoader; import freemarker.ext.jsp.TaglibFactory; import freemarker.template.Configuration; import freemarker.template.TemplateException; @@ -120,7 +120,7 @@ public class FreeMarkerConfigurer extends FreeMarkerConfigurationFactory * for the Spring-provided macros, added to the end of the list. */ @Override - protected void postProcessTemplateLoaders(List templateLoaders) { + protected void postProcessTemplateLoaders(List templateLoaders) { templateLoaders.add(new ClassTemplateLoader(FreeMarkerConfigurer.class, "")); logger.info("ClassTemplateLoader for Spring macros added to FreeMarker configuration"); } diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/view/freemarker/FreeMarkerView.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/view/freemarker/FreeMarkerView.java index ac3a7dcadd2..9e88de553d5 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/view/freemarker/FreeMarkerView.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/view/freemarker/FreeMarkerView.java @@ -16,6 +16,7 @@ package org.springframework.web.servlet.view.freemarker; +import java.io.FileNotFoundException; import java.io.IOException; import java.util.Collections; import java.util.Enumeration; @@ -112,7 +113,7 @@ public class FreeMarkerView extends AbstractTemplateView { /** * Set the FreeMarker Configuration to be used by this view. - * If this is not set, the default lookup will occur: a single {@link FreeMarkerConfig} + *

If this is not set, the default lookup will occur: a single {@link FreeMarkerConfig} * is expected in the current web application context, with any bean name. * Note: using this method will cause a new instance of {@link TaglibFactory} * to created for every single {@link FreeMarkerView} instance. This can be quite expensive @@ -158,8 +159,6 @@ public class FreeMarkerView extends AbstractTemplateView { throw new BeanInitializationException("Initialization of GenericServlet adapter failed", ex); } this.servletContextHashModel = new ServletContextHashModel(servlet, getObjectWrapper()); - - checkTemplate(); } /** @@ -196,12 +195,19 @@ public class FreeMarkerView extends AbstractTemplateView { * Check that the FreeMarker template used for this view exists and is valid. *

Can be overridden to customize the behavior, for example in case of * multiple templates to be rendered into a single view. - * @throws ApplicationContextException if the template cannot be found or is invalid */ - protected void checkTemplate() throws ApplicationContextException { + @Override + public boolean checkResource() throws Exception { try { // Check that we can get the template, even if we might subsequently get it again. - getTemplate(getConfiguration().getLocale()); + getTemplate(getUrl(), getConfiguration().getLocale()); + return true; + } + catch (FileNotFoundException ex) { + if (logger.isDebugEnabled()) { + logger.debug("No FreeMarker view found for URL: " + getUrl()); + } + return false; } catch (ParseException ex) { throw new ApplicationContextException( diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/view/freemarker/FreeMarkerViewResolver.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/view/freemarker/FreeMarkerViewResolver.java index a0c376df35f..a6129a5dedd 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/view/freemarker/FreeMarkerViewResolver.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/view/freemarker/FreeMarkerViewResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2006 the original author or authors. + * Copyright 2002-2009 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,15 +19,15 @@ package org.springframework.web.servlet.view.freemarker; import org.springframework.web.servlet.view.AbstractTemplateViewResolver; /** - * Convenience subclass of UrlBasedViewResolver that supports FreeMarkerView - * (i.e. FreeMarker templates) and custom subclasses of it. + * Convenience subclass of {@link org.springframework.web.servlet.view.UrlBasedViewResolver} + * that supports {@link FreeMarkerView} (i.e. FreeMarker templates) and custom subclasses of it. * *

The view class for all views generated by this resolver can be specified - * via setViewClass. See UrlBasedViewResolver's javadoc for details. + * via the "viewClass" property. See UrlBasedViewResolver's javadoc for details. * - *

Note: When chaining ViewResolvers, a FreeMarkerViewResolver always - * needs to be last, as it will attempt to resolve any view name, no matter - * whether the underlying resource actually exists. + *

Note: When chaining ViewResolvers, a FreeMarkerViewResolver will + * check for the existence of the specified template resources and only return + * a non-null View object if the template was actually found. * * @author Juergen Hoeller * @since 1.1 @@ -40,18 +40,12 @@ import org.springframework.web.servlet.view.AbstractTemplateViewResolver; */ public class FreeMarkerViewResolver extends AbstractTemplateViewResolver { - /** - * Sets default viewClass to requiredViewClass. - * @see #setViewClass - * @see #requiredViewClass - */ public FreeMarkerViewResolver() { setViewClass(requiredViewClass()); } /** - * Requires FreeMarkerView. - * @see FreeMarkerView + * Requires {@link FreeMarkerView}. */ @Override protected Class requiredViewClass() { diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/view/tiles2/TilesConfigurer.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/view/tiles2/TilesConfigurer.java index d56263ec424..7af0f39254b 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/view/tiles2/TilesConfigurer.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/view/tiles2/TilesConfigurer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2007 the original author or authors. + * Copyright 2002-2009 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +18,6 @@ package org.springframework.web.servlet.view.tiles2; import java.util.Enumeration; import java.util.Properties; - import javax.servlet.ServletConfig; import javax.servlet.ServletContext; @@ -49,14 +48,11 @@ import org.springframework.web.context.ServletContextAware; * for more information about Tiles, which basically is a templating * mechanism for JSP-based web applications. * - *

The TilesConfigurer simply configures a TilesContainer using a set - * of files containing definitions, to be accessed by {@link TilesView} - * instances. + *

The TilesConfigurer simply configures a TilesContainer using a set of files + * containing definitions, to be accessed by {@link TilesView} instances. * *

TilesViews can be managed by any {@link org.springframework.web.servlet.ViewResolver}. - * For simple convention-based view resolution, consider using - * {@link org.springframework.web.servlet.view.UrlBasedViewResolver} with the - * "viewClass" property set to "org.springframework.web.servlet.view.tiles2.TilesView". + * For simple convention-based view resolution, consider using {@link TilesViewResolver}. * *

A typical TilesConfigurer bean definition looks as follows: * diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/view/tiles2/TilesView.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/view/tiles2/TilesView.java index a1bdeb3eba7..d476874fe10 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/view/tiles2/TilesView.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/view/tiles2/TilesView.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 the original author or authors. + * Copyright 2002-2009 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,7 +17,6 @@ package org.springframework.web.servlet.view.tiles2; import java.util.Map; - import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; @@ -49,6 +48,12 @@ import org.springframework.web.util.WebUtils; */ public class TilesView extends AbstractUrlBasedView { + @Override + public boolean checkResource() throws Exception { + TilesContainer container = TilesAccess.getContainer(getServletContext()); + return container.isValidDefinition(getUrl()); + } + @Override protected void renderMergedOutputModel( Map model, HttpServletRequest request, HttpServletResponse response) throws Exception { diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/view/tiles2/TilesViewResolver.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/view/tiles2/TilesViewResolver.java new file mode 100644 index 00000000000..593776d68ab --- /dev/null +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/view/tiles2/TilesViewResolver.java @@ -0,0 +1,54 @@ +/* + * Copyright 2002-2009 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.web.servlet.view.tiles2; + +import org.springframework.web.servlet.view.UrlBasedViewResolver; + +/** + * Convenience subclass of {@link org.springframework.web.servlet.view.UrlBasedViewResolver} + * that supports {@link TilesView} (i.e. Tiles definitions) and custom subclasses of it. + * + *

The view class for all views generated by this resolver can be specified + * via the "viewClass" property. See UrlBasedViewResolver's javadoc for details. + * + *

Note: When chaining ViewResolvers, a TilesViewResolver will + * check for the existence of the specified template resources and only return + * a non-null View object if the template was actually found. + * + * @author Juergen Hoeller + * @since 3.0 + * @see #setViewClass + * @see #setPrefix + * @see #setSuffix + * @see #setRequestContextAttribute + * @see TilesView + */ +public class TilesViewResolver extends UrlBasedViewResolver { + + public TilesViewResolver() { + setViewClass(requiredViewClass()); + } + + /** + * Requires {@link TilesView}. + */ + @Override + protected Class requiredViewClass() { + return TilesView.class; + } + +} diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/view/velocity/VelocityLayoutView.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/view/velocity/VelocityLayoutView.java index c923c689383..dcb59a8ff3a 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/view/velocity/VelocityLayoutView.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/view/velocity/VelocityLayoutView.java @@ -17,14 +17,13 @@ package org.springframework.web.servlet.view.velocity; import java.io.StringWriter; - import javax.servlet.http.HttpServletResponse; import org.apache.velocity.Template; import org.apache.velocity.context.Context; import org.apache.velocity.exception.ResourceNotFoundException; -import org.springframework.context.ApplicationContextException; +import org.springframework.core.NestedIOException; /** * VelocityLayoutView emulates the functionality offered by Velocity's @@ -119,19 +118,22 @@ public class VelocityLayoutView extends VelocityToolboxView { * can be changed which may invalidate any early checking done here. */ @Override - protected void checkTemplate() throws ApplicationContextException { - super.checkTemplate(); + public boolean checkResource() throws Exception { + if (!super.checkResource()) { + return false; + } try { // Check that we can get the template, even if we might subsequently get it again. getTemplate(this.layoutUrl); + return true; } catch (ResourceNotFoundException ex) { - throw new ApplicationContextException("Cannot find Velocity template for URL [" + this.layoutUrl + + throw new NestedIOException("Cannot find Velocity template for URL [" + this.layoutUrl + "]: Did you specify the correct resource loader path?", ex); } catch (Exception ex) { - throw new ApplicationContextException( + throw new NestedIOException( "Could not load Velocity template for URL [" + this.layoutUrl + "]", ex); } } diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/view/velocity/VelocityView.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/view/velocity/VelocityView.java index 9cc650df992..5607ac876b9 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/view/velocity/VelocityView.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/view/velocity/VelocityView.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 the original author or authors. + * Copyright 2002-2009 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,6 +34,7 @@ import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactoryUtils; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.context.ApplicationContextException; +import org.springframework.core.NestedIOException; import org.springframework.web.servlet.support.RequestContextUtils; import org.springframework.web.servlet.view.AbstractTemplateView; import org.springframework.web.util.NestedServletException; @@ -52,10 +53,10 @@ import org.springframework.web.util.NestedServletException; * view, or null if not needed. VelocityFormatter is part of standard Velocity. *

  • dateToolAttribute (optional, default=null): the name of the * DateTool helper object to expose in the Velocity context of this view, - * or null if not needed. DateTool is part of Velocity Tools 1.0. + * or null if not needed. DateTool is part of Velocity Tools. *
  • numberToolAttribute (optional, default=null): the name of the * NumberTool helper object to expose in the Velocity context of this view, - * or null if not needed. NumberTool is part of Velocity Tools 1.1. + * or null if not needed. NumberTool is part of Velocity Tools. *
  • cacheTemplate (optional, default=false): whether or not the Velocity * template should be cached. It should normally be true in production, but setting * this to false enables us to modify Velocity templates without restarting the @@ -67,8 +68,8 @@ import org.springframework.web.util.NestedServletException; * accessible in the current web application context, with any bean name. * Alternatively, you can set the VelocityEngine object as bean property. * - *

    Note: Spring's VelocityView requires Velocity 1.3 or higher, and optionally - * Velocity Tools 1.0 or higher (depending on the use of DateTool and/or NumberTool). + *

    Note: Spring 3.0's VelocityView requires Velocity 1.4 or higher, and optionally + * Velocity Tools 1.1 or higher (depending on the use of DateTool and/or NumberTool). * * @author Rod Johnson * @author Juergen Hoeller @@ -194,7 +195,7 @@ public class VelocityView extends AbstractTemplateView { /** * Set the VelocityEngine to be used by this view. - * If this is not set, the default lookup will occur: A single VelocityConfig + *

    If this is not set, the default lookup will occur: A single VelocityConfig * is expected in the current web application context, with any bean name. * @see VelocityConfig */ @@ -222,8 +223,6 @@ public class VelocityView extends AbstractTemplateView { // No explicit VelocityEngine: try to autodetect one. setVelocityEngine(autodetectVelocityEngine()); } - - checkTemplate(); } /** @@ -252,19 +251,22 @@ public class VelocityView extends AbstractTemplateView { * Check that the Velocity template used for this view exists and is valid. *

    Can be overridden to customize the behavior, for example in case of * multiple templates to be rendered into a single view. - * @throws ApplicationContextException if the template cannot be found or is invalid */ - protected void checkTemplate() throws ApplicationContextException { + @Override + public boolean checkResource() throws Exception { try { // Check that we can get the template, even if we might subsequently get it again. - this.template = getTemplate(); + this.template = getTemplate(getUrl()); + return true; } catch (ResourceNotFoundException ex) { - throw new ApplicationContextException("Cannot find Velocity template for URL [" + getUrl() + - "]: Did you specify the correct resource loader path?", ex); + if (logger.isDebugEnabled()) { + logger.debug("No Velocity view found for URL: " + getUrl()); + } + return false; } catch (Exception ex) { - throw new ApplicationContextException( + throw new NestedIOException( "Could not load Velocity template for URL [" + getUrl() + "]", ex); } } diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/view/velocity/VelocityViewResolver.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/view/velocity/VelocityViewResolver.java index b32b0f7432d..41f50bd3b41 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/view/velocity/VelocityViewResolver.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/view/velocity/VelocityViewResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 the original author or authors. + * Copyright 2002-2009 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,14 +21,14 @@ import org.springframework.web.servlet.view.AbstractUrlBasedView; /** * Convenience subclass of {@link org.springframework.web.servlet.view.UrlBasedViewResolver} - * that supports VelocityView (i.e. Velocity templates) and custom subclasses of it. + * that supports {@link VelocityView} (i.e. Velocity templates) and custom subclasses of it. * *

    The view class for all views generated by this resolver can be specified - * via setViewClass. See UrlBasedViewResolver's javadoc for details. + * via the "viewClass" property. See UrlBasedViewResolver's javadoc for details. * - *

    Note: When chaining ViewResolvers, a VelocityViewResolver always needs - * to be last, as it will attempt to resolve any view name, no matter whether - * the underlying resource actually exists. + *

    Note: When chaining ViewResolvers, a VelocityViewResolver will + * check for the existence of the specified template resources and only return + * a non-null View object if the template was actually found. * * @author Juergen Hoeller * @since 13.12.2003 @@ -50,24 +50,19 @@ public class VelocityViewResolver extends AbstractTemplateViewResolver { private String toolboxConfigLocation; - /** - * Sets default viewClass to requiredViewClass. - * @see #setViewClass - * @see #requiredViewClass - */ public VelocityViewResolver() { setViewClass(requiredViewClass()); } /** - * Requires VelocityView. - * @see VelocityView + * Requires {@link VelocityView}. */ @Override protected Class requiredViewClass() { return VelocityView.class; } + /** * Set the name of the DateTool helper object to expose in the Velocity context * of this view, or null if not needed. DateTool is part of Velocity Tools 1.0. diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/view/freemarker/FreeMarkerViewTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/view/freemarker/FreeMarkerViewTests.java index e1fa20605b1..9ec250a8e2b 100644 --- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/view/freemarker/FreeMarkerViewTests.java +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/view/freemarker/FreeMarkerViewTests.java @@ -16,6 +16,7 @@ package org.springframework.web.servlet.view.freemarker; +import java.io.FileNotFoundException; import java.io.IOException; import java.io.StringReader; import java.io.Writer; @@ -26,7 +27,6 @@ import javax.servlet.http.HttpServletResponse; import freemarker.ext.servlet.AllHttpScopesHashModel; import freemarker.template.Configuration; -import freemarker.template.SimpleScalar; import freemarker.template.Template; import freemarker.template.TemplateException; import org.easymock.MockControl; @@ -38,9 +38,13 @@ import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.mock.web.MockServletContext; import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.context.support.StaticWebApplicationContext; import org.springframework.web.servlet.DispatcherServlet; +import org.springframework.web.servlet.View; import org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver; import org.springframework.web.servlet.view.AbstractView; +import org.springframework.web.servlet.view.InternalResourceView; +import org.springframework.web.servlet.view.RedirectView; /** * @author Juergen Hoeller @@ -49,7 +53,7 @@ import org.springframework.web.servlet.view.AbstractView; public class FreeMarkerViewTests { @Test - public void testNoFreemarkerConfig() throws Exception { + public void testNoFreeMarkerConfig() throws Exception { FreeMarkerView fv = new FreeMarkerView(); MockControl wmc = MockControl.createControl(WebApplicationContext.class); @@ -101,7 +105,7 @@ public class FreeMarkerViewTests { Map configs = new HashMap(); FreeMarkerConfigurer configurer = new FreeMarkerConfigurer(); configurer.setConfiguration(new TestConfiguration()); - configs.put("freemarkerConfig", configurer); + configs.put("configurer", configurer); wmc.setReturnValue(configs); wac.getParentBeanFactory(); wmc.setReturnValue(null); @@ -138,7 +142,7 @@ public class FreeMarkerViewTests { Map configs = new HashMap(); FreeMarkerConfigurer configurer = new FreeMarkerConfigurer(); configurer.setConfiguration(new TestConfiguration()); - configs.put("freemarkerConfig", configurer); + configs.put("configurer", configurer); wmc.setReturnValue(configs); wac.getParentBeanFactory(); wmc.setReturnValue(null); @@ -164,19 +168,54 @@ public class FreeMarkerViewTests { assertEquals("myContentType", response.getContentType()); } + @Test + public void testFreeMarkerViewResolver() throws Exception { + FreeMarkerConfigurer configurer = new FreeMarkerConfigurer(); + configurer.setConfiguration(new TestConfiguration()); + + StaticWebApplicationContext wac = new StaticWebApplicationContext(); + wac.setServletContext(new MockServletContext()); + wac.getBeanFactory().registerSingleton("configurer", configurer); + wac.refresh(); + + FreeMarkerViewResolver vr = new FreeMarkerViewResolver(); + vr.setPrefix("prefix_"); + vr.setSuffix("_suffix"); + vr.setApplicationContext(wac); + + View view = vr.resolveViewName("test", Locale.CANADA); + assertEquals("Correct view class", FreeMarkerView.class, view.getClass()); + assertEquals("Correct URL", "prefix_test_suffix", ((FreeMarkerView) view).getUrl()); + + view = vr.resolveViewName("non-existing", Locale.CANADA); + assertNull(view); + + view = vr.resolveViewName("redirect:myUrl", Locale.getDefault()); + assertEquals("Correct view class", RedirectView.class, view.getClass()); + assertEquals("Correct URL", "myUrl", ((RedirectView) view).getUrl()); + + view = vr.resolveViewName("forward:myUrl", Locale.getDefault()); + assertEquals("Correct view class", InternalResourceView.class, view.getClass()); + assertEquals("Correct URL", "myUrl", ((InternalResourceView) view).getUrl()); + } + private class TestConfiguration extends Configuration { public Template getTemplate(String name, final Locale locale) throws IOException { - assertEquals("templateName", name); - return new Template(name, new StringReader("test")) { - public void process(Object model, Writer writer) throws TemplateException, IOException { - assertEquals(Locale.US, locale); - assertTrue(model instanceof AllHttpScopesHashModel); - AllHttpScopesHashModel fmModel = (AllHttpScopesHashModel) model; - assertEquals("myvalue", fmModel.get("myattr").toString()); - } - }; + if (name.equals("templateName") || name.equals("prefix_test_suffix")) { + return new Template(name, new StringReader("test")) { + public void process(Object model, Writer writer) throws TemplateException, IOException { + assertEquals(Locale.US, locale); + assertTrue(model instanceof AllHttpScopesHashModel); + AllHttpScopesHashModel fmModel = (AllHttpScopesHashModel) model; + assertEquals("myvalue", fmModel.get("myattr").toString()); + } + }; + } + else { + throw new FileNotFoundException(); + } } } diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/view/velocity/TestVelocityEngine.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/view/velocity/TestVelocityEngine.java index 76773853480..98179384cdf 100644 --- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/view/velocity/TestVelocityEngine.java +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/view/velocity/TestVelocityEngine.java @@ -21,6 +21,7 @@ import java.util.Map; import org.apache.velocity.Template; import org.apache.velocity.app.VelocityEngine; +import org.apache.velocity.exception.ResourceNotFoundException; /** * @author Juergen Hoeller @@ -46,7 +47,7 @@ public class TestVelocityEngine extends VelocityEngine { public Template getTemplate(String name) { Template template = (Template) this.templates.get(name); if (template == null) { - throw new IllegalStateException("No template registered for name [" + name + "]"); + throw new ResourceNotFoundException("No template registered for name [" + name + "]"); } return template; } diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/view/velocity/VelocityViewTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/view/velocity/VelocityViewTests.java index 809fe1151f5..e8269bc4a43 100644 --- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/view/velocity/VelocityViewTests.java +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/view/velocity/VelocityViewTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 the original author or authors. + * Copyright 2002-2009 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,7 +28,6 @@ import org.apache.velocity.Template; import org.apache.velocity.app.VelocityEngine; import org.apache.velocity.context.Context; import org.apache.velocity.exception.ParseErrorException; -import org.apache.velocity.exception.ResourceNotFoundException; import org.apache.velocity.tools.generic.DateTool; import org.apache.velocity.tools.generic.MathTool; import org.apache.velocity.tools.generic.NumberTool; @@ -72,7 +71,7 @@ public class VelocityViewTests extends TestCase { } catch (ApplicationContextException ex) { // Check there's a helpful error message - assertTrue(ex.getMessage().indexOf("VelocityConfig") != -1); + assertTrue(ex.getMessage().contains("VelocityConfig")); } wmc.verify(); @@ -90,61 +89,6 @@ public class VelocityViewTests extends TestCase { } } - public void testCannotResolveTemplateNameResourceNotFoundException() throws Exception { - testCannotResolveTemplateName(new ResourceNotFoundException("")); - } - - public void testCannotResolveTemplateNameParseErrorException() throws Exception { - testCannotResolveTemplateName(new ParseErrorException("")); - } - - public void testCannotResolveTemplateNameNonspecificException() throws Exception { - testCannotResolveTemplateName(new Exception("")); - } - - /** - * Check for failure to lookup a template for a range of reasons. - */ - private void testCannotResolveTemplateName(final Exception templateLookupException) throws Exception { - final String templateName = "test.vm"; - - MockControl wmc = MockControl.createControl(WebApplicationContext.class); - WebApplicationContext wac = (WebApplicationContext) wmc.getMock(); - wac.getParentBeanFactory(); - wmc.setReturnValue(null); - VelocityConfig vc = new VelocityConfig() { - public VelocityEngine getVelocityEngine() { - return new VelocityEngine() { - public Template getTemplate(String tn) - throws ResourceNotFoundException, ParseErrorException, Exception { - assertEquals(tn, templateName); - throw templateLookupException; - } - }; - } - }; - wac.getBeansOfType(VelocityConfig.class, true, false); - Map configurers = new HashMap(); - configurers.put("velocityConfigurer", vc); - wmc.setReturnValue(configurers); - wmc.replay(); - - VelocityView vv = new VelocityView(); - //vv.setExposeDateFormatter(false); - //vv.setExposeCurrencyFormatter(false); - vv.setUrl(templateName); - - try { - vv.setApplicationContext(wac); - fail(); - } - catch (ApplicationContextException ex) { - assertEquals(ex.getCause(), templateLookupException); - } - - wmc.verify(); - } - public void testMergeTemplateSucceeds() throws Exception { testValidTemplateName(null); } @@ -397,6 +341,7 @@ public class VelocityViewTests extends TestCase { StaticWebApplicationContext wac = new StaticWebApplicationContext(); wac.getBeanFactory().registerSingleton("configurer", vc); + wac.refresh(); VelocityViewResolver vr = new VelocityViewResolver(); vr.setPrefix("prefix_"); @@ -407,6 +352,9 @@ public class VelocityViewTests extends TestCase { assertEquals("Correct view class", VelocityView.class, view.getClass()); assertEquals("Correct URL", "prefix_test_suffix", ((VelocityView) view).getUrl()); + view = vr.resolveViewName("non-existing", Locale.CANADA); + assertNull(view); + view = vr.resolveViewName("redirect:myUrl", Locale.getDefault()); assertEquals("Correct view class", RedirectView.class, view.getClass()); assertEquals("Correct URL", "myUrl", ((RedirectView) view).getUrl()); @@ -425,6 +373,7 @@ public class VelocityViewTests extends TestCase { StaticWebApplicationContext wac = new StaticWebApplicationContext(); wac.getBeanFactory().registerSingleton("configurer", vc); + wac.refresh(); String toolbox = "org/springframework/web/servlet/view/velocity/toolbox.xml"; @@ -452,6 +401,7 @@ public class VelocityViewTests extends TestCase { StaticWebApplicationContext wac = new StaticWebApplicationContext(); wac.getBeanFactory().registerSingleton("configurer", vc); + wac.refresh(); String toolbox = "org/springframework/web/servlet/view/velocity/toolbox.xml"; @@ -480,6 +430,7 @@ public class VelocityViewTests extends TestCase { StaticWebApplicationContext wac = new StaticWebApplicationContext(); wac.getBeanFactory().registerSingleton("configurer", vc); + wac.refresh(); VelocityLayoutViewResolver vr = new VelocityLayoutViewResolver(); vr.setPrefix("prefix_");