Package org.wso2.carbon.cassandra.mgt

Source Code of org.wso2.carbon.cassandra.mgt.CassandraKeyspaceAdmin

/*
*  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.wso2.carbon.cassandra.mgt;

import me.prettyprint.cassandra.model.BasicColumnDefinition;
import me.prettyprint.cassandra.model.BasicColumnFamilyDefinition;
import me.prettyprint.cassandra.serializers.StringSerializer;
import me.prettyprint.cassandra.service.CassandraHost;
import me.prettyprint.cassandra.service.ThriftCfDef;
import me.prettyprint.cassandra.service.ThriftCluster;
import me.prettyprint.hector.api.Cluster;
import me.prettyprint.hector.api.ddl.*;
import me.prettyprint.hector.api.exceptions.HectorException;
import me.prettyprint.hector.api.factory.HFactory;
import org.apache.cassandra.thrift.TokenRange;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.cassandra.dataaccess.ClusterInformation;
import org.wso2.carbon.cassandra.dataaccess.DataAccessService;
import org.wso2.carbon.core.AbstractAdmin;
import org.wso2.carbon.user.api.AuthorizationManager;
import org.wso2.carbon.user.api.UserRealm;
import org.wso2.carbon.user.api.UserStoreException;

import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

/**
* Cassandra Management(Admin) Service
*/

public class CassandraKeyspaceAdmin extends AbstractAdmin {

    //TODO use constants from user manager
    private static final String ACTION_WRITE = "write";
    private static final String ACTION_READ = "read";
    private static final String KEYSPACE_SYSTEM = "system";

    private static final Log log = LogFactory.getLog(CassandraKeyspaceAdmin.class);

    /**
     * @param clusterName The name of the cluster
     * @param username    The name of the current user
     * @param password    The password of the current user
     * @return A list of keyspace names
     * @throws CassandraServerManagementException
     *          for any errors during locating keyspaces
     */
    public String[] listKeyspaces(String clusterName, String username, String password) throws CassandraServerManagementException {

        if (username == null || "".equals(username.trim())) {
            throw new CassandraServerManagementException("The username is empty or null", log);
        }

        if (password == null || "".equals(password.trim())) {
            throw new CassandraServerManagementException("The password is empty or null", log);
        }

        ClusterInformation clusterInformation = new ClusterInformation(username.trim(), password.trim());
        clusterInformation.setClusterName(clusterName);
        return getKeyspaces(clusterInformation);
    }

    /**
     * Returns the all the keyspaces of the current user
     *
     * @return A list of keyspace names
     * @throws CassandraServerManagementException
     *          for any errors during locating keyspaces
     */
    public String[] listKeyspacesOfCurrentUser() throws CassandraServerManagementException {
        return getKeyspaces(null);
    }

    /**
     * Returns the all the column family names of the current user for the given keyspace
     *
     * @param keyspaceName The name of the keyspace
     * @return A list of column family names
     * @throws CassandraServerManagementException
     *          For any errors
     */
    public String[] listColumnFamiliesOfCurrentUser(String keyspaceName) throws CassandraServerManagementException {

        KeyspaceDefinition keyspaceDefinition = getKeyspaceDefinition(keyspaceName);

        List<String> cfNames = new ArrayList<String>();
        for (ColumnFamilyDefinition columnFamilyDefinition : keyspaceDefinition.getCfDefs()) {
            String name = columnFamilyDefinition.getName();
            if (name != null && !"".equals(name)) {
                cfNames.add(name);
            }
        }
        return cfNames.toArray(new String[cfNames.size()]);
    }

