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 com.aliyun.odps;
  
  import com.aliyun.odps.account.BearerTokenAccount;
  import com.aliyun.odps.commons.transport.Headers;
  import com.aliyun.odps.commons.transport.Params;
  import com.aliyun.odps.commons.transport.Response;
  import com.aliyun.odps.rest.SimpleXmlUtils;
  import com.aliyun.odps.simpleframework.xml.Element;
  import com.aliyun.odps.simpleframework.xml.ElementList;
  import com.aliyun.odps.simpleframework.xml.Root;
  import com.aliyun.odps.simpleframework.xml.convert.Convert;
  
  import java.util.ArrayList;
  import java.util.HashMap;
  import java.util.Iterator;
  import java.util.LinkedHashMap;
  import java.util.LinkedList;
  import java.util.List;
  import java.util.Map;
  
  import com.aliyun.odps.Project.ProjectModel;
  import com.aliyun.odps.account.AccountFormat;
  import com.aliyun.odps.rest.ResourceBuilder;
  import com.aliyun.odps.rest.RestClient;
  import com.aliyun.odps.utils.StringUtils;
  
  /**
   * Projects表示ODPS中所有{@link Project}的集合
   */
  public class Projects {
  
    /**
     * the response of list projects call
     */
    @Root(name = "Projects", strict = false)
    static class ListProjectResponse {
  
      @ElementList(entry = "Project", inline = true, required = false)
      List<ProjectModel> projects = new LinkedList<ProjectModel>();
  
      @Element(name = "Marker", required = false)
      @Convert(SimpleXmlUtils.EmptyStringConverter.class)
      String marker;
  
      @Element(name = "MaxItems", required = false)
      int maxItems;
    }
  
    private Odps odps;
    private RestClient client;
  
    Projects(RestClient client) {
      this.client = client;
    }
  
    Projects(Odps odps) {
      this.odps = odps;
      this.client = odps.getRestClient();
    }
  
    /**
     * 获得默认{@link Project}对象
     *
     * @return {@link Project}
     * @throws NoSuchObjectException Project不存在
     */
    public Project get() throws OdpsException {
      return get(getDefaultProjectName());
    }
  
    /**
     * 获取指定{@link Project}
     *
     * @param projectName {@link Project}名称
     * @return {@link Project}对象
     * @throws OdpsException No exception will throw, however for compatibility
     */
    public Project get(String projectName) throws OdpsException {
      ProjectModel model = new ProjectModel();
      model.name = projectName;
      Project prj = new Project(model, odps);
  
      return prj;
    }
  
    /**
     * 检查{@link Project}是否存在
     *
     * @param projectName Project名称
     * @return Project在ODPS中存在返回true, 否则返回false
     */
    public boolean exists(String projectName) throws OdpsException {
      try {
        Project project = get(projectName);
        project.reload();
        return true;
      } catch (NoSuchObjectException e) {
        return false;
      }
    }
  
    private String getDefaultProjectName() {
      String project = client.getDefaultProject();
      if (project == null || project.length() == 0) {
        throw new RuntimeException("No default project specified.");
      }
      return project;
    }
  
    /**
     * 创建 external 项目
     * @param projectName 项目名称
     * @param comment 项目 comment
     * @param refProjectName 项目引用的 managed 项目名称
     * @param extProperties 映射 external 项目需要的属性，当前支持 hive external 项目
     *           <pre>{@code
     *             Project.ExternalProjectProperties extProps = new Project.ExternalProjectProperties("hive");
     *             // required properties for 'hive' source
     *             extProps.addProperty("hms.ips", "10.0.0.1:5300,10.0.0.2:5300");
     *             extProps.addProperty("hive.database.name", "odps");
     *             extProps.addProperty("hdfs.namenode.ips", "192.168.0.12:3829,192.168.0.5:3389");
     *             // network properties
     *             extProps.addNetworkProperty("odps.external.net.vpc", "false");
     *             // if vpc network is enabled by setting 'odps.external.net.vpc' to 'true', the following three properties must
     *             // also be set:
     *             // extProps.addNetworkProperty("odps.vpc.id", "vpc-ajfiewojlfew");
     *             // extProps.addNetworkProperty("odps.vpc.region", "cn-shanghai");
     *             // extProps.addNetworkProperty("odps.vpc.access.ips", "192.168.0.200-210:8900,192.168.0.10:8399");
     *           }</pre>
     * @throws OdpsException OdpsException
     */
    public void createExternalProject(final String projectName,
                                      final String comment,
                                      final String refProjectName,
                                      final Project.ExternalProjectProperties extProperties)
            throws OdpsException {
  
      if (extProperties == null || refProjectName == null) {
        throw new OdpsException("External project must specify refProjectName and extProperties");
      }
  
      String resource = ResourceBuilder.buildProjectsResource();
      HashMap<String, String> properties = new HashMap<>();
      properties.put("external_project_properties", extProperties.toJson());
      properties.put("external_project_ref_project", refProjectName);
  
      String xml = marshal(projectName, Project.ProjectType.external, null,
                           null, null, comment, properties, null, null);
  
      HashMap<String, String> headers = new HashMap<String, String>();
      headers.put(Headers.CONTENT_TYPE, "application/xml");
  
      client.stringRequest(resource, "POST", null, headers, xml);
    }
  
    /**
     * 删除 external 项目，只允许 project owner 删除。
     * @param projectName 项目名称
     * @throws OdpsException OdpsException
     */
    public void deleteExternalProject(final String projectName)
            throws OdpsException {
      String resource = ResourceBuilder.buildProjectResource(projectName);
  
      HashMap<String, String> headers = new HashMap<>();
      headers.put(Headers.CONTENT_TYPE, "application/xml");
      client.stringRequest(resource, "DELETE", null, headers, "");
    }
  
    /**
     * 更新 Project
     *
     * @param properties Project 属性。目前该API仅能够设置Project的属性。Project属性列表在每个ODPS版本下会有不同，
     *                   建议用户直接忽略此参数（忽略此参数时，会依据系统默认值填充）。如果有明确的特殊需求，可以寻求技术支持。 目前，Project的属性： a.
     *                   odps.security.ip.whitelist:能否访问Project的Ip白名单列表； b. READ_TABLE_MAX_ROW:select语句返回数据的最大行数；
     */
    public void updateProject(final String projectName, final Map<String, String> properties)
            throws OdpsException {
      updateProject(projectName, null, null, null, properties, null);
    }
  
    /**
     * 更新 Project，暂时不直接向用户开放
     *
     * @param status     Project状态，包括：AVAILABLE | DELETING | FROZEN。 AVAILABLE
     *                   表示Project可以正常工作，AVAILABLE Project可以FROZEN。 FROZEN 表示Project被冻结，冻结后Project拒绝任何API访问。FROZEN
     *                   Project可以AVAILABLE或者DELETING。 DELETING Project不可以转变为其他状态，表示15天后Project会被系统回收，数据全部清除。
     * @param owner      Project所有者账号
     * @param properties Project 属性。目前该API仅能够设置Project的属性。Project属性列表在每个ODPS版本下会有不同，
     *                   建议用户直接忽略此参数（忽略此参数时，会依据系统默认值填充）。如果有明确的特殊需求，可以寻求技术支持。 目前，Project的属性： a.
     *                   odps.security.ip.whitelist:能否访问Project的Ip白名单列表； b. READ_TABLE_MAX_ROW:select语句返回数据的最大行数；
     * @param clusters   Project对应的计算集群列表
     */
    public void updateProject(final String projectName, final Project.Status status,
                               final String owner, final String comment,
                               final Map<String, String> properties,
                               List<Project.Cluster> clusters)
            throws OdpsException {
      updateProject(projectName, status, owner, comment, properties, clusters, null);
    }
  
    /**
     * 更新 Project，暂时不直接向用户开放
     *
     * @param status     Project状态，包括：AVAILABLE | DELETING | FROZEN。 AVAILABLE
     *                   表示Project可以正常工作，AVAILABLE Project可以FROZEN。 FROZEN 表示Project被冻结，冻结后Project拒绝任何API访问。FROZEN
     *                   Project可以AVAILABLE或者DELETING。 DELETING Project不可以转变为其他状态，表示15天后Project会被系统回收，数据全部清除。
     * @param owner      Project所有者账号
     * @param properties Project 属性。目前该API仅能够设置Project的属性。Project属性列表在每个ODPS版本下会有不同，
     *                   建议用户直接忽略此参数（忽略此参数时，会依据系统默认值填充）。如果有明确的特殊需求，可以寻求技术支持。 目前，Project的属性： a.
     *                   odps.security.ip.whitelist:能否访问Project的Ip白名单列表； b. READ_TABLE_MAX_ROW:select语句返回数据的最大行数；
     * @param clusters   Project对应的计算集群列表
     */
    public void updateProject(final String projectName, final Project.Status status,
                              final String owner, final String comment,
                              final Map<String, String> properties,
                              List<Project.Cluster> clusters, QuotaIdentifier defaultQuota)
        throws OdpsException {
      String resource = ResourceBuilder.buildProjectResource(projectName);
  
      String xml =
          marshal(projectName, null, owner, null, status, comment, properties, clusters, defaultQuota);
  
      HashMap<String, String> headers = new HashMap<String, String>();
      headers.put(Headers.CONTENT_TYPE, "application/xml");
  
      client.stringRequest(resource, "PUT", null, headers, xml);
    }
  
    /**
     * 删除项目
     * @param projectName 项目名称
     * @param isImmediate 是否立即删除
     *                    是 表示立即删除，项目会不可逆的被永久删除
     *                    否 表示逻辑删除，项目被标记成删除状态，还可以恢复
     * @return instance 对象，立即删除时为删除项目的 instance， 逻辑删除时始终返回 null
     *
     * @throws OdpsException 出错异常
     */
    public Instance delete(String projectName, boolean isImmediate) throws OdpsException {
  
      Map<String, String> params = new HashMap<>();
      if (isImmediate) {
        params.put(Params.ODPS_PERMANENT, "true");
      }
  
      Response
          response =
          client.request(ResourceBuilder.buildProjectResource(projectName), "DELETE", params, null,
                         null);
      if (isImmediate && response.getStatus() == 202) {
        String location = response.getHeaders().get(Headers.LOCATION);
        if (StringUtils.isNullOrEmpty(location)) {
          throw new OdpsException("Invalid response, instance location required when delete project immediately.");
        }
  
        String token = response.getHeaders().get(Headers.ODPS_INSTSNCE_TOKEN);
        if (StringUtils.isNullOrEmpty(token)) {
          throw new OdpsException("Invalid response, instance token required when delete project immediately.");
        }
  
        String instanceId = location.substring(location.lastIndexOf("/") + 1);
  
        BearerTokenAccount bearerAccount = new BearerTokenAccount(token);
        Odps bearerOdps = new Odps(bearerAccount);
        bearerOdps.setEndpoint(odps.getEndpoint());
        bearerOdps.setDefaultProject("admin_task_project");
        return bearerOdps.instances().get(instanceId);
      }
  
      return null;
    }
    /**
     * 创建 managed 项目
     *
     * @param param 创建项目的参数对象
     */
    public void create(CreateProjectParam param) throws OdpsException {
      String xml = marshal(param.getProjectModel());
  
      HashMap<String, String> headers = new HashMap<String, String>();
      headers.put(Headers.CONTENT_TYPE, "application/xml");
      client.stringRequest(ResourceBuilder.buildProjectsResource(), "POST", null, headers, xml);
    }
  
    public Iterator<Project> iteratorByFilter(ProjectFilter filter) {
        return new ProjectListIterator(filter);
    }
  
    /**
     * 获取 Project 列表
     */
    public Iterator<Project> iterator(String owner) {
      ProjectFilter filter = new ProjectFilter();
      filter.setOwner(owner);
      return new ProjectListIterator(filter);
    }
  
    /**
     * 获取 Project 列表的 iterable 接口
     */
    public Iterable<Project> iterable(final String owner) {
      return new Iterable<Project>() {
        @Override
        public Iterator<Project> iterator() {
          ProjectFilter filter = new ProjectFilter();
          filter.setOwner(owner);
          return new ProjectListIterator(filter);
        }
      };
    }
  
    private class ProjectListIterator extends ListIterator<Project> {
      Map<String, String> params = new HashMap<String, String>();
  
      private ProjectFilter filter;
  
      ProjectListIterator(ProjectFilter filter) {
        this.filter = filter;
      }
  
      @Override
      public List<Project> list(String marker, long maxItems) {
        if (marker != null) {
          params.put("marker", marker);
        }
        if (maxItems >= 0) {
          params.put("maxitems", String.valueOf(maxItems));
        }
        return list();
      }
  
      @Override
      public String getMarker() {
        return params.get("marker");
      }
  
      @Override
      protected List<Project> list() {
        try {
          ArrayList<Project> projects = new ArrayList<Project>();
  
          String lastMarker = params.get("marker");
  
          if (params.containsKey("marker") && lastMarker.length() == 0) {
            return null;
          }
  
          if (filter != null) {
            filter.addTo(params);
          }
          AccountFormat.setParam(odps.getAccountFormat(), params);
  
          ListProjectResponse resp = client.request(ListProjectResponse.class,
                  ResourceBuilder.buildProjectsResource(),
                  "GET", params);
  
          for (Project.ProjectModel model : resp.projects) {
            Project t = new Project(model, odps);
            projects.add(t);
          }
  
          params.put("marker", resp.marker);
  
          return projects;
        } catch (OdpsException e) {
          throw new RuntimeException(e.getMessage(), e);
        }
      }
    }
  
    private static String marshal(final ProjectModel model) throws OdpsException {
      String xml = null;
      try {
        xml = SimpleXmlUtils.marshal(model);
      } catch (Exception e) {
        throw new OdpsException("Marshal project model failed", e);
      }
      return xml;
    }
  
    private static String marshal(final String projectName, final Project.ProjectType projectType,
                                  final String projectOwner, final String groupName,
                                  final Project.Status status, final String comment,
                                  final Map<String, String> properties, List<Project.Cluster> clusters, QuotaIdentifier defaultQuota)
            throws OdpsException {
      Project.ProjectModel model = new Project.ProjectModel();
  
      model.name = projectName;
  
      // type
      if (projectType != null) {
        model.type = projectType.toString().toLowerCase();
      }
  
      // Owner
      if (projectOwner != null) {
        model.owner = projectOwner.trim();
      } else {
        model.owner = null;
      }
  
      // State
      if (status != null) {
        model.state = status.toString().toUpperCase();
      }
  
      // Comment
      if (comment != null) {
        model.comment = comment;
      }
  
      // ProjectGroupName
      model.projectGroupName = groupName;
  
      // Properties
      if (properties != null) {
        model.properties = new LinkedHashMap<String, String>(properties);
      }
  
      if (clusters != null) {
        model.clusters = new Project.Clusters();
        model.clusters.entries = clusters;
      }
  
      model.defaultQuota = defaultQuota;
  
      return marshal(model);
    }
  }
  