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