    /**
     * Returns meta-data for a given keyspace
     *
     * @param keyspaceName The name of the keyspace      *
     * @return meta-data about the keyspace
     * @throws CassandraServerManagementException
     *          For any errors during accessing a keyspace
     */
    public KeyspaceInformation getKeyspaceofCurrentUser(String keyspaceName) throws CassandraServerManagementException {

        KeyspaceDefinition keyspaceDefinition = getKeyspaceDefinition(keyspaceName);
        KeyspaceInformation keyspaceInformation = new KeyspaceInformation(keyspaceDefinition.getName());
        keyspaceInformation.setStrategyClass(keyspaceDefinition.getStrategyClass());
        keyspaceInformation.setReplicationFactor(keyspaceDefinition.getReplicationFactor());
        List<ColumnFamilyInformation> columnFamilyInformations = new ArrayList<ColumnFamilyInformation>();
        for (ColumnFamilyDefinition definition : keyspaceDefinition.getCfDefs()) {
            if (definition != null) {
                columnFamilyInformations.add(createColumnFamilyInformation(definition));
            }
        }
        keyspaceInformation.setColumnFamilies(
                columnFamilyInformations.toArray(new ColumnFamilyInformation[columnFamilyInformations.size()]));
        return keyspaceInformation;
    }

    /**
     * Retrieve a CF
     *
     * @param keyspaceName     the name of the keyspace
     * @param columnFamilyName the name of the CF
     * @return CF meta-data
     * @throws CassandraServerManagementException
     *          for errors in removing operation
     */
    public ColumnFamilyInformation getColumnFamilyOfCurrentUser(String keyspaceName,
                                                                String columnFamilyName) throws CassandraServerManagementException {

        try {
            KeyspaceDefinition keyspaceDefinition = getKeyspaceDefinition(keyspaceName);
            validateCF(columnFamilyName);
            //TODO change hector to get exactly one CF
            for (ColumnFamilyDefinition definition : keyspaceDefinition.getCfDefs()) {
                if (definition != null && columnFamilyName.equals(definition.getName())) {
                    return createColumnFamilyInformation(definition);
                }
            }
            throw new CassandraServerManagementException("There is no column family with the name :" + columnFamilyName,
                    log);
        } catch (HectorException e) {
            throw new CassandraServerManagementException("Error accessing a column family with the name :" +
                    columnFamilyName, e, log);
        }
    }

    /**
     * Shared a resource with a role
     *
     * @param role the name of the role
     * @param path resource path
     * @return true if the sharing would be successful.
     * @throws CassandraServerManagementException
     *          For any errors
     */
    public boolean shareResource(String role, String path) throws CassandraServerManagementException {

        if (role == null || "".equals(role.trim())) {
            throw new CassandraServerManagementException("Username is null or empty", log);
        }
        role = role.trim();

        if (path == null || "".equals(path.trim())) {
            throw new CassandraServerManagementException("Resource path is null or empty", log);
        }
        path = path.trim();

        try {
            CassandraAdminComponentManager adminComponentManager = CassandraAdminComponentManager.getInstance();
            UserRealm userRealm = adminComponentManager.getRealmForCurrentTenant();
            //TODO ask the best way from security team
            AuthorizationManager authorizationManager = userRealm.getAuthorizationManager();
            authorizationManager.clearRoleAuthorization(role, path, ACTION_WRITE);
            authorizationManager.clearRoleAuthorization(role, path, ACTION_READ);
            authorizationManager.authorizeRole(role, path, ACTION_WRITE);
            authorizationManager.authorizeRole(role, path, ACTION_READ);
            return true;
        } catch (UserStoreException e) {
            throw new CassandraServerManagementException("Error during sharing a resource at path :" + path + " and" +
                    " for role :" + role, e, log);
        }
    }

    /**
     * Clear sharing of a resource with a role
     *
     * @param role the name of the role
     * @param path resource path
     * @return true if the sharing would be successful.
     * @throws CassandraServerManagementException
     *          For any errors
     */
    public boolean clearResource(String role, String path) throws CassandraServerManagementException {

        if (role == null || "".equals(role.trim())) {
            throw new CassandraServerManagementException("Username is null or empty", log);
        }
        role = role.trim();

        if (path == null || "".equals(path.trim())) {
            throw new CassandraServerManagementException("Resource path is null or empty", log);
        }
        path = path.trim();

        try {
            CassandraAdminComponentManager adminComponentManager = CassandraAdminComponentManager.getInstance();
            UserRealm userRealm = adminComponentManager.getRealmForCurrentTenant();
            //TODO ask the best way from security team
            AuthorizationManager authorizationManager = userRealm.getAuthorizationManager();
            authorizationManager.clearRoleAuthorization(role, path, ACTION_WRITE);
            authorizationManager.clearRoleAuthorization(role, path, ACTION_READ);
            return true;
        } catch (UserStoreException e) {
            throw new CassandraServerManagementException("Error during clear sharing a resource at path :" + path + " and" +
                    " for role :" + role, e, log);
        }
    }

