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