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.commons.utils.ThrowableUtils.*;
020import static org.apache.juneau.commons.utils.Utils.*;
021
022import java.lang.reflect.*;
023import java.util.concurrent.*;
024
025import org.apache.juneau.commons.reflect.*;
026import org.apache.juneau.httppart.*;
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   /**
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 final Class<T> type;
089   private final Constructor<T> constructor;
090
091   private final HttpPartSchema schema;
092
093   private HeaderBeanMeta(Class<T> type) {
094      this.type = type;
095
096      var ci = ClassInfo.of(type);
097
098      // @formatter:off
099      constructor = ci.getPublicConstructor(x -> x.hasParameterTypes(String.class))
100         .or(() -> ci.getPublicConstructor(x -> x.hasParameterTypes(Object.class)))
101         .or(() -> ci.getPublicConstructor(x -> x.hasParameterTypes(String.class, String.class)))
102         .or(() -> ci.getPublicConstructor(x -> x.hasParameterTypes(String.class, Object.class)))
103         .map(x -> x.<T>inner())
104         .orElse(null);
105      // @formatter:on
106
107      this.schema = HttpPartSchema.create(org.apache.juneau.http.annotation.Header.class, type);
108   }
109
110   /**
111    * Constructs a header bean with the specified name or value.
112    *
113    * <p>
114    * Can only be used on beans where the header name is known.
115    *
116    * @param value
117    *    The header value.
118    * @return A newly constructed bean.
119    */
120   public T construct(Object value) {
121      return construct(null, value);
122   }
123
124   /**
125    * Constructs a header bean with the specified name or value.
126    *
127    * @param name
128    *    The header name.
129    *    <br>If <jk>null</jk>, uses the value pulled from the {@link org.apache.juneau.http.annotation.Header#name() @Header(name)} or
130    *    {@link org.apache.juneau.http.annotation.Header#value() @Header(value)} annotations.
131    * @param value
132    *    The header value.
133    * @return A newly constructed bean.
134    * @throws UnsupportedOperationException If bean could not be constructed (e.g. couldn't find a constructor).
135    */
136   public T construct(String name, Object value) {
137
138      if (constructor == null)
139         throw unsupportedOp("Constructor for type {0} could not be found.", cn(type));
140
141      if (name == null)
142         name = schema.getName();
143
144      var pt = constructor.getParameterTypes();
145      var args = new Object[pt.length];
146      if (pt.length == 1) {
147         args[0] = pt[0] == String.class ? s(value) : value;
148      } else {
149         if (name == null)
150            throw unsupportedOp("Constructor for type {0} requires a name as the first argument.", cn(type));
151         args[0] = name;
152         args[1] = pt[1] == String.class ? s(value) : value;
153      }
154
155      try {
156         return constructor.newInstance(args);
157      } catch (Exception e) {
158         throw toRex(e);
159      }
160   }
161
162   /**
163    * Returns schema information about this header.
164    *
165    * <p>
166    * This is information pulled from {@link org.apache.juneau.http.annotation.Header @Header} annotation on the class.
167    *
168    * @return The schema information.
169    */
170   public HttpPartSchema getSchema() { return schema; }
171}