I'm using the Metro stack bundled with Java 6 SE to call a web service. The web service expects XML as a parameter. I use JAXB classes to create content and pass my JAXB root element to the web service endpoint.
Now to my Problem: I can't find any way to make the marshaller include the schemaLocation of the XSD file since I can't directly access the marshaller. (If you have direct access to the marshaller it is possible do set the schemalocation as a property, but when using metro, all the marshalling seems to be happening internally.)
I've tried setting the xsi:schemaLocation in the XmlSchema annotation in the generated package-info.java class, but this had no effect on the xml generated.
Another point is that when creating a web service client and calling a web service in an Java SE environment, certain annotations like #UsesJAXBContext, #WebServiceClient and #XmlSchema seem to be ignored. (I must state here that I am a beginner in terms of Java web services)
Ok, here's what I now know. This has been a problem for me for months.
First, you have to change the JAXBContext used by JAX-WS. To do this use the #UsesJAXBContext annotation on the server. (com.sun.xml.ws.developer.UsesJAXBContext)
Then, in your factory implementation, you have to return custom Bridges in this method.
public Bridge createBridge(final TypeReference typereference)
Then your custom bridge needs to set the marshaller property to set the namespace mapper you want to use.
Here's my example.
#WebService(serviceName = ...)
#UsesJAXBContext(MyContextFactory.class)
public class SoapServer { ... }
and the factory class ...
public static class MyContextFactory implements JAXBContextFactory
{
#Override
public JAXBRIContext createJAXBContext(final SEIModel sei,
#SuppressWarnings("rawtypes") final List<Class> classesToBind, final List<TypeReference> typeReferences)
throws JAXBException
{
JAXBRIContext context = JAXBContextFactory.DEFAULT.createJAXBContext(sei, classesToBind, typeReferences);
return new MyJaxwsContext(context);
}
}
and the JAXB Context impelementation...
public class MyContext extends JAXBRIContext
{
/** the actual context */
private final JAXBRIContext delegate;
public MyContext(final JAXBRIContext createContext)
{
this.delegate = createContext;
}
public Bridge createBridge(final TypeReference arg0)
{
return new MyBridge((JAXBContextImpl) delegate, delegate.createBridge(arg0));
}
and now the Bridge implementation...
public class MyBridge extends Bridge
{
private final Bridge delegate;
protected MyBridge(final JAXBContextImpl context, final Bridge delegate)
{
super(context);
this.delegate = delegate;
}
// an example marshal call. There are many more...
public void marshal(final Marshaller m, final Object object, final ContentHandler contentHandler)
throws JAXBException
{
m.setProperty("com.sun.xml.bind.namespacePrefixMapper", namespaceMapper);
delegate.marshal(m, object, contentHandler);
}
NOTE: I have just wrapped the existing implementation. All I wanted was to be able to fix the namespace names. From my reading of the source (JAXWS), this is the only way to get to the marshaller.
NOTE2 There is a downcast to an RI final class. This only works with the reference implementation. YMMV
Related
I am trying to implement a JAXBContext resolver class. What I don't understand is what types of classes are included in cTypes? I've seen several similar examples, but none explain what the cTypes list is.
#Provider
public class JaxbContextResolver implements ContextResolver<JAXBContext> {
private final JAXBContext context;
private final Set<Class<?>> types;
private final Class<?>[] cTypes = {Flights.class, FlightType.class, AircraftType.class};
public JaxbContextResolver() throws Exception {
this.types = new HashSet<Class<?>>(Arrays.asList(cTypes));
this.context = new JettisonJaxbContext(JettisonConfig.DEFAULT, cTypes);
}
#Override
public JAXBContext getContext(Class<?> objectType) {
return (types.contains(objectType)) ? context : null;
}
}
Your cTypes class list must contains all classes that represents root xml elements (Those that you annotated with #XmlRootElement).
Explanation :
When you create a JAXBContext, you give it a list of classes to bound:
this.context = new JettisonJaxbContext(JettisonConfig.DEFAULT, cTypes);
This will make JAXB able to actually associate an element with a class.
JAXB will automatically bind any classes contained in root element classes (Except the ones annotated #XmlTransient).
Then, when the getContext method is used, it will return the initialized context in your class only if the parameter class has been bound by JAXB to the said context, since your JAXBContext won't be able to use any classes that are not bound.
I have a JAXB model generated from MOXy's version of XJC. The xjc:superclass tag was used in the binding's file so all the objects extend a common class.
package my.package
//Base.java
#XmlTransient
public class Base {
//...
}
//MyTag.java (generated from XJC)
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "", propOrder = {/*...*/})
#XmlRootElement(name = "myTag")
public class MyTag extends Base {
//...
}
The model nor the schema can be changed, but I can change the Base class.
I need to extend MyTag, as well as some other classes in the model, so I can customize its method behaviors from the Base class. So I extended MyTag and the other domain classes that needed custom behavior, as well as the ObjectFactory. These classes exist in a separate Java package.
package my.extended.package
//MyTagExtended.java
public class MyTagExtended extends MyTag {
//...
}
//CustomObjectFactory.java
public class CustomObjectFactory extends ObjectFactory {
//...
#Override
public MyTagExtended createMyTag() {
return new MyTagExtended();
}
//...
}
Application code:
package application
//Application.java
System.setProperty("org.eclipse.persistence.moxy.annotation.xml-value-extension", "true");
JAXBContext jc = (JAXBContext) JAXBContext.newInstance(XPSObjectFactory.class);
JAXBUnmarshaller u = jc.createUnmarshaller();
return u.unmarshal(xmlFile);
The problem I'm having is MOXy seems to randomly decide whether to call the CustomObjectFactory's or ObjectFactory's methods.
In the CustomObjectFactory class, if I have only one overridden method, that method is always called. However, when I put in more, MOXy seems to randomly decide whether or not to call ObjectFactory's method's or CustomObjectFactory's.
When I use the same sort of setup with the Oracle's JAXB implementation that's supplied with the JDK, it works fine. The CustomObjectFactory's methods are always called.
Is there a configuration that needs to be set? How can I configure MOXy to always use my CustomObjectFactory's methods?
EDIT:
To clarify, here's how I made the same scenario work with Oracle's JAXB implementation:
JAXBContext jc = JAXBContext.newInstance("my.package");
Unmarshaller u = jc.createUnmarshaller();
u.setProperty("com.sun.xml.internal.bind.ObjectFactory", new CustomObjectFactory());
I tried to set the ObjectFactory property on the MOXy unmarshaller, but it threw an exception.
I found a workaround for MOXy. I found some information on XmlClassExtractor, which can be used to specify which class to instantiate. Since I can't modify the domain objects, an XML file was used instead.
I kept the extended classes the same, but modified Application.java and wrote a binding.xml file and a ClassExtractor:
package application
//Application.java
System.setProperty("org.eclipse.persistence.moxy.annotation.xml-value-extension", "true");
Map<String, Object> properties = new HashMap<String, Object>();
properties.put(JAXBContextProperties.OXM_METADATA_SOURCE, "classExtractor.xml");
JAXBContext jc = (JAXBContext) JAXBContextFactory.createContext(new Class[]{CustomObjectFactory.class}, properties);
JAXBUnmarshaller u = jc.createUnmarshaller();
return u.unmarshal(xmlFile);
classExtractor.xml:
<?xml version="1.0"?>
<xml-bindings
xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm"
package-name="my.package"
version="2.3">
<java-types>
<java-type name="MyTag">
<xml-class-extractor class="my.extended.package.MyTagClassExtractor"/>
</java-type>
</java-types>
</xml-bindings>
Class extractor:
package my.extended.package
//MyTagExtractor.java
public class MyTagExtractor extends ClassExtractor {
#Override
public Class<? extends Base> extractClassFromRow(Record databaseRow, Session session) {
return MyTagExtended.class;
}
}
This seems to work, but it's quirky and prone to programmer errors since multiple source files have to be edited to get the desired functionality. Oracle's ObjectFactory property on the unmarshaller was very easy and streamlined. Does anyone have a better answer?
I upgraded to Java EE 7 from Java EE 6 and some of my REST services stopped working. Narrowing it down, it appears this change seems to have come about when Jersey switched from Jackson to MOXy in Java EE 7.
My service looks like:
#Stateless
#Path("access")
public class AccessFacadeREST extends AbstractFacade<Access> {
#PersistenceContext(unitName = "AccessServerPU")
private EntityManager em;
public AccessFacadeREST() {
super(Access.class);
}
#POST
#Consumes({"application/xml", "application/json"})
#Produces({"application/xml", "application/json"})
public ObjectResponse create(#Context HttpServletRequest requestContext,
#Context SecurityContext context,
ObjectRequest<Access> req) {
req.setHostname(requestContext.getRemoteAddr());
return AccessValidation.create(em, req);
}
}
Here req is null and so generates a NullPointerException when it is presented using JSON. It works fine when presented as XML. ObjectRequest is a generic class I use to wrap the request with authentication content standard for several different types of requests.
I tested a similar #GET service to return an ObjectRequest<Access> instance filled with junk data. It again works well when using XML, but returns null (with no exception raised) when I attempt to return JSON.
I found Java EE7 REST server no longer returning List as JSON useful to understand the underlying problem, but didn't see how I could get this to work in my situation. My ApplicationConfig class looks like:
#javax.ws.rs.ApplicationPath("webresources")
public class ApplicationConfig extends Application {
#Override
public Set<Object> getSingletons() {
Set<Object> set = new HashSet<>();
log.info("Enabling custom Jackson JSON provider");
set.add(new JacksonJsonProvider());
return set;
}
#Override
public Map<String, Object> getProperties() {
Map<String, Object> map = new HashMap<>();
log.info("Disabling MOXy JSON provider");
map.put("jersey.config.disableMoxyJson.server", true);
return map;
}
#Override
public Set<Class<?>> getClasses() {
Set<Class<?>> resources = new java.util.HashSet<>();
addRestResourceClasses(resources);
return resources;
}
private void addRestResourceClasses(Set<Class<?>> resources) {
resources.add(AccessFacadeREST.class);
}
}
I think I need to configure the ApplicationConfig class to use Jackson, but I don't see how to generalize the solutions I've found to cover my situation. So, two questions really:
Have I properly characterized the underlying problem as being some sort of deficiency with MOXy?
How can I fix this using either MOXy or Jackson (I don't have a preference which, just as long as it works)?
Setup:
Netbeans 8.0.2
Glassfish 4.1
JDK 8 / JRE 8U25 (64 bit)
Edit in response to the question below...
The project pom.xml has the following entry:
<dependency>
<groupId>com.fasterxml.jackson.jaxrs</groupId>
<artifactId>jackson-jaxrs-json-provider</artifactId>
<version>2.4.4</version>
</dependency>
You should be able to solve this by disabling MOXy (register the class below in getClasses()) and get rid of your set.add(new JacksonJsonProvider()); singleton. I use this for all my GF4 projects to force Jersey to use Jackson.
/**
* Feature to disable Moxy and Enable Jackson processing
*/
private static class Jackson2Feature implements Feature {
#Override
public boolean configure(FeatureContext context) {
final String disableMoxy = PropertiesHelper.getPropertyNameForRuntime(
CommonProperties.MOXY_JSON_FEATURE_DISABLE,
context.getConfiguration().getRuntimeType());
context.property(disableMoxy, true);
// add the default Jackson exception mappers and allow jaxb annotations
context.register(JsonParseExceptionMapper.class);
context.register(JacksonJaxbJsonProvider.class, MessageBodyReader.class, MessageBodyWriter.class);
return true;
}
}
I've got a typical web service using JAX-RS and JAXB, and upon unmarshalling I would like to know which setters were explicitly called by JAXB. This effectively lets me know which elements were included in the document provided by the caller.
I know I can probably solve this with an XmlAdapter, but I have a lot of classes in a number of different packages, and I don't want to create adapters for each and every one of them. Nor do I want to put hooks into each and every setter. I would like a general solution if possible. Note that all of my classes are setup to use getters and setters; none of them use fields for the access type.
My service uses Jersey 2.4, Spring 3.2, and MOXy 2.5.1, so if there's anything that can be leveraged from any of those, that's all the better. Our original thought was we could dynamically create a factory class (akin to what #XmlType supports) that would return a proxy object that would intercept the setters. We thought we could make this happen using the MetadataSource concept in MOXy, but that does not seem to be possible.
Anyone have any ideas?
My service uses Jersey 2.4, Spring 3.2, and MOXy 2.5.1, so if there's
anything that can be leveraged from any of those, that's all the
better.
Create your own EclipseLink AttributeAccessor
MOXy (which is a component of EclipseLink) leverages a class called AttributeAccessor to do operations with fields and properties. You could wrap this class to capture all the information that you need.
import org.eclipse.persistence.exceptions.DescriptorException;
import org.eclipse.persistence.mappings.AttributeAccessor;
public class MyAttributeAccessor extends AttributeAccessor {
private AttributeAccessor attributeAccessor;
public MyAttributeAccessor(AttributeAccessor attributeAccessor) {
this.attributeAccessor = attributeAccessor;
}
#Override
public Object getAttributeValueFromObject(Object domainObject)
throws DescriptorException {
return attributeAccessor.getAttributeValueFromObject(domainObject);
}
#Override
public void setAttributeValueInObject(Object domainObject, Object value)
throws DescriptorException {
System.out.println("Thread: " + Thread.currentThread().getId() + " - Set value: " + value + " on property: " + attributeAccessor.getAttributeName() + " for object: " + domainObject);
attributeAccessor.setAttributeValueInObject(domainObject, value);
}
}
Tell MOXy to use your AttributeAccessor
We can leverage a SessionEventListener to access the underlying metadata to specify your implementation of AttributeAccessor. This is passed in as a property when creating the JAXBContext.
Map<String, Object> properties = new HashMap<String, Object>(1);
properties.put(JAXBContextProperties.SESSION_EVENT_LISTENER, new SessionEventAdapter() {
#Override
public void postLogin(SessionEvent event) {
Project project = event.getSession().getProject();
for(ClassDescriptor descriptor : project.getOrderedDescriptors()) {
for(DatabaseMapping mapping : descriptor.getMappings()) {
mapping.setAttributeAccessor(new MyAttributeAccessor(mapping.getAttributeAccessor()));
}
}
super.preLogin(event);
}
});
JAXBContext jc = JAXBContext.newInstance(new Class[] {Foo.class}, properties);
Leverage a JAX-RS ContextResolver when Creating the JAXBContext
Since you are in a JAX-RS environment you can leverage a ContextResolver to control how the JAXBContext is created.
http://blog.bdoughan.com/2011/04/moxys-xml-metadata-in-jax-rs-service.html
Standalone Example
Java Model (Foo)
Below is a sample class where we will use field access (no setters).
import javax.xml.bind.annotation.*;
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class Foo {
private String bar;
private String baz;
}
Demo
import java.io.StringReader;
import java.util.*;
import javax.xml.bind.*;
import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.jaxb.JAXBContextProperties;
import org.eclipse.persistence.mappings.DatabaseMapping;
import org.eclipse.persistence.sessions.*;
public class Demo {
public static void main(String[] args) throws Exception {
Map<String, Object> properties = new HashMap<String, Object>(1);
properties.put(JAXBContextProperties.SESSION_EVENT_LISTENER, new SessionEventAdapter() {
#Override
public void postLogin(SessionEvent event) {
Project project = event.getSession().getProject();
for(ClassDescriptor descriptor : project.getOrderedDescriptors()) {
for(DatabaseMapping mapping : descriptor.getMappings()) {
mapping.setAttributeAccessor(new MyAttributeAccessor(mapping.getAttributeAccessor()));
}
}
super.preLogin(event);
}
});
JAXBContext jc = JAXBContext.newInstance(new Class[] {Foo.class}, properties);
Unmarshaller unmarshaller = jc.createUnmarshaller();
StringReader xml = new StringReader("<foo><bar>Hello World</bar></foo>");
Foo foo = (Foo) unmarshaller.unmarshal(xml);
}
}
Output
Thread: 1 - Set value: Hello World on property: bar for object: forum21044956.Foo#37e47e38
UPDATE
So this works, but I have a few issues. First, the domainObject is
always logging as 0 in my system. Not sure why that's occurring.
I have not idea why that is occuring, may need to check the toString() for the object you are logging.
Second, I am not able to tell if the property in question is on the
top-level item that is being unmarshalled or on a sub-element. That's
actually quite annoying.
You will need to beef up the logic here. Based on the objects being set you should be able to do what you want.
Third, your solution is per JAXBContext, but I don't know if I really
want to create a new context for every request. Isn't that bad from an
overhead perspective?
You can cache the created JAXBContext to prevent rebuilding it.
I'm using Jersey 1.17 on the server side to process REST requests and JAXB 2 to unmarshall the XML request content.
Context
This is the Jersey method I use. The MyDTO class uses the #XmlRootElement annotation (otherwise, I'd need to define the parameter with the JAXBElement type).
#Path("/myService")
#POST
#Consumes(MediaType.APPLICATION_XML)
public void myService(MyDTO dto) throws Exception
{
// Shouldn't get this far if the XML content in the request was invalid
System.out.println(dto);
}
Requirement
By default, the Sun/Oracle JAXB implementation doesn't throw exceptions when the XML content has errors. For example providing a string value, say ABC, for an Integer attribute simply leaves the value as null instead of throwing an exception.
In JAXB 2 a ValidationEvenHandler can be defined. Using the following handler handler, makes the XML unmarshalling throw an exception the way I need it to.
public class UnmarshallerValidationEventHandler implements ValidationEventHandler {
#Override
public boolean handleEvent(ValidationEvent event) {
// This indicates JAXB that it should continue processing only if the
// severity level is less than error. NOTE: validation event constants
// go in ascending order in level of severity(i.e., 0 WARNING, 1: ERROR, 2 :FATAL_ERROR)
return event.getSeverity() < ValidationEvent.ERROR;
}
}
Question
How can I get Jersey to use a particular JAXBContext instance in order to use an unmarshaller with my custom validation event handler?
Alternatively, given that my application only uses JAXB in Jersey methods, defining a particular JAXBContext globally for the the JVM instance would be a good option. How could that be done?
Jersey Users Guide covers this in Using custom JAXBContext chapter. Basically you need to provide ContextResolver<T> like:
#Provider
public class PlanetJAXBContextProvider implements ContextResolver<JAXBContext> {
private JAXBContext context = null;
public JAXBContext getContext(Class<?> type) {
if(type != Planet.class)
return null; // we don't support nothing else than Planet
if(context == null) {
try {
context = JAXBContext.newInstance(Planet.class);
} catch (JAXBException e) {
// log warning/error; null will be returned which indicates that this
// provider won't/can't be used.
}
}
return context;
}
}
You can see a sample use in storage-service sample project (see JAXBContextResolver).
Note: Instead of ContextResolver<JAXBContext> you can also provide ContextResolver<Marshaller> or/and ContextResolver<Unmarshaller>.