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.util.Iterator;
  import java.util.Map;
  import java.util.Set;
  
  import jakarta.ws.rs.core.HttpHeaders;
  import jakarta.ws.rs.core.MediaType;
  import jakarta.ws.rs.core.Response;
  import jakarta.ws.rs.core.UriInfo;
  
  import org.apache.ambari.server.api.resources.ResourceInstance;
  import org.apache.ambari.server.api.resources.ResourceInstanceFactory;
  import org.apache.ambari.server.api.resources.ResourceInstanceFactoryImpl;
  import org.apache.ambari.server.api.services.parsers.BodyParseException;
  import org.apache.ambari.server.api.services.parsers.JsonRequestBodyParser;
  import org.apache.ambari.server.api.services.parsers.RequestBodyParser;
  import org.apache.ambari.server.api.services.serializers.CsvSerializer;
  import org.apache.ambari.server.api.services.serializers.JsonSerializer;
  import org.apache.ambari.server.api.services.serializers.ResultSerializer;
  import org.apache.ambari.server.audit.request.RequestAuditLogger;
  import org.apache.ambari.server.controller.spi.Resource;
  import org.apache.ambari.server.utils.RetryHelper;
  import org.eclipse.jetty.util.ajax.JSON;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  /**
   * Provides common functionality to all services.
   */
  public abstract class BaseService {
    public final static MediaType MEDIA_TYPE_TEXT_CSV_TYPE = new MediaType("text", "csv");
  
    public static final String MSG_SUCCESSFUL_OPERATION = "Successful operation";
    public static final String MSG_REQUEST_ACCEPTED = "Request is accepted, but not completely processed yet";
    public static final String MSG_INVALID_ARGUMENTS = "Invalid arguments";
    public static final String MSG_INVALID_REQUEST = "Invalid request";
    public static final String MSG_CLUSTER_NOT_FOUND = "Cluster not found";
    public static final String MSG_CLUSTER_OR_HOST_NOT_FOUND = "Cluster or host not found";
    public static final String MSG_VIEW_NOT_FOUND = "View not found";
    public static final String MSG_NOT_AUTHENTICATED = "Not authenticated";
    public static final String MSG_PERMISSION_DENIED = "Not permitted to perform the operation";
    public static final String MSG_SERVER_ERROR = "Internal server error";
    public static final String MSG_RESOURCE_ALREADY_EXISTS = "The requested resource already exists.";
    public static final String MSG_RESOURCE_NOT_FOUND = "The requested resource doesn't exist.";
  
    public static final String QUERY_FIELDS = "fields";
    public static final String QUERY_FILTER_DESCRIPTION = "Filter fields in the response (identifier fields are mandatory)";
    public static final String QUERY_SORT = "sortBy";
    public static final String QUERY_SORT_DESCRIPTION = "Sort resources in result by (asc | desc)";
    public static final String QUERY_PAGE_SIZE = "page_size";
    public static final String QUERY_PAGE_SIZE_DESCRIPTION = "The number of resources to be returned for the paged response.";
    public static final String DEFAULT_PAGE_SIZE = "10";
    public static final String QUERY_FROM = "from";
    public static final String QUERY_FROM_DESCRIPTION = "The starting page resource (inclusive).  \"start\" is also accepted.";
    public static final String QUERY_FROM_VALUES = "range[0, infinity]";
    public static final String DEFAULT_FROM = "0";
    public static final String QUERY_TO = "to";
    public static final String QUERY_TO_DESCRIPTION = "The ending page resource (inclusive).  \"end\" is also accepted.";
    public static final String QUERY_TO_TYPE = "integer";
    public static final String QUERY_TO_VALUES = "range[1, infinity]";
    public static final String QUERY_PREDICATE = "{predicate}";
    public static final String QUERY_PREDICATE_DESCRIPTION = "The predicate to filter resources by. Omitting the predicate will " +
        "match all resources.";
  
    public static final String RESPONSE_CONTAINER_LIST = "List";
  
    public static final String DATA_TYPE_INT = "integer";
    public static final String DATA_TYPE_STRING = "string";
  
    public static final String PARAM_TYPE_QUERY = "query";
    public static final String PARAM_TYPE_BODY = "body";
  
    public static final String FIELDS_SEPARATOR = ", ";
  
    private final static Logger LOG = LoggerFactory.getLogger(BaseService.class);
  
    /**
     * Factory for creating resource instances.
     */
    private ResourceInstanceFactory m_resourceFactory = new ResourceInstanceFactoryImpl();
  
    /**
     * Result serializer.
     */
    private ResultSerializer m_serializer = new JsonSerializer();
  
    protected static RequestAuditLogger requestAuditLogger;
  
    public static void init(RequestAuditLogger instance) {
      requestAuditLogger = instance;
    }
  
    /**
     * Requests are funneled through this method so that common logic can be executed.
     * Creates a request instance and invokes it's process method.  Uses the default
     * media type.
     *
     * @param headers      http headers
     * @param body         http body
     * @param uriInfo      uri information
     * @param requestType  http request type
     * @param resource     resource instance that is being acted on
     *
     * @return the response of the operation in serialized form
     */
    protected Response handleRequest(HttpHeaders headers, String body, UriInfo uriInfo,
                                     Request.Type requestType, ResourceInstance resource) {
  
      return handleRequest(headers, body, uriInfo, requestType, null, resource);
    }
  
    /**
     * Requests are funneled through this method so that common logic can be executed.
     * Creates a request instance and invokes it's process method.
     *
     * @param headers      http headers
     * @param body         http body
     * @param uriInfo      uri information
     * @param requestType  http request type
     * @param mediaType    the requested media type; may be null
     * @param resource     resource instance that is being acted on
     *
     * @return the response of the operation in serialized form
     */
    protected Response handleRequest(HttpHeaders headers, String body,
                                     UriInfo uriInfo, Request.Type requestType,
                                     MediaType mediaType, ResourceInstance resource) {
  
      // original request and initial result
      RequestBody rb = new RequestBody();
      rb.setBody(body);
      Request request = getRequestFactory().createRequest(headers, rb, uriInfo, requestType, resource);
      Result result = new ResultImpl(new ResultStatus(ResultStatus.STATUS.OK));
  
      try {
        Set<RequestBody> requestBodySet = getBodyParser().parse(body);
  
        Iterator<RequestBody> iterator = requestBodySet.iterator();
        while (iterator.hasNext() && result.getStatus().getStatus().equals(ResultStatus.STATUS.OK)) {
          RequestBody requestBody = iterator.next();
  
          request = getRequestFactory().createRequest(
              headers, requestBody, uriInfo, requestType, resource);
  
          result  = request.process();
          // if it is not OK, then it is logged below
          if(ResultStatus.STATUS.OK.equals(result.getStatus().getStatus())) {
            requestAuditLogger.log(request, result);
          }
        }
  
        if(requestBodySet.isEmpty() || !ResultStatus.STATUS.OK.equals(result.getStatus().getStatus())) {
          requestAuditLogger.log(request, result);
        }
      } catch (BodyParseException e) {
        result =  new ResultImpl(new ResultStatus(ResultStatus.STATUS.BAD_REQUEST, e.getMessage()));
        LOG.error("Bad request received: " + e.getMessage());
        requestAuditLogger.log(request, result);
      } catch (Throwable t) {
        requestAuditLogger.log(request, new ResultImpl(new ResultStatus(ResultStatus.STATUS.SERVER_ERROR, t.getMessage())));
        throw t;
      }
  
      ResultSerializer serializer = mediaType == null ? getResultSerializer() : getResultSerializer(mediaType);
  
      Response.ResponseBuilder builder = Response.status(result.getStatus().getStatusCode()).entity(
          serializer.serialize(result));
  
      if (mediaType != null) {
        builder.type(mediaType);
      }
  
      RetryHelper.clearAffectedClusters();
      return builder.build();
    }
  
    /**
     * Obtain the factory from which to create Request instances.
     *
     * @return the Request factory
     */
    RequestFactory getRequestFactory() {
      return new RequestFactory();
    }
  
    /**
     * Create a resource instance.
     *
     * @param type    the resource type
     * @param mapIds  all primary and foreign key properties and values
     *
     * @return a newly created resource instance
     */
    protected ResourceInstance createResource(Resource.Type type, Map<Resource.Type, String> mapIds) {
      return m_resourceFactory.createResource(type, mapIds);
    }
  
    /**
     * Get a serializer for the given media type.
     *
     * @param mediaType  the media type
     *
     * @return the result serializer
     */
    protected ResultSerializer getResultSerializer(final MediaType mediaType) {
  
      final ResultSerializer serializer = getResultSerializer();
      final JSON jsonParser = new JSON();
  
      if (mediaType.equals(MediaType.TEXT_PLAIN_TYPE)){
        return new ResultSerializer() {
          @Override
          public Object serialize(Result result) {
            return serializer.serialize(result).toString();
          }
  
          @Override
          public Object serializeError(ResultStatus error) {
            return serializer.serializeError(error).toString();
          }
        };
      } else if (mediaType.equals(MediaType.APPLICATION_JSON_TYPE)){
        return new ResultSerializer() {
          @Override
          public Object serialize(Result result) {
            return jsonParser.parse(new JSON.StringSource(serializer.serialize(result).toString()));
          }
  
          @Override
          public Object serializeError(ResultStatus error) {
            return jsonParser.parse(new JSON.StringSource(serializer.serializeError(error).toString()));
          }
        };
      }
      else if (mediaType.equals(MEDIA_TYPE_TEXT_CSV_TYPE)) {
        return new CsvSerializer();
      }
  
      throw new IllegalArgumentException("The media type " + mediaType + " is not supported.");
    }
  
    /**
     * Get the default serializer.
     *
     * @return the default serializer
     */
    protected ResultSerializer getResultSerializer() {
      return m_serializer;
    }
  
    protected RequestBodyParser getBodyParser() {
      return new JsonRequestBodyParser();
    }
  }
  