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}