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