All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.glassfish.jersey.server.filter.UriConnegFilter Maven / Gradle / Ivy

/*
 * Copyright (c) 2010, 2020 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0, which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * This Source Code may also be made available under the following Secondary
 * Licenses when the conditions for such availability set forth in the
 * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
 * version 2 with the GNU Classpath Exception, which is available at
 * https://www.gnu.org/software/classpath/license.html.
 *
 * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
 */

package org.glassfish.jersey.server.filter;

import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import jakarta.ws.rs.Priorities;
import jakarta.ws.rs.container.ContainerRequestContext;
import jakarta.ws.rs.container.ContainerRequestFilter;
import jakarta.ws.rs.container.PreMatching;
import jakarta.ws.rs.core.Configuration;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.HttpHeaders;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.PathSegment;
import jakarta.ws.rs.core.UriInfo;

import jakarta.annotation.Priority;

import org.glassfish.jersey.message.internal.LanguageTag;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.server.ServerProperties;
import org.glassfish.jersey.server.internal.LocalizationMessages;
import org.glassfish.jersey.uri.UriComponent;

/**
 * A URI-based content negotiation filter mapping a dot-declared suffix in
 * URI to media type that is the value of the Accept header
 * or a language that is the value of the Accept-Language header.
 * 

* This filter may be used when the acceptable media type and acceptable * language need to be declared in the URI. *

* This class may be extended to declare the mappings and the extending class, * foo.MyUriConnegFilter say, can be registered as a container request * filter. *

* If a suffix of "atom" is registered with a media type of * "application/atom+xml" then a GET request of: *

GET /resource.atom
*

is transformed to:

*
GET /resource
 * Accept: application/atom+xml
* Any existing "Accept" header value will be replaced. *

* If a suffix of "english" is registered with a language of * "en" then a GET request of: *

GET /resource.english
*

is transformed to:

*
GET /resource
 * Accept-Language: en
* Any existing "Accept-Language"header value will be replaced. *

* The media type mappings are processed before the language type mappings. * * @author Paul Sandoz * @author Martin Matula */ @PreMatching @Priority(Priorities.HEADER_DECORATOR) public final class UriConnegFilter implements ContainerRequestFilter { protected final Map mediaTypeMappings; protected final Map languageMappings; /** * Create a filter that reads the configuration (media type and language mappings) * from the provided {@link ResourceConfig} instance. * This constructor will be called by the Jersey runtime when the filter * class is returned from {@link jakarta.ws.rs.core.Application#getClasses()}. * The {@link ResourceConfig} instance will get auto-injected. * * @param rc ResourceConfig instance that holds the configuration for the filter. */ public UriConnegFilter(@Context final Configuration rc) { this(extractMediaTypeMappings(rc.getProperty(ServerProperties.MEDIA_TYPE_MAPPINGS)), extractLanguageMappings(rc.getProperty(ServerProperties.LANGUAGE_MAPPINGS))); } /** * Create a filter with suffix to media type mappings and suffix to * language mappings. * * @param mediaTypeMappings the suffix to media type mappings. * @param languageMappings the suffix to language mappings. */ public UriConnegFilter(Map mediaTypeMappings, Map languageMappings) { if (mediaTypeMappings == null) { mediaTypeMappings = Collections.emptyMap(); } if (languageMappings == null) { languageMappings = Collections.emptyMap(); } this.mediaTypeMappings = mediaTypeMappings; this.languageMappings = languageMappings; } @Override public void filter(final ContainerRequestContext rc) throws IOException { final UriInfo uriInfo = rc.getUriInfo(); // Quick check for a '.' character String path = uriInfo.getRequestUri().getRawPath(); if (path.indexOf('.') == -1) { return; } final List l = uriInfo.getPathSegments(false); if (l.isEmpty()) { return; } // Get the last non-empty path segment PathSegment segment = null; for (int i = l.size() - 1; i >= 0; i--) { segment = l.get(i); if (segment.getPath().length() > 0) { break; } } if (segment == null) { return; } final int length = path.length(); // Get the suffixes final String[] suffixes = segment.getPath().split("\\."); for (int i = suffixes.length - 1; i >= 1; i--) { final String suffix = suffixes[i]; if (suffix.length() == 0) { continue; } final MediaType accept = mediaTypeMappings.get(suffix); if (accept != null) { rc.getHeaders().putSingle(HttpHeaders.ACCEPT, accept.toString()); final int index = path.lastIndexOf('.' + suffix); path = new StringBuilder(path).delete(index, index + suffix.length() + 1).toString(); suffixes[i] = ""; break; } } for (int i = suffixes.length - 1; i >= 1; i--) { final String suffix = suffixes[i]; if (suffix.length() == 0) { continue; } final String acceptLanguage = languageMappings.get(suffix); if (acceptLanguage != null) { rc.getHeaders().putSingle(HttpHeaders.ACCEPT_LANGUAGE, acceptLanguage); final int index = path.lastIndexOf('.' + suffix); path = new StringBuilder(path).delete(index, index + suffix.length() + 1).toString(); suffixes[i] = ""; break; } } if (length != path.length()) { rc.setRequestUri(uriInfo.getRequestUriBuilder().replacePath(path).build()); } } private static interface TypeParser { public T valueOf(String s); } private static Map extractMediaTypeMappings(final Object mappings) { // parse and validate mediaTypeMappings set through MEDIA_TYPE_MAPPINGS property return parseAndValidateMappings(ServerProperties.MEDIA_TYPE_MAPPINGS, mappings, new TypeParser() { public MediaType valueOf(final String value) { return MediaType.valueOf(value); } }); } private static Map extractLanguageMappings(final Object mappings) { // parse and validate languageMappings set through LANGUAGE_MAPPINGS property return parseAndValidateMappings(ServerProperties.LANGUAGE_MAPPINGS, mappings, new TypeParser() { public String valueOf(final String value) { return LanguageTag.valueOf(value).toString(); } }); } private static Map parseAndValidateMappings(final String property, final Object mappings, final TypeParser parser) { if (mappings == null) { return Collections.emptyMap(); } if (mappings instanceof Map) { return (Map) mappings; } final HashMap mappingsMap = new HashMap<>(); if (mappings instanceof String) { parseMappings(property, (String) mappings, mappingsMap, parser); } else if (mappings instanceof String[]) { final String[] mappingsArray = (String[]) mappings; for (final String aMappingsArray : mappingsArray) { parseMappings(property, aMappingsArray, mappingsMap, parser); } } else { throw new IllegalArgumentException(LocalizationMessages.INVALID_MAPPING_TYPE(property)); } encodeKeys(mappingsMap); return mappingsMap; } private static void parseMappings(final String property, final String mappings, final Map mappingsMap, final TypeParser parser) { if (mappings == null) { return; } final String[] records = mappings.split(","); for (final String record : records) { final String[] mapping = record.split(":"); if (mapping.length != 2) { throw new IllegalArgumentException(LocalizationMessages.INVALID_MAPPING_FORMAT(property, mappings)); } final String trimmedSegment = mapping[0].trim(); final String trimmedValue = mapping[1].trim(); if (trimmedSegment.length() == 0) { throw new IllegalArgumentException(LocalizationMessages.INVALID_MAPPING_KEY_EMPTY(property, record)); } if (trimmedValue.length() == 0) { throw new IllegalArgumentException(LocalizationMessages.INVALID_MAPPING_VALUE_EMPTY(property, record)); } mappingsMap.put(trimmedSegment, parser.valueOf(trimmedValue)); } } private static void encodeKeys(final Map map) { final Map tempMap = new HashMap<>(); for (final Map.Entry entry : map.entrySet()) { tempMap.put(UriComponent.contextualEncode(entry.getKey(), UriComponent.Type.PATH_SEGMENT), entry.getValue()); } map.clear(); map.putAll(tempMap); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy