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

org.glassfish.jersey.server.model.Resource Maven / Gradle / Ivy

Go to download

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.

There is a newer version: 3.1.9
Show newest version
/*
 * 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.model;

import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.IdentityHashMap;
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.stream.Collectors;

import jakarta.ws.rs.Path;
import jakarta.ws.rs.core.MultivaluedHashMap;
import jakarta.ws.rs.core.MultivaluedMap;

import org.glassfish.jersey.Severity;
import org.glassfish.jersey.internal.Errors;
import org.glassfish.jersey.internal.guava.Preconditions;
import org.glassfish.jersey.internal.util.collection.Value;
import org.glassfish.jersey.internal.util.collection.Values;
import org.glassfish.jersey.server.internal.LocalizationMessages;
import org.glassfish.jersey.server.model.internal.ModelHelper;
import org.glassfish.jersey.uri.PathPattern;

/**
 * Model of a single resource component.
 * 

* Resource component model represents a collection of {@link ResourceMethod methods} * grouped under the same parent request path template. {@code Resource} class is also * the main entry point to the programmatic resource modeling API that provides ability * to programmatically extend the existing JAX-RS annotated resource classes or build * new resource models that may be utilized by Jersey runtime. *

*

* For example: *

 * @Path("hello")
 * public class HelloResource {
 *      @GET
 *      @Produces("text/plain")
 *      public String sayHello() {
 *          return "Hello!";
 *      }
 * }
 *
 * ...
 *
 * // Register the annotated resource.
 * ResourceConfig resourceConfig = new ResourceConfig(HelloResource.class);
 *
 * // Add new "hello2" resource using the annotated resource class
 * // and overriding the resource path.
 * Resource.Builder resourceBuilder =
 *         Resource.builder(HelloResource.class, new LinkedList<ResourceModelIssue>())
 *         .path("hello2");
 *
 * // Add a new (virtual) sub-resource method to the "hello2" resource.
 * resourceBuilder.addChildResource("world").addMethod("GET")
 *         .produces("text/plain")
 *         .handledBy(new Inflector<Request, String>() {
 *                 @Override
 *                 public String apply(Request request) {
 *                     return "Hello World!";
 *                 }
 *         });
 *
 * // Register the new programmatic resource in the application's configuration.
 * resourceConfig.registerResources(resourceBuilder.build());
 * 
* The following table illustrates the supported requests and provided responses * for the application configured in the example above. * * * * * * * * * * * * * *
RequestResponseMethod invoked
{@code "GET /hello"}{@code "Hello!"}{@code HelloResource.sayHello()}
{@code "GET /hello2"}{@code "Hello!"}{@code HelloResource.sayHello()}
{@code "GET /hello2/world"}{@code "Hello World!"}{@code Inflector.apply()}
*

* * @author Marek Potociar * @author Miroslav Fuksa */ public final class Resource implements Routed, ResourceModelComponent { /** * Immutable resource data holder. */ private static class Data { private final List names; private final String path; private final PathPattern pathPattern; private final List resourceMethods; private final ResourceMethod.Data subResourceLocator; private final List childResources; private final Set> handlerClasses; private final Set handlerInstances; private final boolean extended; /** * Create a new immutable resource data holder from the supplied parameters. * * @param names resource names. * @param path resource path. * @param resourceMethods child resource methods. * @param subResourceLocator child resource locator. * @param childResources child sub-resources. * @param handlerClasses handler classes handling the resource methods. * @param handlerInstances handler instances handling the resource methods. * @param extended flag indicating whether the resource is extended */ private Data( final List names, final String path, final List resourceMethods, final ResourceMethod.Data subResourceLocator, final List childResources, final Set> handlerClasses, final Set handlerInstances, boolean extended) { this.extended = extended; this.names = Resource.immutableCopy(names); this.path = path; this.pathPattern = (path == null || path.isEmpty()) ? PathPattern.OPEN_ROOT_PATH_PATTERN : new PathPattern(path, PathPattern.RightHandPath.capturingZeroOrMoreSegments); this.resourceMethods = Resource.immutableCopy(resourceMethods); this.subResourceLocator = subResourceLocator; this.childResources = Collections.unmodifiableList(childResources); // no need to deep-copy the list this.handlerClasses = Resource.immutableCopy(handlerClasses); this.handlerInstances = Resource.immutableCopy(handlerInstances); } @Override public String toString() { return "Resource{" + ((path == null) ? "[unbound], " : "\"" + path + "\", ") + childResources.size() + " child resources, " + resourceMethods.size() + " resource methods, " + (subResourceLocator == null ? "0" : "1") + " sub-resource locator, " + handlerClasses.size() + " method handler classes, " + handlerInstances.size() + " method handler instances" + '}'; } } /** * Resource model component builder. */ public static final class Builder { private List names; private String path; private final Set methodBuilders; private final Set childResourceBuilders; private final List childResources; private final List resourceMethods; private ResourceMethod.Data subResourceLocator; private final Set> handlerClasses; private final Set handlerInstances; private final Resource.Builder parentResource; private boolean extended; private Builder(final Resource.Builder parentResource) { this.methodBuilders = new LinkedHashSet<>(); this.childResourceBuilders = new LinkedHashSet<>(); this.childResources = new LinkedList<>(); this.resourceMethods = new LinkedList<>(); this.handlerClasses = Collections.newSetFromMap(new IdentityHashMap<>()); this.handlerInstances = Collections.newSetFromMap(new IdentityHashMap<>()); this.parentResource = parentResource; name("[unnamed]"); } private Builder(final String path) { this((Resource.Builder) null); path(path); } private Builder(final String path, final Resource.Builder parentResource) { this(parentResource); this.path = path; } private Builder() { this((Resource.Builder) null); } private boolean isEmpty() { return this.path == null && methodBuilders.isEmpty() && childResourceBuilders.isEmpty() && resourceMethods.isEmpty() && childResources.isEmpty() && subResourceLocator == null; } /** * Define a new name of the built resource. *

* The resource model name is typically used for reporting * purposes (e.g. validation etc.). * * @param name new name of the resource. * @return updated builder object. * @see org.glassfish.jersey.server.model.Resource#getName() */ public Builder name(final String name) { this.names = new ArrayList<>(); this.names.add(name); return this; } /** * Define a new path for the built resource. *

* NOTE: Invoking this method marks a resource as a root resource. * * @param path new path for the resource. * @return updated builder object. */ public Builder path(final String path) { this.path = path; return this; } /** * Add a new method model to the resource for processing requests of * the specified HTTP method. *

* The returned builder is automatically bound to the the resource. It is * not necessary to invoke the {@link ResourceMethod.Builder#build() build()} * method on the method builder after setting all the data. This will be * done automatically when the resource is built. * * @param httpMethod HTTP method that will be processed by the method. * @return a new resource method builder. */ public ResourceMethod.Builder addMethod(final String httpMethod) { ResourceMethod.Builder builder = new ResourceMethod.Builder(this); methodBuilders.add(builder); return builder.httpMethod(httpMethod); } /** * Add a new arbitrary method model to the resource. *

* The returned builder is automatically bound to the the resource. It is * not necessary to invoke the {@link ResourceMethod.Builder#build() build()} * method on the method builder after setting all the data. This will be * done automatically when the resource is built. * * @return a new resource method builder. */ public ResourceMethod.Builder addMethod() { ResourceMethod.Builder builder = new ResourceMethod.Builder(this); methodBuilders.add(builder); return builder; } /** * Add a new method model that is a copy of the given {@code resourceMethod}. *

* The returned builder is automatically bound to the the resource. It is * not necessary to invoke the {@link ResourceMethod.Builder#build() build()} * method on the method builder after setting all the data. This will be * done automatically when the resource is built. * * @param resourceMethod The resource method based on which the new method builder * should be created. * @return a new resource method builder. */ public ResourceMethod.Builder addMethod(ResourceMethod resourceMethod) { ResourceMethod.Builder builder = new ResourceMethod.Builder(this, resourceMethod); methodBuilders.add(builder); return builder; } /** * Get a method builder for an existing resource method. *

* The original method is removed from the resource and a builder based on it is added. * * @param resourceMethod The resource method to be replaced by a builder. * @return a new resource method builder. * @throws java.lang.IllegalArgumentException in case the method to be updated is not registered * in this resource builder. * @since 2.18 */ public ResourceMethod.Builder updateMethod(ResourceMethod resourceMethod) { final boolean removed = resourceMethods.remove(resourceMethod.getData()); if (!removed) { throw new IllegalArgumentException( LocalizationMessages.RESOURCE_UPDATED_METHOD_DOES_NOT_EXIST(resourceMethod.toString())); } ResourceMethod.Builder builder = new ResourceMethod.Builder(this, resourceMethod); methodBuilders.add(builder); return builder; } /** * Add a new child resource to the resource. *

* The returned builder is automatically bound to the the resource. It is * not necessary to invoke the {@link Resource.Builder#build() build()} * method on the resource builder after setting all the data. This will be * done automatically when the resource is built. * * @param relativePath The path of the new child resource relative to this resource. * @return child resource builder. * @throws java.lang.IllegalStateException in case the child resource for this path exists already. */ public Builder addChildResource(String relativePath) { if (this.parentResource != null) { throw new IllegalStateException(LocalizationMessages.RESOURCE_ADD_CHILD_ALREADY_CHILD()); } final Builder resourceBuilder = new Builder(relativePath, this); childResourceBuilders.add(resourceBuilder); return resourceBuilder; } /** * Add an existing Resource as a child resource of current resource. * * @param resource Resource to be added as child resource. */ public void addChildResource(Resource resource) { this.childResources.add(resource.data); } /** * Replace an existing child resource with a newly supplied one. * * @param replacedResource old resource to be replaced. * @param newResource new resource to add. * @throws java.lang.IllegalArgumentException in case there is no such child resource to be replaced. * @since 2.18 */ public void replaceChildResource(Resource replacedResource, Resource newResource) { final boolean removed = this.childResources.remove(replacedResource.data); if (!removed) { throw new IllegalArgumentException( LocalizationMessages.RESOURCE_REPLACED_CHILD_DOES_NOT_EXIST(replacedResource.toString())); } addChildResource(newResource); } /** * Merge methods from a given resource model into this resource model builder. * * @param resource to be merged into this resource model builder. * @return updated builder object. */ public Builder mergeWith(final Resource resource) { mergeWith(resource.data); return this; } /** * Set the flag indicating whether the resource is extended or is a core of exposed RESTful API. *

* The method defines the flag available at {@link org.glassfish.jersey.server.model.Resource#isExtended()}. *

* Extended resource model components are helper components that are not considered as a core of a * RESTful API. These can be for example {@code OPTIONS} {@link ResourceMethod resource methods} * added by {@link org.glassfish.jersey.server.model.ModelProcessor model processors} * or {@code application.wadl} resource producing the WADL. Both resource are rather supportive * than the core of RESTful API. *

*

* If not set the resource will not be defined as extended by default. *

* * @param extended If {@code true} then resource is marked as extended. * @return updated builder object. * @see org.glassfish.jersey.server.model.ExtendedResource * @since 2.5.1 */ public Builder extended(boolean extended) { this.extended = extended; return this; } /** * Get the flag indicating whether the resource method is extended or is a core of exposed RESTful API. * * @return {@code true} if the method is extended. */ /* package */ boolean isExtended() { return extended; } private Builder mergeWith(final Resource.Data resourceData) { this.resourceMethods.addAll(resourceData.resourceMethods); this.childResources.addAll(resourceData.childResources); if (subResourceLocator != null && resourceData.subResourceLocator != null) { Errors.processWithException(new Runnable() { @Override public void run() { Errors.error( this, LocalizationMessages.RESOURCE_MERGE_CONFLICT_LOCATORS(Builder.this, resourceData, path), Severity.FATAL); } }); } else if (resourceData.subResourceLocator != null) { this.subResourceLocator = resourceData.subResourceLocator; } this.handlerClasses.addAll(resourceData.handlerClasses); this.handlerInstances.addAll(resourceData.handlerInstances); this.names.addAll(resourceData.names); return this; } /** * Merge methods from a given resource model builder into this resource model * builder. *

* NOTE: Any "open" method builders in the supplied {@code resourceBuilder} that have * not been {@link org.glassfish.jersey.server.model.ResourceMethod.Builder#build() * explicitly converted to method models} will be closed as part of this merge operation * before merging the resource builder instances. *

* * @param resourceBuilder to be merged into this resource model builder. * @return updated builder object. */ public Builder mergeWith(final Builder resourceBuilder) { resourceBuilder.processMethodBuilders(); this.resourceMethods.addAll(resourceBuilder.resourceMethods); this.childResources.addAll(resourceBuilder.childResources); if (Resource.Builder.this.subResourceLocator != null && resourceBuilder.subResourceLocator != null) { Errors.processWithException(new Runnable() { @Override public void run() { Errors.warning(this, LocalizationMessages.RESOURCE_MERGE_CONFLICT_LOCATORS(Resource.Builder.this, resourceBuilder, path)); } }); } else if (resourceBuilder.subResourceLocator != null) { this.subResourceLocator = resourceBuilder.subResourceLocator; } this.handlerClasses.addAll(resourceBuilder.handlerClasses); this.handlerInstances.addAll(resourceBuilder.handlerInstances); this.names.addAll(resourceBuilder.names); return this; } /** * Called when a new resource, sub-resource and sub-resource locator method * was built and should be registered with the resource builder. *

* This is a friend call-back API exposed for a use by a {@link ResourceMethod.Builder * ResourceMethod.Builder}. *

* * @param builder builder instance that built the method. * @param methodData new resource, sub-resource or sub-resource locator */ void onBuildMethod(ResourceMethod.Builder builder, ResourceMethod.Data methodData) { Preconditions.checkState(methodBuilders.remove(builder), "Resource.Builder.onBuildMethod() invoked from a resource method builder " + "that is not registered in the resource builder instance."); switch (methodData.getType()) { case RESOURCE_METHOD: resourceMethods.add(methodData); break; case SUB_RESOURCE_LOCATOR: if (subResourceLocator != null) { Errors.processWithException(new Runnable() { @Override public void run() { Errors.error( this, LocalizationMessages.AMBIGUOUS_SRLS(this, path), Severity.FATAL); } }); } subResourceLocator = methodData; break; } final MethodHandler methodHandler = methodData.getInvocable().getHandler(); if (methodHandler.isClassBased()) { handlerClasses.add(methodHandler.getHandlerClass()); } else { handlerInstances.add(methodHandler.getHandlerInstance()); } } private void onBuildChildResource(Builder childResourceBuilder, Resource.Data childResourceData) { Preconditions.checkState(childResourceBuilders.remove(childResourceBuilder), "Resource.Builder.onBuildChildResource() invoked from a resource builder " + "that is not registered in the resource builder instance as a child resource builder."); childResources.add(childResourceData); } private static List mergeResources(List resources) { final MultivaluedMap resourceData = new MultivaluedHashMap<>(); final List mergedResources = new ArrayList<>(); for (Iterator resourcesIterator = resources.iterator(); resourcesIterator.hasNext(); ) { Resource.Data data = resourcesIterator.next(); if (resourceData.containsKey(data.path)) { resourcesIterator.remove(); } resourceData.add(data.path, data); } for (Map.Entry> entry : resourceData.entrySet()) { if (entry.getValue().size() == 1) { mergedResources.add(entry.getValue().get(0)); } else { Resource.Builder builder = null; for (Resource.Data data : entry.getValue()) { if (builder != null) { builder.mergeWith(data); } else { builder = Resource.builder(data); } } mergedResources.add(builder.buildResourceData()); } } return mergedResources; } private Data buildResourceData() { if (parentResource != null && parentResource.isExtended()) { this.extended = true; } processMethodBuilders(); processChildResourceBuilders(); final List mergedChildResources = mergeResources(childResources); Set> classes = new HashSet<>(handlerClasses); Set instances = new HashSet<>(handlerInstances); for (Data childResource : mergedChildResources) { classes.addAll(childResource.handlerClasses); instances.addAll(childResource.handlerInstances); } if (areAllMembersExtended(mergedChildResources)) { extended = true; } final Data resourceData = new Data( names, path, resourceMethods, subResourceLocator, mergedChildResources, classes, instances, extended); if (parentResource != null) { parentResource.onBuildChildResource(this, resourceData); } return resourceData; } private boolean areAllMembersExtended(List mergedChildResources) { boolean allExtended = true; for (ResourceMethod.Data resourceMethod : resourceMethods) { if (!resourceMethod.isExtended()) { allExtended = false; } } if (subResourceLocator != null && !subResourceLocator.isExtended()) { allExtended = false; } for (Data childResource : mergedChildResources) { if (!childResource.extended) { allExtended = false; } } return allExtended; } /** * Build a new resource model. * * @return new (immutable) resource model. */ public Resource build() { final Data resourceData = buildResourceData(); return new Resource(null, resourceData); } private void processMethodBuilders() { // We have to iterate the set this way to prevent ConcurrentModificationExceptions // caused by the nested invocation of Set.remove(...) in Resource.Builder.onBuildMethod(...). while (!methodBuilders.isEmpty()) { methodBuilders.iterator().next().build(); } } private void processChildResourceBuilders() { // We have to iterate the set this way to prevent ConcurrentModificationExceptions // caused by the nested invocation of Set.remove(...) in Resource.Builder.onBuildChildResource(...). while (!childResourceBuilders.isEmpty()) { childResourceBuilders.iterator().next().build(); } } @Override public String toString() { return "Builder{" + "names=" + names + ", path='" + path + '\'' + ", methodBuilders=" + methodBuilders + ", childResourceBuilders=" + childResourceBuilders + ", childResources=" + childResources + ", resourceMethods=" + resourceMethods + ", subResourceLocator=" + subResourceLocator + ", handlerClasses=" + handlerClasses + ", handlerInstances=" + handlerInstances + ", parentResource=" + ((parentResource == null) ? "" : parentResource.shortToString()) + ", extended=" + extended + '}'; } private String shortToString() { return "Builder{names=" + names + ", path='" + path + "'}"; } } /** * Get a new unbound resource model builder. * * @return new unbound resource model builder. * @see Resource.Builder#path(java.lang.String) */ public static Builder builder() { return new Builder(); } /** * Get a new resource model builder for a resource bound to a given path. * * @param path resource path. * @return new resource model builder. * @see Resource.Builder#path(java.lang.String) */ public static Builder builder(final String path) { return new Builder(path); } /** * Creates a {@link Builder resource builder} instance from the list of {@code resource} which can be merged * into a single resource. It must be possible to merge the {@code resources} into a single valid resource. * For example all resources must have the same {@link Resource#getPath() path}, they cannot have ambiguous methods * on the same path, etc. * * @param resources Resources with the same path. * @return Resource builder initialized from merged resources. */ public static Builder builder(List resources) { if (resources == null || resources.isEmpty()) { return builder(); } final Iterator it = resources.iterator(); Resource.Data resourceData = it.next().data; Builder builder = Resource.builder(resourceData); String path = resourceData.path; while (it.hasNext()) { resourceData = it.next().data; if ((resourceData.path == null && path == null) || (path != null && path.equals(resourceData.path))) { builder.mergeWith(resourceData); } else { throw new IllegalArgumentException(LocalizationMessages.ERROR_RESOURCES_CANNOT_MERGE()); } } return builder; } /** * Create a resource model builder initialized by introspecting an annotated * JAX-RS resource class. * * @param resourceClass resource class to be modelled. * @return resource model builder initialized by the class or {@code null} if the * class does not represent a resource. */ public static Builder builder(Class resourceClass) { return builder(resourceClass, false); } /** * Create a resource model builder initialized by introspecting an annotated * JAX-RS resource class. * * @param resourceClass resource class to be modelled. * @param disableValidation if set to {@code true}, then any model validation checks will be disabled. * @return resource model builder initialized by the class or {@code null} if the * class does not represent a resource. */ public static Builder builder(Class resourceClass, boolean disableValidation) { final Builder builder = new IntrospectionModeller(resourceClass, disableValidation).createResourceBuilder(); return builder.isEmpty() ? null : builder; } /** * Create a resource model initialized by introspecting an annotated * JAX-RS resource class. * * @param resourceClass resource class to be modelled. * @return resource model initialized by the class or {@code null} if the * class does not represent a resource. */ public static Resource from(Class resourceClass) { return from(resourceClass, false); } /** * Create a resource model initialized by introspecting an annotated * JAX-RS resource class. * * @param resourceClass resource class to be modelled. * @param disableValidation if set to {@code true}, then any model validation checks will be disabled. * @return resource model initialized by the class or {@code null} if the * class does not represent a resource. */ public static Resource from(Class resourceClass, boolean disableValidation) { final Builder builder = new IntrospectionModeller(resourceClass, disableValidation).createResourceBuilder(); return builder.isEmpty() ? null : builder.build(); } /** * Check if the class is acceptable as a JAX-RS provider or resource. *

* Method returns {@code false} if the class is either *

    *
  • abstract
  • *
  • interface
  • *
  • annotation
  • *
  • primitive
  • *
  • local class
  • *
  • non-static member class
  • *
* * @param c class to be checked. * @return {@code true} if the class is an acceptable JAX-RS provider or * resource, {@code false} otherwise. */ public static boolean isAcceptable(Class c) { return !((c.getModifiers() & Modifier.ABSTRACT) != 0 || c.isPrimitive() || c.isAnnotation() || c.isInterface() || c.isLocalClass() || (c.isMemberClass() && (c.getModifiers() & Modifier.STATIC) == 0)); } /** * Get the resource class {@link Path @Path} annotation. *

* May return {@code null} in case there is no {@code @Path} annotation on the resource. * * @param resourceClass resource class. * @return {@code @Path} annotation instance if present on the resource class (i.e. * the class is a root resource class), or {@code null} otherwise. */ public static Path getPath(Class resourceClass) { return ModelHelper.getAnnotatedResourceClass(resourceClass).getAnnotation(Path.class); } /** * Get a new resource model builder initialized from a given resource model. * * @param resource resource model initializing the resource builder. * @return new resource model builder. */ public static Builder builder(Resource resource) { return builder(resource.data); } private static Builder builder(Resource.Data resourceData) { final Builder b; if (resourceData.path == null) { b = new Builder(); } else { b = new Builder(resourceData.path); } b.resourceMethods.addAll(resourceData.resourceMethods); b.childResources.addAll(resourceData.childResources); b.subResourceLocator = resourceData.subResourceLocator; b.handlerClasses.addAll(resourceData.handlerClasses); b.handlerInstances.addAll(resourceData.handlerInstances); b.names.addAll(resourceData.names); return b; } private static List transform(final Resource parent, final List list) { return list.stream().map(data -> new Resource(parent, data)).collect(Collectors.toList()); } private static List immutableCopy(List list) { return list.isEmpty() ? Collections.emptyList() : Collections.unmodifiableList(list); } private static Set immutableCopy(Set set) { if (set.isEmpty()) { return Collections.emptySet(); } final Set result = Collections.newSetFromMap(new IdentityHashMap<>()); result.addAll(set); return set; } private final Resource parent; private final Data data; private final Value name; private final List resourceMethods; private final ResourceMethod locator; private final List childResources; private Resource(final Resource parent, final Data data) { this.parent = parent; this.data = data; this.name = Values.lazy(new Value() { @Override public String get() { if (data.names.size() == 1) { return data.names.get(0); } else { // return merged name return "Merge of " + data.names.toString(); } } }); this.resourceMethods = immutableCopy(ResourceMethod.transform(Resource.this, data.resourceMethods)); this.locator = data.subResourceLocator == null ? null : new ResourceMethod(Resource.this, data.subResourceLocator); this.childResources = immutableCopy(Resource.transform(Resource.this, data.childResources)); } @Override public String getPath() { return data.path; } @Override public PathPattern getPathPattern() { return data.pathPattern; } /** * Get the parent resource for this resource model or {@code null} in case this * resource is a top-level resource and does not have a parent. * * @return parent resource or {@code null} if the resource does not have a parent. * @since 2.1 */ public Resource getParent() { return parent; } /** * Get the resource name. *

* If the resource was constructed from a JAX-RS annotated resource class, * the resource name will be set to the {@link Class#getName() fully-qualified name} * of the resource class. * * @return reference JAX-RS resource handler class. */ public String getName() { return name.get(); } /** * Return a list of resource names. * * @return a list of resource names. */ public List getNames() { return data.names; } /** * Provides a non-null list of resource methods available on the resource. * * @return non-null abstract resource method list. */ public List getResourceMethods() { return resourceMethods; } /** * Provides a resource locator available on the resource. * * @return Resource locator if it is present, null otherwise. */ public ResourceMethod getResourceLocator() { return locator; } /** * Provides resource methods and resource locator are available on the resource. The list is ordered so that resource * methods are positioned first before resource locator. * * @return List of resource methods and resource locator. */ public List getAllMethods() { final LinkedList methodsAndLocators = new LinkedList<>(getResourceMethods()); final ResourceMethod loc = getResourceLocator(); if (loc != null) { methodsAndLocators.add(loc); } return methodsAndLocators; } /** * Returns the list of child resources available on this resource. * * @return Non-null list of child resources (may be empty). */ public List getChildResources() { return childResources; } /** * Get the method handler classes for the resource methods registered on the resource. * * @return resource method handler classes. */ public Set> getHandlerClasses() { return data.handlerClasses; } /** * Get the method handler (singleton) instances for the resource methods registered * on the resource. * * @return resource method handler instances. */ public Set getHandlerInstances() { return data.handlerInstances; } @Override public void accept(ResourceModelVisitor visitor) { if (getParent() == null) { visitor.visitResource(this); } else { visitor.visitChildResource(this); } } /** * Get the flag indicating whether the resource is extended. *

* I.e. it is not part of the resource set that forms the REST API explicitly defined by the application developer (resource * classes and instances returned from the {@link jakarta.ws.rs.core.Application} subclass getters). *

* Extended resource model components are helper components that are not part of the explicitly defined REST API of * a JAX-RS application, instead they are generated by Jersey runtime. For example, extended resource model components * include {@code OPTIONS} {@link ResourceMethod resource methods} * automatically generated by Jersey {@link org.glassfish.jersey.server.model.ModelProcessor resource model processors} * or {@code application.wadl} resource API that exposes the application WADL descriptor. *

* * @return {@code true} if the resource is part of the application's extended REST API, {@code false} otherwise. * @see org.glassfish.jersey.server.model.ExtendedResource * @since 2.5.1 */ public boolean isExtended() { return data.extended; } @Override public String toString() { return data.toString(); } @Override public List getComponents() { List components = new LinkedList<>(); components.addAll(getChildResources()); components.addAll(getResourceMethods()); final ResourceMethod resourceLocator = getResourceLocator(); if (resourceLocator != null) { components.add(resourceLocator); } return components; } }