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