1 package com.atlassian.plugins.rest.module.filter;
2
3 import java.text.ParseException;
4 import java.util.ArrayList;
5 import java.util.Collections;
6 import java.util.Comparator;
7 import java.util.List;
8 import java.util.Locale;
9
10 import javax.ws.rs.WebApplicationException;
11 import javax.ws.rs.core.HttpHeaders;
12 import javax.ws.rs.core.Response;
13 import javax.ws.rs.ext.Provider;
14
15 import com.sun.jersey.core.header.InBoundHeaders;
16 import com.sun.jersey.core.header.LanguageTag;
17 import com.sun.jersey.core.header.QualityFactor;
18 import com.sun.jersey.core.header.reader.HttpHeaderReader;
19 import com.sun.jersey.spi.container.AdaptingContainerRequest;
20 import com.sun.jersey.spi.container.ContainerRequest;
21 import com.sun.jersey.spi.container.ContainerRequestFilter;
22
23
24
25
26
27
28
29
30
31 @Provider
32 public class AcceptLanguageFilter implements ContainerRequestFilter {
33
34 private static final Comparator<QualityFactor> QUALITY_COMPARATOR = new Comparator<QualityFactor>() {
35 public int compare(QualityFactor o1, QualityFactor o2) {
36 return o2.getQuality() - o1.getQuality();
37 }
38 };
39
40 private static final CustomLanguageTag ANY_LANG = new CustomLanguageTag("*", null);
41
42 @Override
43 public ContainerRequest filter(ContainerRequest request) {
44 return new AdaptingContainerRequest(request) {
45
46 private List<Locale> acceptLanguages;
47
48 @Override
49 public void setHeaders(InBoundHeaders headers) {
50
51
52 super.setHeaders(headers);
53 acceptLanguages = null;
54 }
55
56 @Override
57 public List<Locale> getAcceptableLanguages() {
58 if (acceptLanguages == null) {
59 List<CustomLanguageTag> alts = parseAcceptLanguage();
60
61 acceptLanguages = new ArrayList<Locale>(alts.size());
62 for (CustomLanguageTag alt : alts) {
63 acceptLanguages.add(alt.getAsLocale());
64 }
65 }
66
67 return acceptLanguages;
68 }
69
70
71
72 private List<CustomLanguageTag> parseAcceptLanguage() {
73 final String acceptLanguage = getHeaderValue(HttpHeaders.ACCEPT_LANGUAGE);
74 if (acceptLanguage == null || acceptLanguage.length() == 0) {
75 return Collections.singletonList(ANY_LANG);
76 }
77
78 try {
79 List<CustomLanguageTag> result = new ArrayList<>();
80 HttpHeaderReader reader = HttpHeaderReader.newInstance(acceptLanguage);
81 HttpHeaderListAdapter adapter = new HttpHeaderListAdapter(reader);
82 while (reader.hasNext()) {
83 result.add(parserLanguageTag(adapter));
84 adapter.reset();
85
86 if (reader.hasNext()) {
87 reader.next();
88 }
89 }
90
91 Collections.sort(result, QUALITY_COMPARATOR);
92 return result;
93 } catch (java.text.ParseException e) {
94 throw new WebApplicationException(
95 e,
96 Response.status(Response.Status.BAD_REQUEST)
97 .entity("Bad Accept-Language header value: '" + acceptLanguage + "'")
98 .type("text/plain")
99 .build()
100 );
101 }
102 }
103
104
105 private CustomLanguageTag parserLanguageTag(HttpHeaderReader reader) throws ParseException {
106
107 reader.hasNext();
108 String primaryTag = null, subTags = null, languageTag = reader.nextToken();
109
110 if (!languageTag.equals("*")) {
111 if (!isLanguageTagValid(languageTag)) {
112 throw new ParseException("String, " + languageTag + ", is not a valid language tag", 0);
113 }
114
115 final int index = languageTag.indexOf('-');
116 if (index == -1) {
117 primaryTag = languageTag;
118 subTags = null;
119 } else {
120 primaryTag = languageTag.substring(0, index);
121 subTags = languageTag.substring(index + 1, languageTag.length());
122 }
123 } else {
124 primaryTag = languageTag;
125 }
126
127 int quality;
128 if (reader.hasNext()) {
129 quality = HttpHeaderReader.readQualityFactorParameter(reader);
130 } else {
131 quality = CustomLanguageTag.DEFAULT_QUALITY_FACTOR;
132 }
133
134 return new CustomLanguageTag(languageTag, primaryTag, subTags, quality);
135 }
136
137
138 private boolean isLanguageTagValid(String tag) {
139 int alphaCount = 0, parts = 0;
140 for (int i = 0; i < tag.length(); i++) {
141 final char c = tag.charAt(i);
142 if (c == '-') {
143 if (alphaCount == 0) {
144 return false;
145 }
146 alphaCount = 0;
147 parts++;
148 } else if (
149 ('A' <= c && c <= 'Z') ||
150 ('a' <= c && c <= 'z') ||
151 (Character.isDigit(c) && (parts > 0 || alphaCount > 0))
152 ) {
153 alphaCount++;
154 if (alphaCount > 8)
155 return false;
156 } else {
157 return false;
158 }
159 }
160 return (alphaCount != 0);
161 }
162 };
163 }
164
165
166 private static final class CustomLanguageTag implements QualityFactor {
167
168 protected int quality = DEFAULT_QUALITY_FACTOR;
169
170 protected String tag;
171
172 protected String primaryTag;
173
174 protected String subTags;
175
176 public CustomLanguageTag(String primaryTag, String subTags) {
177 this(
178 (subTags != null && subTags.length() > 0 ? primaryTag + "-" + subTags : primaryTag),
179 primaryTag,
180 subTags,
181 DEFAULT_QUALITY_FACTOR
182 );
183 }
184
185 public CustomLanguageTag(String tag, String primaryTag, String subTags, int quality) {
186 this.tag = tag;
187 this.primaryTag = primaryTag;
188 this.subTags = subTags;
189 this.quality = quality;
190 }
191
192 public final Locale getAsLocale() {
193 return (subTags == null)
194 ? new Locale(primaryTag)
195 : new Locale(primaryTag, subTags);
196 }
197
198 @Override
199 public int getQuality() {
200 return quality;
201 }
202
203 @Override
204 public boolean equals(Object object) {
205 if (object instanceof LanguageTag) {
206 LanguageTag lt = (LanguageTag) object;
207
208 if (this.tag != null)
209 if (!this.tag.equals(lt.getTag()))
210 return false;
211 else if (lt.getTag() != null)
212 return false;
213
214 if (this.primaryTag != null)
215 if (!this.primaryTag.equals(lt.getPrimaryTag()))
216 return false;
217 else if (lt.getPrimaryTag() != null)
218 return false;
219
220 if (this.subTags != null)
221 if (!this.subTags.equals(lt.getSubTags()))
222 return false;
223 else if (lt.getSubTags() != null)
224 return false;
225
226 return true;
227 } else {
228 return false;
229 }
230 }
231
232 @Override
233 public int hashCode() {
234 return (tag == null ? 0 : tag.hashCode()) +
235 (primaryTag == null ? 0 : primaryTag.hashCode()) +
236 (subTags == null ? 0 : primaryTag.hashCode());
237 }
238
239 @Override
240 public String toString() {
241 return primaryTag + (subTags == null ? "" : subTags);
242 }
243 }
244 }