/***
* Copyright (c) 2009 Caelum - www.caelum.com.br/opensource
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package br.com.caelum.vraptor.view;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import br.com.caelum.vraptor.cache.CacheStore;
import br.com.caelum.vraptor.cache.LRU;
import com.google.common.base.Function;
import com.google.common.base.Supplier;
import com.google.common.collect.FluentIterable;
/**
* The default AcceptHeaderToFormat implementation searches for registered mime types. It also
* handles conneg with extended media types (i.e. vnd+xml)
*
* @author Sérgio Lopes
* @author Jonas Abreu
* @author Guilherme Silveira
*/
@ApplicationScoped
public class DefaultAcceptHeaderToFormat implements AcceptHeaderToFormat {
private final CacheStore<String, String> acceptToFormatCache;
private static final String DEFAULT_FORMAT = "html";
private static final double DEFAULT_QUALIFIER_VALUE = 0.01;
protected final Map<String, String> mimeToFormat;
/**
* @deprecated CDI eyes only
*/
protected DefaultAcceptHeaderToFormat() {
this(null);
}
@Inject
public DefaultAcceptHeaderToFormat(@LRU CacheStore<String, String> acceptToFormatCache) {
this.acceptToFormatCache = acceptToFormatCache;
mimeToFormat = new ConcurrentHashMap<>();
mimeToFormat.put("text/html", "html");
mimeToFormat.put("application/json", "json");
mimeToFormat.put("application/xml", "xml");
mimeToFormat.put("text/xml", "xml");
mimeToFormat.put("xml", "xml");
}
@Override
public String getFormat(final String acceptHeader) {
if (acceptHeader == null || acceptHeader.trim().equals("")) {
return DEFAULT_FORMAT;
}
if (acceptHeader.contains(DEFAULT_FORMAT)) {
// HACK! Opera may send "application/json, text/html, */*" and this should return html.
return DEFAULT_FORMAT;
}
return acceptToFormatCache.fetch(acceptHeader, new Supplier<String>() {
@Override
public String get() {
return chooseMimeType(acceptHeader);
}
});
}
private String chooseMimeType(String acceptHeader) {
String[] mimeTypes = getOrderedMimeTypes(acceptHeader);
for (String mimeType : mimeTypes) {
if (mimeToFormat.containsKey(mimeType)) {
return mimeToFormat.get(mimeType);
}
}
return mimeTypes[0];
}
private static class MimeType implements Comparable<MimeType> {
private final String type;
private final double qualifier;
public MimeType(String type, double qualifier) {
this.type = type;
this.qualifier = qualifier;
}
@Override
public int compareTo(MimeType mime) {
// reverse order
return Double.compare(mime.qualifier, this.qualifier);
}
@Override
public boolean equals(Object obj) {
if (obj == null || getClass() != obj.getClass()) {
return false;
}
MimeType other = (MimeType) obj;
return Objects.equals(type, other.type) && Objects.equals(qualifier, other.qualifier);
}
@Override
public int hashCode() {
return Objects.hash(type, qualifier);
}
public String getType() {
return type;
}
@Override
public String toString() {
return type;
}
}
String[] getOrderedMimeTypes(String acceptHeader) {
String[] types = acceptHeader.split(",");
if (types.length == 0) {
return new String[] { types[0].split(";")[0] };
}
Set<MimeType> mimes = new TreeSet<>();
for (String string : types) {
mimes.add(convertToMimeType(string));
}
return FluentIterable.from(mimes)
.transform(mimeType())
.toArray(String.class);
}
private Function<MimeType, String> mimeType() {
return new Function<MimeType, String>() {
@Override
public String apply(MimeType mime) {
return mime.getType().trim();
}
};
}
private MimeType convertToMimeType(String string) {
if (string.contains("*/*")) {
return new MimeType("text/html", DEFAULT_QUALIFIER_VALUE);
} else if (string.contains(";")) {
String type = string.substring(0, string.indexOf(';'));
return new MimeType(type, extractQualifier(string));
}
return new MimeType(string, 1);
}
private static double extractQualifier(String string) {
double qualifier = DEFAULT_QUALIFIER_VALUE;
if (string.contains("q=")) {
Matcher matcher = Pattern.compile("\\s*q=(.+)\\s*").matcher(string);
matcher.find();
String value = matcher.group(1);
qualifier = Double.parseDouble(value);
}
return qualifier;
}
}