001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau.rest.server.processor;
018
019import static org.apache.juneau.commons.utils.ClassUtils.*;
020import static org.apache.juneau.commons.utils.Shorts.*;
021
022import java.io.*;
023import java.util.*;
024
025import org.apache.juneau.commons.httppart.*;
026import org.apache.juneau.http.*;
027import org.apache.juneau.http.HttpPart;
028import org.apache.juneau.http.header.*;
029import org.apache.juneau.http.response.*;
030import org.apache.juneau.marshall.httppart.*;
031import org.apache.juneau.rest.server.*;
032
033/**
034 * Response handler for {@link Response @Response}-annotated objects.
035 *
036 * <h5 class='section'>See Also:</h5><ul>
037 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/ResponseProcessors">Response Processors</a>
038 * </ul>
039 */
040@SuppressWarnings({
041   "java:S3740", // Raw Class/BeanMeta types used for reflective response bean processing where generic type parameters are erased
042   "resource" // ResponseBeanProcessor manages Closeable resources
043})
044public class ResponseBeanProcessor implements ResponseProcessor {
045
046   @Override /* Overridden from ResponseProcessor */
047   @SuppressWarnings({
048      "java:S3776", // Cognitive complexity acceptable for this specific logic
049      "java:S6541" // Session objects are single-threaded by design
050   })
051   public int process(RestOpSession opSession) throws IOException {
052
053      var req = opSession.getRequest();
054      var res = opSession.getResponse();
055      var defaultPartSerializer = req.getOpContext().getPartSerializer();
056
057      var output = res.getContent(Object.class);
058
059      if (output == null || ! (nn(output.getClass().getAnnotation(Response.class)) || nn(res.getResponseBeanMeta())))
060         return NEXT;
061
062      var rm = res.getResponseBeanMeta();
063      if (rm == null)
064         rm = req.getOpContext().getResponseBeanMeta(output);
065
066      var stm = rm.getStatusMethod();
067      if (nn(stm)) {
068         try {
069            res.setStatus((int)stm.getGetter().invoke(output));
070         } catch (Exception e) {
071            throw new InternalServerError(e, "Could not get status.");
072         }
073      } else if (rm.getCode() != 0) {
074         res.setStatus(rm.getCode());
075      }
076
077      for (var hm : rm.getHeaderMethods()) {
078         var n = hm.getPartName().orElse(null);
079         try {
080            var o = hm.getGetter().invoke(output);
081            var ps = hm.getSchema();
082            var serializer = hm.getSerializer().orElse(defaultPartSerializer).getPartSession();
083            if (eq(n, "*")) {
084               for (var o2 : iterate(o)) {
085                  HttpHeader h;
086                  if (o2 instanceof Map.Entry o3) {
087                     var k = s(o3.getKey());
088                     h = toHeader(k, o3.getValue(), serializer, ps != null ? ps.getProperty(k) : null);
089                  } else if (o2 instanceof HttpHeader o3) {
090                     h = o3;
091                  } else if (o2 instanceof HttpPart o3) {
092                     h = HttpStringHeader.of(o3.getName(), o3.getValue());
093                  } else {
094                     throw new InternalServerError("Invalid type '%s' for header '%s'", cn(o2), n);
095                  }
096                  res.addHeader(h);
097               }
098            } else {
099               HttpHeader h;
100               if (o instanceof HttpHeader o2)
101                  h = o2;
102               else if (o instanceof HttpPart o2)
103                  h = HttpStringHeader.of(o2.getName(), o2.getValue());
104               else
105                  h = toHeader(n, o, serializer, ps);
106               res.addHeader(h);
107            }
108         } catch (Exception e) {
109            throw new InternalServerError(e, "Could not set header '%s'", n);
110         }
111      }
112
113      var bm = rm.getContentMethod();
114
115      if (nn(bm)) {
116         var m = bm.getGetter();
117         try {
118            var pt = m.getParameterTypes();
119            if (pt.length == 1) {
120               var ptt = pt[0];
121               if (ptt == OutputStream.class)
122                  m.invoke(output, res.getOutputStream());
123               else if (ptt == Writer.class)
124                  m.invoke(output, res.getWriter());
125               return 1;
126            }
127            res.setContent(m.invoke(output));
128         } catch (Exception e) {
129            throw new InternalServerError(e, "Could not get content.");
130         }
131      }
132
133      return NEXT;  // Let PojoProcessor serialize it.
134   }
135
136   /**
137    * Serializes the given value to a String using the given part serializer/schema, then returns it as an
138    * {@link HttpStringHeader}. Returns {@code null} only if the serialized value would be a blank string
139    * and {@code skipIfEmpty} semantics apply.
140    */
141   @SuppressWarnings({
142      "java:S112" // Propagates the checked exceptions thrown by HttpPartSerializerSession.write(...); the caller already re-wraps them in InternalServerError.
143   })
144   private static HttpHeader toHeader(String name, Object value, HttpPartSerializerSession session, HttpPartSchema schema) throws Exception {
145      String v;
146      if (session != null)
147         v = session.write(HttpPartType.HEADER, schema, value);
148      else
149         v = value == null ? null : value.toString();
150      if (v != null && v.isEmpty())
151         v = null;
152      return HttpStringHeader.of(name, v);
153   }
154
155   private static Iterable<?> iterate(Object o) {
156      if (o == null)
157         return Collections.emptyList();
158      if (o instanceof Map<?,?> m)
159         return m.entrySet();
160      if (isArray(o))
161         return l((Object[])o);
162      if (o instanceof Collection<?> c)
163         return c;
164      throw new InternalServerError("Could not iterate over Headers of type '%s'", cn(o));
165   }
166}