Как мне аннотированные ответы JAXB JAX-RS (18) - PullRequest
4 голосов
/ 20 января 2012

Мы используем аннотации JAXB для объектов нашей модели, которые мы возвращаем из нашего веб-API, и мы хотим, чтобы данные были локализованы, а другие значения были отформатированы в соответствии с предпочтениями пользователя (т. Е. Метрика против статута). Мы делаем это путем добавления пользовательских адаптеров в Marshaller.

Marshaller marshaller = jc.createMarshaller();
marshaller.setAdapter(NumberPersonalizedXmlAdapter.class,
          new NumberPersonalizedXmlAdapter(locale);
marshaller.marshal(expected, writer);

Пытаясь сделать самый простой подход, я просто получу Locale из заголовков HTTP-запросов и предоставлю его Marshaller в одном из классов MessageBodyWriter.

Я посмотрел на расширение зарегистрированных провайдеров по умолчанию, таких как XMLRootElementProvider, но понял, что они написаны в основном как окончательные, поэтому я отказался от этого подхода. Было бы по крайней мере 10 классов, которые мне нужно было бы расширить в любом случае, чтобы это было не идеально.

Кто-нибудь знает, как лучше настроить маршаллер в MessageBodyWriter с пользовательскими адаптерами для каждого запроса? Я почти уверен, что это как-то связано с ContextResolver .

Ответы [ 3 ]

4 голосов
/ 21 января 2012

Написание ContextResolver для Marshalling приводит к гораздо более чистому и более подходящему решению, чем написание MessageBodyWriter.Все классы JAXB используют метод Providers.getContextResolver для получения маршаллера.Я предоставляю свой пользовательский ContextResolver и у меня есть ответы i18n.

@Provider
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
public class JaxbPersonalizerContextResolver implements ContextResolver<Marshaller> {

    private HttpHeaders requestHeaders;

    public JaxbPersonalizerContextResolver(@Context HttpHeaders requestHeaders) {
        this.requestHeaders = requestHeaders;
    }

    @Override
    public Marshaller getContext(Class<?> type) {
        Locale locale = If.first(this.requestHeaders.
                                    getAcceptableLanguages(), Locale.US);
        NumberFormat formatter = NumberFormat.getNumberInstance(locale);
        formatter.setMaximumFractionDigits(1);

        Marshaller marshaller;
        try {
            JAXBContext jc = JAXBContext.newInstance(type);
            marshaller = jc.createMarshaller();
        } catch (JAXBException e) {
            throw new RuntimeException(e);
        }
        marshaller.setAdapter(QuantityXmlAdapter.class, 
                           new QuantityXmlAdapter.Builder().locale(locale).build());
        marshaller.setAdapter(NumberPersonalizedXmlAdapter.class,
                new NumberPersonalizedXmlAdapter.Builder().
                                    formatter(formatter).build());
        return marshaller;
    }
}

JSON не был локализован, и после некоторого исследования я понял, что JSON-библиотеки Джексона используются вместо JAXBJSONElementProvider, распространяемого с библиотеками Джерси.Я удалил конфигурацию POJOMappingFeature в web.xml и локализовал JSON, однако он не так хорош, как JSON Джексона.

Очень чистое решение, которое заставляет меня думать, что реализации JAX-RS и Jersey сделаны очень хорошо.

1 голос
/ 21 января 2012

Я решил проблему, написав свой собственный MessageBodyWriter , который получает HttpHeaders, внедренный в конструктор, который я использую позже при написании ответа. Я включу весь класс, так как он не такой большой.

@Produces(MediaType.APPLICATION_XML)
@Provider
public class JaxbPersonalizationProvider implements MessageBodyWriter<Object> {

    private HttpHeaders requestHeaders;
    private Providers providers;

    public JaxbPersonalizationProvider(@Context HttpHeaders requestHeaders, @Context Providers providers) {
        this.requestHeaders = requestHeaders;
        this.providers = providers;
    }

    @Override
    public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
        return type.getAnnotation(XmlRootElement.class) != null && mediaType.equals(MediaType.APPLICATION_XML_TYPE);
    }

    @Override
    public long getSize(Object t, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
        return -1;
    }

    @Override
    public void writeTo(Object t, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType,
            MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream) throws IOException,
            WebApplicationException {
        Locale locale = If.first(this.requestHeaders.getAcceptableLanguages(), Locale.US);
        NumberFormat formatter = NumberFormat.getNumberInstance(locale);
        formatter.setMaximumFractionDigits(1);

        Marshaller marshaller;
        try {
            JAXBContext jc = JAXBContext.newInstance(TrackInfo.class);
            marshaller = jc.createMarshaller();
            marshaller.setAdapter(QuantityXmlAdapter.class, new QuantityXmlAdapter.Builder().locale(locale).build());
            marshaller.setAdapter(NumberPersonalizedXmlAdapter.class, new NumberPersonalizedXmlAdapter.Builder()
                    .formatter(formatter).build());

            marshaller.marshal(t, entityStream);
        } catch (JAXBException e) {
            throw new RuntimeException(e);
        }
    }
}

Который создает этот отрывок xml со стандартным языком en-US:

 <display lang="en_US">
    <value>3,286.1</value>
 </display>

и этот фрагмент xml, когда в заголовках отправляется язык fr-FR:

 <display lang="fr_FR">
   <value>3 286,1</value>
 </display>

Этот подход все еще не идеален, поскольку теперь мне нужно написать аналогичный MessageBodyWriter для JSON или добавить поддержку JSON для этого MessageBodyWriter. Кроме того, я предполагаю, что провайдеры JAXB по умолчанию вносят некоторые изменения, которыми я не пользуюсь.

0 голосов
/ 20 января 2012

A MessageBodyWriter - правильный подход для этого варианта использования.Я бы рекомендовал добавить следующее поле к вашему MessageBodyWriter:

@javax.ws.rs.core.Context
protected Providers providers;

и затем использовать его для доступа к JAXBContext для создания Marshaller

public void writeTo(DataObject dataObject, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, Object> multivaluedMap, OutputStream outputStream) throws IOException, WebApplicationException {
    JAXBContext jaxbContext = null;
    ContextResolver<JAXBContext> resolver = providers.getContextResolver(JAXBContext.class, arg3);
    if(null != resolver) {
        jaxbContext = resolver.getContext(type);
    }
    if(null == jaxbContext) {
        jaxbContext = JAXBContext.newInstance(type);
    }
    Marshaller marshaller = jaxbContext.createMarshaller();
}

Связанный пример

...