com.sun.jersey.api.core.ResourceConfig Maven / Gradle / Ivy
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2010-2011 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 com.sun.jersey.api.core;
import com.sun.jersey.api.container.filter.LoggingFilter;
import com.sun.jersey.api.uri.UriComponent;
import com.sun.jersey.core.header.LanguageTag;
import com.sun.jersey.core.util.FeaturesAndProperties;
import com.sun.jersey.server.wadl.WadlGenerator;
import com.sun.jersey.spi.container.ContainerListener;
import com.sun.jersey.spi.container.ContainerNotifier;
import com.sun.jersey.spi.container.ContainerRequestFilter;
import com.sun.jersey.spi.container.ContainerResponseFilter;
import com.sun.jersey.spi.container.ResourceFilterFactory;
import javax.ws.rs.Path;
import javax.ws.rs.core.Application;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.ext.Provider;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;
/**
* The resource configuration for configuring a web application.
*/
public abstract class ResourceConfig extends Application implements FeaturesAndProperties {
private static final Logger LOGGER =
Logger.getLogger(ResourceConfig.class.getName());
/**
* If true and {@link #FEATURE_CANONICALIZE_URI_PATH} is true then the
* request URI will be normalized as specified by
* {@link java.net.URI#normalize}. If not true the request URI is not
* modified.
*
* The default value is false.
*/
public static final String FEATURE_NORMALIZE_URI
= "com.sun.jersey.config.feature.NormalizeURI";
/**
* If true the request URI path component will be canonicalized by removing
* contiguous slashes (i.e. all /+ will be replaced by /). If not true the
* request URI path component is mot modified.
*
* The default value is false.
*/
public static final String FEATURE_CANONICALIZE_URI_PATH
= "com.sun.jersey.config.feature.CanonicalizeURIPath";
/**
* If true, and {@link #FEATURE_CANONICALIZE_URI_PATH} is true,
* and the canonicalization/normalization operations on the
* request URI result in a new URI that is not equal to the request URI,
* then the client is (temporarily) redirected to the new URI.
*
* If true, and the path value of a {@link javax.ws.rs.Path} annotation ends
* in a slash, the request URI path does not end in a '/' and would otherwise
* match the path value if it did, then the client is (temporarily)
* redirected to a new URI that is the request URI with a '/' appended to the
* the end of the path.
*
* The default value is false.
*/
public static final String FEATURE_REDIRECT
= "com.sun.jersey.config.feature.Redirect";
/**
* If true then matrix parameters (if present) in the request URI path component
* will not be ignored when matching the path to URI templates declared by
* resource classes.
*
* The default value is false.
*/
public static final String FEATURE_MATCH_MATRIX_PARAMS
= "com.sun.jersey.config.feature.IgnoreMatrixParams";
/**
* If true then the matching algorithm will attempt to match and accept
* any static content or templates associated with a resource that were
* not explicitly declared by that resource.
*
* If a template is matched then the model for the viewable will be the
* resource instance associated with the template.
*
* The default value is false.
*/
public static final String FEATURE_IMPLICIT_VIEWABLES
= "com.sun.jersey.config.feature.ImplicitViewables";
/**
* If true then disable WADL generation.
*
* By default WADL generation is automatically enabled, if JAXB is
* present in the classpath.
*
* The default value is false.
*/
public static final String FEATURE_DISABLE_WADL
= "com.sun.jersey.config.feature.DisableWADL";
/**
* If true then enable tracing.
*
* Tracing provides useful information that describes how a request
* is processed and dispatched to JAX-RS/Jersey components. This can aid
* debugging when the application is not behaving as expected either
* because of a bug in the application code or in the Jersey code.
*
* Trace messages will be primarily output as response headers
* with a header name of the form "X-Jersey-Trace-XXX", where XXX is a
* decimal value corresponding to the trace message number, and a header
* value that is the trace message.
*
* In certain cases trace messages will be logged on the server-side if such
* messages are not suitable as response headers, for example
* if such messages are too verbose.
*
* Trace messages will be output in the same order as traces occur.
*
* To log response header trace messages on the server-side enable response
* logging, see {@link LoggingFilter}.
*
* The default value is false.
*/
public static final String FEATURE_TRACE
= "com.sun.jersey.config.feature.Trace";
/**
* If true then enable tracing on a per-request basis.
*
* Tracing provides useful information that describes how a request
* is processed and dispatched to JAX-RS/Jersey components. This can aid
* debugging when the application is not behaving as expected either
* because of a bug in the application code or in the Jersey code.
*
* Trace messages will be output if a request header is present with a
* header name of "X-Jersey-Trace-Accept" (the header value is ignored).
*
* Trace messages will be primarily output as response headers
* with a header name of the form "X-Jersey-Trace-XXX", where XXX is a
* decimal value corresponding to the trace message number, and a header
* value that is the trace message.
*
* In certain cases trace messages will be logged on the server-side if such
* messages are not suitable as response headers, for example
* if such messages are too verbose.
*
* Trace messages will be output in the same order as traces occur.
*
* To log response header trace messages on the server-side enable response
* logging, see {@link LoggingFilter}.
*
* The default value is false.
*/
public static final String FEATURE_TRACE_PER_REQUEST
= "com.sun.jersey.config.feature.TracePerRequest";
/**
* If set the map of file extension to media type mappings that will be
* added to the map that is returned from {@link #getMediaTypeMappings() }.
*
* This type of this property must be a String or String[] that contains
* one or more comma separated key value pairs of the form
* "<key> : <value>, <key> : <value>" where the key
* is the file extension and the value is the media type. For example,
* the following declares two mappings for XML and JSON:
* "xml : application/xml, json : application/json"
*
* The value of this property will be validated, and keys/values added to
* map returned from {@link #getMediaTypeMappings()}, when the
* {@link #validate()} method is invoked.
*
* Validation will verify that media types are valid.
*/
public static final String PROPERTY_MEDIA_TYPE_MAPPINGS
= "com.sun.jersey.config.property.MediaTypeMappings";
/**
* If set the map of file extension to langauge mappings that will be
* added to the map that is returned from {@link #getLanguageMappings() }.
*
* This type of this property must be a String or String[] that contains
* one or more comma separated key value pairs of the form
* "<key> : <value>, <key> : <value>" where the key
* is the file extension and the value is the language tag. For example,
* the following declares two mappings for the languages "en" and "en-US:
* "english : en, american : en-US".
*
* The value of this property will be validated, and keys/values added to
* map returned from {@link #getMediaTypeMappings()}, when the
* {@link #validate()} method is invoked.
*
* Validation will verify that language tags are valid according to HTTP/1.1
* and are of the form:
*
* language-tag = primary-tag *( "-" subtag )
* primary-tag = 1*8ALPHA
* subtag = 1*8ALPHA
*
*/
public static final String PROPERTY_LANGUAGE_MAPPINGS
= "com.sun.jersey.config.property.LanguageMappings";
/**
* If set the default resource component provider factory for the
* life-cycle of resource classes.
*
* The type of this property must be a Class or a String that is a Class name,
* and the Class must a sub-class of
* {@link com.sun.jersey.server.spi.component.ResourceComponentProviderFactory}.
*
* If not set the default resource component provider factory will be the
* per-request resource component provider factory.
*/
public static final String PROPERTY_DEFAULT_RESOURCE_COMPONENT_PROVIDER_FACTORY_CLASS
= "com.sun.jersey.config.property.DefaultResourceComponentProviderFactoryClass";
/**
* If set the instance of {@link ContainerNotifier} to register
* {@link ContainerListener} instances.
*
* If the instance does not implement the {@link ContainerNotifier}
* then the property is ignored.
*/
public static final String PROPERTY_CONTAINER_NOTIFIER =
"com.sun.jersey.spi.container.ContainerNotifier";
/**
* If set the list of {@link ContainerRequestFilter} that are applied
* to filter the request. When applying the list of request filters to
* a request each request filter is applied, in order, from the first to
* the last entry in the list.
*
* The instance may be a String[] or String that contains one or more fully
* qualified class name of a request filter class separated by ';', ','
* or ' ' (space).
* Otherwise the instance may be List containing instances of String,
* String[], Class<? extends ContainerRequestFilter;> or instances
* of ContainerRequestFilter.
*
* If a String[] or String of fully qualified class names or a Class then
* each class is instantiated as a singleton. Thus, if there is more than one
* class registered for this property or the same class is also registered for
* the {@link #PROPERTY_CONTAINER_RESPONSE_FILTERS} property then only
* one instance will be instantiated.
*
* @see com.sun.jersey.api.container.filter
*/
public static final String PROPERTY_CONTAINER_REQUEST_FILTERS =
"com.sun.jersey.spi.container.ContainerRequestFilters";
/**
* If set the list of {@link ContainerResponseFilter} that are applied
* to filter the response. When applying the list of response filters to
* a response each response filter is applied, in order, from the first to
* the last entry in the list.
*
* The instance may be a String[] or String that contains one or more fully
* qualified class name of a request filter class separated by ';', ','
* or ' ' (space).
* Otherwise the instance may be List containing instances of String,
* String[], Class<? extends ContainerResponseFilter;> or instances
* of ContainerResponseFilter.
*
* If a String[] or String of fully qualified class names or a Class then
* each class is instantiated as a singleton. Thus, if there is more than one
* class registered for this property or the same class is also registered for
* the {@link #PROPERTY_CONTAINER_REQUEST_FILTERS} property then only
* one instance will be instantiated.
*
* @see com.sun.jersey.api.container.filter
*/
public static final String PROPERTY_CONTAINER_RESPONSE_FILTERS =
"com.sun.jersey.spi.container.ContainerResponseFilters";
/**
* If set the list of {@link ResourceFilterFactory} that are applied
* to resources. When applying the list of resource filters factories to a
* request each resource filter factory is applied, in order, from the first
* to last entry in the list.
*
* The instance may be a String[] or String that contains one or more fully
* qualified class name of a response filter class separated by ';', ','
* or ' ' (space).
* Otherwise the instance may be List containing instances of String,
* String[], Class<? extends ResourceFilterFactory;> or instances
* of ResourceFilterFactory.
*
* If a String[] or String of fully qualified class names or a Class then
* each class is instantiated as a singleton. Thus, if there is more than one
* class registered for this property one instance will be instantiated.
*
* @see com.sun.jersey.api.container.filter
*/
public static final String PROPERTY_RESOURCE_FILTER_FACTORIES =
"com.sun.jersey.spi.container.ResourceFilters";
/**
* If set the wadl generator configuration that provides a {@link WadlGenerator}.
*
* The type of this property must be a subclass or an instance of a subclass of
* {@link com.sun.jersey.api.wadl.config.WadlGeneratorConfig}.
*
*
* If this property is not set the default wadl generator will be used for generating wadl.
*
*/
public static final String PROPERTY_WADL_GENERATOR_CONFIG =
"com.sun.jersey.config.property.WadlGeneratorConfig";
/**
* Common delimiters used by various properties.
*/
public static final String COMMON_DELIMITERS = " ,;";
/**
* Get the map of features associated with the Web application.
*
* @return the features.
* The returned value shall never be null.
*/
public abstract Map getFeatures();
/**
* Get the value of a feature.
*
* @param featureName the feature name.
* @return true if the feature is present and set to true, otherwise false
* if the feature is present and set to false or the feature is not
* present.
*/
public abstract boolean getFeature(String featureName);
/**
* Get the map of properties associated with the Web application.
*
* @return the properties.
* The returned value shall never be null.
*/
public abstract Map getProperties();
/**
* Get the value of a property.
*
* @param propertyName the property name.
* @return the property, or null if there is no property present for the
* given property name.
*/
public abstract Object getProperty(String propertyName);
/**
* Get a map of file extension to media type. This is used to drive
* URI-based content negotiation such that, e.g.:
* GET /resource.atom
* is equivalent to:
* GET /resource
*Accept: application/atom+xml
*
* The default implementation returns an empty map.
*
* @return a map of file extension to media type
*/
public Map getMediaTypeMappings() {
return Collections.emptyMap();
}
/**
* Get a map of file extension to language. This is used to drive
* URI-based content negotiation such that, e.g.:
* GET /resource.english
* is equivalent to:
* GET /resource
*Accept-Language: en
*
* The default implementation returns an empty map.
*
* @return a map of file extension to language
*/
public Map getLanguageMappings() {
return Collections.emptyMap();
}
/**
* Get a map of explicit root resource classes and root resource singleton
* instances. The default lifecycle for root resource class instances is
* per-request.
*
* The root resource path template is declared using the key in the map. This
* is a substitute for the declaration of a {@link Path} annotation on a root
* resource class or singleton instance. The key has the same semantics as the
* {@link Path#value() }. If such a {@link Path} annotation is present
* it will be ignored.
*
* For example, the following will register two root resources, first
* a root resource class at the path "class" and a root resource singleton
* at the path "singleton":
*
* getExplicitRootResources().put("class", RootResourceClass.class);
* getExplicitRootResources().put("singleton", new RootResourceSingleton());
*
*
* @return a map of explicit root resource classes and root resource
* singleton instances.
*/
public Map getExplicitRootResources() {
return Collections.emptyMap();
}
/**
* Validate the set of classes and singletons.
*
* A registered class is removed from the set of registered classes
* if an instance of that class is a member of the set of registered
* singletons.
*
* A registered class that is an interface or an abstract class
* is removed from the registered classes.
*
* File extension to media type and language mappings in the properties
* {@link #PROPERTY_MEDIA_TYPE_MAPPINGS} and {@link #PROPERTY_LANGUAGE_MAPPINGS},
* respectively, are processed and key/values pairs added to the maps
* returned from {@link #getMediaTypeMappings() } and
* {@link #getLanguageMappings() }, respectively. The characters of file
* extension values will be contextually encoded according to the set of
* valid characters defined for a path segment.
*
* @throws IllegalArgumentException if the set of registered singletons
* contains more than one instance of the same root resource class,
* or validation of media type and language mappings failed.
*/
public void validate() {
// Remove any registered classes if instances exist in registered
// singletons
Iterator> i = getClasses().iterator();
while (i.hasNext()) {
Class c = i.next();
for (Object o : getSingletons()) {
if (c.isInstance(o)) {
i.remove();
LOGGER.log(Level.WARNING,
"Class " + c.getName() +
" is ignored as an instance is registered in the set of singletons");
}
}
}
// Find conflicts
Set> objectClassSet = new HashSet>();
Set> conflictSet = new HashSet>();
for (Object o : getSingletons()) {
if (o.getClass().isAnnotationPresent(Path.class)) {
if (objectClassSet.contains(o.getClass())) {
conflictSet.add(o.getClass());
} else {
objectClassSet.add(o.getClass());
}
}
}
if (!conflictSet.isEmpty()) {
for (Class c : conflictSet) {
LOGGER.log(Level.SEVERE,
"Root resource class " + c.getName() +
" is instantiated more than once in the set of registered singletons");
}
throw new IllegalArgumentException(
"The set of registered singletons contains " +
"more than one instance of the same root resource class");
}
// parse and validate mediaTypeMappings set thru PROPERTY_MEDIA_TYPE_MAPPINGS property
parseAndValidateMappings(ResourceConfig.PROPERTY_MEDIA_TYPE_MAPPINGS,
getMediaTypeMappings(), new TypeParser() {
public MediaType valueOf(String value) {
return MediaType.valueOf(value);
}
});
// parse and validate language mappings set thru PROPERTY_LANGUAGE_MAPPINGS property
parseAndValidateMappings(ResourceConfig.PROPERTY_LANGUAGE_MAPPINGS,
getLanguageMappings(), new TypeParser() {
public String valueOf(String value) {
return LanguageTag.valueOf(value).toString();
}
});
// encode key values of mediaTypeMappings and languageMappings maps
encodeKeys(getMediaTypeMappings());
encodeKeys(getLanguageMappings());
}
private interface TypeParser {
public T valueOf(String s);
}
private void parseAndValidateMappings(String property,
Map mappingsMap, TypeParser parser) {
Object mappings = getProperty(property);
if (mappings == null)
return;
if (mappings instanceof String) {
parseMappings(property, (String) mappings, mappingsMap, parser);
} else if (mappings instanceof String[]) {
final String[] mappingsArray = (String[])mappings;
for (int i = 0; i < mappingsArray.length; i++)
parseMappings(property, mappingsArray[i], mappingsMap, parser);
} else {
throw new IllegalArgumentException("Provided " + property +
" mappings is invalid. Acceptable types are String" +
" and String[].");
}
}
private void parseMappings(String property, String mappings,
Map mappingsMap, TypeParser parser) {
if (mappings == null)
return;
String[] records = mappings.split(",");
for(int i = 0; i < records.length; i++) {
String[] record = records[i].split(":");
if (record.length != 2)
throw new IllegalArgumentException("Provided " + property +
" mapping \"" + mappings + "\" is invalid. It " +
"should contain two parts, key and value, separated by ':'.");
String trimmedSegment = record[0].trim();
String trimmedValue = record[1].trim();
if (trimmedSegment.length() == 0)
throw new IllegalArgumentException("The key in " + property +
" mappings record \"" + records[i] + "\" is empty.");
if (trimmedValue.length() == 0)
throw new IllegalArgumentException("The value in " + property +
" mappings record \"" + records[i] + "\" is empty.");
mappingsMap.put(trimmedSegment, parser.valueOf(trimmedValue));
}
}
private void encodeKeys(Map map) {
Map tempMap = new HashMap();
for(Map.Entry entry : map.entrySet())
tempMap.put(UriComponent.contextualEncode(entry.getKey(), UriComponent.Type.PATH_SEGMENT), entry.getValue());
map.clear();
map.putAll(tempMap);
}
/**
* Get the set of root resource classes.
*
* A root resource class is a registered class that is annotated with
* Path.
*
* @return the set of root resource classes.
*/
public Set> getRootResourceClasses() {
Set> s = new LinkedHashSet>();
for (Class c : getClasses()) {
if (isRootResourceClass(c))
s.add(c);
}
return s;
}
/**
* Get the set of provider classes.
*
* A provider class is a registered class that is not annotated with
* Path.
*
* @return the set of provider classes.
*/
public Set> getProviderClasses() {
Set> s = new LinkedHashSet>();
for (Class c : getClasses()) {
if (!isRootResourceClass(c))
s.add(c);
}
return s;
}
/**
* Get the set of root resource singleton instances.
*
* A root resource singleton instance is a registered instance whose class
* is annotated with Path.
*
* @return the set of root resource singleton instances.
*/
public Set