Announcement Announcement Module
Collapse
No announcement yet.
InternalResourceViewResolver and multiple content types ? Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • InternalResourceViewResolver and multiple content types ?

    It seems like it isn't possible to have a SpringMVC app with multiple content types using InternalResourceViewResolver.

    The content type put on a response in a controller's handleRequestInternal or at the top of a jsp/jstl view is ignored.

    Since only one view class is set on the view resolver and the view is what returns getContentType(), I can't seem to have one controller use a JSP of type "text/html" and one of type "text/xml".

    Is chaining the only way to have multiple view resolvers? This obviously won't work if you want two InternalResourceViewResolvers so that each can use a different view class and different mime type.

    Why is there not a method on the handler or controller to set a view resolver?

  • #2
    Does anyone know which class actually does the figuring out of which viewResolver to use?

    If there is a hook in the Controller or Handler or ModelAndView or Servlet, then I could override the existing functionality to get this to work the way I think it should.

    Comment


    • #3
      Digging deeper...

      I had to write my own Dispatcher servlet to be able to specify which resolver bean name to use.

      I woudn't have had to do that if org.springframework.web.servlet.DispatcherServlet. VIEW_RESOLVER_BEAN_NAME wasn't just a private constant, but rather something configurable.

      Code:
      public class MyDispatcherServlet extends
      	org.springframework.web.servlet.DispatcherServlet {
      
          protected String viewResolverName = null;
          protected ViewResolver namedViewResolver = null;
      
          /**
           * 
           */
          public MyDispatcherServlet() {
      	super();
          }
      
          /**
           * Initialize the specific view resolver used by this class.
           */
          private void initNamedResolver(ApplicationContext context) {
      	try {
      	    this.namedViewResolver = (ViewResolver) context.getBean(
      		    getViewResolverName(), ViewResolver.class);
      	} catch (NoSuchBeanDefinitionException ex) {
      	    // Ignore, we'll add a default ViewResolver later.
      	}
      
          }
      
          /**
           * Try to used the specified view resolver.
           */
          protected View resolveViewName(String viewName, Map model, Locale locale,
      	    HttpServletRequest request) throws Exception {
      	View view = null;
      	if (this.namedViewResolver != null) {
      	    view = this.namedViewResolver.resolveViewName(viewName, locale);	    
      	}
      	if (view != null) {
      	    return view;
      	} else {
      	    return super.resolveViewName(viewName, model, locale, request);
      	}
      
          }
      
          /**
           * Initialize the strategy objects that this servlet uses.
           */
          protected void initStrategies(ApplicationContext context) {
      	super.initStrategies(context);
      	initNamedResolver(context);
          }
      
          public String getViewResolverName() {
      	return viewResolverName;
          }
      
          public void setViewResolverName(String viewResolverName) {
      	this.viewResolverName = viewResolverName;
          }
      
      }
      Now that I have that setup, I just had to add an init parameter in my web.xml to specify which "viewResolverName" to use. This init param automatically fills in the corresponding property of the servlet. Pretty cool.

      Sure enough, One servlet uses one viewResolver and another servlet uses a different view resolver.


      Still have a problem.


      One viewResolver has the normal Spring JSTLView class set:

      Code:
      	<bean id="pageViewResolver"
      		class="org.springframework.web.servlet.view.InternalResourceViewResolver">
      		<property name="viewClass">
      			<value>org.springframework.web.servlet.view.JstlView</value>
      		</property>
      		<property name="prefix">
      			<value>/WEB-INF/jsp/page/</value>
      		</property>
      		<property name="suffix">
      			<value>.jsp</value>
      		</property>
      	</bean>
      but the other has a class I've overridden called "XMLJSTLView".

      The only difference is that my XML subclass overrides the getContentType() to return "text/xml".

      The problem is, that while the new view class is being used, getContentType() never gets called, and the browser is still getting "text/html" !

      This really shouldn't be this difficult.

      Comment


      • #4
        This is a bug

        The problem happens in org.springframework.web.servlet.view.InternalResou rceView.renderMergedOutputModel()



        Code:
        	/**
        	 * Render the internal resource given the specified model.
        	 * This includes setting the model as request attributes.
        	 */
        	protected void renderMergedOutputModel(
        			Map model, HttpServletRequest request, HttpServletResponse response) throws Exception {
        
        		// Determine which request handle to expose to the RequestDispatcher.
        		HttpServletRequest requestToExpose = getRequestToExpose(request);
        
        		// Expose the model object as request attributes.
        		exposeModelAsRequestAttributes(model, requestToExpose);
        
        		// Expose helpers as request attributes, if any.
        		exposeHelpers(requestToExpose);
        
        		// Determine the path for the request dispatcher.
        		String dispatcherPath = prepareForRendering(requestToExpose, response);
        
        		// Obtain a RequestDispatcher for the target resource (typically a JSP).
        		RequestDispatcher rd = requestToExpose.getRequestDispatcher(dispatcherPath);
        		if (rd == null) {
        			throw new ServletException(
        					"Could not get RequestDispatcher for [" + getUrl() + "]: check that this file exists within your WAR");
        		}
        
        		// If already included or response already committed, perform include, else forward.
        		if (useInclude(requestToExpose, response)) {
        			response.setContentType(getContentType());
        			if (logger.isDebugEnabled()) {
        				logger.debug("Including resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
        			}
        			rd.include(requestToExpose, response);
        		}
        
        		else {
        			// Note: The forwarded resource is supposed to determine the content type itself.
        			exposeForwardRequestAttributes(requestToExpose);
        			if (logger.isDebugEnabled()) {
        				logger.debug("Forwarding to resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
        			}
        			rd.forward(requestToExpose, response);
        
        		}
        	}
        At the bottom, before the call to rd.forward(requestToExpose, response), the response's contentType is null. After the call it is "text/html".
        It never bothers to try and set the contentType based on the ViewResolver or the View.

        I changed the code to set it before to "text/xml", but the rd.forward() overwrites it back to "text/html" anyway. If i set it after the call to rd.forward() it gets ignored and just stays "text/html".


        This is extremely frustrating. Surely someone else wants to use the JSTLView functionality to creat XML output?

        Comment


        • #5
          Found a workaround


          Since I have a custom DispatcherServlet that allows for the setting of a specific viewResolver, I configure two different ones.


          First is for JSP generated html files:
          Code:
          <bean id="pageViewResolver"
          		class="org.springframework.web.servlet.view.InternalResourceViewResolver">
          		<property name="viewClass">
          			<value>org.springframework.web.servlet.view.JstlView</value>
          		</property>
          		<property name="prefix">
          			<value>/WEB-INF/jsp/page/</value>
          		</property>
          		<property name="suffix">
          			<value>.jsp</value>
          		</property>
          	</bean>

          Second is for JSP generated XML files:
          Code:
          <bean id="ajaxViewResolver"
          		class="org.springframework.web.servlet.view.InternalResourceViewResolver">
          		<property name="viewClass">
          			<value>org.springframework.web.servlet.view.JstlView</value>
          		</property>
          		<property name="prefix">
          			<value>/WEB-INF/jsp/ajax/</value>
          		</property>
          		<property name="suffix">
          			<value>.jsp</value>
          		</property>
          		<property name="contentType" value="text/xml"/>
          		<property name="alwaysInclude" value="true"/>		
          	</bean>
          The second one changes the behavior of:
          Code:
          InternalResourceViewResolver.renderMergedOutputModel
          (Map model, HttpServletRequest request, 
          	HttpServletResponse response)
           throws Exception {
          So that it sets the response's contentType based on the View's contentType (which is in turn, set by the viewResolver) and does an include instead of a forward with the requestDispatcher.

          When a forward is performed by the requestDispatcher, the contentType is set by the app server (tomcat 6.0.14 in my case) and that app server may choose to ignore the content type specified in your jsp files.

          Comment

          Working...
          X