001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions copyright [year] [name of copyright owner]".
013 *
014 * Copyright 2015 ForgeRock AS.
015 */
016
017package org.forgerock.http.header;
018
019import static java.util.Collections.*;
020
021import java.util.ArrayList;
022import java.util.Arrays;
023import java.util.HashMap;
024import java.util.List;
025import java.util.Map;
026import java.util.TreeMap;
027
028import org.forgerock.http.protocol.Header;
029
030/**
031 * Creates instances of {@link Header} classes from String representation.
032 * @param <H> The type of {@link Header} produced by the factory.
033 */
034public abstract class HeaderFactory<H extends Header> {
035    /**
036     * A map of {@link Header} types to the names of the headers they
037     * implement.
038     */
039    public static final Map<Class<? extends Header>, String> HEADER_NAMES = unmodifiableMap(
040            new HashMap<Class<? extends Header>, String>() {
041                {
042                    put(AcceptApiVersionHeader.class, AcceptApiVersionHeader.NAME);
043                    put(AcceptLanguageHeader.class, AcceptLanguageHeader.NAME);
044                    put(ConnectionHeader.class, ConnectionHeader.NAME);
045                    put(ContentApiVersionHeader.class, ContentApiVersionHeader.NAME);
046                    put(ContentEncodingHeader.class, ContentEncodingHeader.NAME);
047                    put(ContentLengthHeader.class, ContentLengthHeader.NAME);
048                    put(ContentTypeHeader.class, ContentTypeHeader.NAME);
049                    put(CookieHeader.class, CookieHeader.NAME);
050                    put(LocationHeader.class, LocationHeader.NAME);
051                    put(SetCookieHeader.class, SetCookieHeader.NAME);
052                    put(TransactionIdHeader.class, TransactionIdHeader.NAME);
053                    put(WarningHeader.class, WarningHeader.NAME);
054                }
055            });
056
057    /**
058     * A map of header names to known {@code HeaderFactory} implementations.
059     */
060    public static final Map<String, HeaderFactory<?>> FACTORIES = unmodifiableMap(
061            new TreeMap<String, HeaderFactory<?>>(String.CASE_INSENSITIVE_ORDER) {
062                {
063                    put(AcceptApiVersionHeader.NAME, new AcceptApiVersionHeader.Factory());
064                    put(AcceptLanguageHeader.NAME, new AcceptLanguageHeader.Factory());
065                    put(ConnectionHeader.NAME, new ConnectionHeader.Factory());
066                    put(ContentApiVersionHeader.NAME, new ContentApiVersionHeader.Factory());
067                    put(ContentEncodingHeader.NAME, new ContentEncodingHeader.Factory());
068                    put(ContentLengthHeader.NAME, new ContentLengthHeader.Factory());
069                    put(ContentTypeHeader.NAME, new ContentTypeHeader.Factory());
070                    put(CookieHeader.NAME, new CookieHeader.Factory());
071                    put(LocationHeader.NAME, new LocationHeader.Factory());
072                    put(SetCookieHeader.NAME, new SetCookieHeader.Factory());
073                    put(TransactionIdHeader.NAME, new TransactionIdHeader.Factory());
074                    put(WarningHeader.NAME, new WarningHeader.Factory());
075                    if (size() != HEADER_NAMES.size()) {
076                        throw new IllegalStateException("Misconfigured maps");
077                    }
078                }
079
080                @Override
081                public HeaderFactory<?> put(String key, HeaderFactory<?> value) {
082                    if (!HEADER_NAMES.containsValue(key)) {
083                        throw new IllegalStateException("Misconfigured maps");
084                    }
085                    return super.put(key, value);
086                }
087            });
088    /**
089     * Create a header instance from String representation, which may contain
090     * multiple values if the header supports them.
091     *
092     * @param value The string representation.
093     * @return The parsed header.
094     * @throws MalformedHeaderException When the value cannot be parsed.
095     */
096    protected abstract H parse(String value) throws MalformedHeaderException;
097
098    /**
099     * Create a header instance from a list of String representations, each of
100     * which may in turn contain multiple values if the header supports them.
101     * @param values The string representations.
102     * @return The parsed header.
103     * @throws MalformedHeaderException When the value cannot be parsed.
104     */
105    protected abstract H parse(List<String> values) throws MalformedHeaderException;
106
107    /**
108     * Create a header instance from a provided object representation.
109     * <p>
110     * Subclasses may wish to override this method in order to support other
111     * types of value.
112     *
113     * @param value An object representation - may be a string, a collection
114     *              of strings, an array of strings, an instance of {@code Header},
115     *              or some other object type supported by the subclass.
116     * @return The parsed header.
117     * @throws MalformedHeaderException When the value cannot be parsed.
118     */
119    public H parse(Object value) throws MalformedHeaderException {
120        try {
121            if (value instanceof Header) {
122                return parse(new ArrayList<>(((Header) value).getValues()));
123            } else if (value instanceof String) {
124                final String s = (String) value;
125                return s.isEmpty() ? null : parse(s);
126            } else if (value instanceof List) {
127                return parse((List<String>) value);
128            } else if (value.getClass().isArray()) {
129                return parse(Arrays.asList((String[]) value));
130            }
131        } catch (RuntimeException e) {
132            throw new MalformedHeaderException("Could not parse header", e);
133        }
134        throw new IllegalArgumentException("Cannot parse header value from type: " + value);
135    }
136}