View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.juneau.http;
18  
19  import static org.apache.juneau.commons.utils.CollectionUtils.*;
20  import static org.apache.juneau.commons.utils.Utils.*;
21  import static org.apache.juneau.http.HttpHeaders.*;
22  import static org.junit.jupiter.api.Assertions.*;
23  
24  import java.util.*;
25  
26  import org.apache.juneau.*;
27  import org.apache.juneau.json.*;
28  import org.junit.jupiter.params.*;
29  import org.junit.jupiter.params.provider.*;
30  
31  /**
32   * Verifies that the Accept class handles matching correctly.
33   */
34  class MediaRanges_FindMatch_Test extends TestBase {
35  
36  	private static final Input[] INPUT = {
37  
38  		// label, accept-header, media-types, expected-index
39  
40  		// Simple matches
41  		input("SimpleMatch-1", "text/json", "['text/json']", 0, 0),
42  		input("SimpleMatch-2", "text/json", "['text/json','text/foo']", 0, 0),
43  		input("SimpleMatch-3", "text/json", "['text/foo','text/json']", 1, 1),
44  
45  		// Simple no-matches
46  		input("SimpleNoMatch-1", "text/jsonx", "['text/json']", -1, -1),
47  		input("SimpleNoMatch-2", "text/jso", "['text/json']", -1, -1),
48  		input("SimpleNoMatch-3", "text/json", "['application/json']", -1, -1),
49  		input("SimpleNoMatch-4", "text/json", "[]", -1, -1),
50  
51  		// Meta-character matches
52  		input("MetaMatch-1", "text/*", "['text/a','text/b+c','text/b+d+e']", 0, 2),
53  		input("MetaMatch-2", "text/b+*", "['text/a','text/b+c','text/b+d+e']", 1, 2),
54  		input("MetaMatch-3", "text/c+*", "['text/a','text/b+c','text/b+d+e']", 1, 1),
55  		input("MetaMatch-4", "text/b+d+e", "['text/a','text/b+c','text/b+d']", -1, -1),
56  		input("MetaMatch-5", "text/b+*", "['text/a','text/b+c','text/b+d+e']", 1, 2),
57  		input("MetaMatch-6", "text/d+e+*", "['text/a','text/b+c','text/b+d+e']", 2, 2),
58  
59  		input("MetaMatch-7", "*/a", "['text/a','application/b']", 0, 0),
60  		input("MetaMatch-8", "*/*", "['text/a','text/b+c']", 0, 1),
61  		input("MetaMatch-9", "*/*", "['text/b+c','text/a']", 0, 1),
62  
63  		// Reverse meta-character matches
64  		input("RevMetaMatch-1", "text/a", "['text/*']", 0, 0),
65  		input("RevMetaMatch-2", "text/a", "['*/a']", 0, 0),
66  		input("RevMetaMatch-3", "text/a", "['*/*']", 0, 0),
67  
68  		// Meta-character mixture matches
69  		input("MixedMetaMatch-1", "text/*", "['text/*','text/a','text/a+b','text/b+c','text/d+*']", 0, 0),
70  		input("MixedMetaMatch-2", "*/a", "['text/*','text/a','text/a+b','text/b+c','text/d+*']", 1, 1),
71  		input("MixedMetaMatch-3", "*/*", "['text/*','text/a','text/a+b','text/b+c','text/d+*']", 0, 0),
72  		input("MixedMetaMatch-4", "text/a+*", "['text/*','text/a','text/a+b','text/b+c','text/d+*']", 1, 2),
73  		input("MixedMetaMatch-5", "text/c+*", "['text/*','text/a','text/a+b','text/b+c','text/d+*']", 3, 3),
74  		input("MixedMetaMatch-6", "text/d+*", "['text/*','text/a','text/a+b','text/b+c','text/d+*']", 4, 4),
75  
76  		// Fuzzy matches
77  		input("Fuzzy-1", "text/1+2", "['text/1+2']", 0, 0),
78  		// Order of subtype parts shouldn't matter.
79  		input("Fuzzy-2", "text/2+1", "['text/1+2']", 0, 0),
80  
81  		// Should match if Accept has 'extra' subtypes.
82  		// For example, "Accept: text/json+activity" should match against the "text/json" serializer.
83  		input("Fuzzy-3", "text/json+foo", "['text/json+*']", 0, 0),
84  
85  		// Shouldn't match because the accept media type must be at least a subset of the real media type
86  		// For example, "Accept: text/json" should not match against the "text/json+lax" serializer.
87  		input("Fuzzy-4", "text/json", "['text/json+lax']", -1, -1),
88  
89  		input("Fuzzy-5", "text/1+2", "['text/1','text/1+3']", -1, -1),
90  
91  		// "text/1+2" should be a better match than just "text/1"
92  		input("Fuzzy-6", "text/1+2", "['text/1','text/1+2','text/1+2+3']", 1, 1),
93  		// Same as last, but mix up the order a bit.
94  		input("Fuzzy-7", "text/1+2", "['text/1+2+3','text/1','text/1+2']", 2, 2),
95  		// Same as last, but mix up the order of the subtypes as well.
96  		input("Fuzzy-8", "text/1+2", "['text/3+2+1','text/1','text/2+1']", 2, 2),
97  		input("Fuzzy-9", "text/1+2+3+4", "['text/1+2','text/1+2+3']", -1, -1),
98  		input("Fuzzy-10", "text/1+2+3+4", "['text/1+2+3','text/1+2']", -1, -1),
99  		input("Fuzzy-11", "text/4+2+3+1", "['text/1+2+3','text/1+2']", -1, -1),
100 		input("Fuzzy-12", "text/4+2+3+1", "['text/1+2','text/1+2+3']", -1, -1),
101 
102 		// Q metrics
103 		input("Q-1", "text/A;q=0.9,text/B;q=0.1", "['text/A','text/B']", 0, 0),
104 		input("Q-2", "text/A;q=0.9,text/B;q=0.1", "['text/B','text/A']", 1, 1),
105 		input("Q-3", "text/A+1;q=0.9,text/B;q=0.1", "['text/A','text/B']", 1, 1),
106 		input("Q-4", "text/A;q=0.9,text/B+1;q=0.1", "['text/A','text/B+1']", 0, 0),
107 		input("Q-5", "text/A;q=0.9,text/A+1;q=0.1", "['text/A+1','text/A']", 1, 1),
108 
109 		// Test q=0
110 		input("Q0-1", "text/A;q=0,text/B;q=0.1", "['text/A','text/B']", 1, 1),
111 		input("Q0-2", "text/A;q=0,text/B;q=0.1", "['text/A','text/A+1']", -1, -1),
112 
113 		// Test media types with parameters
114 		input("Parms-1", "text/A", "['text/A;foo=bar','text/B']", 0, 0),
115 		input("Parms-2", "text/A;foo=bar", "['text/A','text/B']", 0, 0),
116 
117 		// Real-world JSON
118 		input("Json-1a", "text/json", "['text/json','text/json+*','text/*','text/json+lax','text/json+lax+*','text/foo']", 0, 0),
119 		input("Json-1b", "text/json", "['text/json+*','text/*','text/json+lax','text/json+lax+*','text/foo','text/json']", 5, 5),
120 		input("Json-1c", "text/json", "['text/json+*','text/*','text/json+lax','text/json+lax+*','text/foo']", 0, 0),
121 		input("Json-1d", "text/json", "['text/*','text/json+lax','text/json+lax+*','text/foo']", 0, 0),
122 		input("Json-1e", "text/json", "['text/json+lax','text/json+lax+*','text/foo']", -1, -1),
123 
124 		input("Json-2a", "text/json+lax", "['text/json+lax','text/json+lax+*','text/json+*','text/lax+*','text/*','text/json','text/lax']", 0, 0),
125 		input("Json-2b", "text/json+lax", "['text/json+lax+*','text/json+*','text/lax+*','text/*','text/json','text/lax']", 0, 0),
126 		input("Json-2c", "text/json+lax", "['text/json+*','text/lax+foo+*','text/*','text/json','text/lax']", 0, 0),
127 		input("Json-2d", "text/json+lax", "['text/lax+*','text/*','text/json','text/lax']", 0, 0),
128 		input("Json-2e", "text/json+lax", "['text/*','text/json','text/lax']", 0, 0),
129 		input("Json-2f", "text/json+lax", "['text/json','text/lax']", -1, -1),
130 
131 		input("Json-3a", "text/json+activity", "['text/json+activity','text/activity+json','text/json+activity+*','text/json+*','text/*','text/json','text/json+lax','text/json+lax+*','text/foo']", 0, 0),
132 		input("Json-3b", "text/json+activity", "['text/activity+json','text/json+activity+*','text/json+*','text/*','text/json','text/json+lax','text/json+lax+*','text/foo']", 0, 0),
133 		input("Json-3c", "text/json+activity", "['text/json+activity+*','text/json+*','text/*','text/json','text/json+lax','text/json+lax+*','text/foo']", 0, 0),
134 		input("Json-3d", "text/json+activity", "['text/json+*','text/*','text/json','text/json+lax','text/json+lax+*','text/foo']", 0, 0),
135 		input("Json-3e", "text/json+activity", "['text/*','text/json','text/json+lax','text/json+lax+*','text/foo']", 0, 0),
136 		input("Json-3f", "text/json+activity", "['text/json','text/json+lax','text/json+lax+*','text/foo']", -1, -1),
137 
138 		// Real-world XML
139 		input("Xml-1a", "text/xml", "['text/xml','text/xml+*','text/xml+rdf','text/foo']", 0, 0),
140 		input("Xml-1b", "text/xml", "['text/xml+*','text/xml+rdf','text/foo']", 0, 0),
141 		input("Xml-1c", "text/xml", "['text/xml+rdf','text/foo']", -1, -1),
142 		input("Xml-1d", "text/xml", "['text/foo']", -1, -1),
143 
144 		input("Xml-2a", "text/xml+id", "['text/xml+*','text/xml','text/xml+rdf']", 0, 0),
145 		input("Xml-2b", "text/xml+id", "['text/xml','text/xml+rdf']", -1, -1),
146 		input("Xml-2c", "text/xml+id", "['text/xml+rdf']", -1, -1),
147 
148 		// Real-world RDF
149 		input("Rdf-1a", "text/xml+rdf", "['text/xml+rdf','text/xml+*','text/xml']", 0, 0),
150 		input("Rdf-1b", "text/xml+rdf", "['text/xml+*','text/xml']", 0, 0),
151 		input("Rdf-1c", "text/xml+rdf", "['text/xml']", -1, -1)
152 	};
153 
154 	private static Input input(String label, String accept, String mediaTypes, int expected, int expectedReverse) {
155 		return new Input(label, accept, mediaTypes, expected, expectedReverse);
156 	}
157 
158 	private static class Input {
159 		private String label, accept, mediaTypes;
160 		private int expected, expectedReverse;
161 
162 		public Input(String label, String accept, String mediaTypes, int expected, int expectedReverse) {
163 			this.label = label;
164 			this.accept = accept;
165 			this.mediaTypes = mediaTypes;
166 			this.expected = expected;
167 			this.expectedReverse = expectedReverse;
168 		}
169 	}
170 
171 	static Input[] input() {
172 		return INPUT;
173 	}
174 
175 	@ParameterizedTest
176 	@MethodSource("input")
177 	void a01_basic(Input input) throws Exception {
178 		var a = accept(input.accept);
179 		var mt = JsonParser.DEFAULT.parse(input.mediaTypes, MediaType[].class);
180 		var r = a.match(l(mt));
181 		assertEquals(input.expected, r, fs("{0} failed", input.label));
182 	}
183 
184 	@ParameterizedTest
185 	@MethodSource("input")
186 	void a02_reversed(Input input) throws Exception {
187 		var a = accept(input.accept);
188 		var mt = JsonParser.DEFAULT.parse(input.mediaTypes, MediaType[].class);
189 		Collections.reverse(l(mt));
190 		var r = a.match(l(mt));
191 		var expected2 = input.expectedReverse == -1 ? -1 : mt.length-input.expectedReverse-1;
192 		assertEquals(expected2, r);
193 	}
194 }