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.reshandlers; 014 015import static org.apache.juneau.internal.StringUtils.*; 016import static org.apache.juneau.httppart.HttpPartType.*; 017 018import java.io.*; 019import java.lang.reflect.*; 020import java.util.*; 021 022import org.apache.http.*; 023import org.apache.juneau.http.*; 024import org.apache.juneau.httppart.*; 025import org.apache.juneau.httppart.bean.*; 026import org.apache.juneau.internal.*; 027import org.apache.juneau.rest.*; 028import org.apache.juneau.http.exception.*; 029import org.apache.juneau.http.header.*; 030import org.apache.juneau.rest.util.FinishablePrintWriter; 031import org.apache.juneau.rest.util.FinishableServletOutputStream; 032import org.apache.juneau.serializer.*; 033import org.apache.juneau.utils.*; 034 035/** 036 * Response handler for POJOs not handled by other handlers. 037 * 038 * <p> 039 * This uses the serializers defined on the response to serialize the POJO. 040 * 041 * <p> 042 * The {@link Serializer} used is based on the <c>Accept</c> header on the request. 043 * 044 * <p> 045 * The <c>Content-Type</c> header is set to the mime-type defined on the selected serializer based on the 046 * <c>produces</c> value passed in through the constructor. 047 * 048 * <ul class='seealso'> 049 * <li class='link'>{@doc RestmReturnTypes} 050 * </ul> 051 */ 052public class DefaultHandler implements ResponseHandler { 053 054 @SuppressWarnings("resource") 055 @Override /* ResponseHandler */ 056 public boolean handle(RestRequest req, RestResponse res) throws IOException, InternalServerError, NotAcceptable { 057 SerializerGroup g = res.getSerializers(); 058 String accept = req.getHeaders().getString("Accept", "*/*"); 059 SerializerMatch sm = g.getSerializerMatch(accept); 060 HttpPartSchema schema = null; 061 062 Object o = res.getOutput(); 063 064 ResponseBeanMeta rm = res.getResponseMeta(); 065 if (rm == null) 066 rm = req.getResponseBeanMeta(o); 067 068 if (rm != null) { 069 070 boolean isThrowable = rm.getClassMeta().isType(Throwable.class); 071 if (isThrowable) { 072 res.setHeaderSafe("Exception-Name", rm.getClassMeta().getName()); 073 res.setHeaderSafe("Exception-Message", ((Throwable)o).getMessage()); 074 if (req.isDebug()) 075 ((Throwable)o).printStackTrace(); 076 } 077 078 ResponseBeanPropertyMeta stm = rm.getStatusMethod(); 079 if (stm != null) { 080 try { 081 res.setStatus((int)stm.getGetter().invoke(o)); 082 } catch (Exception e) { 083 throw new InternalServerError(e, "Could not get status."); 084 } 085 } else if (rm.getCode() != 0) { 086 res.setStatus(rm.getCode()); 087 } 088 089 for (ResponseBeanPropertyMeta hm : rm.getHeaderMethods()) { 090 try { 091 Object ho = hm.getGetter().invoke(o); 092 String n = hm.getPartName(); 093 if ("*".equals(n)) { 094 for (Object ho2 : iterate(ho)) { 095 if (ho2 instanceof Map.Entry) { 096 @SuppressWarnings("rawtypes") 097 Map.Entry e = (Map.Entry)ho2; 098 String k = stringify(e.getKey()); 099 Object v = e.getValue(); 100 HttpPartSchema s = hm.getSchema().getProperty(k); 101 res.setHeader(new HttpPart(k, RESPONSE_HEADER, s, hm.getSerializer(req.getPartSerializer()), v)); 102 } else if (ho2 instanceof NameValuePair) { 103 NameValuePair p = (NameValuePair)ho2; 104 res.setHeader(p.getName(), p.getValue()); 105 } else { 106 throw new InternalServerError("Invalid type ''{0}'' for header ''{1}''", hm.getPartName(), ho2 == null ? null : ho2.getClass().getName()); 107 } 108 } 109 } else { 110 if (ho instanceof NameValuePair) { 111 NameValuePair p = (NameValuePair)ho; 112 res.setHeader(p.getName(), p.getValue()); 113 } else { 114 res.setHeader(new HttpPart(n, RESPONSE_HEADER, hm.getSchema(), hm.getSerializer(req.getPartSerializer()), ho)); 115 } 116 } 117 } catch (Exception e) { 118 throw new InternalServerError(e, "Could not set header ''{0}''", hm.getPartName()); 119 } 120 } 121 122 ResponseBeanPropertyMeta bm = rm.getBodyMethod(); 123 124 if (bm != null) { 125 Method m = bm.getGetter(); 126 try { 127 Class<?>[] pt = m.getParameterTypes(); 128 if (pt.length == 1) { 129 Class<?> ptt = pt[0]; 130 if (ptt == OutputStream.class) 131 m.invoke(o, res.getOutputStream()); 132 else if (ptt == Writer.class) 133 m.invoke(o, res.getWriter()); 134 return true; 135 } 136 o = m.invoke(o); 137 } catch (Exception e) { 138 throw new InternalServerError(e, "Could not get body."); 139 } 140 } 141 142 schema = rm.getSchema(); 143 } 144 145 if (o instanceof HttpEntity) { 146 HttpEntity e = (HttpEntity)o; 147 res.header(e.getContentType()).header(e.getContentEncoding()); 148 long contentLength = e.getContentLength(); 149 if (contentLength >= 0) 150 res.header(ContentLength.of(contentLength)); 151 try (OutputStream os = res.getNegotiatedOutputStream()) { 152 e.writeTo(os); 153 os.flush(); 154 } 155 return true; 156 } 157 158 if (sm != null) { 159 Serializer s = sm.getSerializer(); 160 MediaType mediaType = res.getMediaType(); 161 if (mediaType == null) 162 mediaType = sm.getMediaType(); 163 164 MediaType responseType = s.getResponseContentType(); 165 if (responseType == null) 166 responseType = mediaType; 167 168 res.setContentType(responseType.toString()); 169 170 try { 171 if (req.isPlainText()) 172 res.setContentType("text/plain"); 173 SerializerSession session = s.createSession( 174 SerializerSessionArgs 175 .create() 176 .properties(req.getAttributes()) 177 .javaMethod(req.getJavaMethod()) 178 .locale(req.getLocale()) 179 .timeZone(req.getHeaders().getTimeZone()) 180 .mediaType(mediaType) 181 .streamCharset(res.getCharset()) 182 .schema(schema) 183 .debug(req.isDebug() ? true : null) 184 .uriContext(req.getUriContext()) 185 .useWhitespace(req.isPlainText() ? true : null) 186 .resolver(req.getVarResolverSession()) 187 ); 188 189 for (Map.Entry<String,String> h : session.getResponseHeaders().entrySet()) 190 res.setHeaderSafe(h.getKey(), h.getValue()); 191 192 if (! session.isWriterSerializer()) { 193 if (req.isPlainText()) { 194 FinishablePrintWriter w = res.getNegotiatedWriter(); 195 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 196 session.serialize(o, baos); 197 w.write(StringUtils.toSpacedHex(baos.toByteArray())); 198 w.flush(); 199 w.finish(); 200 } else { 201 FinishableServletOutputStream os = res.getNegotiatedOutputStream(); 202 session.serialize(o, os); 203 os.flush(); 204 os.finish(); 205 } 206 } else { 207 FinishablePrintWriter w = res.getNegotiatedWriter(); 208 session.serialize(o, w); 209 w.flush(); 210 w.finish(); 211 } 212 } catch (SerializeException e) { 213 throw new InternalServerError(e); 214 } 215 return true; 216 } 217 218 // Non-existent Accept or plain/text can just be serialized as-is. 219 if (o != null && (isEmpty(accept) || accept.startsWith("text/plain") || accept.contains("*/*"))) { 220 String out = null; 221 if (isEmpty(res.getContentType())) 222 res.setContentType("text/plain"); 223 if (o instanceof InputStream) { 224 try (OutputStream os = res.getNegotiatedOutputStream()) { 225 IOPipe.create(o, os).run(); 226 os.flush(); 227 } 228 } else if (o instanceof Reader) { 229 try (FinishablePrintWriter w = res.getNegotiatedWriter()) { 230 IOPipe.create(o, w).run(); 231 w.flush(); 232 w.finish(); 233 } 234 } else { 235 out = req.getBeanSession().getClassMetaForObject(o).toString(o); 236 FinishablePrintWriter w = res.getNegotiatedWriter(); 237 w.append(out); 238 w.flush(); 239 w.finish(); 240 } 241 return true; 242 } 243 244 throw new NotAcceptable( 245 "Unsupported media-type in request header ''Accept'': ''{0}''\n\tSupported media-types: {1}", 246 req.getHeaders().getString("Accept", ""), g.getSupportedMediaTypes() 247 ); 248 } 249 250 private Iterable<?> iterate(Object o) { 251 if (o == null) 252 return Collections.emptyList(); 253 if (o instanceof Map) 254 return ((Map<?,?>)o).entrySet(); 255 if (o.getClass().isArray()) 256 return Arrays.asList(o); 257 if (o instanceof Collection) 258 return (Collection<?>)o; 259 throw new InternalServerError("Could not iterate over Headers of type ''{0}''", o.getClass().getName()); 260 } 261 }