    /**
     * Create a new keyspace
     *
     * @param keyspaceInformation information about a keyspace
     * @throws CassandraServerManagementException
     *          For any error
     */
    public void addKeyspace(KeyspaceInformation keyspaceInformation) throws CassandraServerManagementException {

        validateKeyspaceInformation(keyspaceInformation);
        addOrUpdateKeyspace(true, keyspaceInformation.getName(), keyspaceInformation.getReplicationFactor(),
                keyspaceInformation.getStrategyClass());
    }

    /**
     * Update an existing keyspace
     *
     * @param keyspaceInformation information about a keyspace
     * @throws CassandraServerManagementException
     *          For any error during update operation
     */
    public void updatedKeyspace(KeyspaceInformation keyspaceInformation) throws CassandraServerManagementException {

        validateKeyspaceInformation(keyspaceInformation);
        addOrUpdateKeyspace(false, keyspaceInformation.getName(), keyspaceInformation.getReplicationFactor(),
                keyspaceInformation.getStrategyClass());
    }

    /**
     * All the users signed into the Cassandra
     *
     * @return A list of user names
     * @throws CassandraServerManagementException
     *          For errors in  loading user names
     */
    public String[] getAllRoles() throws CassandraServerManagementException {
        try {
            return super.getUserRealm().getUserStoreManager().getRoleNames();
        } catch (org.wso2.carbon.user.core.UserStoreException e) {
            throw new CassandraServerManagementException("Error loading all the users", e, log);
        }
    }

    /**
     * Remove a keyspace
     *
     * @param keyspaceName the name of the keyspace to be removed
     * @return true for success in removing operation
     * @throws CassandraServerManagementException
     *          for errors in removing operation
     */
    public boolean deleteKeyspace(String keyspaceName) throws CassandraServerManagementException {

        validateKeyspace(keyspaceName);
        try {
            Cluster cluster = getCluster(null);
            cluster.dropKeyspace(keyspaceName.trim());
        } catch (HectorException e) {
            throw new CassandraServerManagementException("Error removing a keyspace with the name :" + keyspaceName, e, log);
        }
        return true;
    }

    /**
     * Create a ColumnFamily in a given key space
     *
     * @param columnFamilyInformation mata-data about a CF
     * @throws CassandraServerManagementException
     *          For errors during adding a CF
     */
    public void addColumnFamily(ColumnFamilyInformation columnFamilyInformation) throws CassandraServerManagementException {
        addOrUpdateCF(true, columnFamilyInformation);
    }

    /**
     * Update an existing ColumnFamily in a given keyspace
     *
     * @param columnFamilyInformation mata-data about a CF
     * @throws CassandraServerManagementException
     *          For errors during updating a CF
     */
    public void updateColumnFamily(ColumnFamilyInformation columnFamilyInformation) throws CassandraServerManagementException {
        addOrUpdateCF(false, columnFamilyInformation);
    }

    /**
     * Remove a column family from a given keyspace
     *
     * @param keyspaceName     the name of the keyspace of the CF to be deleted
     * @param columnFamilyName the name of the CF to be deleleted
     * @return true for success in removing operation
     * @throws CassandraServerManagementException
     *          for errors in removing operation
     */
    public boolean deleteColumnFamily(String keyspaceName, String columnFamilyName) throws CassandraServerManagementException {

        validateKeyspace(keyspaceName);
        validateCF(columnFamilyName);
        try {
            Cluster cluster = getCluster(null);
            cluster.dropColumnFamily(keyspaceName.trim(), columnFamilyName.trim());
        } catch (HectorException e) {
            throw new CassandraServerManagementException("Error removing a column family with the name :" +
                    columnFamilyName, e, log);
        }
        return true;
    }

