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