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}