    /**
     * Access the teken range of a keyspace
     *
     * @param keyspace keyspace name
     * @return a list of <code>TokenRangeInformation </code>
     * @throws CassandraServerManagementException
     *          for errors during getting the token ring
     */
    public TokenRangeInformation[] getTokenRange(String keyspace) throws CassandraServerManagementException {
        validateKeyspace(keyspace);
        ThriftCluster thriftCluster = (ThriftCluster) getCluster(null);     // TODO  hector limitation
        Set<CassandraHost> cassandraHosts = thriftCluster.getKnownPoolHosts(true)// This returns all endpoints if only auto discovery is set.
        int rpcPort = CassandraHost.DEFAULT_PORT;
        for (CassandraHost cassandraHost : cassandraHosts) {
            if (cassandraHost != null) {
                rpcPort = cassandraHost.getPort()// With hector, each node has the same RPC port.
                break;
            }
        }

        List<TokenRangeInformation> tokenRangeInformations = new ArrayList<TokenRangeInformation>();

        if (!KEYSPACE_SYSTEM.equals(keyspace)) {
            List<TokenRange> tokenRanges = thriftCluster.describeRing(keyspace);
            for (TokenRange tokenRange : tokenRanges) {
                if (tokenRange != null) {
                    TokenRangeInformation tokenRangeInformation = new TokenRangeInformation();
                    tokenRangeInformation.setStartToken(tokenRange.getStart_token());
                    tokenRangeInformation.setEndToken(tokenRange.getEnd_token());
                    List<String> eps = new ArrayList<String>();
                    for (String ep : tokenRange.getEndpoints()) {
                        if (ep != null && !"".equals(ep.trim())) {
                            eps.add(ep + ":" + rpcPort); // With hector, each node has the same RPC port.
                        }
                    }
                    if (!eps.isEmpty()) {
                        tokenRangeInformation.setEndpoints(eps.toArray(new String[eps.size()]));
                    }
                    tokenRangeInformations.add(tokenRangeInformation);
                }
            }
        }
        return tokenRangeInformations.toArray(new TokenRangeInformation[tokenRangeInformations.size()]);
    }

    /**
     * Helper method to get all keyspace names
     *
     * @param clusterInformation Information about the target cluster
     * @return A list of keyspace names
     * @throws CassandraServerManagementException
     *          for errors during accessing keyspaces
     */
    private String[] getKeyspaces(ClusterInformation clusterInformation) throws CassandraServerManagementException {
        Cluster cluster = getCluster(clusterInformation);
        List<String> keyspaceNames = new ArrayList<String>();
        for (KeyspaceDefinition keyspaceDefinition : cluster.describeKeyspaces()) {
            String name = keyspaceDefinition.getName();
            if (name != null && !"".equals(name)) {
                keyspaceNames.add(name);
            }
        }
        return keyspaceNames.toArray(new String[keyspaceNames.size()]);
    }

    /**
     * helper method to get a Cassandra cluster
     *
     * @param clusterInformation Information about the target cluster
     * @return <code>Cluster</code> Instance
     * @throws CassandraServerManagementException
     *          for errors during accessing a hector cluster
     */
    private Cluster getCluster(ClusterInformation clusterInformation) throws CassandraServerManagementException {
        DataAccessService dataAccessService =
                CassandraAdminComponentManager.getInstance().getDataAccessService();
        Cluster cluster;
        if (clusterInformation != null) {
            cluster = dataAccessService.getCluster(clusterInformation);
        } else {
            cluster = dataAccessService.getClusterForCurrentUser();
        }
        return cluster;
    }

    /* Helper method for adding or updating a keyspace */

    private void addOrUpdateKeyspace(boolean isAdd,
                                     String keyspaceName,
                                     int replicationFactor,
                                     String replicationStrategy) throws CassandraServerManagementException {

        Cluster cluster = getCluster(null);
        try {
            KeyspaceDefinition definition =
                    HFactory.createKeyspaceDefinition(keyspaceName.trim(), replicationStrategy, replicationFactor, null);
            if (isAdd) {
                cluster.addKeyspace(definition);
            } else {
                cluster.updateKeyspace(definition);
            }
        } catch (HectorException e) {
            throw new CassandraServerManagementException("Error " + (isAdd ? "adding" : "updating") + " a keyspace" +
                    " with name :" + keyspaceName, e, log);
        }
    }

