package pl.matisoft.soy;
import static java.util.Arrays.asList;
import static java.util.Collections.list;
import static java.util.Collections.unmodifiableList;
import static java.util.regex.Pattern.quote;
import static org.springframework.util.Assert.isInstanceOf;
import static org.springframework.util.CollectionUtils.isEmpty;
import static org.springframework.web.context.request.RequestContextHolder.getRequestAttributes;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.ServletRequestAttributes;
/**
* This class is responsible for determining whether or not it can support the requested content types.
*
* @author Abhijit Sarkar
*/
public class DefaultContentNegotiator implements ContentNegotiator {
private static final Logger logger = LoggerFactory.getLogger(DefaultContentNegotiator.class);
public static final String DEFAULT_FAVORED_PARAMETER_NAME = "format";
public static final List<String> DEFAULT_SUPPORTED_CONTENT_TYPES = unmodifiableList(asList("text/html"));
public static final String ACCEPT_HEADER = "Accept";
private static final String CONTENT_TYPE_DELIMITER = ",\\s*";
private List<String> supportedContentTypes;
private boolean favorParameterOverAcceptHeader;
private String favoredParameterName;
private Comparator<List<String>> contentTypeComparator;
public DefaultContentNegotiator() {
supportedContentTypes = DEFAULT_SUPPORTED_CONTENT_TYPES;
favorParameterOverAcceptHeader = false;
favoredParameterName = DEFAULT_FAVORED_PARAMETER_NAME;
contentTypeComparator = new DefaultContentTypeComparator();
}
public List<String> getSupportedContentTypes() {
return supportedContentTypes;
}
public void setSupportedContentTypes(final List<String> supportedContentTypes) {
this.supportedContentTypes = supportedContentTypes;
}
public boolean isFavorParameterOverAcceptHeader() {
return favorParameterOverAcceptHeader;
}
public void setFavorParameterOverAcceptHeader(final boolean favorParameterOverAcceptHeader) {
this.favorParameterOverAcceptHeader = favorParameterOverAcceptHeader;
}
public String getFavoredParameterName() {
return favoredParameterName;
}
public void setFavoredParameterName(final String parameterName) {
this.favoredParameterName = parameterName;
}
public Comparator<List<String>> getContentTypeComparator() {
return contentTypeComparator;
}
public void setContentTypeComparator(final Comparator<List<String>> contentTypeComparator) {
this.contentTypeComparator = contentTypeComparator;
}
/**
* Returns true if able to support the requested content types; false otherwise.
*
* @return True if able to support the requested content types; false otherwise.
*/
@Override
public boolean isSupportedContentTypes(final List<String> contentTypes) {
return contentTypeComparator.compare(supportedContentTypes, contentTypes) == 0;
}
/**
* Returns requested content types or default content type if none found.
*
* @return Requested content types or default content type if none found.
*/
@Override
public List<String> contentTypes() {
List<String> contentTypes = null;
final HttpServletRequest request = getHttpRequest();
if (favorParameterOverAcceptHeader) {
contentTypes = getFavoredParameterValueAsList(request);
} else {
contentTypes = getAcceptHeaderValues(request);
}
if (isEmpty(contentTypes)) {
logger.debug("Setting content types to default: {}.", DEFAULT_SUPPORTED_CONTENT_TYPES);
contentTypes = DEFAULT_SUPPORTED_CONTENT_TYPES;
}
return unmodifiableList(contentTypes);
}
private HttpServletRequest getHttpRequest() {
final RequestAttributes attrs = getRequestAttributes();
isInstanceOf(ServletRequestAttributes.class, attrs);
return ((ServletRequestAttributes) attrs).getRequest();
}
private List<String> getFavoredParameterValueAsList(HttpServletRequest request) {
logger.debug("Retrieving content type from favored parameter: {}.", favoredParameterName);
final String favoredParameterValue = request.getParameter(favoredParameterName);
return favoredParameterValue != null ? splitContentTypes(asList(favoredParameterValue)) : null;
}
@SuppressWarnings("unchecked")
private List<String> getAcceptHeaderValues(HttpServletRequest request) {
logger.debug("Retrieving content type from header: {}.", ACCEPT_HEADER);
return splitContentTypes(list(request.getHeaders(ACCEPT_HEADER)));
}
private List<String> splitContentTypes(final List<String> contentTypes) {
final List<String> splitContentTypes = new ArrayList<String>();
for (String aContentType : contentTypes) {
splitContentTypes.addAll(asList(aContentType.split(CONTENT_TYPE_DELIMITER)));
}
logger.debug("Split content types {} into {}.", contentTypes, splitContentTypes);
return splitContentTypes;
}
private static final class DefaultContentTypeComparator implements Comparator<List<String>> {
private static final int NOT_EQUAL = 1;
private static final int EQUAL = 0;
private static final String ASTERISK = "*";
private static final String PERIOD_PLUS_ASTERISK = ".*";
@Override
public int compare(List<String> supportedContentTypes, List<String> contentTypes) {
logger.debug("Comparing supported content types with request content types: {}, {}.",
supportedContentTypes, contentTypes);
if (isEmpty(contentTypes)) {
return NOT_EQUAL;
}
for (String aContentType : contentTypes) {
for (String aSupportedContentType : supportedContentTypes) {
if (aSupportedContentType.matches(convertToRegex(aContentType))) {
return EQUAL;
}
}
}
return NOT_EQUAL;
}
private String convertToRegex(final String aContentType) {
return aContentType.trim().replaceAll(quote(ASTERISK), PERIOD_PLUS_ASTERISK);
}
}
}