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}