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.httppart.bean; 018 019import static org.apache.juneau.commons.InvalidAnnotationException.*; 020import static org.apache.juneau.commons.httppart.HttpPartType.*; 021import static org.apache.juneau.commons.reflect.ReflectionUtils.*; 022import static org.apache.juneau.commons.utils.ClassUtils.*; 023import static org.apache.juneau.commons.utils.CollectionUtils.*; 024import static org.apache.juneau.commons.utils.Shorts.*; 025import static org.apache.juneau.httppart.bean.MethodInfoUtils.*; 026 027import java.io.*; 028import java.lang.reflect.*; 029import java.util.*; 030 031import org.apache.juneau.commons.lang.*; 032import org.apache.juneau.commons.reflect.*; 033import org.apache.juneau.http.*; 034import org.apache.juneau.marshall.*; 035import org.apache.juneau.marshall.httppart.*; 036 037/** 038 * Represents the metadata gathered from a parameter or class annotated with {@link Response}. 039 * 040 * <h5 class='section'>See Also:</h5><ul> 041 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/HttpPartSerializersParsers">HTTP Part Serializers and Parsers</a> 042 * </ul> 043 */ 044public class ResponseBeanMeta { 045 046 private static final AnnotationProvider AP = AnnotationProvider.INSTANCE; 047 048 static class Builder { 049 ClassMeta<?> cm; 050 int code; 051 AnnotationWorkList annotations; 052 Class<? extends HttpPartSerializer> partSerializer; 053 Class<? extends HttpPartParser> partParser; 054 HttpPartSchema.Builder schema = HttpPartSchema.create(); 055 056 Map<String,ResponseBeanPropertyMeta.Builder> headerMethods = map(); 057 ResponseBeanPropertyMeta.Builder contentMethod; 058 ResponseBeanPropertyMeta.Builder statusMethod; 059 060 Builder(AnnotationWorkList annotations) { 061 this.annotations = annotations; 062 } 063 064 Builder apply(Response a) { 065 if (nn(a)) { 066 // Serializer/parser now specified via @HttpPartMarshalling annotation instead of @Response attributes 067 schema.apply(a.schema()); 068 } 069 return this; 070 } 071 072 Builder apply(StatusCode a) { 073 if (nn(a) && a.value().length > 0) 074 code = a.value()[0]; 075 return this; 076 } 077 078 Builder apply(Type t) { 079 var c = toClass(t); 080 this.cm = MarshallingContext.DEFAULT.getClassMeta(c); 081 cm.getPublicMethods().forEach(x -> { 082 assertNoInvalidAnnotations(x, Query.class, FormData.class); 083 if (x.hasAnnotation(Header.class)) { 084 assertNoArgs(x, Header.class); 085 assertReturnNotVoid(x, Header.class); 086 var s = HttpPartSchema.create(x.getAnnotations(Header.class).findFirst().map(AnnotationInfo::inner).orElse(null), x.getPropertyName()); 087 headerMethods.put(s.getName(), ResponseBeanPropertyMeta.create(RESPONSE_HEADER, s, x)); 088 } else if (x.hasAnnotation(StatusCode.class)) { 089 assertNoArgs(x, Header.class); 090 assertReturnType(x, Header.class, int.class, Integer.class); 091 statusMethod = ResponseBeanPropertyMeta.create(RESPONSE_STATUS, x); 092 } else if (x.hasAnnotation(Content.class)) { 093 if (x.getParameterCount() == 0) 094 assertReturnNotVoid(x, Header.class); 095 else 096 assertArgType(x, Header.class, OutputStream.class, Writer.class); 097 contentMethod = ResponseBeanPropertyMeta.create(RESPONSE_BODY, x); 098 } 099 }); 100 return this; 101 } 102 103 ResponseBeanMeta build() { 104 return new ResponseBeanMeta(this); 105 } 106 } 107 108 /** 109 * Represents a non-existent meta object. 110 */ 111 public static final ResponseBeanMeta NULL = new ResponseBeanMeta(new Builder(AnnotationWorkList.create())); 112 113 /** 114 * Create metadata from specified method return. 115 * 116 * @param m The method annotated with {@link Response}. 117 * @param annotations The annotations to apply to any new part serializers or parsers. 118 * @return Metadata about the class, or <jk>null</jk> if class not annotated with {@link Response}. 119 */ 120 public static ResponseBeanMeta create(MethodInfo m, AnnotationWorkList annotations) { 121 if (! (m.hasAnnotation(Response.class) || m.getReturnType().unwrap(Holder.class, Optional.class).hasAnnotation(Response.class))) 122 return null; 123 var b = new Builder(annotations); 124 b.apply(m.getReturnType().unwrap(Holder.class, Optional.class).innerType()); 125 var ap = AP; 126 rstream(ap.find(Response.class, m)).forEach(x -> b.apply(x.inner())); 127 rstream(ap.find(StatusCode.class, m)).forEach(x -> b.apply(x.inner())); 128 return b.build(); 129 } 130 131 /** 132 * Create metadata from specified method parameter. 133 * 134 * @param mpi The method parameter. 135 * @param annotations The annotations to apply to any new part serializers or parsers. 136 * @return Metadata about the class, or <jk>null</jk> if class not annotated with {@link Response}. 137 */ 138 public static ResponseBeanMeta create(ParameterInfo mpi, AnnotationWorkList annotations) { 139 if (! AP.has(Response.class, mpi)) 140 return null; 141 var b = new Builder(annotations); 142 b.apply(mpi.getParameterType().unwrap(Holder.class, Optional.class).innerType()); 143 rstream(AP.find(Response.class, mpi)).forEach(x -> b.apply(x.inner())); 144 rstream(AP.find(StatusCode.class, mpi)).forEach(x -> b.apply(x.inner())); 145 return b.build(); 146 } 147 148 /** 149 * Create metadata from specified class. 150 * 151 * @param t The class annotated with {@link Response}. 152 * @param annotations The annotations to apply to any new part serializers or parsers. 153 * @return Metadata about the class, or <jk>null</jk> if class not annotated with {@link Response}. 154 */ 155 public static ResponseBeanMeta create(Type t, AnnotationWorkList annotations) { 156 var ci = info(t).unwrap(Holder.class, Optional.class); 157 if (! ci.hasAnnotation(Response.class)) 158 return null; 159 var b = new Builder(annotations); 160 b.apply(ci.innerType()); 161 var ai = AP; 162 rstream(ai.find(Response.class, ci)).forEach(x -> b.apply(x.inner())); 163 rstream(ai.find(StatusCode.class, ci)).forEach(x -> b.apply(x.inner())); 164 return b.build(); 165 } 166 167 private final ClassMeta<?> cm; 168 private final Map<String,ResponseBeanPropertyMeta> properties; 169 private final int code; 170 private final Map<String,ResponseBeanPropertyMeta> headerMethods; 171 private final ResponseBeanPropertyMeta statusMethod; 172 private final ResponseBeanPropertyMeta contentMethod; 173 private final Optional<HttpPartSerializer> partSerializer; 174 private final Optional<HttpPartParser> partParser; 175 176 private final HttpPartSchema schema; 177 178 ResponseBeanMeta(Builder b) { 179 cm = b.cm; 180 code = b.code; 181 partSerializer = o(b.partSerializer).map(x -> HttpPartSerializer.creator().type(x).apply(b.annotations).create()); 182 partParser = o(b.partParser).map(x -> HttpPartParser.creator().type(x).apply(b.annotations).create()); 183 schema = b.schema.build(); 184 185 Map<String,ResponseBeanPropertyMeta> properties2 = map(); 186 187 Map<String,ResponseBeanPropertyMeta> hm = map(); 188 b.headerMethods.forEach((k, v) -> { 189 ResponseBeanPropertyMeta pm = v.build(partSerializer, partParser); 190 hm.put(k, pm); 191 properties2.put(pm.getGetter().getName(), pm); 192 }); 193 this.headerMethods = u(hm); 194 195 contentMethod = b.contentMethod == null ? null : b.contentMethod.schema(schema).build(partSerializer, partParser); 196 statusMethod = b.statusMethod == null ? null : b.statusMethod.build(oe(), oe()); 197 198 var cMethod = contentMethod; 199 if (cMethod != null) { 200 var getter = cMethod.getGetter(); 201 if (getter != null) 202 properties2.put(getter.getName(), cMethod); 203 } 204 var sMethod = statusMethod; 205 if (sMethod != null) { 206 var getter = sMethod.getGetter(); 207 if (getter != null) 208 properties2.put(getter.getName(), sMethod); 209 } 210 211 this.properties = u(properties2); 212 } 213 214 /** 215 * Returns metadata about the class. 216 * 217 * @return Metadata about the class. 218 */ 219 @SuppressWarnings({ 220 "java:S1452" // Wildcard required - ClassMeta<?> for response metadata 221 }) 222 public ClassMeta<?> getBeanInfo() { return cm; } 223 224 /** 225 * Returns the HTTP status code. 226 * 227 * @return The HTTP status code. 228 */ 229 public int getCode() { return code; } 230 231 /** 232 * Returns the <ja>@Content</ja>-annotated method. 233 * 234 * @return The <ja>@Content</ja>-annotated method, or <jk>null</jk> if it doesn't exist. 235 */ 236 public ResponseBeanPropertyMeta getContentMethod() { return contentMethod; } 237 238 /** 239 * Returns metadata about the <ja>@Header</ja>-annotated methods. 240 * 241 * @return Metadata about the <ja>@Header</ja>-annotated methods, or an empty collection if none exist. 242 */ 243 public Collection<ResponseBeanPropertyMeta> getHeaderMethods() { return headerMethods.values(); } 244 245 /** 246 * Returns the part serializer to use to serialize this response. 247 * 248 * @return The part serializer to use to serialize this response. 249 */ 250 public Optional<HttpPartSerializer> getPartSerializer() { return partSerializer; } 251 252 /** 253 * Returns all the annotated methods on this bean. 254 * 255 * @return All the annotated methods on this bean. 256 */ 257 public Collection<ResponseBeanPropertyMeta> getProperties() { return properties.values(); } 258 259 /** 260 * Returns metadata about the bean property with the specified method getter name. 261 * 262 * @param name The bean method getter name. 263 * @return Metadata about the bean property, or <jk>null</jk> if none found. 264 */ 265 public ResponseBeanPropertyMeta getProperty(String name) { 266 return properties.get(name); 267 } 268 269 /** 270 * Returns the schema information about the response object. 271 * 272 * @return The schema information about the response object. 273 */ 274 public HttpPartSchema getSchema() { return schema; } 275 276 /** 277 * Returns the <ja>@StatusCode</ja>-annotated method. 278 * 279 * @return The <ja>@StatusCode</ja>-annotated method, or <jk>null</jk> if it doesn't exist. 280 */ 281 public ResponseBeanPropertyMeta getStatusMethod() { return statusMethod; } 282}