Announcement Announcement Module
Collapse
No announcement yet.
No XML with JSON/XML xmlfree webmvc jax2 jackson and tiles config with spring 3.2 Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • No XML with JSON/XML xmlfree webmvc jax2 jackson and tiles config with spring 3.2

    Based on the tiles sample project, I want to add RestFUL features, which support JSON and XML either accept-header-based or path-based. Addionally I tried to use a mostly xml-free (java- an annotationbased) approach.

    Everything works as expected but XML -> 406 Error

    This is my controller, which should respond to path based XML and JSON requests

    Code:
    @Controller
    @RequestMapping("services/underlyings")
    public class SampleController {
    
    	@Autowired
    	SampleService sampleService;
    
    	@RequestMapping(method = RequestMethod.GET)
    	@ResponseBody
    	public List<&gt;> get() {
    		List<Sample> sampleList = sampleService.findAll();
    		return sampleList;
    	}
    ...
    with a domain object:

    Code:
    @XmlRootElement
    @XmlAccessorType(XmlAccessType.FIELD)
    public class Sample implements java.io.Serializable {
    
    and some @XmlAttribute 
    ...
    My calls to either return XML or JSON looks like this:

    Code:
    		$("#xmltest").click(function() {
    			$.ajax({
    				url : "services/underlyings.xml",
    				dataType : "xml",
    				accepts :  {xml: 'application/xml'},
    				success : function(retValue) {
    					...
    				},
    				error: ...
    				}
     			});
    		});
    json is configured with dataType: "json" and accepts : {json: 'application/json'}

    As (every)one can expect, both clicks will result in a "Error Not Acceptable" (Error 406) together with a java error
    Resolving exception from handler [...] org.springframework.web.HttpMediaTypeNotAcceptable Exception: Could not find acceptable representation

    You will find a first solution to solve this problem using the mvc defaults:
    "<mvc:annotation-driven/> configures support for JSON if Jackson is in the classpath, and support for XML if JAXB is present in the classpath"

    In the javabased configuration using the WebMvcConfigAdapter the annotation @EnableWebMvc should be the synonym of <mvc:annotation-driven/>
    So I add to my pom.xml two dependencies

    Code:
    		<dependency>
    			<groupId>org.codehaus.jackson</groupId>
    			<artifactId>jackson-mapper-asl</artifactId>
    			<version>1.9.12</version>
    			<type>jar</type>
    			<scope>compile</scope>
    		</dependency>
    		<dependency>
    			<groupId>javax.xml.bind</groupId>
    			<artifactId>jaxb-api</artifactId>
    			<version>2.2.7</version>
    		</dependency>
    And voilá, the JSON call works as expected - but the XML request still returns the same 406 error.

    At this point the easiest solution would be to do no additional configuration but make sure, that I choose the "right" dependency for XML-support.
    There are mentioned several issues regarding this theme in spring 3.1 and 3.2 but with no working solution for me.

    After 3 days of crossing the "spring-sea", I haven't found any working sample yet. Either the samples uses
    not the current spring version or the samples have xml configuration and no tiles.

    Before I post my different approches you should be aware of those two config files, which are part of the initial tiles sample project

    Code:
    @Configuration
    @EnableWebMvc
    @ComponentScan(basePackages = { "de.mw.sample.web.controller",
    		"de.mw.sample.web.rest.controller", "de.mw.sample.persistence",
    		"de.mw.sample.service" })
    public class WebMvcConfigAdapter extends WebMvcConfigurerAdapter {
    
    	private static final String MESSAGE_SOURCE = "/WEB-INF/i18n/messages";
    	private static final String TILES = "/WEB-INF/tiles/tiles.xml";
    	private static final String VIEWS = "/WEB-INF/views/**/views.xml";
    
    	private static final String RESOURCES_LOCATION = "/resources/";
    	private static final String RESOURCES_HANDLER = RESOURCES_LOCATION + "**";
    
    	@Bean(name = "messageSource")
    	public MessageSource configureMessageSource() {
    		ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
    		messageSource.setBasename(MESSAGE_SOURCE);
    		messageSource.setCacheSeconds(5);
    		return messageSource;
    	}
    
    	@Bean
    	public TilesViewResolver configureTilesViewResolver() {
    		TilesViewResolver tilesViewResolver = new TilesViewResolver();
    
    		tilesViewResolver.setOrder(2);
    
    		return tilesViewResolver;
    	}
    
    	@Bean
    	public TilesConfigurer configureTilesConfigurer() {
    		TilesConfigurer configurer = new TilesConfigurer();
    		configurer.setDefinitions(new String[] { TILES, VIEWS });
    		return configurer;
    	}
    
    	@Override
    	public Validator getValidator() {
    		...
    	}
    
    	@Override
    	public void addResourceHandlers(ResourceHandlerRegistry registry) {
    		...
    	}
    
    	@Override
    	public void configureDefaultServletHandling(
    			DefaultServletHandlerConfigurer configurer) {
    		configurer.enable();
    	}
    
    	@Override
    	public void addArgumentResolvers(
    			List<HandlerMethodArgumentResolver> argumentResolvers) {
    		...
    	}
    
    	// custom argument resolver inner classes
    	...
    
    }
    and the webapplication initializer

    Code:
    public class WebAppInitializer implements WebApplicationInitializer {
    
    	@Override
    	public void onStartup(ServletContext servletContext)
    			throws ServletException {
    		AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
    		context.setConfigLocation("de.mw.sample.config");
    
    		... do some securityfiltering
    
    		FilterRegistration.Dynamic characterEncodingFilter = servletContext
    				.addFilter("characterEncodingFilter",
    						new CharacterEncodingFilter());
    		characterEncodingFilter.addMappingForUrlPatterns(
    				EnumSet.allOf(DispatcherType.class), true, "/*");
    		characterEncodingFilter.setInitParameter("encoding", "UTF-8");
    		characterEncodingFilter.setInitParameter("forceEncoding", "true");
    
    		servletContext.addListener(new ContextLoaderListener(context));
    		servletContext.setInitParameter("defaultHtmlEscape", "true");
    
    		DispatcherServlet servlet = new DispatcherServlet();
    		// no explicit configuration reference here: everything is configured in
    		// the root container for simplicity
    		servlet.setContextConfigLocation("");
    
    		ServletRegistration.Dynamic appServlet = servletContext.addServlet(
    				"appServlet", servlet);
    		appServlet.setLoadOnStartup(1);
    		appServlet.setAsyncSupported(true);
    
    		Set<String> mappingConflicts = appServlet.addMapping("/");
    		if (!mappingConflicts.isEmpty()) {
    			throw new IllegalStateException(
    					"'appServlet' cannot be mapped to '/' under Tomcat versions <= 7.0.14");
    		}
    	}
    }
    I changed code only in the configuration class file of my WebMvcConfigAdapter.
    At this point, I wonder, how jaxb should be aware of my domain classes to convert. On the other hand json can convert java objects
    without a explicit configuration as well, so why should this be necessary for XML ?

    First, I tried several ways to configure (or add) ContentNegotiation:

    This piece of code should replace the default configuration of contentnegotiation that, as described above, should be automatically done
    by spring if the proper libraries are present:

    Code:
    @Override
    public void configureContentNegotiation(
    		ContentNegotiationConfigurer configurer) {
    	Map<String, MediaType> mediaTypes = new HashMap<String, MediaType>();
    	mediaTypes.put("json", MediaType.APPLICATION_JSON);
    	mediaTypes.put("xml", MediaType.APPLICATION_XML);
    
    	configurer.mediaTypes(mediaTypes);
    }
    Stop/Start: Still not working.

    Next I add a ContentNegotiationViewResolver to explicitly define views to be used for XML and JSON. Here I have to add the marshallingView and the
    jaxb2Marshaller as well.

    Code:
    @Bean
    public ContentNegotiatingViewResolver setUpContentNegotiatingViewResolver() {
    
    	ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();
    
    	Map<String, MediaType> mediaTypes = new HashMap<String, MediaType>();
    	mediaTypes.put("json", MediaType.APPLICATION_JSON);
    	mediaTypes.put("xml", MediaType.APPLICATION_XML);
    	PathExtensionContentNegotiationStrategy pathExtensionContentNegotiationStrategy = new PathExtensionContentNegotiationStrategy(
    			mediaTypes);
    
    	ContentNegotiationManager manager = new ContentNegotiationManager(
    			pathExtensionContentNegotiationStrategy);
    
    	List<View> defaultViews = new ArrayList<View>();
    	defaultViews.add(new MappingJacksonJsonView());
    	defaultViews.add(marshallingView());
    
    	resolver.setDefaultViews(defaultViews);
    	resolver.setContentNegotiationManager(manager);
    	resolver.setOrder(-1);
    
    	return resolver;
    }
    
    @Bean
    public MarshallingView marshallingView() {
    	final MarshallingView view = new MarshallingView();
    	view.setMarshaller(jaxb2Marshaller());
    	return view;
    }
    
    @Bean
    public Jaxb2Marshaller jaxb2Marshaller() {
    	Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
    	marshaller.setPackagesToScan(new String[] { "de.mw.sample.domain" });
    	return marshaller;
    }
    At this point, the setPackagesToScan method of the jaxb2Marshaller should resolve my fear that jaxb is not aware of
    my domain class (with @XmlRootElement).

    Stop/Start: Still not working.

    Even the app knows how to "convert" the result to JSON, I decided to bring up something called HttpMessageConverter...

    Code:
    @Override
    public void configureMessageConverters(
    		List<HttpMessageConverter<?>> converters) {
    	converters.add(mappingJaxb2HttpMessageConverter());
    }
    
    @Bean
    public Jaxb2RootElementHttpMessageConverter mappingJaxb2HttpMessageConverter() {
    	Jaxb2RootElementHttpMessageConverter jaxb2RootElementHttpMessageConverter = new Jaxb2RootElementHttpMessageConverter();
    	List<MediaType> mediaTypes = new ArrayList<MediaType>();
    	mediaTypes.add(MediaType.APPLICATION_XML);
    	jaxb2RootElementHttpMessageConverter.setSupportedMediaTypes(mediaTypes);
    	return jaxb2RootElementHttpMessageConverter;
    }
    Stop/Start: Still not working.
    BTW. At this point, even JSON is not working any longer :-(

    Is it just a start order problem or some cross side effect with tiles ?
    Now, I have high hopes on you, the community ;-)
    Thx


    Cheers
    Frank
Working...
X