    /* Helper method for adding or updating a CF */

    private void addOrUpdateCF(boolean isAdd, ColumnFamilyInformation columnFamilyInformation) throws CassandraServerManagementException {

        String keyspaceName = columnFamilyInformation.getKeyspace();
        String columnFamilyName = columnFamilyInformation.getName();

        validateKeyspace(keyspaceName);
        validateCF(columnFamilyName);

        ColumnType columnType = ColumnType.STANDARD;
        String type = columnFamilyInformation.getType();
        if (type != null && !"".equals(type.trim())) {
            columnType = ColumnType.getFromValue(type.trim());
        }

        BasicColumnFamilyDefinition familyDefinition = new BasicColumnFamilyDefinition();   //TODO remove with a thrift cfd
        familyDefinition.setColumnType(columnType);
        familyDefinition.setId(columnFamilyInformation.getId());
        familyDefinition.setName(columnFamilyName);
        familyDefinition.setKeyspaceName(keyspaceName);
        familyDefinition.setKeyCacheSize(columnFamilyInformation.getKeyCacheSize());
        familyDefinition.setComment(columnFamilyInformation.getComment());
        familyDefinition.setGcGraceSeconds(columnFamilyInformation.getGcGraceSeconds());
        familyDefinition.setRowCacheSize(columnFamilyInformation.getRowCacheSize());
        familyDefinition.setReadRepairChance(columnFamilyInformation.getReadRepairChance());
        familyDefinition.setComparatorType(ComparatorType.getByClassName(columnFamilyInformation.getComparatorType()));
        if (ColumnType.SUPER == columnType) {
            familyDefinition.setSubComparatorType(
                    ComparatorType.getByClassName(columnFamilyInformation.getSubComparatorType()));
        } else {
            familyDefinition.setSubComparatorType(null);
        }
        familyDefinition.setMaxCompactionThreshold(columnFamilyInformation.getMaxCompactionThreshold());
        familyDefinition.setMinCompactionThreshold(columnFamilyInformation.getMinCompactionThreshold());

        String defaultValidationClass = columnFamilyInformation.getDefaultValidationClass();
        if (defaultValidationClass != null && !"".equals(defaultValidationClass.trim())) {
            familyDefinition.setDefaultValidationClass(defaultValidationClass.trim());
        }

        ColumnInformation[] columns = columnFamilyInformation.getColumns();
        if (columns != null && columns.length > 0) {
            for (ColumnInformation column : columns) {
                validateColumnInformation(column);

                BasicColumnDefinition columnDefinition = new BasicColumnDefinition();
                columnDefinition.setName(StringSerializer.get().toByteBuffer(column.getName().trim()));

                String indexName = column.getIndexName();
                if (indexName != null && !"".equals(indexName.trim())) {
                    columnDefinition.setIndexName(indexName.trim());
                }

                String validationClass = column.getValidationClass();
                if (validationClass != null && !"".equals(validationClass.trim())) {
                    columnDefinition.setValidationClass(validationClass.trim());
                }

                String indexType = column.getIndexType();
                if (indexType != null && !"".equals(indexType.trim())) {
                    columnDefinition.setIndexType(ColumnIndexType.valueOf(indexType.trim().toUpperCase()));
                }
                familyDefinition.addColumnDefinition(columnDefinition);
            }
        }

        try {
            Cluster cluster = getCluster(null);
            if (isAdd) {
                cluster.addColumnFamily(new ThriftCfDef(familyDefinition));
            } else {
                cluster.updateColumnFamily(new ThriftCfDef(familyDefinition));
            }
        } catch (HectorException e) {
            throw new CassandraServerManagementException("Error " + (isAdd ? "adding" : "updating ") + " a column family with" +
                    " the name :" + columnFamilyName, e, log);
        }
    }

    private void validateKeyspaceInformation(KeyspaceInformation information) throws CassandraServerManagementException {
        if (information == null) {
            throw new CassandraServerManagementException("The keyspace information is null", log);
        }
        validateKeyspace(information.getName());
    }

