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