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}