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.collections.*;
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 * <ul class='seealso'>
038 *    <li class='link'>{@doc juneau-rest-server-jaxrs}
039 * </ul>
040 */
041public class BaseProvider implements MessageBodyReader<Object>, MessageBodyWriter<Object> {
042
043   private SerializerGroup serializers;
044   private ParserGroup parsers;
045   private OMap properties = new OMap();
046
047   /**
048    * Constructor.
049    */
050   @SuppressWarnings("deprecation")
051   protected BaseProvider() {
052      try {
053         properties = new OMap();
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            .swaps((Object[])jp.pojoSwaps())
064            .swaps((Object[])jp.swaps())
065            .set(properties)
066            .build();
067
068         parsers = ParserGroup.create()
069            .append(jp.parsers())
070            .swaps((Object[])jp.pojoSwaps())
071            .swaps((Object[])jp.swaps())
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 OMap getMethodProperties(Annotation[] a) {
089      OMap m = new OMap().inner(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         OMap 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(
125            SerializerSessionArgs
126               .create()
127               .properties(mp)
128               .locale(locale)
129               .timeZone(timeZone)
130               .mediaType(sm.getMediaType())
131         );
132
133         // Leave this open in case an error occurs.
134         Closeable c = s.isWriterSerializer() ? new OutputStreamWriter(os, UTF8) : os;
135         session.serialize(o, c);
136
137      } catch (SerializeException e) {
138         throw new IOException(e);
139      }
140   }
141
142   @Override /* MessageBodyReader */
143   public boolean isReadable(Class<?> type, Type gType, Annotation[] a, MediaType mediaType) {
144      return parsers.getParserMatch(mediaType.toString()) != null;
145   }
146
147   @Override /* MessageBodyReader */
148   public Object readFrom(Class<Object> type, Type gType, Annotation[] a, MediaType mediaType,
149         MultivaluedMap<String,String> headers, InputStream in) throws IOException, WebApplicationException {
150      try {
151         ParserMatch pm = parsers.getParserMatch(mediaType.toString());
152         if (pm == null)
153            throw new WebApplicationException(SC_UNSUPPORTED_MEDIA_TYPE);
154         Parser p = pm.getParser();
155         OMap mp = getMethodProperties(a);
156         mp.put("mediaType", mediaType.toString());
157         Locale locale = getLocale(headers);
158         TimeZone timeZone = getTimeZone(headers);
159         ParserSession session = p.createSession(
160            ParserSessionArgs
161               .create()
162               .properties(mp)
163               .locale(locale)
164               .timeZone(timeZone)
165               .mediaType(pm.getMediaType())
166         );
167         Object in2 = session.isReaderParser() ? new InputStreamReader(in, UTF8) : in;
168         return session.parse(in2, p.getClassMeta(gType));
169      } catch (ParseException e) {
170         throw new IOException(e);
171      }
172   }
173
174   @SuppressWarnings("rawtypes")
175   private static Locale getLocale(MultivaluedMap headers) {
176      if (headers.containsKey("Accept-Language") && headers.get("Accept-Language") != null) {
177         String h = String.valueOf(headers.get("Accept-Language"));
178         if (h != null) {
179            StringRanges mr = StringRanges.of(h);
180            if (! mr.getRanges().isEmpty())
181               return toLocale(mr.getRange(0).getName());
182         }
183      }
184      return null;
185   }
186
187   /*
188    * Converts an Accept-Language value entry to a Locale.
189    */
190   private static Locale toLocale(String lang) {
191      String country = "";
192      int i = lang.indexOf('-');
193      if (i > -1) {
194         country = lang.substring(i+1).trim();
195         lang = lang.substring(0,i).trim();
196      }
197      return new Locale(lang, country);
198   }
199
200   @SuppressWarnings("rawtypes")
201   private static TimeZone getTimeZone(MultivaluedMap headers) {
202      if (headers.containsKey("Time-Zone") && headers.get("Time-Zone") != null) {
203         String h = String.valueOf(headers.get("Time-Zone"));
204         return TimeZone.getTimeZone(h);
205      }
206      return null;
207   }
208
209}