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}