如何使用RestEasy和Tomcat使MessageBodyWriter与HashMap一起使用?

时间:2011-08-08 13:01:08

标签: java jaxb hashmap resteasy concurrenthashmap

我正在使用RestEasy 2.2.2开发一个JAX-RS Web服务,以部署到Tomcat 7.该服务通过使用JAXB返回(应该返回)XML。返回的XML应该包含ConcurrentHashMap的表示,类似于在以下代码中使用它的方式:

@XmlRootElement(name="items")
@XmlAccessorType(XmlAccessType.NONE)
public class ItemCollection 
{
    @XmlElement(name="item")
    private ConcurrentHashMap<String, Item> items;

    public ItemCollection()
    {
        items = new ConcurrentHashMap<String, item>();
        // fill the map
    }
}

Item类还包含需要序列化为XML的ConcurrentHashMap

这是资源类:

@Path("/items")
public class ItemResource 
{
    @GET
    @Produces(MediaType.APPLICATION_XML)
    public ItemCollection getAllItems() 
    {
        // get itemManager
        return itemManager.getItems(); // ItemManager holds an instance of ItemCollection
    }
}

此代码运行,但生成的XML没有内容:

<items>
    <item/>
</items>

我想要输出的内容如下:

<items>
    <item id="...">
        <data>...</data>
        <otheritems>
            <otheritem id="...">
                <someotherdata>...</someotherdata>
            </otheritem>
        </otheritems>
    </item>
    <item id="...">
        <data>...</data>
        <otheritems>
            <otheritem id="...">
                <someotherdata>...</someotherdata>
            </otheritem>
            <otheritem id="...">
                <someotherdata>...</someotherdata>
            </otheritem>
            <otheritem id="...">
                <someotherdata>...</someotherdata>
            </otheritem>
        </otheritems>
    </item>
</items>

我发现在内置功能不足的情况下需要MessageBodyWriter实现。我试图提出一个MessageBodyWriter实现来编组ConcurrentHashMap,但到目前为止我还没有能够使它工作(即我可以调用代码,但它会因各种异常而停止)。

我似乎不太了解应该如何实现和使用MessageBodyWriter(和MessageBodyReader)接口。我有Bill Burke的“RESTful Java with JAX-RS”一书。它在帮助设计JAX-RS服务方面非常有用,但我在相关章节中找不到关于MessageBodyWriter功能的足够详细信息。我的互联网搜索也没有产生任何可以指导我正确方向的东西。

如果有人能帮我弄清楚如何正确实现MessageBodyWriter(和MessageBodyReader)接口,我将不胜感激。我不知道我是否错过了注释,错误注释,或者我是否需要一种全新的方法。

提前感谢您的帮助。

修改

将代码修改为以下内容会让我在那里:

@XmlRootElement(name="items")
@XmlAccessorType(XmlAccessType.NONE)
public class ItemCollection 
{
    private ConcurrentHashMap<String, Item> items;

    public ItemCollection()
    {
        items = new ConcurrentHashMap<String, item>();
        // fill the map
    }

    @XmlElement(name="item")
    public Collection<Item> getItems()
    {
        return items.values();
    }
}

这会生成我需要的XML(上面包含的示例)。但是,这个代码在解组时不起作用。我得到以下异常:

java.lang.UnsupportedOperationException
java.util.AbstractCollection.add(AbstractCollection.java:221)
  com.sun.xml.internal.bind.v2.runtime.reflect.Lister$CollectionLister.addToPack(Lister.java:290)
com.sun.xml.internal.bind.v2.runtime.reflect.Lister$CollectionLister.addToPack(Lister.java:254)
com.sun.xml.internal.bind.v2.runtime.unmarshaller.Scope.add(Scope.java:106)
com.sun.xml.internal.bind.v2.runtime.property.ArrayERProperty$ReceiverImpl.receive(ArrayERProperty.java:195)
com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallingContext.endElement(UnmarshallingContext.java:507)
com.sun.xml.internal.bind.v2.runtime.unmarshaller.SAXConnector.endElement(SAXConnector.java:145)
com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.endElement(AbstractSAXParser.java:601)
com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanEndElement(XMLDocumentFragmentScannerImpl.java:1782)
com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl$FragmentContentDriver.next(XMLDocumentFragmentScannerImpl.java:2938)
com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl.next(XMLDocumentScannerImpl.java:648)
com.sun.org.apache.xerces.internal.impl.XMLNSDocumentScannerImpl.next(XMLNSDocumentScannerImpl.java:140)
com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanDocument(XMLDocumentFragmentScannerImpl.java:511)
com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:808)
com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:737)
com.sun.org.apache.xerces.internal.parsers.XMLParser.parse(XMLParser.java:119)
com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.parse(AbstractSAXParser.java:1205)
com.sun.org.apache.xerces.internal.jaxp.SAXParserImpl$JAXPSAXParser.parse(SAXParserImpl.java:522)
com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallerImpl.unmarshal0(UnmarshallerImpl.java:200)
com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallerImpl.unmarshal(UnmarshallerImpl.java:173)
javax.xml.bind.helpers.AbstractUnmarshallerImpl.unmarshal(AbstractUnmarshallerImpl.java:137)
javax.xml.bind.helpers.AbstractUnmarshallerImpl.unmarshal(AbstractUnmarshallerImpl.java:142)
javax.xml.bind.helpers.AbstractUnmarshallerImpl.unmarshal(AbstractUnmarshallerImpl.java:151)
javax.xml.bind.helpers.AbstractUnmarshallerImpl.unmarshal(AbstractUnmarshallerImpl.java:169)
// ... the rest

