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