Package org.apache.hadoop.hbase.security.visibility

Source Code of org.apache.hadoop.hbase.security.visibility.DefaultVisibilityLabelServiceImpl

/**
* 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.hadoop.hbase.security.visibility;

import static org.apache.hadoop.hbase.TagType.VISIBILITY_TAG_TYPE;
import static org.apache.hadoop.hbase.security.visibility.VisibilityConstants.LABELS_TABLE_FAMILY;
import static org.apache.hadoop.hbase.security.visibility.VisibilityConstants.LABELS_TABLE_NAME;
import static org.apache.hadoop.hbase.security.visibility.VisibilityConstants.LABEL_QUALIFIER;
import static org.apache.hadoop.hbase.security.visibility.VisibilityConstants.SORTED_ORDINAL_SERIALIZATION_FORMAT;
import static org.apache.hadoop.hbase.security.visibility.VisibilityUtils.SYSTEM_LABEL;

import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.classification.InterfaceAudience;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.Cell;
import org.apache.hadoop.hbase.CellUtil;
import org.apache.hadoop.hbase.HConstants.OperationStatusCode;
import org.apache.hadoop.hbase.Tag;
import org.apache.hadoop.hbase.client.Delete;
import org.apache.hadoop.hbase.client.Mutation;
import org.apache.hadoop.hbase.client.Put;
import org.apache.hadoop.hbase.client.Scan;
import org.apache.hadoop.hbase.coprocessor.RegionCoprocessorEnvironment;
import org.apache.hadoop.hbase.filter.Filter;
import org.apache.hadoop.hbase.io.util.StreamUtils;
import org.apache.hadoop.hbase.regionserver.HRegion;
import org.apache.hadoop.hbase.regionserver.OperationStatus;
import org.apache.hadoop.hbase.regionserver.RegionScanner;
import org.apache.hadoop.hbase.security.User;
import org.apache.hadoop.hbase.security.access.AccessControlLists;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.hbase.util.Pair;
import org.apache.hadoop.hbase.zookeeper.ZooKeeperWatcher;

import com.google.common.collect.Lists;

@InterfaceAudience.Private
public class DefaultVisibilityLabelServiceImpl implements VisibilityLabelService {

  private static final Log LOG = LogFactory.getLog(DefaultVisibilityLabelServiceImpl.class);

  // "system" label is having an ordinal value 1.
  private static final int SYSTEM_LABEL_ORDINAL = 1;
  private static final Tag[] LABELS_TABLE_TAGS = new Tag[1];
  private static final byte[] DUMMY_VALUE = new byte[0];

  private volatile int ordinalCounter = -1;
  private Configuration conf;
  private HRegion labelsRegion;
  private VisibilityLabelsCache labelsCache;
  private List<ScanLabelGenerator> scanLabelGenerators;

  static {
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    DataOutputStream dos = new DataOutputStream(baos);
    try {
      StreamUtils.writeRawVInt32(dos, SYSTEM_LABEL_ORDINAL);
    } catch (IOException e) {
      // We write to a byte array. No Exception can happen.
    }
    LABELS_TABLE_TAGS[0] = new Tag(VISIBILITY_TAG_TYPE, baos.toByteArray());
  }

  public DefaultVisibilityLabelServiceImpl() {

  }

  @Override
  public void setConf(Configuration conf) {
    this.conf = conf;
  }

  @Override
  public Configuration getConf() {
    return this.conf;
  }

  @Override
  public void init(RegionCoprocessorEnvironment e) throws IOException {
    ZooKeeperWatcher zk = e.getRegionServerServices().getZooKeeper();
    try {
      labelsCache = VisibilityLabelsCache.createAndGet(zk, this.conf);
    } catch (IOException ioe) {
      LOG.error("Error creating VisibilityLabelsCache", ioe);
      throw ioe;
    }
    this.scanLabelGenerators = VisibilityUtils.getScanLabelGenerators(this.conf);
    if (e.getRegion().getRegionInfo().getTable().equals(LABELS_TABLE_NAME)) {
      this.labelsRegion = e.getRegion();
      Pair<Map<String, Integer>, Map<String, List<Integer>>> labelsAndUserAuths =
          extractLabelsAndAuths(getExistingLabelsWithAuths());
      Map<String, Integer> labels = labelsAndUserAuths.getFirst();
      Map<String, List<Integer>> userAuths = labelsAndUserAuths.getSecond();
      // Add the "system" label if it is not added into the system yet
      addSystemLabel(this.labelsRegion, labels, userAuths);
      int ordinal = SYSTEM_LABEL_ORDINAL; // Ordinal 1 is reserved for "system" label.
      for (Integer i : labels.values()) {
        if (i > ordinal) {
          ordinal = i;
        }
      }
      this.ordinalCounter = ordinal + 1;
      if (labels.size() > 0) {
        // If there is no data need not write to zk
        byte[] serialized = VisibilityUtils.getDataToWriteToZooKeeper(labels);
        this.labelsCache.writeToZookeeper(serialized, true);
        this.labelsCache.refreshLabelsCache(serialized);
      }
      if (userAuths.size() > 0) {
        byte[] serialized = VisibilityUtils.getUserAuthsDataToWriteToZooKeeper(userAuths);
        this.labelsCache.writeToZookeeper(serialized, false);
        this.labelsCache.refreshUserAuthsCache(serialized);
      }
    }
  }

  protected List<List<Cell>> getExistingLabelsWithAuths() throws IOException {
    Scan scan = new Scan();
    RegionScanner scanner = labelsRegion.getScanner(scan);
    List<List<Cell>> existingLabels = new ArrayList<List<Cell>>();
    try {
      while (true) {
        List<Cell> cells = new ArrayList<Cell>();
        scanner.next(cells);
        if (cells.isEmpty()) {
          break;
        }
        existingLabels.add(cells);
      }
    } finally {
      scanner.close();
    }
    return existingLabels;
  }

  protected Pair<Map<String, Integer>, Map<String, List<Integer>>> extractLabelsAndAuths(
      List<List<Cell>> labelDetails) {
    Map<String, Integer> labels = new HashMap<String, Integer>();
    Map<String, List<Integer>> userAuths = new HashMap<String, List<Integer>>();
    for (List<Cell> cells : labelDetails) {
      for (Cell cell : cells) {
        if (Bytes.equals(cell.getQualifierArray(), cell.getQualifierOffset(),
            cell.getQualifierLength(), LABEL_QUALIFIER, 0, LABEL_QUALIFIER.length)) {
          labels.put(
              Bytes.toString(cell.getValueArray(), cell.getValueOffset(), cell.getValueLength()),
              Bytes.toInt(cell.getRowArray(), cell.getRowOffset()));
        } else {
          // These are user cells who has authorization for this label
          String user = Bytes.toString(cell.getQualifierArray(), cell.getQualifierOffset(),
              cell.getQualifierLength());
          List<Integer> auths = userAuths.get(user);
          if (auths == null) {
            auths = new ArrayList<Integer>();
            userAuths.put(user, auths);
          }
          auths.add(Bytes.toInt(cell.getRowArray(), cell.getRowOffset()));
        }
      }
    }
    return new Pair<Map<String, Integer>, Map<String, List<Integer>>>(labels, userAuths);
  }

  protected void addSystemLabel(HRegion region, Map<String, Integer> labels,
      Map<String, List<Integer>> userAuths) throws IOException {
    if (!labels.containsKey(SYSTEM_LABEL)) {
      Put p = new Put(Bytes.toBytes(SYSTEM_LABEL_ORDINAL));
      p.addImmutable(LABELS_TABLE_FAMILY, LABEL_QUALIFIER, Bytes.toBytes(SYSTEM_LABEL));
      // Set auth for "system" label for all super users.
      List<String> superUsers = getSystemAndSuperUsers();
      for (String superUser : superUsers) {
        p.addImmutable(LABELS_TABLE_FAMILY, Bytes.toBytes(superUser), DUMMY_VALUE,
            LABELS_TABLE_TAGS);
      }
      region.put(p);
      labels.put(SYSTEM_LABEL, SYSTEM_LABEL_ORDINAL);
      for (String superUser : superUsers) {
        List<Integer> auths = userAuths.get(superUser);
        if (auths == null) {
          auths = new ArrayList<Integer>(1);
          userAuths.put(superUser, auths);
        }
        auths.add(SYSTEM_LABEL_ORDINAL);
      }
    }
  }

  protected List<String> getSystemAndSuperUsers() throws IOException {
    User user = User.getCurrent();
    if (user == null) {
      throw new IOException("Unable to obtain the current user, "
          + "authorization checks for internal operations will not work correctly!");
    }
    if (LOG.isTraceEnabled()) {
      LOG.trace("Current user name is " + user.getShortName());
    }
    String currentUser = user.getShortName();
    List<String> superUsers = Lists.asList(currentUser,
        this.conf.getStrings(AccessControlLists.SUPERUSER_CONF_KEY, new String[0]));
    return superUsers;
  }

  @Override
  public OperationStatus[] addLabels(List<byte[]> labels) throws IOException {
    assert labelsRegion != null;
    OperationStatus[] finalOpStatus = new OperationStatus[labels.size()];
    List<Mutation> puts = new ArrayList<Mutation>(labels.size());
    int i = 0;
    for (byte[] label : labels) {
      String labelStr = Bytes.toString(label);
      if (this.labelsCache.getLabelOrdinal(labelStr) > 0) {
        finalOpStatus[i] = new OperationStatus(OperationStatusCode.FAILURE,
            new LabelAlreadyExistsException("Label '" + labelStr + "' already exists"));
      } else {
        Put p = new Put(Bytes.toBytes(ordinalCounter));
        p.addImmutable(LABELS_TABLE_FAMILY, LABEL_QUALIFIER, label, LABELS_TABLE_TAGS);
        if (LOG.isDebugEnabled()) {
          LOG.debug("Adding the label " + labelStr);
        }
        puts.add(p);
        ordinalCounter++;
      }
      i++;
    }
    if (mutateLabelsRegion(puts, finalOpStatus)) {
      updateZk(true);
    }
    return finalOpStatus;
  }

  @Override
  public OperationStatus[] setAuths(byte[] user, List<byte[]> authLabels) throws IOException {
    assert labelsRegion != null;
    OperationStatus[] finalOpStatus = new OperationStatus[authLabels.size()];
    List<Mutation> puts = new ArrayList<Mutation>(authLabels.size());
    int i = 0;
    for (byte[] auth : authLabels) {
      String authStr = Bytes.toString(auth);
      int labelOrdinal = this.labelsCache.getLabelOrdinal(authStr);
      if (labelOrdinal == 0) {
        // This label is not yet added. 1st this should be added to the system
        finalOpStatus[i] = new OperationStatus(OperationStatusCode.FAILURE,
            new InvalidLabelException("Label '" + authStr + "' doesn't exists"));
      } else {
        Put p = new Put(Bytes.toBytes(labelOrdinal));
        p.addImmutable(LABELS_TABLE_FAMILY, user, DUMMY_VALUE, LABELS_TABLE_TAGS);
        puts.add(p);
      }
      i++;
    }
    if (mutateLabelsRegion(puts, finalOpStatus)) {
      updateZk(false);
    }
    return finalOpStatus;
  }

  @Override
  public OperationStatus[] clearAuths(byte[] user, List<byte[]> authLabels) throws IOException {
    assert labelsRegion != null;
    OperationStatus[] finalOpStatus = new OperationStatus[authLabels.size()];
    List<String> currentAuths = this.getAuths(user, true);
    List<Mutation> deletes = new ArrayList<Mutation>(authLabels.size());
    int i = 0;
    for (byte[] authLabel : authLabels) {
      String authLabelStr = Bytes.toString(authLabel);
      if (currentAuths.contains(authLabelStr)) {
        int labelOrdinal = this.labelsCache.getLabelOrdinal(authLabelStr);
        assert labelOrdinal > 0;
        Delete d = new Delete(Bytes.toBytes(labelOrdinal));
        d.deleteColumns(LABELS_TABLE_FAMILY, user);
        deletes.add(d);
      } else {
        // This label is not set for the user.
        finalOpStatus[i] = new OperationStatus(OperationStatusCode.FAILURE,
            new InvalidLabelException("Label '" + authLabelStr + "' is not set for the user "
                + Bytes.toString(user)));
      }
      i++;
    }
    if (mutateLabelsRegion(deletes, finalOpStatus)) {
      updateZk(false);
    }
    return finalOpStatus;
  }

  /**
   * Adds the mutations to labels region and set the results to the finalOpStatus. finalOpStatus
   * might have some entries in it where the OpStatus is FAILURE. We will leave those and set in
   * others in the order.
   * @param mutations
   * @param finalOpStatus
   * @return whether we need a ZK update or not.
   */
  private boolean mutateLabelsRegion(List<Mutation> mutations, OperationStatus[] finalOpStatus)
      throws IOException {
    OperationStatus[] opStatus = this.labelsRegion.batchMutate(mutations
        .toArray(new Mutation[mutations.size()]));
    int i = 0;
    boolean updateZk = false;
    for (OperationStatus status : opStatus) {
      // Update the zk when atleast one of the mutation was added successfully.
      updateZk = updateZk || (status.getOperationStatusCode() == OperationStatusCode.SUCCESS);
      for (; i < finalOpStatus.length; i++) {
        if (finalOpStatus[i] == null) {
          finalOpStatus[i] = status;
          break;
        }
      }
    }
    return updateZk;
  }

  @Override
  public List<String> getAuths(byte[] user, boolean systemCall) throws IOException {
    assert (labelsRegion != null || systemCall);
    if (systemCall || labelsRegion == null) {
      return this.labelsCache.getAuths(Bytes.toString(user));
    }
    Scan s = new Scan();
    s.addColumn(LABELS_TABLE_FAMILY, user);
    Filter filter = VisibilityUtils.createVisibilityLabelFilter(this.labelsRegion,
        new Authorizations(SYSTEM_LABEL));
    s.setFilter(filter);
    List<String> auths = new ArrayList<String>();
    RegionScanner scanner = this.labelsRegion.getScanner(s);
    List<Cell> results = new ArrayList<Cell>(1);
    while (true) {
      scanner.next(results);
      if (results.isEmpty()) break;
      Cell cell = results.get(0);
      int ordinal = Bytes.toInt(cell.getRowArray(), cell.getRowOffset(), cell.getRowLength());
      String label = this.labelsCache.getLabel(ordinal);
      if (label != null) {
        auths.add(label);
      }
      results.clear();
    }
    return auths;
  }

  @Override
  public List<Tag> createVisibilityExpTags(String visExpression, boolean withSerializationFormat,
      boolean checkAuths) throws IOException {
    Set<Integer> auths = null;
    if (checkAuths) {
      auths = this.labelsCache.getAuthsAsOrdinals(VisibilityUtils.getActiveUser().getShortName());
    }
    return VisibilityUtils.createVisibilityExpTags(visExpression, withSerializationFormat,
        checkAuths, auths, labelsCache);
  }

  protected void updateZk(boolean labelAddition) throws IOException {
    // We will add to zookeeper here.
    // TODO we should add the delta only to zk. Else this will be a very heavy op and when there are
    // so many labels and auth in the system, we will end up adding lots of data to zk. Most
    // possibly we will exceed zk node data limit!
    Pair<Map<String, Integer>, Map<String, List<Integer>>> labelsAndUserAuths =
        extractLabelsAndAuths(getExistingLabelsWithAuths());
    Map<String, Integer> existingLabels = labelsAndUserAuths.getFirst();
    Map<String, List<Integer>> userAuths = labelsAndUserAuths.getSecond();
    if (labelAddition) {
      byte[] serialized = VisibilityUtils.getDataToWriteToZooKeeper(existingLabels);
      this.labelsCache.writeToZookeeper(serialized, true);
    } else {
      byte[] serialized = VisibilityUtils.getUserAuthsDataToWriteToZooKeeper(userAuths);
      this.labelsCache.writeToZookeeper(serialized, false);
    }
  }

  @Override
  public VisibilityExpEvaluator getVisibilityExpEvaluator(Authorizations authorizations)
      throws IOException {
    // If a super user issues a get/scan, he should be able to scan the cells
    // irrespective of the Visibility labels
    if (isReadFromSuperUser()) {
      return new VisibilityExpEvaluator() {
        @Override
        public boolean evaluate(Cell cell) throws IOException {
          return true;
        }
      };
    }
    List<String> authLabels = null;
    for (ScanLabelGenerator scanLabelGenerator : scanLabelGenerators) {
      try {
        // null authorizations to be handled inside SLG impl.
        authLabels = scanLabelGenerator.getLabels(VisibilityUtils.getActiveUser(), authorizations);
        authLabels = (authLabels == null) ? new ArrayList<String>() : authLabels;
        authorizations = new Authorizations(authLabels);
      } catch (Throwable t) {
        LOG.error(t);
        throw new IOException(t);
      }
    }
    int labelsCount = this.labelsCache.getLabelsCount();
    final BitSet bs = new BitSet(labelsCount + 1); // ordinal is index 1 based
    if (authLabels != null) {
      for (String authLabel : authLabels) {
        int labelOrdinal = this.labelsCache.getLabelOrdinal(authLabel);
        if (labelOrdinal != 0) {
          bs.set(labelOrdinal);
        }
      }
    }

    return new VisibilityExpEvaluator() {
      @Override
      public boolean evaluate(Cell cell) throws IOException {
        boolean visibilityTagPresent = false;
        // Save an object allocation where we can
        if (cell.getTagsLength() > 0) {
          Iterator<Tag> tagsItr = CellUtil.tagsIterator(cell.getTagsArray(), cell.getTagsOffset(),
              cell.getTagsLength());
          while (tagsItr.hasNext()) {
            boolean includeKV = true;
            Tag tag = tagsItr.next();
            if (tag.getType() == VISIBILITY_TAG_TYPE) {
              visibilityTagPresent = true;
              int offset = tag.getTagOffset();
              int endOffset = offset + tag.getTagLength();
              while (offset < endOffset) {
                Pair<Integer, Integer> result = StreamUtils
                    .readRawVarint32(tag.getBuffer(), offset);
                int currLabelOrdinal = result.getFirst();
                if (currLabelOrdinal < 0) {
                  // check for the absence of this label in the Scan Auth labels
                  // ie. to check BitSet corresponding bit is 0
                  int temp = -currLabelOrdinal;
                  if (bs.get(temp)) {
                    includeKV = false;
                    break;
                  }
                } else {
                  if (!bs.get(currLabelOrdinal)) {
                    includeKV = false;
                    break;
                  }
                }
                offset += result.getSecond();
              }
              if (includeKV) {
                // We got one visibility expression getting evaluated to true. Good to include this
                // KV in the result then.
                return true;
              }
            }
          }
        }
        return !(visibilityTagPresent);
      }
    };
  }

  protected boolean isReadFromSuperUser() throws IOException {
    byte[] user = Bytes.toBytes(VisibilityUtils.getActiveUser().getShortName());
    return havingSystemAuth(user);
  }

  @Override
  public boolean havingSystemAuth(byte[] user) throws IOException {
    List<String> auths = this.getAuths(user, true);
    if (LOG.isTraceEnabled()) {
      LOG.trace("The auths for user " + Bytes.toString(user) + " are " + auths);
    }
    return auths.contains(SYSTEM_LABEL);
  }

  @Override
  public boolean matchVisibility(List<Tag> putVisTags, Byte putTagsFormat, List<Tag> deleteVisTags,
      Byte deleteTagsFormat) throws IOException {
    if ((deleteTagsFormat != null && deleteTagsFormat == SORTED_ORDINAL_SERIALIZATION_FORMAT)
        && (putTagsFormat == null || putTagsFormat == SORTED_ORDINAL_SERIALIZATION_FORMAT)) {
      if (putVisTags.size() == 0) {
        // Early out if there are no tags in the cell
        return false;
      }
      if (putTagsFormat == null) {
        return matchUnSortedVisibilityTags(putVisTags, deleteVisTags);
      } else {
        return matchOrdinalSortedVisibilityTags(putVisTags, deleteVisTags);
      }
    }
    throw new IOException("Unexpected tag format passed for comparison, deleteTagsFormat : "
        + deleteTagsFormat + ", putTagsFormat : " + putTagsFormat);
  }

  /**
   * @param putVisTags Visibility tags in Put Mutation
   * @param deleteVisTags Visibility tags in Delete Mutation
   * @return true when all the visibility tags in Put matches with visibility tags in Delete.
   * This is used when, at least one set of tags are not sorted based on the label ordinal.
   */
  private static boolean matchUnSortedVisibilityTags(List<Tag> putVisTags,
      List<Tag> deleteVisTags) throws IOException {
    return compareTagsOrdinals(sortTagsBasedOnOrdinal(putVisTags),
        sortTagsBasedOnOrdinal(deleteVisTags));
  }

  /**
   * @param putVisTags Visibility tags in Put Mutation
   * @param deleteVisTags Visibility tags in Delete Mutation
   * @return true when all the visibility tags in Put matches with visibility tags in Delete.
   * This is used when both the set of tags are sorted based on the label ordinal.
   */
  private static boolean matchOrdinalSortedVisibilityTags(List<Tag> putVisTags,
      List<Tag> deleteVisTags) {
    boolean matchFound = false;
    // If the size does not match. Definitely we are not comparing the equal tags.
    if ((deleteVisTags.size()) == putVisTags.size()) {
      for (Tag tag : deleteVisTags) {
        matchFound = false;
        for (Tag givenTag : putVisTags) {
          if (Bytes.equals(tag.getBuffer(), tag.getTagOffset(), tag.getTagLength(),
              givenTag.getBuffer(), givenTag.getTagOffset(), givenTag.getTagLength())) {
            matchFound = true;
            break;
          }
        }
        if (!matchFound) break;
      }
    }
    return matchFound;
  }

  private static List<List<Integer>> sortTagsBasedOnOrdinal(List<Tag> tags) throws IOException {
    List<List<Integer>> fullTagsList = new ArrayList<List<Integer>>();
    for (Tag tag : tags) {
      if (tag.getType() == VISIBILITY_TAG_TYPE) {
        getSortedTagOrdinals(fullTagsList, tag);
      }
    }
    return fullTagsList;
  }

  private static void getSortedTagOrdinals(List<List<Integer>> fullTagsList, Tag tag)
      throws IOException {
    List<Integer> tagsOrdinalInSortedOrder = new ArrayList<Integer>();
    int offset = tag.getTagOffset();
    int endOffset = offset + tag.getTagLength();
    while (offset < endOffset) {
      Pair<Integer, Integer> result = StreamUtils.readRawVarint32(tag.getBuffer(), offset);
      tagsOrdinalInSortedOrder.add(result.getFirst());
      offset += result.getSecond();
    }
    Collections.sort(tagsOrdinalInSortedOrder);
    fullTagsList.add(tagsOrdinalInSortedOrder);
  }

  /*
   * @return true when all the visibility tags in Put matches with visibility tags in Delete.
   */
  private static boolean compareTagsOrdinals(List<List<Integer>> putVisTags,
      List<List<Integer>> deleteVisTags) {
    boolean matchFound = false;
    if (deleteVisTags.size() == putVisTags.size()) {
      for (List<Integer> deleteTagOrdinals : deleteVisTags) {
        matchFound = false;
        for (List<Integer> tagOrdinals : putVisTags) {
          if (deleteTagOrdinals.equals(tagOrdinals)) {
            matchFound = true;
            break;
          }
        }
        if (!matchFound) break;
      }
    }
    return matchFound;
  }
}
TOP

Related Classes of org.apache.hadoop.hbase.security.visibility.DefaultVisibilityLabelServiceImpl

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.