/**
 * 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.tez.common.security;

import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.regex.Pattern;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.hadoop.classification.InterfaceAudience.Private;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.yarn.conf.YarnConfiguration;
import org.apache.tez.common.TezCommonUtils;
import org.apache.tez.dag.api.TezConfiguration;
import org.apache.tez.dag.api.TezConstants;

import com.google.common.collect.Sets;

/**
 * Parser for extracting ACL information from Configs
 */
@Private
public class ACLConfigurationParser {

  private static final Logger LOG = LoggerFactory.getLogger(ACLConfigurationParser.class);

  private final Configuration conf;
  private final Map<ACLType, Set<String>> allowedUsers;
  private final Map<ACLType, Set<String>> allowedGroups;
  private static final Pattern splitPattern = Pattern.compile("\\s+");

  public ACLConfigurationParser(Configuration conf) {
    this(conf, false);
  }

  public ACLConfigurationParser(Configuration conf, boolean dagACLs) {
    this.conf = conf;
    allowedUsers = new HashMap<ACLType, Set<String>>(2);
    allowedGroups = new HashMap<ACLType, Set<String>>(2);
    parse(dagACLs);
  }


  private void parse(boolean dagACLs) {
    if (!dagACLs) {
      parseACLType(YarnConfiguration.YARN_ADMIN_ACL, ACLType.YARN_ADMIN_ACL);
      parseACLType(TezConfiguration.TEZ_AM_VIEW_ACLS, ACLType.AM_VIEW_ACL);
      parseACLType(TezConfiguration.TEZ_AM_MODIFY_ACLS, ACLType.AM_MODIFY_ACL);
    } else {
      parseACLType(YarnConfiguration.YARN_ADMIN_ACL, ACLType.YARN_ADMIN_ACL);
      parseACLType(TezConstants.TEZ_DAG_VIEW_ACLS, ACLType.DAG_VIEW_ACL);
      parseACLType(TezConstants.TEZ_DAG_MODIFY_ACLS, ACLType.DAG_MODIFY_ACL);
    }
  }

  private boolean isWildCard(String aclStr) {
    return aclStr.trim().equals(ACLManager.WILDCARD_ACL_VALUE);
  }

  private void parseACLType(String configProperty, ACLType aclType) {
    String aclsStr = conf.get(configProperty);
    if (aclsStr == null || aclsStr.isEmpty()) {
      return;
    }
    if (isWildCard(aclsStr)) {
      allowedUsers.put(aclType, Sets.newHashSet(ACLManager.WILDCARD_ACL_VALUE));
      return;
    }

    final String[] splits = splitPattern.split(aclsStr);
    int counter = -1;
    String userListStr = null;
    String groupListStr = null;
    for (String s : splits) {
      if (s.isEmpty()) {
        if (userListStr != null) {
          continue;
        }
      }
      ++counter;
      if (counter == 0) {
        userListStr = s;
      } else if (counter == 1) {
        groupListStr = s;
      } else {
        LOG.warn("Invalid configuration specified for " + configProperty
            + ", ignoring configured ACLs, value=" + aclsStr);
        return;
      }
    }

    if (userListStr == null) {
      return;
    }
    if (userListStr.length() >= 1) {
      allowedUsers.put(aclType, Sets.newLinkedHashSet(Arrays.asList(TezCommonUtils.getTrimmedStrings(userListStr))));
    }
    if (groupListStr != null && groupListStr.length() >= 1) {
      allowedGroups.put(aclType, Sets.newLinkedHashSet(Arrays.asList(TezCommonUtils.getTrimmedStrings(groupListStr))));
    }
  }

  public Map<ACLType, Set<String>> getAllowedUsers() {
    return Collections.unmodifiableMap(allowedUsers);
  }

  public Map<ACLType, Set<String>> getAllowedGroups() {
    return Collections.unmodifiableMap(allowedGroups);
  }

  public void addAllowedUsers(Map<ACLType, Set<String>> additionalAllowedUsers) {
    for (Entry<ACLType, Set<String>> entry : additionalAllowedUsers.entrySet()) {
      if (allowedUsers.containsKey(entry.getKey())) {
        allowedUsers.get(entry.getKey()).addAll(entry.getValue());
      } else {
        allowedUsers.put(entry.getKey(), entry.getValue());
      }
    }
  }

  public void addAllowedGroups(Map<ACLType, Set<String>> additionalAllowedGroups) {
    for (Entry<ACLType, Set<String>> entry : additionalAllowedGroups.entrySet()) {
      if (allowedGroups.containsKey(entry.getKey())) {
        allowedGroups.get(entry.getKey()).addAll(entry.getValue());
      } else {
        allowedGroups.put(entry.getKey(), entry.getValue());
      }
    }
  }

}
