content: /*
   * Licensed to the Apache Software Foundation (ASF) under one
   * or more contributor license agreements.  See the NOTICE file
   * distributed with this work for additional information
   * regarding copyright ownership.  The ASF licenses this file
   * to you under the Apache License, Version 2.0 (the
   * "License"); you may not use this file except in compliance
   * with the License.  You may obtain a copy of the License at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package org.apache.ambari.server.api.services;
  
  import java.io.UnsupportedEncodingException;
  import java.net.URLDecoder;
  import java.util.ArrayList;
  import java.util.Collection;
  import java.util.Collections;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.List;
  import java.util.Map;
  import java.util.Set;
  import java.util.regex.Matcher;
  import java.util.regex.Pattern;
  
  import jakarta.ws.rs.core.HttpHeaders;
  import jakarta.ws.rs.core.UriInfo;
  
  import org.apache.ambari.server.api.handlers.RequestHandler;
  import org.apache.ambari.server.api.predicate.InvalidQueryException;
  import org.apache.ambari.server.api.predicate.PredicateCompiler;
  import org.apache.ambari.server.api.predicate.QueryLexer;
  import org.apache.ambari.server.api.query.render.Renderer;
  import org.apache.ambari.server.api.resources.ResourceInstance;
  import org.apache.ambari.server.controller.internal.PageRequestImpl;
  import org.apache.ambari.server.controller.internal.SortRequestImpl;
  import org.apache.ambari.server.controller.internal.TemporalInfoImpl;
  import org.apache.ambari.server.controller.spi.PageRequest;
  import org.apache.ambari.server.controller.spi.Predicate;
  import org.apache.ambari.server.controller.spi.SortRequest;
  import org.apache.ambari.server.controller.spi.SortRequestProperty;
  import org.apache.ambari.server.controller.spi.TemporalInfo;
  import org.apache.ambari.server.utils.RequestUtils;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  /**
   * Request implementation.
   */
  public abstract class BaseRequest implements Request {
  
    /**
     * URI information
     */
    private UriInfo m_uriInfo;
  
    /**
     * Http headers
     */
    private HttpHeaders m_headers;
  
    /**
     * Http Body
     */
    private RequestBody m_body;
  
    /**
     * Remote address
     */
    private String m_remoteAddress;
  
    /**
     * Query Predicate
     */
    private Predicate m_predicate;
  
    /**
     * Associated resource definition
     */
    private ResourceInstance m_resource;
  
    /**
     * Default page size for pagination request.
     */
    public static final int DEFAULT_PAGE_SIZE = 20;
  
    /**
     * Page size property key
     */
    public static final String PAGE_SIZE_PROPERTY_KEY = "Request_Info/max_results";
  
    /**
     * Sort order property key. (true - ASC , false - DESC)
     */
    public static final String ASC_ORDER_PROPERTY_KEY = "Request_Info/asc_order";
  
    /**
     * Associated resource renderer.
     * Will default to the default renderer if non is specified.
     */
    private Renderer m_renderer;
  
    /**
     *  Logger instance.
     */
    private final static Logger LOG = LoggerFactory.getLogger(Request.class);
  
  
    /**
     * Constructor.
     *
     * @param headers           http headers
     * @param body              http body
     * @param uriInfo           uri information
     * @param resource          associated resource definition
     *
     */
    public BaseRequest(HttpHeaders headers, RequestBody body, UriInfo uriInfo, ResourceInstance resource) {
      m_headers     = headers;
      m_uriInfo     = uriInfo;
      m_resource    = resource;
      m_body        = body;
      m_remoteAddress  = RequestUtils.getRemoteAddress();
    }
  
    @Override
    public Result process() {
      if (LOG.isDebugEnabled()) {
        LOG.debug("Handling API Request: '{}'", getURI());
      }
  
      Result result;
      try {
        parseRenderer();
        parseQueryPredicate();
        result = getRequestHandler().handleRequest(this);
      } catch (InvalidQueryException e) {
        String message = "Unable to compile query predicate: " + e.getMessage();
        LOG.error(message, e);
        result = new ResultImpl(new ResultStatus(ResultStatus.STATUS.BAD_REQUEST, message));
      } catch (IllegalArgumentException e) {
        String message = "Invalid Request: " + e.getMessage();
        LOG.error(message, e);
        result = new ResultImpl(new ResultStatus(ResultStatus.STATUS.BAD_REQUEST, message));
      }
  
      if (! result.getStatus().isErrorState()) {
        getResultPostProcessor().process(result);
      }
  
      return result;
    }
  
    @Override
    public ResourceInstance getResource() {
      return m_resource;
    }
  
    @Override
    public String getURI() {
      return m_uriInfo.getRequestUri().toASCIIString();
    }
  
    @Override
    public int getAPIVersion() {
      return 1;
    }
  
    @Override
    public Predicate getQueryPredicate() {
      return m_predicate;
    }
  
    @Override
    public Map<String, TemporalInfo> getFields() {
      Map<String, TemporalInfo> mapProperties;
      String partialResponseFields = m_uriInfo.getQueryParameters().getFirst(QueryLexer.QUERY_FIELDS);
      if (partialResponseFields == null) {
        mapProperties = Collections.emptyMap();
      } else {
        Set<String> setMatches = new HashSet<>();
        // Pattern basically splits a string using ',' as the deliminator unless ',' is between '[' and ']'.
        // Actually, captures char sequences between ',' and all chars between '[' and ']' including ','.
        Pattern re = Pattern.compile("[^,\\[]*?\\[[^\\]]*?\\]|[^,]+");
        Matcher m = re.matcher(partialResponseFields);
        while (m.find()){
          for (int groupIdx = 0; groupIdx < m.groupCount() + 1; groupIdx++) {
            setMatches.add(m.group(groupIdx));
          }
        }
  
        mapProperties = new HashMap<>(setMatches.size());
        for (String field : setMatches) {
          TemporalInfo temporalInfo = null;
          if (field.contains("[")) {
            String[] temporalData = field.substring(field.indexOf('[') + 1,
                field.indexOf(']')).split(",");
            field = field.substring(0, field.indexOf('['));
            long start = Long.parseLong(temporalData[0].trim());
            long end   = -1;
            long step  = -1;
            if (temporalData.length >= 2) {
              end = Long.parseLong(temporalData[1].trim());
              if (temporalData.length == 3) {
                step = Long.parseLong(temporalData[2].trim());
              }
            }
            temporalInfo = new TemporalInfoImpl(start, end, step);
          }
          mapProperties.put(field, temporalInfo);
        }
      }
  
      return mapProperties;
    }
  
    @Override
    public Renderer getRenderer() {
     return m_renderer;
    }
  
    @Override
    public Map<String, List<String>> getHttpHeaders() {
      return m_headers.getRequestHeaders();
    }
  
    @Override
    public PageRequest getPageRequest() {
  
      String pageSize = m_uriInfo.getQueryParameters().getFirst(QueryLexer.QUERY_PAGE_SIZE);
      String from     = m_uriInfo.getQueryParameters().getFirst(QueryLexer.QUERY_FROM);
      String to       = m_uriInfo.getQueryParameters().getFirst(QueryLexer.QUERY_TO);
  
      if (pageSize == null && from == null && to == null) {
        return null;
      }
  
      int offset = 0;
      PageRequest.StartingPoint startingPoint;
  
      // TODO : support other starting points
      if (from != null) {
        if(from.equals("start")) {
          startingPoint = PageRequest.StartingPoint.Beginning;
        } else {
          offset = Integer.parseInt(from);
          startingPoint = PageRequest.StartingPoint.OffsetStart;
        }
      } else if (to != null ) {
        if (to.equals("end")) {
          startingPoint = PageRequest.StartingPoint.End;
        } else {
          offset = Integer.parseInt(to);
          startingPoint = PageRequest.StartingPoint.OffsetEnd;
        }
      } else {
        startingPoint = PageRequest.StartingPoint.Beginning;
      }
  
      // TODO : support predicate and comparator
      return new PageRequestImpl(startingPoint,
          pageSize == null ? DEFAULT_PAGE_SIZE : Integer.parseInt(pageSize), offset, null, null);
    }
  
    @Override
    public RequestBody getBody() {
      return m_body;
    }
  
    /**
     * Obtain the result post processor for the request.
     *
     * @return the result post processor
     */
    protected ResultPostProcessor getResultPostProcessor() {
      return m_renderer.getResultPostProcessor(this);
    }
  
    /**
     * Obtain the predicate compiler which is used to compile the query string into
     * a predicate.
     *
     * @return the predicate compiler
     */
    protected PredicateCompiler getPredicateCompiler() {
      return new PredicateCompiler();
    }
  
    /**
     * Check to see if 'minimal_response=true' is specified in the query string.
     *
     * @return true if 'minimal_response=true' is specified, false otherwise
     */
    private boolean isMinimal() {
      String minimal = m_uriInfo.getQueryParameters().getFirst(QueryLexer.QUERY_MINIMAL);
      return minimal != null && minimal.equalsIgnoreCase("true");
    }
  
    /**
     * Parse the query string and compile it into a predicate.
     * The query string may have already been extracted from the http body.
     * If the query string didn't exist in the body use the query string in the URL.
     *
     * @throws InvalidQueryException  if unable to parse a non-null query string into a predicate
     */
    private void parseQueryPredicate() throws InvalidQueryException {
      String queryString = m_body.getQueryString();
      if (queryString == null) {
        String uri = getURI();
        int qsBegin = uri.indexOf("?");
  
        queryString = (qsBegin == -1) ? null : uri.substring(qsBegin + 1);
      }
  
      if (queryString != null) {
        try {
          Collection<String> ignoredProperties = null;
          switch (getRequestType()) {
            case PUT:
              ignoredProperties = m_resource.getResourceDefinition().getUpdateDirectives();
              break;
            case POST:
              ignoredProperties = m_resource.getResourceDefinition().getCreateDirectives();
              break;
            case GET:
              ignoredProperties = m_resource.getResourceDefinition().getReadDirectives();
              break;
            case DELETE:
              ignoredProperties = m_resource.getResourceDefinition().getDeleteDirectives();
              break;
            default:
              break;
          }
  
  
          m_predicate = (ignoredProperties == null)
              ? getPredicateCompiler().compile(URLDecoder.decode(queryString, "UTF-8"))
              : getPredicateCompiler().compile(URLDecoder.decode(queryString, "UTF-8"), ignoredProperties);
        } catch (UnsupportedEncodingException e) {
          throw new RuntimeException("Unable to decode URI: " + e, e);
        }
      }
    }
  
    /**
     * Parse the query string for the {@link QueryLexer#QUERY_FORMAT} property and obtain
     * a renderer from the associated resource definition based on this property value.
     */
    private void parseRenderer() {
      String rendererName = isMinimal() ? "minimal" :
          m_uriInfo.getQueryParameters().getFirst(QueryLexer.QUERY_FORMAT);
      m_renderer = m_resource.getResourceDefinition().
          getRenderer(rendererName);
    }
  
    @Override
    public SortRequest getSortRequest() {
      String sortByParams = m_uriInfo.getQueryParameters().getFirst(QueryLexer.QUERY_SORT);
      if (sortByParams != null && !sortByParams.isEmpty()) {
        String[] params = sortByParams.split(",");
        List<SortRequestProperty> properties = new ArrayList<>();
        if (params.length > 0) {
          for (String property : params) {
            SortRequest.Order order = SortRequest.Order.ASC;
            String propertyId = property;
            int idx = property.indexOf(".");
            if (idx != -1) {
              order = SortRequest.Order.valueOf(property.substring(idx + 1).toUpperCase());
              propertyId = property.substring(0, idx);
            }
            properties.add(new SortRequestProperty(propertyId, order));
          }
        }
  
        return new SortRequestImpl(properties);
      }
      return null;
    }
  
    /**
     * Obtain the underlying request handler for the request.
     *
     * @return  the request handler
     */
    protected abstract RequestHandler getRequestHandler();
  
    @Override
    public String getRemoteAddress() {
      return m_remoteAddress;
    }
  }
  