    private void validateColumnInformation(ColumnInformation information) throws CassandraServerManagementException {
        if (information == null) {
            throw new CassandraServerManagementException("The column information is null", log);
        }
        String name = information.getName();
        if (name == null || "".equals(name.trim())) {
            throw new CassandraServerManagementException("The column name is null", log);
        }
    }

    private void validateKeyspace(String keyspaceName) throws CassandraServerManagementException {
        if (keyspaceName == null || "".equals(keyspaceName.trim())) {
            throw new CassandraServerManagementException("The keyspace name is empty or null", log);
        }
    }

    private void validateCF(String columnFamilyName) throws CassandraServerManagementException {
        if (columnFamilyName == null || "".equals(columnFamilyName.trim())) {
            throw new CassandraServerManagementException("The column family name is empty or null", log);
        }
    }

    private KeyspaceDefinition getKeyspaceDefinition(String keyspace) throws CassandraServerManagementException {
        validateKeyspace(keyspace);
        Cluster cluster = getCluster(null);
        KeyspaceDefinition keyspaceDefinition = cluster.describeKeyspace(keyspace.trim());
        if (keyspaceDefinition == null) {
            throw new CassandraServerManagementException("Cannot find a keyspace for : " + keyspace, log);
        }
        return keyspaceDefinition;
    }

    private ColumnFamilyInformation createColumnFamilyInformation(ColumnFamilyDefinition definition) throws CassandraServerManagementException {
        ColumnFamilyInformation information =
                new ColumnFamilyInformation(definition.getKeyspaceName(), definition.getName());
        information.setId(definition.getId());
        information.setComment(definition.getComment());
        information.setComparatorType(definition.getComparatorType().getClassName());
        information.setKeyCacheSize(definition.getKeyCacheSize());
        int gcGrace = definition.getGcGraceSeconds();
        if (gcGrace > 0) {
            information.setGcGraceSeconds(gcGrace);
        }
        int maxThreshold = definition.getMaxCompactionThreshold();
        if (maxThreshold > 0) {
            information.setMaxCompactionThreshold(maxThreshold);
        }
        int minThreshold = definition.getMinCompactionThreshold();
        if (maxThreshold > 0) {
            information.setMinCompactionThreshold(minThreshold);
        }
        information.setReadRepairChance(definition.getReadRepairChance());
        information.setRowCacheSavePeriodInSeconds(definition.getRowCacheSavePeriodInSeconds());
        information.setType(definition.getColumnType().getValue());
        information.setRowCacheSize(definition.getRowCacheSize());
        information.setSubComparatorType(definition.getSubComparatorType().getClassName());
        information.setDefaultValidationClass(definition.getDefaultValidationClass());

        //TODO change hector to get a columns of a CF on demand
        List<ColumnDefinition> columnDefinitions = definition.getColumnMetadata();
        ColumnInformation[] columnInformations = new ColumnInformation[columnDefinitions.size()];
        int index = 0;
        for (ColumnDefinition column : columnDefinitions) {
            if (column == null) {
                throw new CassandraServerManagementException("Column cannot be null", log);
            }

            ByteBuffer byteBuffer = column.getName();
            if (byteBuffer == null) {
                throw new CassandraServerManagementException("Column name cannot be null", log);
            }

            byte[] byteArray = new byte[byteBuffer.remaining()];   //TODO best way to do this
            byteBuffer.get(byteArray);
            String name = new String(byteArray);
            if (name.isEmpty()) {
                throw new CassandraServerManagementException("Column name cannot be empty", log);
            }

            ColumnInformation columnInformation = new ColumnInformation();
            columnInformation.setName(name);
            columnInformation.setIndexName(column.getIndexName());
            columnInformation.setValidationClass(column.getValidationClass());
            ColumnIndexType columnIndexType = column.getIndexType();
            if (columnIndexType != null) {
                columnInformation.setIndexType(columnIndexType.name());
            }
            columnInformations[index] = columnInformation;
            index++;
        }
        information.setColumns(columnInformations);
        return information;
    }
}
TOP

Related Classes of org.wso2.carbon.cassandra.mgt.CassandraKeyspaceAdmin

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.