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