我认为原因是缺少“正确的设置器”,因此unmarshaller不会尝试将项添加到Collection中,但我不知道该设置器的外观如何。如果有人知道如何做到这一点,我将不胜感激。

提前致谢。

编辑2:

顺便说一下,我看到克里斯的回复,他建议使用@XmlJavaTypeAdapter。我已经尝试了这个建议,它让我接近我需要的XML。但是,我使用@XmlJavaTypeAdapter获取的XML中有一个额外的级别(<class><items><item>而不是<items><item> ---如我的示例中所示,我将ConcurrentHashMap个实例作为成员变量一堂课)。我似乎也无法更改各个地图项的元素名称(它们总是被称为“项目”)。

这些不是大问题,我可以进行必要的修改,并在必要时与他们一起生活。但是,如果可能的话,我想首先没有它们。出于教育目的,我还想了解为什么编辑1中的代码不适用于解组(以及如果可能的话如何修复它)。

提前感谢所有帮助。

2 个答案:

答案 0 :(得分:1)

我认为您的问题是您正在尝试将JAXB与MessageBodyReader / MessageBodyWriter混合使用。你有一个JAXB对象,所以你不希望RestEasy使用你的MessageBodyWriter进行所有的序列化,因为它不会考虑你的JAXB对象。你可以这样做,但你也需要序列化你的对象模型的其余部分。

MessageBodyReader / Writer适合使用Streams,这可能是它没有多大意义的原因。它不会假设你要使用XML。

您可能想要做的是为地图创建一个JAXB XmlJavaTypeAdapter,让JAXB进行XML创建。您可以在JavaDoc页面上找到更多信息:

http://download.oracle.com/javase/6/docs/api/javax/xml/bind/annotation/adapters/XmlJavaTypeAdapter.html

我确实通过Metro邮件列表here找到了一篇好文章。此代码应该为您提供所需的内容。上下文围绕JAX-WS,但您正在寻找JAXB注释来进行自定义绑定。

@XmlRootElement 
public class Root 
{ 
  private String name; 
  private int    junk; 

  @XmlJavaTypeAdapter(MapAdapter.class) 
  private Map<String, Boolean> lights; 

  public Root() 
  { 
    name = ""; junk = 0; lights = new HashMap<String, Boolean>(); 
  } 

  public void   setName(String newName)  { name = newName; } 
  public String getName()                { return name; } 

  public void setJunk(int newJunk)  { junk = newJunk; } 
  public int  getJunk()             { return junk; } 

  public void turnLightOn(String lightName)   { lights.put(lightName, true); } 
  public void turnLightOff(String lightName)  { lights.put(lightName, false); } 
} 

class MapAdapter extends XmlAdapter<MapElements[], Map<String, Boolean>> 
{ 
  public MapElements[] marshal(Map<String, Boolean> arg0) throws Exception 
  { 
    MapElements[] mapElements = new MapElements[arg0.size()]; 

    int i = 0; 
    for (Map.Entry<String, Boolean> entry : arg0.entrySet()) 
      mapElements[i++] = new MapElements(entry.getKey(), entry.getValue()); 

    return mapElements; 
  } 

  public Map<String, Boolean> unmarshal(MapElements[] arg0) throws Exception 
  { 
    Map<String,Boolean> r = new HashMap<String,Boolean>(); 
    for(MapElements mapelement : arg0) 
      r.put(mapelement.key, mapelement.value); 
    return r; 
  } 
} 

class MapElements 
{ 
  @XmlElement public String  key; 
  @XmlElement public Boolean value; 

  private MapElements() {} //Required by JAXB 

  public MapElements(String key, Boolean value) 
  { 
    this.key   = key; 
    this.value = value; 
  } 
} 

答案 1 :(得分:0)

XmlJavaTypeAdapter方法工作正常,因为它允许JAXB进行序列化并返回。但是,它没有给我我需要的XML。我认为这不会太重要,但最后我发现我需要以这种形式获取XML。

我使用@XmlJavaTypeAdapter获得的XML中有一个额外的级别(类似于<collection><mapitem><item><mapitem><item>而不是<collection><item><item>)。我在论坛上发现了一个帖子(如果我能再次找到它,我会添加链接)解释了这个问题,并指出为了获得这种类型的XML,JAXB需要查看集合,而不是地图。

所以,希望它对别人有用,这就是我所做的:

首先,我定义了这个界面:

public interface MyCollectionInterface
{
    public String getItemId();
}

然后我修改了要放入集合中的项目,如下所示:

public class CollectionItem implements MyCollectionInterface
{
    @XmlAttribute
    private String id; // This was already a class member

    @Override
    public String getItemId() 
    {
        return id; 
    }
}

我们的想法是让密钥与HashMap一起使用。

然后是MyCollection

的声明
public class MyCollection<E extends MyCollectionInterface> extends AbstractSet<E>
{
    private ConcurrentHashMap<String, E> items;

    public MyCollection()
    {
        items = new ConcurrentHashMap<String, E>();
    }

    @Override
    public boolean add(E e)
    {
        return items.putIfAbsent(e.getItemId(), e) != null;
    }

    @Override
    public Iterator<E> iterator() 
    {
        return items.values().iterator();
    }

    @Override
    public int size() 
    {
        return items.size();
    }

    // other functionality as needed
}

现在,将代码修改为以下形式可以完成所有工作:

@XmlRootElement(name="items")
@XmlAccessorType(XmlAccessType.NONE)
public class ItemCollection 
{
    @XmlElement(name="item")
    private MyDictionary<CollectionItem> items;

    public ItemCollection()
    {
        items = new MyDictionary<CollectionItem>();
    }
}

这产生了我以后的XML(参见我的原始帖子中的一个例子)。