Как заставить MessageBodyWriter работать с HashMap, используя RestEasy и Tomcat? - PullRequest
1 голос
/ 08 августа 2011

Я разрабатываю веб-сервис JAX-RS с использованием RestEasy 2.2.2 для развертывания в Tomcat 7. Сервис возвращает (должен возвращать) XML с использованием JAXB.Возвращенный 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 также содержит ConcurrentHashMap, который необходимо сериализовать в XML.

Это класс ресурсов:

@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, но я до сих пор не смог заставить ее работать (т.е. я могу получить вызываемый код, но он останавливается с различными исключениями).

Кажется, я не совсем понимаю, как интерфейсы MessageBodyWriterMessageBodyReader) должны быть реализованы и использованы.У меня есть книга Билла Бёрка "RESTful Java с JAX-RS".Это очень полезно с точки зрения помощи в разработке сервисов JAX-RS, но я не смог найти достаточно подробностей о функциональности MessageBodyWriter в соответствующем разделе.Мои интернет-поиски также не дали ничего, что могло бы направить меня в правильном направлении.

Я был бы признателен, если бы кто-нибудь мог помочь мне выяснить, как правильно реализовать интерфейсы MessageBodyWriterMessageBodyReader),Я не знаю, пропускаю ли я аннотацию, неправильно ее размещаю или мне нужен совершенно новый подход.

Заранее благодарен за помощь.

РЕДАКТИРОВАТЬ:

Изменение кода на следующий приводит меня на полпути:

@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, который мне нужен.Однако в XML, который я получаю с помощью @XmlJavaTypeAdapter, есть дополнительный уровень (<class><items><item> вместо <items><item> --- как видно из моих примеров, у меня есть ConcurrentHashMap экземпляры в качестве переменной-члена класса).Я также не могу изменить имя элемента отдельных элементов карты (они всегда называются «элемент»).

Это не большие проблемы, и я могу внести необходимые изменения и жить с ними при необходимости.Однако, если это возможно, я бы не хотел, чтобы они были в первую очередь.В образовательных целях я также хотел бы понять, почему код в EDIT 1 не работает для демаршаллинга (и как это исправить, если это возможно).

Заранее спасибо за помощь.

Ответы [ 2 ]

1 голос
/ 08 августа 2011

Я думаю, что ваша проблема в том, что вы пытаетесь смешать JAXB с вашим MessageBodyReader / MessageBodyWriter.У вас есть объект JAXB, поэтому вы не хотите, чтобы RestEasy выполнял всю сериализацию с использованием вашего MessageBodyWriter, поскольку он не учитывал бы ваши объекты JAXB.Вы можете сделать это таким образом, но вам также нужно будет сериализовать остальную часть вашей объектной модели.

MessageBodyReader / Writer предназначен для работы с потоками, поэтому, вероятно, это не имело особого смысла.Он не предполагает, что вы переходите на XML.

Что вы, вероятно, хотите сделать, - это создать JAXB XmlJavaTypeAdapter для карты и позволить JAXB создавать XML.Вы можете найти больше информации на странице JavaDoc:

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

Я нашел один хороший пост по этому вопросу в списке рассылки Metro здесь .Этот код должен дать вам то, что вы хотите.Контекст был вокруг 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; 
  } 
} 
0 голосов
/ 27 августа 2011

Метод XmlJavaTypeAdapter работает нормально, так как позволяет JAXB выполнять сериализацию и обратно.Тем не менее, он не дает мне XML, который мне нужен.Я думал, что это не будет иметь большого значения, но в конце концов оказалось, что мне нужно получить XML в этой форме.

XML, который я использую @XmlJavaTypeAdapter, имеет дополнительный уровень (аналогично<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, который я получил (например, см. Мой оригинальный пост).

...