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.*; 020import static org.forgerock.http.header.HeaderUtil.*; 021import static org.forgerock.http.routing.Version.*; 022 023import java.util.List; 024import java.util.regex.Matcher; 025import java.util.regex.Pattern; 026 027import org.forgerock.http.protocol.Header; 028import org.forgerock.http.protocol.Message; 029import org.forgerock.http.routing.Version; 030import org.forgerock.util.Pair; 031 032/** 033 * Processes the <strong>{@code Accept-API-Version}</strong> message header. 034 * Represents the accepted protocol and resource versions. 035 */ 036public final class AcceptApiVersionHeader extends Header { 037 038 /** 039 * Constructs a new header, initialized from the specified message. 040 * 041 * @param message The message to initialize the header from. 042 * @return The parsed header. 043 * @throws IllegalArgumentException If the version header is in an invalid format. 044 */ 045 public static AcceptApiVersionHeader valueOf(Message message) { 046 return valueOf(parseSingleValuedHeader(message, NAME)); 047 } 048 049 /** 050 * Constructs a new header, initialized from the specified string value. 051 * 052 * @param string The value to initialize the header from. 053 * @return The parsed header. 054 * @throws IllegalArgumentException If the version header is in an invalid format. 055 */ 056 public static AcceptApiVersionHeader valueOf(String string) { 057 Pair<Version, Version> parsedValue = parse(string); 058 return new AcceptApiVersionHeader(parsedValue.getFirst(), parsedValue.getSecond()); 059 } 060 061 static Pair<Version, Version> parse(String string) { 062 if (string == null || string.isEmpty()) { 063 return Pair.empty(); 064 } 065 066 Matcher matcher = EXPECTED_VERSION_FORMAT.matcher(string); 067 if (!matcher.matches()) { 068 throw new IllegalArgumentException("Version string is in an invalid format: " + string); 069 } 070 071 Version protocolVersion = null; 072 Version resourceVersion = null; 073 if (matcher.group(1) != null) { 074 protocolVersion = version(matcher.group(1)); 075 } else if (matcher.group(3) != null) { 076 resourceVersion = version(matcher.group(3)); 077 } else if (matcher.group(5) != null) { 078 protocolVersion = version(matcher.group(5)); 079 resourceVersion = version(matcher.group(7)); 080 } else { 081 resourceVersion = version(matcher.group(9)); 082 protocolVersion = version(matcher.group(11)); 083 } 084 return Pair.of(protocolVersion, resourceVersion); 085 } 086 087 /** The name of this header. */ 088 public static final String NAME = "Accept-API-Version"; 089 private static final String PROTOCOL = "protocol"; 090 private static final String RESOURCE = "resource"; 091 private static final String EQUALS = "="; 092 093 /** 094 * Regex that accepts a comma separated protocol and resource version. 095 * The version {@code String}s can either by {@code Integer}s or {@code Double}s. 096 * It is invalid to contain neither a protocol or resource version. 097 * 098 * Pattern matches: 099 * <ul> 100 * <li>protocol=123.123,resource=123.123</li> 101 * <li>protocol=123,resource=123</li> 102 * <li>protocol=123, resource=123</li> 103 * <li>protocol=123.123</li> 104 * <li>protocol=123</li> 105 * <li>resource=123.123</li> 106 * <li>resource=123</li> 107 * <li>resource=123.123,protocol=123.123</li> 108 * </ul> 109 * 110 * Pattern does not matches: 111 * <ul> 112 * <li>protocol=123.123.123,resource=123.123.123</li> 113 * <li>protocol=123.123resource=123.123</li> 114 * <li>protocol=123.123 resource=123.123</li> 115 * <li>protocol= resource=</li> 116 * </ul> 117 */ 118 private static final String PROTOCOL_VERSION_REGEX = PROTOCOL + "=(\\d+(\\.\\d+)?)"; 119 private static final String RESOURCE_VERSION_REGEX = RESOURCE + "=(\\d+(\\.\\d+)?)"; 120 private static final Pattern EXPECTED_VERSION_FORMAT = 121 Pattern.compile("^" + PROTOCOL_VERSION_REGEX + "$" 122 + "|^" + RESOURCE_VERSION_REGEX + "$" 123 + "|^" + PROTOCOL_VERSION_REGEX + "\\s*,\\s*" + RESOURCE_VERSION_REGEX + "$" 124 + "|^" + RESOURCE_VERSION_REGEX + "\\s*,\\s*" + PROTOCOL_VERSION_REGEX + "$"); 125 126 private Version protocolVersion; 127 private Version resourceVersion; 128 129 /** 130 * Constructs a new header, initialized with the specified protocol and resource versions. 131 * 132 * @param protocol The accepted protocol version. 133 * @param resource The accepted resource version. 134 */ 135 public AcceptApiVersionHeader(Version protocol, Version resource) { 136 this.protocolVersion = protocol; 137 this.resourceVersion = resource; 138 } 139 140 @Override 141 public String getName() { 142 return NAME; 143 } 144 145 /** 146 * Gets the acceptable protocol version. 147 * 148 * @return The acceptable protocol version. 149 */ 150 public Version getProtocolVersion() { 151 return protocolVersion; 152 } 153 154 /** 155 * Gets the acceptable resource version. 156 * 157 * @return The acceptable resource version. 158 */ 159 public Version getResourceVersion() { 160 return resourceVersion; 161 } 162 163 /** 164 * Will set the accepted protocol version, if not provided in the 165 * {@literal Accept-API-Version} header. 166 * 167 * @param version The default protocol version. 168 * @return The accept api version header. 169 */ 170 public AcceptApiVersionHeader withDefaultProtocolVersion(Version version) { 171 if (protocolVersion == null && version != null) { 172 this.protocolVersion = version; 173 } 174 return this; 175 } 176 177 /** 178 * Will set the accepted resource version, if not provided in the 179 * {@literal Accept-API-Version} header. 180 * 181 * @param version The default resource version. 182 * @return The accept api version header. 183 */ 184 public AcceptApiVersionHeader withDefaultResourceVersion(Version version) { 185 if (resourceVersion == null && version != null) { 186 this.resourceVersion = version; 187 } 188 return this; 189 } 190 191 @Override 192 public List<String> getValues() { 193 StringBuilder sb = new StringBuilder(); 194 if (protocolVersion != null) { 195 sb.append(PROTOCOL).append(EQUALS).append(protocolVersion.toString()); 196 } 197 if (protocolVersion != null && resourceVersion != null) { 198 sb.append(","); 199 } 200 if (resourceVersion != null) { 201 sb.append(RESOURCE).append(EQUALS).append(resourceVersion.toString()); 202 } 203 return singletonList(sb.toString()); 204 } 205 206 static class Factory extends AbstractSingleValuedHeaderFactory<AcceptApiVersionHeader> { 207 208 @Override 209 public AcceptApiVersionHeader parse(String value) { 210 return valueOf(value); 211 } 212 213 } 214 215}