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 * <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 ObjectMap properties = new ObjectMap();
046
047   /**
048    * Constructor.
049    */
050   protected BaseProvider() {
051      try {
052         properties = new ObjectMap();
053         JuneauProvider jp = getClass().getAnnotation(JuneauProvider.class);
054
055         for (Property p : jp.properties())
056            properties.put(p.name(), p.value());
057         for (String p : jp.flags())
058            properties.put(p, true);
059
060         serializers = SerializerGroup.create()
061            .append(jp.serializers())
062            .beanFilters(jp.beanFilters())
063            .pojoSwaps(jp.pojoSwaps())
064            .set(properties)
065            .build();
066
067         parsers = ParserGroup.create()
068            .append(jp.parsers())
069            .beanFilters(jp.beanFilters())
070            .pojoSwaps(jp.pojoSwaps())
071            .set(properties)
072            .build();
073
074      } catch (Exception e) {
075         throw new RuntimeException(e);
076      }
077   }
078
079   /**
080    * Returns properties defined on the specified method through the {@link RestMethod#properties() @RestMethod(properties)}
081    * annotation specified on the method and the {@link JuneauProvider#properties()} annotation specified on the
082    * provider class.
083    *
084    * @param a All annotations defined on the method.
085    * @return A map of all properties define on the method.
086    */
087   protected ObjectMap getMethodProperties(Annotation[] a) {
088      ObjectMap m = new ObjectMap().setInner(properties);
089      for (Annotation aa : a) {
090         if (aa instanceof RestMethod) {
091            for (Property p : ((RestMethod)aa).properties())
092               m.put(p.name(), p.value());
093            for (String p : ((RestMethod)aa).flags())
094               m.put(p, true);
095         }
096      }
097      return m;
098   }
099
100   @Override /* MessageBodyWriter */
101   public long getSize(Object o, Class<?> type, Type gType, Annotation[] a, MediaType mediaType) {
102      return -1;
103   }
104
105   @Override /* MessageBodyWriter */
106   public boolean isWriteable(Class<?> type, Type gType, Annotation[] a, MediaType mediaType) {
107      return serializers.getSerializerMatch(mediaType.toString()) != null;
108   }
109
110   @Override /* MessageBodyWriter */
111   public void writeTo(Object o, Class<?> type, Type gType, Annotation[] a, MediaType mediaType,
112         MultivaluedMap<String,Object> headers, OutputStream os) throws IOException, WebApplicationException {
113      try {
114         SerializerMatch sm = serializers.getSerializerMatch(mediaType.toString());
115         if (sm == null)
116            throw new WebApplicationException(SC_NOT_ACCEPTABLE);
117         Serializer s = sm.getSerializer();
118         ObjectMap mp = getMethodProperties(a);
119         mp.append("mediaType", mediaType.toString());
120         Locale locale = getLocale(headers);
121         TimeZone timeZone = getTimeZone(headers);
122
123         SerializerSession session = s.createSession(
124            SerializerSessionArgs
125               .create()
126               .properties(mp)
127               .locale(locale)
128               .timeZone(timeZone)
129               .mediaType(sm.getMediaType())
130         );
131
132         // Leave this open in case an error occurs.
133         Closeable c = s.isWriterSerializer() ? new OutputStreamWriter(os, UTF8) : os;
134         session.serialize(o, c);
135
136      } catch (SerializeException e) {
137         throw new IOException(e);
138      }
139   }
140
141   @Override /* MessageBodyReader */
142   public boolean isReadable(Class<?> type, Type gType, Annotation[] a, MediaType mediaType) {
143      return parsers.getParserMatch(mediaType.toString()) != null;
144   }
145
146   @Override /* MessageBodyReader */
147   public Object readFrom(Class<Object> type, Type gType, Annotation[] a, MediaType mediaType,
148         MultivaluedMap<String,String> headers, InputStream in) throws IOException, WebApplicationException {
149      try {
150         ParserMatch pm = parsers.getParserMatch(mediaType.toString());
151         if (pm == null)
152            throw new WebApplicationException(SC_UNSUPPORTED_MEDIA_TYPE);
153         Parser p = pm.getParser();
154         ObjectMap mp = getMethodProperties(a);
155         mp.put("mediaType", mediaType.toString());
156         Locale locale = getLocale(headers);
157         TimeZone timeZone = getTimeZone(headers);
158         ParserSession session = p.createSession(
159            ParserSessionArgs
160               .create()
161               .properties(mp)
162               .locale(locale)
163               .timeZone(timeZone)
164               .mediaType(pm.getMediaType())
165         );
166         Object in2 = session.isReaderParser() ? new InputStreamReader(in, UTF8) : in;
167         return session.parse(in2, p.getClassMeta(gType));
168      } catch (ParseException e) {
169         throw new IOException(e);
170      }
171   }
172
173   @SuppressWarnings("rawtypes")
174   private static Locale getLocale(MultivaluedMap headers) {
175      if (headers.containsKey("Accept-Language") && headers.get("Accept-Language") != null) {
176         String h = String.valueOf(headers.get("Accept-Language"));
177         if (h != null) {
178            MediaTypeRange[] mr = MediaTypeRange.parse(h);
179            if (mr.length > 0)
180               return toLocale(mr[0].getMediaType().getType());
181         }
182      }
183      return null;
184   }
185
186   /*
187    * Converts an Accept-Language value entry to a Locale.
188    */
189   private static Locale toLocale(String lang) {
190      String country = "";
191      int i = lang.indexOf('-');
192      if (i > -1) {
193         country = lang.substring(i+1).trim();
194         lang = lang.substring(0,i).trim();
195      }
196      return new Locale(lang, country);
197   }
198
199   @SuppressWarnings("rawtypes")
200   private static TimeZone getTimeZone(MultivaluedMap headers) {
201      if (headers.containsKey("Time-Zone") && headers.get("Time-Zone") != null) {
202         String h = String.valueOf(headers.get("Time-Zone"));
203         return TimeZone.getTimeZone(h);
204      }
205      return null;
206   }
207
208}