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.http.part;
014
015import static org.apache.juneau.common.internal.StringUtils.*;
016import static org.apache.juneau.common.internal.ThrowableUtils.*;
017
018import java.lang.reflect.*;
019import java.util.concurrent.*;
020
021import org.apache.juneau.http.annotation.*;
022import org.apache.juneau.httppart.*;
023import org.apache.juneau.reflect.*;
024
025/**
026 * Holds metadata about http part beans (POJOs that get serialized as HTTP parts such as form data or query parameters).
027 *
028 * <p>
029 * HTTP part beans are typically annotated with {@link Query @Query}, {@link FormData @FormData}, or {@link Path @Path}
030 * although it's not an absolute requirement.
031 *
032 * <p>
033 * HTTP part beans must have one of the following public constructors:
034 * <ul>
035 *    <li><c><jk>public</jk> X(String <jv>partValue</jv>)</c>
036 *    <li><c><jk>public</jk> X(Object <jv>partValue</jv>)</c>
037 *    <li><c><jk>public</jk> X(String <jv>partName</jv>, String <jv>partValue</jv>)</c>
038 *    <li><c><jk>public</jk> X(String <jv>partName</jv>, Object <jv>partValue</jv>)</c>
039 * </ul>
040 *
041 * <p>
042 * <h5 class='figure'>Example</h5>
043 * <p class='bjava'>
044 *    <jc>// Our part bean.</jc>
045 *    <ja>@Query</ja>(<js>"foo"</js>)
046 *    <jk>public class</jk> FooPart <jk>extends</jk> BasicStringPart {
047 *
048 *       <jk>public</jk> FooPart(String <jv>partValue</jv>) {
049 *          <jk>super</jk>(<js>"foo"</js>, <jv>partValue</jv>);
050 *       }
051 *  }
052 *
053 *  <jc>// Code to retrieve a part bean from a part list in a request.</jc>
054 *  PartList <jv>parts</jv> = <jv>httpRequest</jv>.getFormDataList();
055 *  FooPart <jv>foo</jv> = <jv>parts</jv>.get(FooPart.<jk>class</jk>);
056 * </p>
057 *
058 * <h5 class='section'>See Also:</h5><ul>
059 *    <li class='link'><a class="doclink" href="../../../../../index.html#juneau-rest-common">juneau-rest-common</a>
060 * </ul>
061 *
062 * @param <T> The HTTP part bean type.
063 */
064public class PartBeanMeta<T> {
065
066   private static final ConcurrentHashMap<Class<?>,PartBeanMeta<?>> CACHE = new ConcurrentHashMap<>();
067
068   private final Class<T> type;
069   private final Constructor<T> constructor;
070   private final HttpPartSchema schema;
071
072   /**
073    * Finds the part bean meta for the specified type.
074    *
075    * @param <T> The part bean type.
076    * @param type The part bean type.
077    * @return The metadata, or <jk>null</jk> if a valid constructor could not be found.
078    */
079   @SuppressWarnings("unchecked")
080   public static <T> PartBeanMeta<T> of(Class<T> type) {
081      PartBeanMeta<?> m = CACHE.get(type);
082      if (m == null) {
083         m = new PartBeanMeta<>(type);
084         CACHE.put(type, m);
085      }
086      return (PartBeanMeta<T>)m;
087   }
088
089   private PartBeanMeta(Class<T> type) {
090      this.type = type;
091
092      ClassInfo ci = ClassInfo.of(type);
093
094      ConstructorInfo cci = ci.getPublicConstructor(x -> x.hasParamTypes(String.class));
095      if (cci == null)
096         cci = ci.getPublicConstructor(x -> x.hasParamTypes(Object.class));
097      if (cci == null)
098         cci = ci.getPublicConstructor(x -> x.hasParamTypes(String.class, String.class));
099      if (cci == null)
100         cci = ci.getPublicConstructor(x -> x.hasParamTypes(String.class, Object.class));
101      constructor = cci == null ? null : cci.inner();
102
103      if (ci.hasAnnotation(Query.class))
104         this.schema = HttpPartSchema.create(Query.class, type);
105      else if (ci.hasAnnotation(FormData.class))
106         this.schema = HttpPartSchema.create(FormData.class, type);
107      else if (ci.hasAnnotation(Path.class))
108         this.schema = HttpPartSchema.create(Path.class, type);
109      else
110         this.schema = HttpPartSchema.create(org.apache.juneau.http.annotation.Header.class, type);
111   }
112
113   /**
114    * Returns schema information about this part.
115    *
116    * <p>
117    * This is information pulled from {@link Query @Query}, {@link FormData @FormData}, or {@link Path @Path} annotations
118    * on the class.
119    *
120    * @return The schema information.
121    */
122   public HttpPartSchema getSchema() {
123      return schema;
124   }
125
126   /**
127    * Constructs a part bean with the specified name or value.
128    *
129    * <p>
130    * Can only be used on beans where the part name is known.
131    *
132    * @param value
133    *    The part value.
134    * @return A newly constructed bean.
135    */
136   public T construct(Object value) {
137      return construct(null, value);
138   }
139
140   /**
141    * Constructs a part bean with the specified name or value.
142    *
143    * @param name
144    *    The part name.
145    *    <br>If <jk>null</jk>, uses the value pulled from the {@link org.apache.juneau.http.annotation.Header#name() @Header(name)} or
146    *    {@link org.apache.juneau.http.annotation.Header#value() @Header(value)} annotations.
147    * @param value
148    *    The part value.
149    * @return A newly constructed bean.
150    * @throws UnsupportedOperationException If bean could not be constructed (e.g. couldn't find a constructor).
151    */
152   public T construct(String name, Object value) {
153
154      if (constructor == null)
155         throw new UnsupportedOperationException("Constructor for type "+type.getName()+" could not be found.");
156
157      if (name == null)
158         name = schema.getName();
159
160      Class<?>[] pt = constructor.getParameterTypes();
161      Object[] args = new Object[pt.length];
162      if (pt.length == 1) {
163         args[0] = pt[0] == String.class ? stringify(value) : value;
164      } else {
165         if (name == null)
166            throw new UnsupportedOperationException("Constructor for type "+type.getName()+" requires a name as the first argument.");
167         args[0] = name;
168         args[1] = pt[1] == String.class ? stringify(value) : value;
169      }
170
171      try {
172         return constructor.newInstance(args);
173      } catch (Exception e) {
174         throw asRuntimeException(e);
175      }
176   }
177}