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 }