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