Skip to main content

Unable to get XmlAdapter example (from Javadoc) working

10 replies [Last post]
malachid
Offline
Joined: 2004-05-16
Points: 0

I wrote the code very similar to the example in the Javadoc, with the following exceptions:
1) key is string instead of int
2) map is ConcurrentHashMap instead of HashMap

When I go to create the JAXBContext and marshal it, I get one of two things:

1) If I do anything other than JAXBContext.newInstance(ConcurrentHashMap.class);, I get: Exception: java.util.concurrent.ConcurrentHashMap nor any of its super class is known to this context

2) If I do JAXBContext.newInstance(ConcurrentHashMap.class);, then the entire thing is marshalled to just the XML declaration, without the name/value pairs being marshalled.

I thought perhaps it was my implementation, so I put some System.out's inside the adapter constructor and methods and even inside a static block. The adapter class was never referenced, even though the test class said:
@XmlJavaTypeAdapter(MalMapAdapter.class)
protected ConcurrentHashMap map;

Any thoughts?

Reply viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
kohsuke
Offline
Joined: 2003-06-09
Points: 0

Hey, good to see you again.

XmlAdapter takes two things; the class to be adapted (ConcurrentHashMap for you), and the class that you wrote.

Pass in the class that you wrote to JAXBContext.newInstance.

malachid
Offline
Joined: 2004-05-16
Points: 0

Hey kohsuke, yeah I haven't been around much lately. I am really liking the changes to JAXB though ;)

I was passing both to the XmlAdapter... Passing in my class to the newInstance causes the error:
[code]
Exception: java.util.concurrent.ConcurrentHashMap nor any of its super class is known to this context
javax.xml.bind.JAXBException: java.util.concurrent.ConcurrentHashMap nor any of its super class is known to this context
at com.sun.xml.bind.v2.runtime.JAXBContextImpl.getBeanInfo(JAXBContextImpl.java:364)
at com.sun.xml.bind.v2.runtime.MarshallerImpl.write(MarshallerImpl.java:225)
at com.sun.xml.bind.v2.runtime.MarshallerImpl.marshal(MarshallerImpl.java:162)
at javax.xml.bind.helpers.AbstractMarshallerImpl.marshal(AbstractMarshallerImpl.java:68)
at MalTest3Tester.(MalTest3Tester.java:41)
at MalTest3Tester.main(MalTest3Tester.java:16)
[/code]

Here's the code...

[code]
-----------------------------
package maltest3;

import java.util.*;
import java.util.concurrent.*;
import javax.xml.bind.annotation.*;

public class MalMap
{
protected List entries;
}
-----------------------------
package maltest3;

import java.util.*;
import java.util.concurrent.*;
import javax.xml.bind.annotation.*;

public class MalMapEntry
{
@XmlAttribute
protected String key;

@XmlValue
protected String value;
}
-----------------------------
package maltest3;

import javax.xml.bind.*;
import javax.xml.bind.annotation.*;
import javax.xml.bind.annotation.adapters.*;
import java.util.concurrent.*;
import java.util.*;

public final class MalMapAdapter
extends XmlAdapter>
{
static{
System.out.println("Hmm, we were referenced.");
}

public MalMapAdapter()
{
System.out.println("Adapater initialized");
}

public ConcurrentHashMap unmarshal(MalMap malMap)
{
System.out.println("Unmarshalling");
ConcurrentHashMap hashMap = new ConcurrentHashMap();
for(MalMapEntry entry : malMap.entries)
hashMap.put(entry.key, entry.value);

return hashMap;
}

public MalMap marshal(ConcurrentHashMap hashMap)
{
System.out.println("Marshalling");
MalMap malMap = new MalMap();
malMap.entries = new ArrayList();
for(String key : hashMap.keySet())
{
MalMapEntry entry = new MalMapEntry();
entry.key = key;
entry.value = hashMap.get(key);
malMap.entries.add(entry);
}

return malMap;
}
}
-----------------------------
import javax.xml.bind.*;
import javax.xml.bind.annotation.*;
import javax.xml.bind.annotation.adapters.*;
import maltest3.*;
import java.util.concurrent.*;
import java.util.*;

public class MalTest3Tester
{
@XmlJavaTypeAdapter(MalMapAdapter.class)
protected ConcurrentHashMap map;

public static void main(String[] args)
{
try{
MalTest3Tester tester = new MalTest3Tester();
}catch(Exception e){
System.out.println("Exception: " + e.getMessage());
e.printStackTrace();
}
}

public MalTest3Tester()
throws Exception
{
map = new ConcurrentHashMap();
map.put("http.proxyHost", "somewhere.com");
map.put("http.proxyPort", "123");

JAXBContext ctx = JAXBContext.newInstance(MalMap.class);
/* this doesn't give an error, but never even calls the static block of the Adapter
JAXBContext ctx = JAXBContext.newInstance(ConcurrentHashMap.class);
*/
ctx.createMarshaller().marshal(map, System.out);
}
}
-----------------------------
[/code]

