001// ***************************************************************************************************************************
002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
003// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
005// * with the License.  You may obtain a copy of the License at                                                              *
006// *                                                                                                                         *
007// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
008// *                                                                                                                         *
009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
011// * specific language governing permissions and limitations under the License.                                              *
012// ***************************************************************************************************************************
013package org.apache.juneau.rest.jaxrs;
014
015import static javax.servlet.http.HttpServletResponse.*;
016import static org.apache.juneau.internal.IOUtils.*;
017
018import java.io.*;
019import java.lang.annotation.*;
020import java.lang.reflect.*;
021import java.util.*;
022
023import javax.ws.rs.*;
024import javax.ws.rs.core.*;
025import javax.ws.rs.core.MediaType;
026import javax.ws.rs.ext.*;
027
028import org.apache.juneau.*;
029import org.apache.juneau.http.*;
030import org.apache.juneau.parser.*;
031import org.apache.juneau.rest.annotation.*;
032import org.apache.juneau.serializer.*;
033
034/**
035 * Base class for defining JAX-RS providers based on Juneau serializers and parsers.
036 *
037 * <h5 class='section'>See Also:</h5>
038 * <ul>
039 *    <li class='link'>{@doc juneau-rest-server-jaxrs}
040 * </ul>
041 */
042public class BaseProvider implements MessageBodyReader<Object>, MessageBodyWriter<Object> {
043
044   private SerializerGroup serializers;
045   private ParserGroup parsers;
046   private ObjectMap properties = new ObjectMap();
047
048   /**
049    * Constructor.
050    */
051   protected BaseProvider() {
052      try {
053         properties = new ObjectMap();
054         JuneauProvider jp = getClass().getAnnotation(JuneauProvider.class);
055
056         for (Property p : jp.properties())
057            properties.put(p.name(), p.value());
058         for (String p : jp.flags())
059            properties.put(p, true);
060
061         serializers = SerializerGroup.create()
062            .append(jp.serializers())
063            .beanFilters(jp.beanFilters())
064            .pojoSwaps(jp.pojoSwaps())
065            .set(properties)
066            .build();
067
068         parsers = ParserGroup.create()
069            .append(jp.parsers())
070            .beanFilters(jp.beanFilters())
071            .pojoSwaps(jp.pojoSwaps())
072            .set(properties)
073            .build();
074
075      } catch (Exception e) {
076         throw new RuntimeException(e);
077      }
078   }
079
080   /**
081    * Returns properties defined on the specified method through the {@link RestMethod#properties() @RestMethod(properties)}
082    * annotation specified on the method and the {@link JuneauProvider#properties()} annotation specified on the
083    * provider class.
084    *
085    * @param a All annotations defined on the method.
086    * @return A map of all properties define on the method.
087    */
088   protected ObjectMap getMethodProperties(Annotation[] a) {
089      ObjectMap m = new ObjectMap().setInner(properties);
090      for (Annotation aa : a) {
091         if (aa instanceof RestMethod) {
092            for (Property p : ((RestMethod)aa).properties())
093               m.put(p.name(), p.value());
094            for (String p : ((RestMethod)aa).flags())
095               m.put(p, true);
096         }
097      }
098      return m;
099   }
100
101   @Override /* MessageBodyWriter */
102   public long getSize(Object o, Class<?> type, Type gType, Annotation[] a, MediaType mediaType) {
103      return -1;
104   }
105
106   @Override /* MessageBodyWriter */
107   public boolean isWriteable(Class<?> type, Type gType, Annotation[] a, MediaType mediaType) {
108      return serializers.getSerializerMatch(mediaType.toString()) != null;
109   }
110
111   @Override /* MessageBodyWriter */
112   public void writeTo(Object o, Class<?> type, Type gType, Annotation[] a, MediaType mediaType,
113         MultivaluedMap<String,Object> headers, OutputStream os) throws IOException, WebApplicationException {
114      try {
115         SerializerMatch sm = serializers.getSerializerMatch(mediaType.toString());
116         if (sm == null)
117            throw new WebApplicationException(SC_NOT_ACCEPTABLE);
118         Serializer s = sm.getSerializer();
119         ObjectMap mp = getMethodProperties(a);
120         mp.append("mediaType", mediaType.toString());
121         Locale locale = getLocale(headers);
122         TimeZone timeZone = getTimeZone(headers);
123
124         SerializerSession session = s.createSession(new SerializerSessionArgs(mp, null, locale, timeZone, sm.getMediaType(), null, null, null, null));
125
126         // Leave this open in case an error occurs.
127         Closeable c = s.isWriterSerializer() ? new OutputStreamWriter(os, UTF8) : os;
128         session.serialize(o, c);
129
130      } catch (SerializeException e) {
131         throw new IOException(e);
132      }
133   }
134
135   @Override /* MessageBodyReader */
136   public boolean isReadable(Class<?> type, Type gType, Annotation[] a, MediaType mediaType) {
137      return parsers.getParserMatch(mediaType.toString()) != null;
138   }
139
140   @Override /* MessageBodyReader */
141   public Object readFrom(Class<Object> type, Type gType, Annotation[] a, MediaType mediaType,
142         MultivaluedMap<String,String> headers, InputStream in) throws IOException, WebApplicationException {
143      try {
144         ParserMatch pm = parsers.getParserMatch(mediaType.toString());
145         if (pm == null)
146            throw new WebApplicationException(SC_UNSUPPORTED_MEDIA_TYPE);
147         Parser p = pm.getParser();
148         ObjectMap mp = getMethodProperties(a);
149         mp.put("mediaType", mediaType.toString());
150         Locale locale = getLocale(headers);
151         TimeZone timeZone = getTimeZone(headers);
152         ParserSession session = p.createSession(new ParserSessionArgs(mp, null, locale, timeZone, pm.getMediaType(), null, null, null));
153         Object in2 = session.isReaderParser() ? new InputStreamReader(in, UTF8) : in;
154         return session.parse(in2, p.getClassMeta(gType));
155      } catch (ParseException e) {
156         throw new IOException(e);
157      }
158   }
159
160   @SuppressWarnings("rawtypes")
161   private static Locale getLocale(MultivaluedMap headers) {
162      if (headers.containsKey("Accept-Language") && headers.get("Accept-Language") != null) {
163         String h = String.valueOf(headers.get("Accept-Language"));
164         if (h != null) {
165            MediaTypeRange[] mr = MediaTypeRange.parse(h);
166            if (mr.length > 0)
167               return toLocale(mr[0].getMediaType().getType());
168         }
169      }
170      return null;
171   }
172
173   /*
174    * Converts an Accept-Language value entry to a Locale.
175    */
176   private static Locale toLocale(String lang) {
177      String country = "";
178      int i = lang.indexOf('-');
179      if (i > -1) {
180         country = lang.substring(i+1).trim();
181         lang = lang.substring(0,i).trim();
182      }
183      return new Locale(lang, country);
184   }
185
186   @SuppressWarnings("rawtypes")
187   private static TimeZone getTimeZone(MultivaluedMap headers) {
188      if (headers.containsKey("Time-Zone") && headers.get("Time-Zone") != null) {
189         String h = String.valueOf(headers.get("Time-Zone"));
190         return TimeZone.getTimeZone(h);
191      }
192      return null;
193   }
194
195}