/**
* Copyright 2005-2014 Restlet
*
* The contents of this file are subject to the terms of one of the following
* open source licenses: Apache 2.0 or or EPL 1.0 (the "Licenses"). You can
* select the license that you prefer but you may not use this file except in
* compliance with one of these Licenses.
*
* You can obtain a copy of the Apache 2.0 license at
* http://www.opensource.org/licenses/apache-2.0
*
* You can obtain a copy of the EPL 1.0 license at
* http://www.opensource.org/licenses/eclipse-1.0
*
* See the Licenses for the specific language governing permissions and
* limitations under the Licenses.
*
* Alternatively, you can obtain a royalty free commercial license with less
* limitations, transferable or non-transferable, directly at
* http://restlet.com/products/restlet-framework
*
* Restlet is a registered trademark of Restlet S.A.S.
*/
package org.restlet.engine.application;
import java.util.ArrayList;
import java.util.List;
import org.restlet.Request;
import org.restlet.data.CharacterSet;
import org.restlet.data.ClientInfo;
import org.restlet.data.Encoding;
import org.restlet.data.Language;
import org.restlet.data.MediaType;
import org.restlet.data.Metadata;
import org.restlet.data.Preference;
import org.restlet.service.MetadataService;
/**
* Content negotiation algorithm that flexibly interprets the content
* negotiation preferences to try to always return a variant even if the client
* preferences don't exactly match.
*
* @author Jerome Louvel
*/
public class FlexibleConneg extends StrictConneg {
/** The enriched list of character set preferences. */
private volatile List<Preference<CharacterSet>> characterSetPrefs;
/** The enriched list of encoding preferences. */
private volatile List<Preference<Encoding>> encodingPrefs;
/** The enriched list of language preferences. */
private volatile List<Preference<Language>> languagePrefs;
/** The enriched list of media type preferences. */
private volatile List<Preference<MediaType>> mediaTypePrefs;
/**
* Constructor.
*
* @param request
* The request including client preferences.
* @param metadataService
* The metadata service used to get default metadata values.
*/
public FlexibleConneg(Request request, MetadataService metadataService) {
super(request, metadataService);
ClientInfo clientInfo = request.getClientInfo();
if (clientInfo != null) {
// Get the enriched user preferences
this.languagePrefs = getEnrichedPreferences(
clientInfo.getAcceptedLanguages(),
(metadataService == null) ? null : metadataService
.getDefaultLanguage(), Language.ALL);
this.mediaTypePrefs = getEnrichedPreferences(
clientInfo.getAcceptedMediaTypes(),
(metadataService == null) ? null : metadataService
.getDefaultMediaType(), MediaType.ALL);
this.characterSetPrefs = getEnrichedPreferences(
clientInfo.getAcceptedCharacterSets(),
(metadataService == null) ? null : metadataService
.getDefaultCharacterSet(), CharacterSet.ALL);
this.encodingPrefs = getEnrichedPreferences(
clientInfo.getAcceptedEncodings(),
(metadataService == null) ? null : metadataService
.getDefaultEncoding(), Encoding.ALL);
}
}
/**
* Returns true if the metadata can be added.
*
* @param <T>
* @param metadata
* The metadata to add.
* @param undesired
* The list of prohibited metadata.
* @return True if the metadata can be added.
*/
protected <T extends Metadata> boolean canAdd(T metadata, List<T> undesired) {
boolean add = true;
if (undesired != null) {
for (T u : undesired) {
if (u.equals(metadata)) {
add = false;
break;
}
}
}
return add;
}
/**
* Returns the enriched list of character set preferences.
*
* @return The enriched list of character set preferences.
*/
protected List<Preference<CharacterSet>> getCharacterSetPrefs() {
return characterSetPrefs;
}
/**
* Returns the enriched list of encoding preferences.
*
* @return The enriched list of encoding preferences.
*/
protected List<Preference<Encoding>> getEncodingPrefs() {
return encodingPrefs;
}
/**
* Returns an enriched list of preferences. Contains the user preferences,
* implied user parent preferences (quality between 0.005 and 0.006),
* default preference (quality of 0.003), default parent preference (quality
* of 0.002), all preference (quality of 0.001).<br>
* <br>
* This necessary to compensate the misconfiguration of many browsers which
* don't expose all the metadata actually understood by end users.
*
* @param <T>
* @param userPreferences
* The user preferences to enrich.
* @param defaultValue
* The default value.
* @param allValue
* The ALL value.
* @return The enriched user preferences.
*/
@SuppressWarnings("unchecked")
protected <T extends Metadata> List<Preference<T>> getEnrichedPreferences(
List<Preference<T>> userPreferences, T defaultValue, T allValue) {
List<Preference<T>> result = new ArrayList<Preference<T>>();
// 0) List all undesired metadata
List<T> undesired = null;
for (Preference<T> pref : userPreferences) {
if (pref.getQuality() == 0) {
if (undesired == null) {
undesired = new ArrayList<T>();
}
undesired.add(pref.getMetadata());
}
}
// 1) Add the user preferences
result.addAll(userPreferences);
// 2) Add the user parent preferences
T parent;
for (int i = 0; i < result.size(); i++) {
Preference<T> userPref = result.get(i);
parent = (T) userPref.getMetadata().getParent();
// Add the parent, if it is not proscribed.
if ((parent != null)) {
if (canAdd(parent, undesired)) {
result.add(new Preference<T>(parent,
0.005f + (0.001f * userPref.getQuality())));
}
}
}
// 3) Add the default preference
if (defaultValue != null && canAdd(defaultValue, undesired)) {
Preference<T> defaultPref = new Preference<T>(defaultValue, 0.003f);
result.add(defaultPref);
T defaultParent = (T) defaultValue.getParent();
if (defaultParent != null && canAdd(defaultParent, undesired)) {
result.add(new Preference<T>(defaultParent, 0.002f));
}
}
// 5) Add "all" preference
for (int i = result.size() - 1; i >= 0; i--) {
// Remove any existing preference
if (result.get(i).getMetadata().equals(allValue)) {
result.remove(i);
}
}
result.add(new Preference<T>(allValue, 0.001f));
// 6) Return the enriched preferences
return result;
}
/**
* Returns the enriched list of language preferences.
*
* @return The enriched list of language preferences.
*/
protected List<Preference<Language>> getLanguagePrefs() {
return languagePrefs;
}
/**
* Returns the enriched list of media type preferences.
*
* @return The enriched list of media type preferences.
*/
protected List<Preference<MediaType>> getMediaTypePrefs() {
return mediaTypePrefs;
}
}