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