I need to pass a namespaceMap to the JSONProvider configuration via web.xml file in Apache CXF.
I do not use Spring on this application and do not do programmatic configuration of Providers like this question suggests: CXF: No message body writer found for class - automatically mapping non-simple resources
Rather use configuration in the style of this non-Spring example from the CXF code.
But, the example does not show how to provide a namespaceMap and I'm unsure of how to specify a map in that style of configuration.
I'm going ahead with some trial-and-error.
Anyone know of a reference guide for the syntax for the JSONProvider config via web.xml?
I was not able to find a reference guide, but did manage to solve the configuration issue by some code spelunking and this mailing list thread from the archives.
For some reason, the suggestion from that mailing list thread didn't work (it was ignoring the custom JSONProvider from the web.xml). I may have had other problems.
Ultimately, I moved away from the idea of web.xml configuration since it was already used to provide a javax.ws.rs.Application and (from looking # CXF code), it appears that CXF ignore init-param elements from web.xml if it finds an Application.
Further, there doesn't seem be a way of expressing Map types in the web.xml config. That's conjecture based on the thread and looking at the code, so I can't 100% confirm.
This is what my web.xml looks like (same before and after these changes):
<servlet>
<servlet-name>CXFServlet</servlet-name>
<display-name>CXF Servlet</display-name>
<servlet-class>org.apache.cxf.jaxrs.servlet.CXFNonSpringJaxrsServlet</servlet-class>
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>my.javax-ws-rs.Application</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
It was in the my.javax-ws-rs.Application class that I needed to make changes.
Simply added a new JSONProvider to the list of singletons in there:
#Override
public Set< Object> getSingletons() {
final Set< Object> singletons = new HashSet<>();
// [SNIP -- existing singletons]
singletons.add( new my.provider.MyJSONProvider() );
return singletons;
}
The definition of my.provider.MyJSONProvider is where the rest of the magic happens (yes, it's the "programmatic" configuration that I said I didn't have, but ultimately had to resort to):
#Produces({MediaType.APPLICATION_JSON})
#Consumes({MediaType.APPLICATION_JSON})
#Provider
public class MyJSONProvider<T> extends JSONProvider<T> {
public AdministrationUtilisatuerJSONProvider() {
{
Map<String, String> newNamespaceMap = new ConcurrentHashMap<>();
XmlSchema resource1SchemaAnnotation = Resource1.class.getPackage().getAnnotation(javax.xml.bind.annotation.XmlSchema.class);
String resource1Namespace = resource1SchemaAnnotation.namespace();
newNamespaceMap.put( resource1Namespace, "resource1JsonPrefix" );
XmlSchema resource2SchemaAnnotation = Resource2.class.getPackage().getAnnotation(javax.xml.bind.annotation.XmlSchema.class);
String resource2Namespace = resource2SchemaAnnotation.namespace();
newNamespaceMap.put( resource2Namespace, "resource2JsonPrefix" );
setNamespaceMap(newNamespaceMap);
}
// Or set this to "true" to ignore all that namespace stuff
// setIgnoreNamespaces(true);
// Don't write namespace for default xsi-type elements.
setWriteXsiType(false);
// [SNIP] -- Other JSONProvider configuration.
// Check source from CXF, but few comments in code.
}
One final note about getting Jettison-based JSON working in CXF: you also need the cxf-rt-rs-extension-providers-X.Y.Z.jar on your classpath. This isn't documented in the CXF WHICH_JARS file, but is required.
Related
I am trying to create a very simple REST service using Jersey. Here is the service code
#Path("/UserService")
public class UserService {
#GET
#Path("/users")
#Produces(MediaType.APPLICATION_XML)
public List<User> getUsers() {
User user = new User(1, "Thomas", "Greene");
List<User> userList = new ArrayList<User>();
userList.add(user);
return userList;
}
}
When I run it through Postman, it returns me a XML response
Now, I want to get a JSON response back. So, I changed the mediatype to application/json:
#Path("/UserService")
public class UserService {
#GET
#Path("/users")
#Produces(MediaType.APPLICATION_JSON)
public List<User> getUsers(){
User user = new User(1, "Thomas", "Greene");
List<User> userList = new ArrayList<User>();
userList.add(user);
return userList;
}
}
It gives me the below error in Tomcat logs:
SEVERE: MessageBodyWriter not found for media type=application/json, type=class java.util.ArrayList, genericType=java.util.List.
Can someone please guide me how to get a JSON response back?
To use Jackson 2.x as your JSON provider you need to add jersey-media-json-jackson module to your pom.xml file:
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
<version>2.22.2</version>
</dependency>
And then register the JacksonFeature in your Application/ResourceConfig subclass.
For more details, have a look at Jersey documentation.
I am a bit upset about JAXB binding as well at the moment, therefore let me summarize my findings here - please correct me if I say something stupid:
Of course you have to have a library to do the JSON (de)serialization, in my case it is Moxy.
You have to tell JAXB which classes it should support. There are multiple ways to do that, the simplest seems to be to add a jaxb.properties file in the directory matching your classes and its sole content is the text javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory. With the directory I mean, if your classes are in the directory src/main/java and there the package com.pkg1.entities, add this file in src/main/resources and there as com/pkg1/entities/jaxb.properties.
By default JAXB works on POJOs. So you need to have a constructor without arguments, a get- and a set-method. Only then this field will be present in the JSON.
What I do often is to add a second constructor that gets the runtime object passed in and sets all fields to be exposed directly. Hence I do not need and do not want a set-method. Solution is to annotate the get method with #XmlElement.
Did I say you need an empty/default constructor? Took me once three hours to find out why class1 was working fine, class2 got the MessageBodyWriter error. I had forgotten the constructor. Grrrrr.
You get the same error (I believe) when the class is configured fine but one of its fields returns a type it cannot serialize.
I believe to have had one case where the class annotation #XmlRootElement caused that error. Not sure but I barely use that annotation at the moment.
In case you have a List as one of the elements to be turned into a Json array, JAXB will use the myAbstract class to do the serialization. Not very useful, you want the actual objects to be serialized. But how should JAXB know who implemented/extended this class? You have to tell with the annotation #XmlSeeAlso. So the MyAbstract class gets a class annotation #XmlSeeAlso({MyConcrete1.class, MyConcrete2.class}). At least Moxy does add then an additional type field telling the consumer which class it was. Makes total sense.
Although you can return userList the better option would be to return Response.ok().entity(userList).build(); Then you can return errors as well. Logically it is the same.
Watchout what kind of data types you use. String is fine, ArrayList as well, Hashtable not. Depends on the serializer you use as well.
I hope this helps others.
You need a json serializer on your class path to make this work.
Just add jackson and jersey will use this in the writer.
E.g. if you are using maven, add this to the pom.xml
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.7.4</version>
</dependency>
Your xml was working so I assume that you have #XmlRootElement annotation in your User class.
The thing is, it knows how to convert it to xml with the annotation #XmlRootElement but it doesn't know how to convert it to JSON.
So for making it convert everything to JSON with the same annotation of xml(ie #XmlRootElement) we can add
jersey-media-moxy-<whatever version>.jar
or for maven users
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-moxy</artifactId>
</dependency>
Also it should have a no argument constructor
I tried a ton of these dependencies but none of them worked for my version of Jersey3. What I needed to do was turn the Arraylist into an actual array. I managed this with toArray() and it started serializing correctly!
I have an app that uses spring security 4.0 and i am concern now about the content negotiation response that this app could send on a REST web service i.e.
my target is to restrict the response on a global basis irrelevant of the type of the request i.e. if that would be REST http get request through MVC or some kind of websocket (although i am not sure if that apply for the websocket) the response should be only returned as a json and NOT as XML. I do not want to support xml or any negotiation format.
The reason i am concerned about this is because i watched
a video on infoq made by a gentlemen called Mike Wiesner about spring application security pitfalls.
i know i can use in this case the annotation #RequestMapping and the sub-option "produces", i.e. something like
#RequestMapping(produces={MediaType.APPLICATION_JSON_VALUE} , value = "/target/get", method=RequestMethod.GET)
but since i have so many controllers it will be a nightmare for me to put that additional sub-option on all of them.
and i know that there are other annotations such as
#XmlTransient
#JsonIgnore
that could help me with what i want to do i.e. make some filds (getter/setters) to not be exposed in case the content negotiation changes but putting those annotations on each
getter/setter will even be bigger problem
Thus my question how do i do that on a global basis. I suppose this should be done in the MVCConfig class that extends WebMvcConfigurerAdapter?
By that i mean overriding the configureContentNegotiation method There are multiple examples doing that but those only explaing how to set up the the default behavior. My question is how do we restrict the behavior i.e. if http request is coming with "Accept" header application/xml how do i reject that on a global basis.
examples of the default behavior:
Spring boot controller content negotiation
so what i do is someting like
#Configuration
#EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
#Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.favorPathExtension(false).
If anything else then a json comms into the http request
reject this request or smply ignore it on a global basis.
Do not send/support xml, xhtml, html etc.
}
}
I coincidentally was looking into a related issue to this question in the last couple of days. We manually configure a ContentNegotiationManager in our code base, and in that process we limit the header based portion of the Spring PPA Strategy by providing an overridden HeaderContentNegotiationStrategy that does limiting by the Accept header similar to what you want. I took a quick look at ContentNegotiationConfigurer (which I have never used) and it does not appear to provide an option for which to alter mappings for the HeaderContentNegotiationStrategy, so here is a code snippet of the way we setup our ContentNegotiationManager.
#Bean
public ContentNegotiationManager contentNegotiationManager() {
//Supply a Map<String, org.springframework.http.MediaType>
PathExtensionContentNegotiationStrategy pathBased = new PathExtensionContentNegotiationStrategy(supportedMediaTypes());
//Supply a Map<org.springframework.http.MediaType, org.springframework.http.MediaType>
HeaderContentNegotiationStrategy headerBased = new MappingHeaderContentNegotiationStrategy(contentTypeMap());
FixedContentNegotiationStrategy defaultStrategy = new FixedContentNegotiationStrategy(MediaType.APPLICATION_JSON);
return ContentNegotiationManager(pathBased, headerBased, defaultStrategy);
return retval;
}
That bean is created in our config that overrides WebMvcConfigurerAdapter and is injected into this bean:
#Bean
#Override
public RequestMappingHandlerMapping requestMappingHandlerMapping() {
RequestMappingHandlerMapping handlerMapping = new RequestMappingHandlerMapping();
handlerMapping.setOrder(0);
handlerMapping.setRemoveSemicolonContent(false);
handlerMapping.getFileExtensions().add("json");
handlerMapping.setUseRegisteredSuffixPatternMatch(true);
handlerMapping.setInterceptors(getInterceptors());
handlerMapping.setContentNegotiationManager(mvcContentNegotiationManager());
return handlerMapping;
}
I'm practicing web programming by using Spring 3.1, Hibernate and SiteMesh3.
I want to move 'sitemesh3.xml' file to other directory as WEB-INF/spring/ (not in WEB-INF directly). I've tried it, but sitemesh didn't work. Is it possible to move it? If it is, what properties, if any, should I add on other files like web.xml?
(I've read http://wiki.sitemesh.org/wiki/display/sitemesh3/Configuring+SiteMesh+3, which says "The configuration file should live in /WEB-INF/sitemesh3.xml in your web-application.")
Consider using java config, you can get rid of xml configuration totally.
Follow Sitemesh Java Config
Create a filter like this and register it in your web.xml or in java configuration file.
#WebFilter(urlPatterns = "/*")
public class ConfiguredSiteMeshFilter extends ConfigurableSiteMeshFilter {
#Override
protected void applyCustomConfiguration(SiteMeshFilterBuilder builder) {
builder.addDecoratorPath("/*", "/WEB-INF/decorators/defaultDecorator.jsp");
}
}
i try to use spring-test(3.2.10) and integration tests with TestNG by this link.
I created RootTest.java
#WebAppConfiguration
#ContextConfiguration("file:src/test/resources/root-context2.xml")
public class ReferenceServiceTest extends AbstractTestNGSpringContextTests {
...
spring context loaded success. But my global variables not instantiated because web.xml ignored. In web.xml i have my own "listener-class"(implementation of ServletContextListener) and "context-param". How i can load web.xml context(and calls all application startup listeners) with spring integration test context?
As stated in the reference manual, the Spring MVC Test Framework...
"loads the actual Spring configuration through the TestContext
framework and always uses the DispatcherServlet to process requests
thus approximating full integration tests without requiring a running
Servlet container."
The key point there is "without ... a Servlet container". Thus web.xml does not come into the picture here. In other words, there is no way for configuration in web.xml to have an affect on integration tests using the Spring MVC Test Framework.
Now, having said that, it is possible to register a Servlet Filter with MockMvc like this:
mockMvcBuilder.addFilters(myServletFilter);
or
mockMvcBuilder.addFilters(myResourceFilter, "/resources/*");
And you can configure context-param entries by adding them manually to the ServletContext (which is actually Spring's MockServletContext) before you execute assertions on MockMvc like this:
wac.getServletContext().setInitParameter(name, value);
But... there is no way to configure a ServletContextListener using Spring MVC Test. If you want to have a listener applied to all of your requests that pass through Spring MVC, as an alternative you could consider implementing a custom HandlerInterceptor or WebRequestInterceptor (see Configuring interceptors in the reference manual).
Regards,
Sam
Try with a MockServletContext
#Before
public void before() {
MockServletContext mockServletContext = new MockServletContext();
mockServletContext.setInitParameter("parameterName", "parameterValue");
new MyListenerClass().contextInitialized(new ServletContextEvent(mockServletContext));
}
in our architecture, we have a bunch of models like this
public class UserModel
{
public string FirstName {get;set;}
}
and since we're using MvvmCross for our view models, we need our properties to look like this
public class UserViewModel: MvxViewModel
{
private string _firstName;
public string FirstName
{
get { return _firstName; }
set { _firstName = value; RaisePropertyChanged(() => FirstName); }
}
}
Now I've already got an R# template to write my own mvx properties by simply typing propmvx, but I still need to type in the type and the name.
I'm wondering if there's a way to setup a custom conversion template in order to have the alt + enter context menu to have a second option... something like
[T]o property with backing field
To [M]vx property with backing field
This is possible, even without creating any custom plugins or patterns, by using ReSharper Annotations. I have recently recorded a webinar with JetBrains, demonstrating exactly how to solve this with annotations. You can watch it here.
Short answer: the method
public void RaisePropertyChanged<T>(Expression<Func<T>> property)
of MvxNotifyPropertyChange.cs needs to be annotated with the NotifyPropertyChangedInvocatorAttribute, and then you could simply Alt-Enter on the property, and change it to a Property with change notificaton.
Now, since you can't (or don't want to) modify the source code of MvvmCross, you could apply those annotations externally, via XML. Take a look at the ExternalAnnotations directory, located in your ReSharper installation directory. It contains a bunch of external annotations for other MVVM frameworks. It's a simple XMLDoc format, so you could create an XML for MvvmCross and write the appropriate methods there. After that, save the file under a directory MvvmCross (or however the assembly is called), reload your solution, and it should just work!
For more information, please watch my webinar (link above), or JetBrains help
Here's an External Annotations file that will work with that method.
Cirrious.MvvmCross.ExternalAnnotations.xml
<?xml version="1.0" encoding="utf-8"?>
<assembly name="Cirrious.MvvmCross">
<member name="M:Cirrious.MvvmCross.ViewModels.MvxNotifyPropertyChanged.RaisePropertyChanged``1(System.Linq.Expressions.Expression{System.Func{``0}})">
<attribute ctor="M:JetBrains.Annotations.NotifyPropertyChangedInvocatorAttribute.#ctor" />
</member>
</assembly>
And here it is in action: