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.httppart.HttpPartType.*;
020import static org.apache.juneau.commons.reflect.ReflectionUtils.*;
021import static org.apache.juneau.commons.utils.CollectionUtils.*;
022import static org.apache.juneau.commons.utils.Shorts.*;
023import static org.apache.juneau.httppart.bean.MethodInfoUtils.*;
024
025import java.util.*;
026
027import org.apache.juneau.commons.inject.*;
028import org.apache.juneau.commons.reflect.*;
029import org.apache.juneau.http.*;
030import org.apache.juneau.marshall.*;
031import org.apache.juneau.marshall.httppart.*;
032
033/**
034 * Represents the metadata gathered from a parameter or class annotated with {@link Request}.
035 *
036 * <h5 class='section'>See Also:</h5><ul>
037 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/HttpPartSerializersParsers">HTTP Part Serializers and Parsers</a>
038 * </ul>
039 */
040public class RequestBeanMeta {
041
042   private static final AnnotationProvider AP = AnnotationProvider.INSTANCE;
043
044   static class Builder {
045      ClassMeta<?> cm;
046      AnnotationWorkList annotations;
047      BeanInstantiator.Builder<HttpPartSerializer> serializer = BeanInstantiator.of(HttpPartSerializer.class);
048      BeanInstantiator.Builder<HttpPartParser> parser = BeanInstantiator.of(HttpPartParser.class);
049      Map<String,RequestBeanPropertyMeta.Builder> properties = map();
050
051      Builder(AnnotationWorkList annotations) {
052         this.annotations = annotations;
053      }
054
055      Builder apply(Class<?> c) {
056         this.cm = MarshallingContext.DEFAULT.getClassMeta(c);
057         var ap = cm.getMarshallingContext().getAnnotationProvider();
058         apply(ap.find(Request.class, cm).stream().findFirst().map(x -> x.inner()).orElse(null));
059         cm.getPublicMethods().forEach(x -> {
060            var n = x.getNameSimple();
061            if (x.hasAnnotation(Header.class)) {
062               assertNoArgs(x, Header.class);
063               assertReturnNotVoid(x, Header.class);
064               properties.put(n, RequestBeanPropertyMeta.create(HEADER, Header.class, x));
065            } else if (x.hasAnnotation(Query.class)) {
066               assertNoArgs(x, Query.class);
067               assertReturnNotVoid(x, Query.class);
068               properties.put(n, RequestBeanPropertyMeta.create(QUERY, Query.class, x));
069            } else if (x.hasAnnotation(FormData.class)) {
070               assertNoArgs(x, FormData.class);
071               assertReturnNotVoid(x, FormData.class);
072               properties.put(n, RequestBeanPropertyMeta.create(FORMDATA, FormData.class, x));
073            } else if (x.hasAnnotation(Path.class)) {
074               assertNoArgs(x, Path.class);
075               assertReturnNotVoid(x, Path.class);
076               properties.put(n, RequestBeanPropertyMeta.create(PATH, Path.class, x));
077            } else if (x.hasAnnotation(Content.class)) {
078               assertNoArgs(x, Content.class);
079               assertReturnNotVoid(x, Content.class);
080               properties.put(n, RequestBeanPropertyMeta.create(BODY, Content.class, x));
081            }
082         });
083         return this;
084      }
085
086      Builder apply(ParameterInfo mpi) {
087         return apply(mpi.getParameterType().inner()).apply(AP.find(Request.class, mpi).stream().findFirst().map(AnnotationInfo::inner).orElse(null));
088      }
089
090      @SuppressWarnings({
091         "java:S1172", // 'a' retained for builder apply-overload symmetry; @Request attributes are now handled via @HttpPartMarshalling
092         "unused" // Parameter intentionally unused — placeholder overload
093      })
094      Builder apply(Request a) {
095         // Serializer/parser now specified via @HttpPartMarshalling annotation instead of @Request attributes
096         return this;
097      }
098
099      RequestBeanMeta build() {
100         return new RequestBeanMeta(this);
101      }
102   }
103
104   /**
105    * Create metadata from specified class.
106    *
107    * @param c The class annotated with {@link Request}.
108    * @param annotations The annotations to apply to any new part serializers or parsers.
109    * @return Metadata about the class, or <jk>null</jk> if class not annotated with {@link Request}.
110    */
111   public static RequestBeanMeta create(Class<?> c, AnnotationWorkList annotations) {
112      var ci = info(c);
113      if (! ci.hasAnnotation(Request.class))
114         return null;
115      return new RequestBeanMeta.Builder(annotations).apply(c).build();
116   }
117
118   /**
119    * Create metadata from specified parameter.
120    *
121    * @param mpi The method parameter.
122    * @param annotations The annotations to apply to any new part serializers or parsers.
123    * @return Metadata about the parameter, or <jk>null</jk> if parameter or parameter type not annotated with {@link Request}.
124    */
125   public static RequestBeanMeta create(ParameterInfo mpi, AnnotationWorkList annotations) {
126      if (! AP.has(Request.class, mpi))
127         return null;
128      return new RequestBeanMeta.Builder(annotations).apply(mpi).build();
129   }
130
131   private final ClassMeta<?> cm;
132   private final Map<String,RequestBeanPropertyMeta> properties;
133   private final HttpPartSerializer serializer;
134
135   private final HttpPartParser parser;
136
137   RequestBeanMeta(Builder b) {
138      cm = b.cm;
139      serializer = b.serializer.asOptional().orElse(null);
140      parser = b.parser.asOptional().orElse(null);
141      Map<String,RequestBeanPropertyMeta> pm = map();
142      b.properties.forEach((k, v) -> pm.put(k, v.build(serializer, parser)));
143      properties = u(pm);
144   }
145
146   /**
147    * Returns metadata about the class.
148    *
149    * @return Metadata about the class.
150    */
151   @SuppressWarnings({
152      "java:S1452"  // Wildcard required - ClassMeta<?> for request metadata
153   })
154   public ClassMeta<?> getBeanInfo() { return cm; }
155
156   /**
157    * Returns all the annotated methods on this bean.
158    *
159    * @return All the annotated methods on this bean.
160    */
161   public Collection<RequestBeanPropertyMeta> getProperties() { return properties.values(); }
162
163   /**
164    * Returns metadata about the bean property with the specified property name.
165    *
166    * @param name The bean property name.
167    * @return Metadata about the bean property, or <jk>null</jk> if none found.
168    */
169   public RequestBeanPropertyMeta getProperty(String name) {
170      return properties.get(name);
171   }
172}