001/*
002 * Copyright 2015-2024 Ping Identity Corporation
003 *
004 * This program is free software; you can redistribute it and/or modify
005 * it under the terms of the GNU General Public License (GPLv2 only)
006 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
007 * as published by the Free Software Foundation.
008 *
009 * This program is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
012 * GNU General Public License for more details.
013 *
014 * You should have received a copy of the GNU General Public License
015 * along with this program; if not, see <http://www.gnu.org/licenses>.
016 */
017
018package com.unboundid.scim2.server.resources;
019
020import com.unboundid.scim2.common.GenericScimResource;
021import com.unboundid.scim2.common.ScimResource;
022import com.unboundid.scim2.common.annotations.NotNull;
023import com.unboundid.scim2.common.annotations.Nullable;
024import com.unboundid.scim2.common.filters.Filter;
025import com.unboundid.scim2.common.messages.ListResponse;
026import com.unboundid.scim2.common.types.ResourceTypeResource;
027import com.unboundid.scim2.common.exceptions.ForbiddenException;
028import com.unboundid.scim2.common.exceptions.ResourceNotFoundException;
029import com.unboundid.scim2.common.exceptions.ScimException;
030import com.unboundid.scim2.server.annotations.ResourceType;
031import com.unboundid.scim2.server.utils.ResourcePreparer;
032import com.unboundid.scim2.server.utils.ResourceTypeDefinition;
033import com.unboundid.scim2.server.utils.SchemaAwareFilterEvaluator;
034
035import jakarta.ws.rs.GET;
036import jakarta.ws.rs.Path;
037import jakarta.ws.rs.PathParam;
038import jakarta.ws.rs.Produces;
039import jakarta.ws.rs.QueryParam;
040import jakarta.ws.rs.core.Application;
041import jakarta.ws.rs.core.Context;
042import jakarta.ws.rs.core.MediaType;
043import jakarta.ws.rs.core.UriInfo;
044import java.util.ArrayList;
045import java.util.Collection;
046import java.util.HashSet;
047import java.util.Set;
048
049import static com.unboundid.scim2.common.utils.ApiConstants.*;
050
051/**
052 * An abstract JAX-RS resource class for servicing the Resource Types
053 * endpoint.
054 */
055@ResourceType(
056    description = "SCIM 2.0 Resource Type",
057    name = "ResourceType",
058    schema = ResourceTypeResource.class,
059    discoverable = false)
060@Path("ResourceTypes")
061public class ResourceTypesEndpoint
062{
063  @NotNull
064  private static final ResourceTypeDefinition RESOURCE_TYPE_DEFINITION =
065      ResourceTypeDefinition.fromJaxRsResource(
066          ResourceTypesEndpoint.class);
067
068  @NotNull
069  @Context
070  private Application application;
071
072  /**
073   * Service SCIM request to retrieve all resource types defined at the
074   * service provider using GET.
075   *
076   * @param filterString The filter string used to request a subset of
077   *                     resources. Will throw 403 Forbidden if specified.
078   * @param uriInfo UriInfo of the request.
079   * @return All resource types in a ListResponse container.
080   * @throws ScimException If an error occurs.
081   */
082  @GET
083  @Produces({MEDIA_TYPE_SCIM, MediaType.APPLICATION_JSON})
084  @NotNull
085  public ListResponse<GenericScimResource> search(
086      @Nullable @QueryParam(QUERY_PARAMETER_FILTER) final String filterString,
087      @NotNull @Context final UriInfo uriInfo)
088          throws ScimException
089  {
090    if(filterString != null)
091    {
092      throw new ForbiddenException("Filtering not allowed");
093    }
094
095    // https://tools.ietf.org/html/draft-ietf-scim-api-19#section-4 says
096    // query params should be ignored for discovery endpoints so we can't use
097    // SimpleSearchResults.
098    ResourcePreparer<GenericScimResource> preparer =
099        new ResourcePreparer<GenericScimResource>(
100            RESOURCE_TYPE_DEFINITION, uriInfo);
101    Collection<ResourceTypeResource> resourceTypes = getResourceTypes();
102    Collection<GenericScimResource> preparedResources =
103        new ArrayList<GenericScimResource>(resourceTypes.size());
104    for(ResourceTypeResource resourceType : resourceTypes)
105    {
106      GenericScimResource preparedResource =
107          resourceType.asGenericScimResource();
108      preparer.setResourceTypeAndLocation(preparedResource);
109      preparedResources.add(preparedResource);
110    }
111    return new ListResponse<GenericScimResource>(preparedResources);
112  }
113
114  /**
115   * Service SCIM request to retrieve a resource type by ID.
116   *
117   * @param id The ID of the resource type to retrieve.
118   * @param uriInfo UriInfo of the request.
119   * @return The retrieved resource type.
120   * @throws ScimException If an error occurs.
121   */
122  @Path("{id}")
123  @GET
124  @Produces({MEDIA_TYPE_SCIM, MediaType.APPLICATION_JSON})
125  @NotNull
126  public ScimResource get(@NotNull @PathParam("id") final String id,
127                          @NotNull @Context final UriInfo uriInfo)
128      throws ScimException
129  {
130    Filter filter = Filter.or(Filter.eq("id", id), Filter.eq("name", id));
131    SchemaAwareFilterEvaluator filterEvaluator =
132        new SchemaAwareFilterEvaluator(RESOURCE_TYPE_DEFINITION);
133    ResourcePreparer<GenericScimResource> resourcePreparer =
134        new ResourcePreparer<GenericScimResource>(
135            RESOURCE_TYPE_DEFINITION, uriInfo);
136    for(ResourceTypeResource resourceType : getResourceTypes())
137    {
138      GenericScimResource resource = resourceType.asGenericScimResource();
139      if(filter.visit(filterEvaluator, resource.getObjectNode()))
140      {
141        resourcePreparer.setResourceTypeAndLocation(resource);
142        return resource;
143      }
144    }
145
146    throw new ResourceNotFoundException(
147        "No resource type defined with ID or name " + id);
148  }
149
150  /**
151   * Retrieve all resource types defined at the service provider. The default
152   * implementation will generate ResourceType definitions from all JAX-RS
153   * resource classes with the ResourceType annotation.
154   *
155   * @return All resource types defined at the service provider.
156   * @throws ScimException If an error occurs.
157   */
158  @NotNull
159  public Collection<ResourceTypeResource> getResourceTypes()
160      throws ScimException
161  {
162    Set<ResourceTypeResource> resourceTypes =
163        new HashSet<ResourceTypeResource>();
164    for(Class<?> resourceClass : application.getClasses())
165    {
166      ResourceTypeDefinition resourceTypeDefinition =
167          ResourceTypeDefinition.fromJaxRsResource(resourceClass);
168      if(resourceTypeDefinition != null &&
169          resourceTypeDefinition.isDiscoverable())
170      {
171        resourceTypes.add(resourceTypeDefinition.toScimResource());
172      }
173    }
174
175    for(Object resourceInstance : application.getSingletons())
176    {
177      ResourceTypeDefinition resourceTypeDefinition =
178          ResourceTypeDefinition.fromJaxRsResource(resourceInstance.getClass());
179      if(resourceTypeDefinition != null &&
180          resourceTypeDefinition.isDiscoverable())
181      {
182        resourceTypes.add(resourceTypeDefinition.toScimResource());
183      }
184    }
185
186    return resourceTypes;
187  }
188}