org.glassfish.jersey.message.internal.VariantSelector Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jaxrs-ri Show documentation
Show all versions of jaxrs-ri Show documentation
A bundle project producing JAX-RS RI bundles. The primary artifact is an "all-in-one" OSGi-fied JAX-RS RI bundle
(jaxrs-ri.jar).
Attached to that are two compressed JAX-RS RI archives. The first archive (jaxrs-ri.zip) consists of binary RI bits and
contains the API jar (under "api" directory), RI libraries (under "lib" directory) as well as all external
RI dependencies (under "ext" directory). The secondary archive (jaxrs-ri-src.zip) contains buildable JAX-RS RI source
bundle and contains the API jar (under "api" directory), RI sources (under "src" directory) as well as all external
RI dependencies (under "ext" directory). The second archive also contains "build.xml" ANT script that builds the RI
sources. To build the JAX-RS RI simply unzip the archive, cd to the created jaxrs-ri directory and invoke "ant" from
the command line.
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2011-2014 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* http://glassfish.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package org.glassfish.jersey.message.internal;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Variant;
import org.glassfish.jersey.internal.util.collection.Ref;
import jersey.repackaged.com.google.common.base.Function;
import jersey.repackaged.com.google.common.collect.Lists;
/**
* Utility for selecting variant that best matches request from a list of variants.
*
* @author Paul Sandoz
* @author Marek Potociar (marek.potociar at oracle.com)
*/
public final class VariantSelector {
private VariantSelector() {
}
/**
* Interface to get a dimension value from a variant and check if an
* acceptable dimension value is compatible with a dimension value.
*/
private static interface DimensionChecker {
/**
* Get the dimension value from the variant.
*
* @param v the variant.
* @return the dimension value.
*/
U getDimension(VariantHolder v);
/**
* Get the quality source of the dimension.
*
* @return quality source.
*/
int getQualitySource(VariantHolder v, U u);
/**
* Ascertain if the acceptable dimension value is compatible with
* the dimension value.
*
* @param t the acceptable dimension value.
* @param u the dimension value.
* @return {@code true} if the acceptable dimension value is compatible with
* the dimension value.
*/
boolean isCompatible(T t, U u);
/**
* Get the value of the Vary header.
*
* @return the value of the Vary header.
*/
String getVaryHeaderValue();
}
private static final DimensionChecker MEDIA_TYPE_DC =
new DimensionChecker() {
@Override
public MediaType getDimension(VariantHolder v) {
return v.v.getMediaType();
}
@Override
public boolean isCompatible(AcceptableMediaType t, MediaType u) {
return t.isCompatible(u);
}
@Override
public int getQualitySource(VariantHolder v, MediaType u) {
return v.mediaTypeQs;
}
@Override
public String getVaryHeaderValue() {
return HttpHeaders.ACCEPT;
}
};
private static final DimensionChecker LANGUAGE_TAG_DC =
new DimensionChecker() {
@Override
public Locale getDimension(VariantHolder v) {
return v.v.getLanguage();
}
@Override
public boolean isCompatible(AcceptableLanguageTag t, Locale u) {
return t.isCompatible(u);
}
@Override
public int getQualitySource(VariantHolder qsv, Locale u) {
return Quality.MINIMUM_QUALITY;
}
@Override
public String getVaryHeaderValue() {
return HttpHeaders.ACCEPT_LANGUAGE;
}
};
private static final DimensionChecker CHARSET_DC =
new DimensionChecker() {
@Override
public String getDimension(VariantHolder v) {
MediaType m = v.v.getMediaType();
return (m != null) ? m.getParameters().get("charset") : null;
}
@Override
public boolean isCompatible(AcceptableToken t, String u) {
return t.isCompatible(u);
}
@Override
public int getQualitySource(VariantHolder qsv, String u) {
return Quality.MINIMUM_QUALITY;
}
@Override
public String getVaryHeaderValue() {
return HttpHeaders.ACCEPT_CHARSET;
}
};
private static final DimensionChecker ENCODING_DC =
new DimensionChecker() {
@Override
public String getDimension(VariantHolder v) {
return v.v.getEncoding();
}
@Override
public boolean isCompatible(AcceptableToken t, String u) {
return t.isCompatible(u);
}
@Override
public int getQualitySource(VariantHolder qsv, String u) {
return Quality.MINIMUM_QUALITY;
}
@Override
public String getVaryHeaderValue() {
return HttpHeaders.ACCEPT_ENCODING;
}
};
/**
* Select variants for a given dimension.
*
* @param variantHolders collection of variants.
* @param acceptableValues the list of acceptable dimension values, ordered by the quality
* parameter, with the highest quality dimension value occurring
* first.
* @param dimensionChecker the dimension checker
* @param vary output list of generated vary headers.
*/
private static LinkedList selectVariants(
List variantHolders,
List acceptableValues,
DimensionChecker dimensionChecker,
Set vary) {
int cq = Quality.MINIMUM_QUALITY;
int cqs = Quality.MINIMUM_QUALITY;
final LinkedList selected = new LinkedList();
// Iterate over the acceptable entries
// This assumes the entries are ordered by the quality
for (final T a : acceptableValues) {
final int q = a.getQuality();
final Iterator iv = variantHolders.iterator();
while (iv.hasNext()) {
final VariantHolder v = iv.next();
// Get the dimension value of the variant to check
final U d = dimensionChecker.getDimension(v);
if (d != null) {
vary.add(dimensionChecker.getVaryHeaderValue());
// Check if the acceptable entry is compatable with
// the dimension value
final int qs = dimensionChecker.getQualitySource(v, d);
if (qs >= cqs && dimensionChecker.isCompatible(a, d)) {
if (qs > cqs) {
cqs = qs;
cq = q;
// Remove all entries that were added for qs < cqs
selected.clear();
selected.add(v);
} else if (q > cq) {
cq = q;
// Add variant with higher accept quality at the front
selected.addFirst(v);
} else if (q == cq) {
// Ensure selection is stable with order of variants
// with same quality of source and accept quality
selected.add(v);
}
iv.remove();
}
}
}
}
// Add all variants that are not compatible with this dimension
// to the end
for (VariantHolder v : variantHolders) {
if (dimensionChecker.getDimension(v) == null) {
selected.add(v);
}
}
return selected;
}
private static class VariantHolder {
private final Variant v;
private final int mediaTypeQs;
public VariantHolder(Variant v) {
this(v, Quality.DEFAULT_QUALITY);
}
public VariantHolder(Variant v, int mediaTypeQs) {
this.v = v;
this.mediaTypeQs = mediaTypeQs;
}
}
private static LinkedList getVariantHolderList(final List variants) {
final LinkedList l = new LinkedList();
for (Variant v : variants) {
final MediaType mt = v.getMediaType();
if (mt != null) {
if (mt instanceof QualitySourceMediaType || mt.getParameters().
containsKey(QualitySourceMediaType.QUALITY_SOURCE_PARAMETER_NAME)) {
int qs = QualitySourceMediaType.getQualitySource(mt);
l.add(new VariantHolder(v, qs));
} else {
l.add(new VariantHolder(v));
}
} else {
l.add(new VariantHolder(v));
}
}
return l;
}
/**
* Select the representation variant that best matches the request. More explicit
* variants are chosen ahead of less explicit ones.
*
* @param context inbound message context.
* @param variants list of possible variants.
* @param varyHeaderValue an output reference of vary header value that should be put
* into the response Vary header.
* @return selected variant.
*/
public static Variant selectVariant(InboundMessageContext context, List variants, Ref varyHeaderValue) {
final List selectedVariants = selectVariants(context, variants, varyHeaderValue);
return selectedVariants.isEmpty() ? null : selectedVariants.get(0);
}
/**
* Select possible representation variants in order in which they best matches the request.
*
* @param context inbound message context.
* @param variants list of possible variants.
* @param varyHeaderValue an output reference of vary header value that should be put into the response Vary header.
* @return possible variants.
*/
public static List selectVariants(InboundMessageContext context, List variants, Ref varyHeaderValue) {
LinkedList vhs = getVariantHolderList(variants);
Set vary = new HashSet();
vhs = selectVariants(vhs, context.getQualifiedAcceptableMediaTypes(), MEDIA_TYPE_DC, vary);
vhs = selectVariants(vhs, context.getQualifiedAcceptableLanguages(), LANGUAGE_TAG_DC, vary);
vhs = selectVariants(vhs, context.getQualifiedAcceptCharset(), CHARSET_DC, vary);
vhs = selectVariants(vhs, context.getQualifiedAcceptEncoding(), ENCODING_DC, vary);
if (vhs.isEmpty()) {
return Collections.emptyList();
} else {
StringBuilder varyHeader = new StringBuilder();
for (String v : vary) {
if (varyHeader.length() > 0) {
varyHeader.append(',');
}
varyHeader.append(v);
}
String varyValue = varyHeader.toString();
if (!varyValue.isEmpty()) {
varyHeaderValue.set(varyValue);
}
return Lists.transform(vhs, new Function() {
@Override
public Variant apply(final VariantHolder holder) {
return holder.v;
}
});
}
}
}