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'><a class="doclink" href="../../../../../overview-summary.html#juneau-rest-server-jaxrs">Overview > juneau-rest-server-jaxrs</a> 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)); 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)); 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}