kohsuke
Offline
Joined: 2003-06-09
Points: 0

I see. If you createa JAXBContext like:

JAXBContext ctx = JAXBContext.newInstance(MalTest3Tester .class);

and then do:

MalTest3Tester mtt = new MalTest3Tester();
mtt.map = map;
ctx.createMarshaller().marshal(mtt, System.out);

it should work. Oh, I guess you'll also need @XmlRootElement on MalTest3Tester (or otherwise you have to wrap that object into JAXBElement.)

@XmlJavaTypeAdapter is currently always associated with a property.

I think at one point we thought about providing a way to associate an adapter with a class globally (in a given JAXBContext) so that you don't have to put @XmlJavaTypeAdapter everywhere. But this is not done in spec ED2 nor in the EA.

(and how did you format the Java source code?)

malachid
Offline
Joined: 2004-05-16
Points: 0

> I see. If you createa JAXBContext like:
>
> JAXBContext ctx =
> JAXBContext.newInstance(MalTest3Tester .class);

I'm not following. If you use XJC to create some package, say 'maltest4' and then create a test application to use it, you don't do newInstance(MyTestClass.class) you put the root element in the newInstance.

> and then do:
>
> MalTest3Tester mtt = new MalTest3Tester();
> mtt.map = map;
> ctx.createMarshaller().marshal(mtt, System.out);
>
> it should work. Oh, I guess you'll also need
> @XmlRootElement on MalTest3Tester (or otherwise you
> have to wrap that object into JAXBElement.)

Ok, changing the JAXBContext to use the non-package class, changing the marshaller to use 'this' instead of the map, adding @XmlRootElement on the MalMap -- that got it to do this:
[code]

[/code]

Checking the adapter, it says that the MalMap has 2 MalMapEntry(-ies) -- but they are not getting marshalled. Looking at MalMapEntry, I thought perhaps I had to put @XmlElement on it, but that didn't work. Not sure why they are not getting marshalled.

> @XmlJavaTypeAdapter is currently always associated
> with a property.
>
> I think at one point we thought about providing a way
> to associate an adapter with a class globally (in a
> given JAXBContext) so that you don't have to put
> @XmlJavaTypeAdapter everywhere. But this is not done
> in spec ED2 nor in the EA.

That would be a lot better. I had thought that by telling it to marshall a class that it would figure it out through inspection. The one piece that is really confusing here is why the non-package class (specifically a class that is not an XmlElement/XmlRootElement and that should not show up in the output XML) should be the one referenced in the JAXBContext. It is counter-intuitive based on the classes that XJC produces. As an example, if there were a maltest3.ObjectFactory, it would have no clue about MalTest3Tester, as MalTest3Tester would be the one calling it.

> (and how did you format the Java source code?)

try [ code ] your code [ / code ]

kohsuke
Offline
Joined: 2003-06-09
Points: 0

> I'm not following. If you use XJC to create some package, say 'maltest4' and then create a test application to use it, you don't do > newInstance(MyTestClass.class) you put the root element in the newInstance.

Right now, the adapter can be used only where the adapted type is referenced from somebody. I noticed that your MalTest3Tester class is conveniently referencing a ConcurrentHashMap, so that's why I used it. It could just well be:

[code]
@XmlRootElement
class Foo {
@XmlJavaTypeAdapter
ConcurrentHashMap ...
}
[/code]

It doesn't matter which package this class belongs to. But I'm using this class as a root of the object tree for marshalling to XML.

>

Hmm I'd imagine @XmlElement on the entries field would do. Sometimes it also helps to generate a schema from the class to find out what JAXB thinks.

> I had thought that by telling it to marshall a class that it would figure it out through inspection

But if you look at your original code, JAXB isn't told of the existence of the adapter. You only put that on MalTest3Tester, and that's completely outside the knowledge of JAXB. So there's no way it would have worked.

And I hope I addressed your confusion about why I used MalTest3Tester class to JAXBContext. I needed to marshal a class that refers to ConcurrentHashMap. There's no more meaning to it.

malachid
Offline
Joined: 2004-05-16
Points: 0

> Right now, the adapter can be used only where the
> adapted type is referenced from somebody. I noticed

Oh, so that is what you were talking about with Global references.

So, what would be the best layout to make it work the same as if we compiled the schema into source?

> It doesn't matter which package this class belongs
> to. But I'm using this class as a root of the object
> tree for marshalling to XML.

It would seem that the MalMap should be the root... is there perhaps a way to make it work from there?

> > > standalone="yes"?>
>
> Hmm I'd imagine @XmlElement on the entries field

Ah, I tried putting it on the MalMapEntry class, and that was not very successful. Putting it on the entries field did the trick.

> would do. Sometimes it also helps to generate a
> schema from the class to find out what JAXB thinks.

That was actually a great idea.

Here's before adding the [code]@XmlElement(name="entry")[/code]:
[code]















[/code]

and after:
[code]



















[/code]

