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.providers;
019
020import com.fasterxml.jackson.core.JsonParser;
021import com.fasterxml.jackson.databind.ObjectReader;
022import com.unboundid.scim2.common.annotations.NotNull;
023import com.unboundid.scim2.common.messages.SearchRequest;
024import com.unboundid.scim2.common.utils.JsonUtils;
025import com.unboundid.scim2.common.utils.StaticUtils;
026import com.unboundid.scim2.server.utils.ServerUtils;
027
028import jakarta.annotation.Priority;
029import jakarta.ws.rs.BadRequestException;
030import jakarta.ws.rs.HttpMethod;
031import jakarta.ws.rs.NotSupportedException;
032import jakarta.ws.rs.Priorities;
033import jakarta.ws.rs.container.ContainerRequestContext;
034import jakarta.ws.rs.container.ContainerRequestFilter;
035import jakarta.ws.rs.container.PreMatching;
036import jakarta.ws.rs.core.MediaType;
037import jakarta.ws.rs.core.NoContentException;
038import jakarta.ws.rs.core.PathSegment;
039import jakarta.ws.rs.core.UriBuilder;
040import jakarta.ws.rs.ext.Provider;
041import java.io.IOException;
042import java.util.List;
043
044import static com.unboundid.scim2.common.utils.ApiConstants.*;
045
046/**
047 * A ContainerRequestFilter implementation to convert a search request using
048 * HTTP POST combine with the "{@code .search}" path extension to a regular search
049 * using HTTP GET.
050 */
051@Provider
052@PreMatching
053@Priority(Priorities.ENTITY_CODER)
054public class DotSearchFilter implements ContainerRequestFilter
055{
056  /**
057   * {@inheritDoc}
058   */
059  public void filter(@NotNull final ContainerRequestContext requestContext)
060      throws IOException
061  {
062    if(requestContext.getMethod().equals(HttpMethod.POST) &&
063        requestContext.getUriInfo().getPath().endsWith(
064            SEARCH_WITH_POST_PATH_EXTENSION))
065
066    {
067      if(requestContext.getMediaType() == null ||
068          !(requestContext.getMediaType().isCompatible(
069              ServerUtils.MEDIA_TYPE_SCIM_TYPE) ||
070              requestContext.getMediaType().isCompatible(
071                  MediaType.APPLICATION_JSON_TYPE)))
072      {
073        throw new NotSupportedException();
074      }
075
076      ObjectReader reader =
077          JsonUtils.getObjectReader().forType(SearchRequest.class);
078      JsonParser p = reader.getFactory().createParser(
079          requestContext.getEntityStream());
080      if(p.nextToken() == null)
081      {
082        throw new BadRequestException(
083            new NoContentException("Empty Entity"));
084      }
085      SearchRequest searchRequest = reader.readValue(p);
086      UriBuilder builder = requestContext.getUriInfo().getBaseUriBuilder();
087      List<PathSegment> pathSegments =
088          requestContext.getUriInfo().getPathSegments();
089      for(int i = 0; i < pathSegments.size() - 1; i ++)
090      {
091        builder.path(pathSegments.get(i).getPath());
092      }
093      if(searchRequest.getAttributes() != null)
094      {
095        builder.queryParam(QUERY_PARAMETER_ATTRIBUTES,
096            ServerUtils.encodeTemplateNames(
097                StaticUtils.collectionToString(
098                    searchRequest.getAttributes(), ",")));
099      }
100      if(searchRequest.getExcludedAttributes() != null)
101      {
102        builder.queryParam(QUERY_PARAMETER_EXCLUDED_ATTRIBUTES,
103            ServerUtils.encodeTemplateNames(
104                    StaticUtils.collectionToString(
105                    searchRequest.getExcludedAttributes(), ",")));
106      }
107      if(searchRequest.getFilter() != null)
108      {
109        builder.queryParam(QUERY_PARAMETER_FILTER,
110            ServerUtils.encodeTemplateNames(searchRequest.getFilter()));
111      }
112      if(searchRequest.getSortBy() != null)
113      {
114        builder.queryParam(QUERY_PARAMETER_SORT_BY,
115            ServerUtils.encodeTemplateNames(searchRequest.getSortBy()));
116      }
117      if(searchRequest.getSortOrder() != null)
118      {
119        builder.queryParam(QUERY_PARAMETER_SORT_ORDER,
120            ServerUtils.encodeTemplateNames(
121                searchRequest.getSortOrder().getName()));
122      }
123      if(searchRequest.getStartIndex() != null)
124      {
125        builder.queryParam(QUERY_PARAMETER_PAGE_START_INDEX,
126            searchRequest.getStartIndex());
127      }
128      if(searchRequest.getCount() != null)
129      {
130        builder.queryParam(QUERY_PARAMETER_PAGE_SIZE,
131            searchRequest.getCount());
132      }
133      requestContext.setRequestUri(builder.build());
134      requestContext.setMethod(HttpMethod.GET);
135    }
136  }
137}