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