> But if you look at your original code, JAXB isn't
> told of the existence of the adapter. You only put
> that on MalTest3Tester, and that's completely outside
> the knowledge of JAXB. So there's no way it would
> have worked.

Yeah, perhaps we could update the documentation to include that little bit... or maybe include a full working sample of the hashmap converter, since that is the one that most people want.

> And I hope I addressed your confusion about why I
> used MalTest3Tester class to JAXBContext. I needed to
> marshal a class that refers to ConcurrentHashMap.
> There's no more meaning to it.

Yeah, you did. Now it is just a matter of figuring out what the 'cleanest' way to do it would be.

I did actually create the schema from it, then xjc the schema into a new package... didn't work quite as well, since it autogenerated empty implementation adapters...

Oh, as a side note... I always install the JDK under C:\java, but this last time I let the installer put it under the default C:\Program Files\Java\.... With it doing that, both schemagen.bat and xjc.bat both fail to run -- but they can both easily be fixed:
XJC.BAT: [code]set JAVA="%JAVA_HOME%\bin\java"[/code]
SCHEMAGEN.BAT: [code]set APT="%JAVA_HOME%\bin\apt"[/code]
(they need the quotes if you use the default JDK install)

Malachi

kohsuke
Offline
Joined: 2003-06-09
Points: 0

> So, what would be the best layout to make it work the same as if we compiled the schema into source?

It's not quite obvious to me, but one approach someone (I think Sekhar) came up with is to allow @XmlJavaTypeAdapter to be put on a package (for example you define ConcurrentHashMap<->MulMap adapter on your package), and then if you create a JAXBContext that includes any of the class in that package, it will take effect globally.

Or I think we can also expand JAXBContext.newInstance method to accept adapter classes as additional parameters.

> > Hmm I'd imagine @XmlElement on the entries field
>
> Ah, I tried putting it on the MalMapEntry class, and that was not very successful.
> Putting it on the entries field did the trick.

The EA version lacks proper error checking, so misplaced annotations often get
unnoticed. We'll be working on it.

> Yeah, perhaps we could update the documentation to include that little bit...
> or maybe include a full working sample of the hashmap converter, since that is
> the one that most people want.

A sample is a relatively easy for us to do. A good documentation on this topic
is difficult until the spec finalizes, I think.

And thanks for a bug fix in the scripts. I incorporated them.

malachid
Offline
Joined: 2004-05-16
Points: 0

> It's not quite obvious to me, but one approach
> someone (I think Sekhar) came up with is to allow
> @XmlJavaTypeAdapter to be put on a package (for
> example you define ConcurrentHashMap<->MulMap adapter
> on your package), and then if you create a
> JAXBContext that includes any of the class in that
> package, it will take effect globally.

Or perhaps we could do it like we do the NamespacePrefixMapper, where the user has a single method that they could override to return different adapter implementations... something like:

[code]
public XmlAdapter getPreferredAdapter(String namespaceUri, Class javaType);
[/code]

Well, that's not quite right either...
On the other hand, perhaps we should just support xsd:key. Seems like it would be really easy to make a Map using that.

> Or I think we can also expand JAXBContext.newInstance
> method to accept adapter classes as additional
> parameters.

That would work well.

> The EA version lacks proper error checking, so
> misplaced annotations often get
> unnoticed. We'll be working on it.

It did catch it, I just wasn't sure what was wrong with it.

> A sample is a relatively easy for us to do. A good
> documentation on this topic
> is difficult until the spec finalizes, I think.

I was just thinking of adding a few lines to Step 4 of the XmlAdapter javadoc.

> And thanks for a bug fix in the scripts. I
> incorporated them.

No problem. Honestly, I would never have caught it if it wasn't for me accepting the defaults during install, which I rarely do.

kohsuke
Offline
Joined: 2003-06-09
Points: 0

> Or perhaps we could do it like we do the NamespacePrefixMapper, where the user has a single
> method that they could override to return different adapter implementations... something like:

That's an idea.

This isn't directly related, but in the 2.0 JAXB API we do let applications to set a configured instance of adapter to Unmarshaller/Marshaller, if you want adapters to maintain some kind of states.

You still have to use @XmlJavaTypeAdapter to specify when the adapter kicks in, but instead of letting the runtime create a new instance of an adapter class, you can have the runtime use the one you created.

malachid
Offline
Joined: 2004-05-16
Points: 0

> This isn't directly related, but in the 2.0 JAXB API
> we do let applications to set a configured instance
> of adapter to Unmarshaller/Marshaller, if you want
> adapters to maintain some kind of states.
>
> You still have to use @XmlJavaTypeAdapter to specify
> when the adapter kicks in, but instead of letting the
> runtime create a new instance of an adapter class,
> you can have the runtime use the one you created.

That actually might work really well for me. I have a JaxbHandler class that manages setting up the NamespacePrefixMapper, the JAXBContext, the Marshaller and Unmarshaller... I then just call read/write on the class.... That just might work out really well.

Mal