craterdog.marketplace.DigitalMarketplaceCli.java Source code

Java tutorial

Introduction

Here is the source code for craterdog.marketplace.DigitalMarketplaceCli.java

Source

/************************************************************************
 * Copyright (c) Crater Dog Technologies(TM).  All Rights Reserved.     *
 ************************************************************************
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.        *
 *                                                                      *
 * This code is free software; you can redistribute it and/or modify it *
 * under the terms of The MIT License (MIT), as published by the Open   *
 * Source Initiative. (See http://opensource.org/licenses/MIT)          *
 ************************************************************************/
package craterdog.marketplace;

import com.fasterxml.jackson.databind.ObjectMapper;
import craterdog.accounting.Accounting;
import craterdog.accounting.DigitalTransaction;
import craterdog.accounting.V1AccountingProvider;
import craterdog.identities.DigitalIdentity;
import craterdog.identities.Identification;
import craterdog.identities.V1IdentificationProvider;
import craterdog.notary.Notarization;
import craterdog.notary.NotaryCertificate;
import craterdog.notary.NotaryKey;
import craterdog.notary.V1NotarizationProvider;
import craterdog.primitives.Tag;
import craterdog.smart.SmartObject;
import craterdog.tokens.DigitalToken;
import craterdog.tokens.Tokenization;
import craterdog.tokens.V1TokenizationProvider;
import java.io.BufferedReader;
import java.io.Console;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.io.FileUtils;
import org.slf4j.ext.XLogger;
import org.slf4j.ext.XLoggerFactory;
import org.springframework.web.client.ResourceAccessException;

/**
 * This class provides a command line interface to the digital marketplace services.
 *
 * @author web-online
 * @author Derk Norton
 */
public class DigitalMarketplaceCli {

    static private final XLogger logger = XLoggerFactory.getXLogger(DigitalMarketplaceCli.class);
    static private final String defaultDigitalAccountantUri = "http://localhost:8080/DigitalMarketplace";
    static private final String dataDirectory = System.getProperty("user.home") + "/.cdt";
    static private final ObjectMapper mapper = SmartObject.createMapper();

    static private final String[] commands = { "register-identity", "retrieve-identity", "retrieve-identities",
            "query-identities", "retrieve-certificate", "renew-certificate", "certify-batch", "retrieve-batch",
            "retrieve-token", "transfer-token", "retrieve-transaction", "retrieve-ledger", "quit" };

    static private final Option pseudonymOption = Option.builder("pseudonym").hasArg().argName("pseudonym")
            .desc("the pseudonym for the identity").required().build();

    static private final Option identityOption = Option.builder("identity").hasArg().argName("identity")
            .desc("the identifier of the identity").required().build();

    static private final Option attributeOption = Option.builder("attribute").hasArg().argName("attribute")
            .desc("the name of the attribute").required().build();

    static private final Option valueOption = Option.builder("value").hasArg().argName("value")
            .desc("the value of the attribute").required().build();

    static private final Option merchantOption = Option.builder("merchant").hasArg().argName("merchant")
            .desc("the name of the merchant").required().build();

    static private final Option accountantOption = Option.builder("accountant").hasArg().argName("accountant")
            .desc("the name of the accountant").required().build();

    static private final Option senderOption = Option.builder("sender").hasArg().argName("sender")
            .desc("the name of the sender").required().build();

    static private final Option receiverOption = Option.builder("receiver").hasArg().argName("receiver")
            .desc("the name of the receiver").required().build();

    static private final Option typeOption = Option.builder("type").hasArg().argName("type")
            .desc("the type of the token").required().build();

    static private final Option countOption = Option.builder("count").hasArg().argName("count")
            .desc("the number of tokens").required().build();

    static private final Option batchOption = Option.builder("batch").hasArg().argName("batch")
            .desc("the identifier of the batch of tokens").required().build();

    static private final Option tokenOption = Option.builder("token").hasArg().argName("token")
            .desc("the identifier of the token").required().build();

    static private final Option transactionOption = Option.builder("transaction").hasArg().argName("transaction")
            .desc("the identifier of the transaction").required().build();

    static private final Option ledgerOption = Option.builder("ledger").hasArg().argName("ledger")
            .desc("the identifier of the ledger").required().build();

    private final Notarization notary;
    private final Identification identificationProvider;
    private final Tokenization tokenizationProvider;
    private final Accounting accountingProvider;
    private final URI digitalMarketplaceUri;
    private final DigitalMarketplaceClient digitalMarketplaceClient;

    private final Console console;
    private final BufferedReader reader;
    private final PrintWriter writer;
    private boolean done = false;

    public DigitalMarketplaceCli(URI digitalMarketplaceUri) {
        this.digitalMarketplaceUri = digitalMarketplaceUri;
        digitalMarketplaceClient = new DigitalMarketplaceClient(digitalMarketplaceUri);
        notary = new V1NotarizationProvider();
        identificationProvider = new V1IdentificationProvider();
        tokenizationProvider = new V1TokenizationProvider();
        accountingProvider = new V1AccountingProvider();
        console = System.console();
        if (console != null) {
            reader = new BufferedReader(console.reader());
            writer = console.writer();
        } else {
            reader = new BufferedReader(new InputStreamReader(System.in));
            writer = new PrintWriter(new OutputStreamWriter(System.out), true); // autoflush
        }
    }

    static public void main(String[] args) {
        logger.entry((Object) args);

        logger.debug("Ensuring the data directory exists...");
        File directory = new File(dataDirectory);
        if (!directory.exists() && !directory.mkdir()) {
            logger.error("Unable to create ~/.cdt/ directory, exiting...");
            System.exit(1);
        }

        logger.debug("Configuring the CLI...");
        String digitalMarketplaceUriString = defaultDigitalAccountantUri;
        if (args.length > 0) {
            digitalMarketplaceUriString = args[0];
        }
        URI digitalMarketplaceUri = URI.create(digitalMarketplaceUriString);
        DigitalMarketplaceCli cli = new DigitalMarketplaceCli(digitalMarketplaceUri);

        logger.debug("Running the CLI...");
        int status = cli.run();

        logger.exit(status);
    }

    private int run() {

        if (console != null)
            printCommands();

        while (!done) {

            if (console != null)
                writer.format("%nEnter command: ");

            String line;
            try {
                line = reader.readLine();
                if (line == null)
                    line = "quit"; // user closed the CLI
            } catch (IOException e) {
                logger.error("Unable to read the command from standard input: {}", e);
                return 1;
            }

            String[] values = line.split("\\s");
            if (values.length == 0) {
                if (console != null)
                    printCommands();
                continue;
            }

            String command = values[0];
            Options options = new Options();
            try {
                switch (command) {

                case "":
                    if (console != null)
                        printCommands();
                    break;

                case "register-identity":
                    registerIdentity(options, values);
                    break;

                case "retrieve-identity":
                    retrieveIdentity(options, values);
                    break;

                case "retrieve-identities":
                    retrieveIdentities(options, values);
                    break;

                case "query-identities":
                    queryIdentities(options, values);
                    break;

                case "retrieve-certificate":
                    retrieveCertificate(options, values);
                    break;

                case "renew-certificate":
                    renewCertificate(options, values);
                    break;

                case "certify-batch":
                    certifyBatch(options, values);
                    break;

                case "retrieve-batch":
                    retrieveBatch(options, values);
                    break;

                case "retrieve-token":
                    retrieveToken(options, values);
                    break;

                case "transfer-token":
                    transferToken(options, values);
                    break;

                case "retrieve-transaction":
                    retrieveTransaction(options, values);
                    break;

                case "retrieve-ledger":
                    retrieveLedger(options, values);
                    break;

                case "quit":
                    quit(options, values);
                    break;

                default:
                    logger.error("Unknown command entered: {}", command);
                    if (console != null)
                        printCommands();
                }

            } catch (ParseException e) {
                if (console != null)
                    printHelp(command, options);
            } catch (IOException | ResourceAccessException e) {
                logger.error("There was a problem processing the command: {}", e);
            }
        }
        return 0;
    }

    private CommandLine parse(Options options, String[] arguments) throws ParseException {
        CommandLineParser parser = new DefaultParser();

        Option helpOption = new Option("help", false, "print this message");
        options.addOption(helpOption);

        // NOTE: this parse method may throw a different ParseException and miss the code below...
        CommandLine commandLine = parser.parse(options, arguments);
        if (commandLine == null || commandLine.hasOption(helpOption.getOpt())) {
            throw new ParseException(helpOption.getOpt());
        }
        return commandLine;
    }

    private char[] readPassword(String owner) throws IOException {
        char[] password;
        if (console != null) {
            password = console.readPassword("Enter notary key password for the " + owner + ": ");
        } else {
            password = reader.readLine().toCharArray();
        }
        return password;
    }

    static private void printHelp(String command, Options options) {
        new HelpFormatter().printHelp(command, options, true);
    }

    private void printCommands() {
        writer.println("Possible commands:");
        for (String command : commands) {
            writer.println("  " + command);
        }
    }

    private void registerIdentity(Options options, String[] values) throws ParseException, IOException {
        logger.debug("Parsing the command line options...");
        options.addOption(pseudonymOption);
        CommandLine commandLine = parse(options, values);
        String pseudonym = commandLine.getOptionValue("pseudonym");
        File notaryFile = new File(dataDirectory, pseudonym + "-notary-key.json");
        File identityFile = new File(dataDirectory, pseudonym + "-identity.json");

        logger.debug("Generating a new notary key...");
        char[] password = readPassword(pseudonym);
        if (password == null)
            return;
        NotaryKey notaryKey = notary.generateNotaryKey(digitalMarketplaceUri);
        mapper.writeValue(notaryFile, notaryKey);
        FileUtils.writeStringToFile(notaryFile, notary.serializeNotaryKey(notaryKey, password));
        Arrays.fill(password, ' ');

        logger.debug("Creating a new identity...");
        Map<String, Object> additionalAttributes = new LinkedHashMap<>();
        additionalAttributes.put("pseudonym", pseudonym);
        DigitalIdentity identity = identificationProvider.createIdentity(additionalAttributes, notaryKey);
        mapper.writeValue(identityFile, identity);

        logger.info("Registering the new identity...");
        RegisterIdentityRequest registerIdentityRequest = new RegisterIdentityRequest();
        registerIdentityRequest.identity = identity;
        registerIdentityRequest.certificate = notaryKey.verificationCertificate;
        RegisterIdentityResponse registerResponse = digitalMarketplaceClient
                .registerIdentity(registerIdentityRequest);
        logger.info("registerResponse: {}", registerResponse);
    }

    private void retrieveIdentity(Options options, String[] values) throws ParseException, IOException {
        logger.debug("Parsing the command line options...");
        options.addOption(identityOption);
        CommandLine commandLine = parse(options, values);
        String identityId = commandLine.getOptionValue("identity");
        String identityUri = digitalMarketplaceUri + "/identity/" + identityId;
        URI identityLocation = URI.create(identityUri);

        logger.info("Retrieving the identity...");
        RetrieveIdentityResponse retrieveResponse = digitalMarketplaceClient.retrieveIdentity(identityLocation);
        logger.info("retrieveResponse: {}", retrieveResponse);
    }

    private void retrieveIdentities(Options options, String[] values) throws ParseException, IOException {
        logger.info("Retrieving all identities...");
        QueryIdentitiesResponse queryResponse = digitalMarketplaceClient.queryAllIdentities();
        logger.info("retrieveResponse: {}", queryResponse);
    }

    private void queryIdentities(Options options, String[] values) throws ParseException, IOException {
        logger.debug("Parsing the command line options...");
        options.addOption(attributeOption);
        options.addOption(valueOption);
        CommandLine commandLine = parse(options, values);
        String attributeName = commandLine.getOptionValue("attribute");
        String attributeValue = commandLine.getOptionValue("value");

        logger.info("Querying for identities matching: {} = {}.", attributeName, attributeValue);
        QueryIdentitiesResponse queryResponse = digitalMarketplaceClient.queryIdentitiesByAttribute(attributeName,
                attributeValue);
        logger.info("queryResponse: {}", queryResponse);
    }

    private void retrieveCertificate(Options options, String[] values) throws ParseException, IOException {
        logger.debug("Parsing the command line options...");
        options.addOption(pseudonymOption);
        CommandLine commandLine = parse(options, values);
        String pseudonym = commandLine.getOptionValue("pseudonym");

        logger.debug("Retrieving the identity...");
        QueryIdentitiesResponse queryResponse = digitalMarketplaceClient.queryIdentitiesByAttribute("pseudonym",
                pseudonym);
        if (queryResponse.status != RequestStatus.Succeeded || queryResponse.identities.isEmpty()) {
            logger.error("Attempt to retrieve identity information failed: {}", queryResponse);
            return;
        }
        URI identityLocation = queryResponse.identities.get(0).attributes.myLocation;

        logger.info("Retrieving the latest certificate for the identity...");
        RetrieveCertificateResponse retrieveResponse = digitalMarketplaceClient
                .retrieveLatestCertificate(identityLocation);
        logger.info("retrieveResponse: {}", retrieveResponse);
    }

    private void renewCertificate(Options options, String[] values) throws ParseException, IOException {
        logger.debug("Parsing the command line options...");
        options.addOption(pseudonymOption);
        CommandLine commandLine = parse(options, values);
        String pseudonym = commandLine.getOptionValue("pseudonym");
        RenewCertificateRequest renewRequest = new RenewCertificateRequest();

        logger.debug("Retrieving the existing notary key...");
        char[] password = readPassword(pseudonym);
        File notaryFile = new File(dataDirectory, pseudonym + "-notary-key.json");
        if (!notaryFile.canRead()) {
            logger.error("The specified pseudonym does not exist: {}", pseudonym);
            return;
        }
        NotaryKey oldKey = notary.deserializeNotaryKey(FileUtils.readFileToString(notaryFile), password);

        logger.debug("Generating a new notary key...");
        NotaryKey newKey = notary.generateNotaryKey(digitalMarketplaceUri, oldKey);
        FileUtils.writeStringToFile(notaryFile, notary.serializeNotaryKey(newKey, password));
        Arrays.fill(password, ' ');

        logger.info("Renewing the certificate...");
        renewRequest.newCertificate = newKey.verificationCertificate;
        RenewCertificateResponse renewResponse = digitalMarketplaceClient.renewCertificate(renewRequest);
        logger.info("renewResponse: {}", renewResponse);
    }

    private void certifyBatch(Options options, String[] values) throws ParseException, IOException {
        logger.debug("Parsing the command line options...");
        options.addOption(merchantOption);
        options.addOption(accountantOption);
        options.addOption(typeOption);
        options.addOption(countOption);
        CommandLine commandLine = parse(options, values);
        String merchant = commandLine.getOptionValue("merchant");
        String accountant = commandLine.getOptionValue("accountant");
        String tokenType = commandLine.getOptionValue("type");
        int numberOfTokens = Integer.parseInt(commandLine.getOptionValue("count"));

        logger.debug("Retrieving the identity of the merchant...");
        QueryIdentitiesResponse queryResponse = digitalMarketplaceClient.queryIdentitiesByAttribute("pseudonym",
                merchant);
        if (queryResponse.status != RequestStatus.Succeeded || queryResponse.identities.isEmpty()) {
            logger.error("Attempt to retrieve merchant identity information failed: {}", queryResponse);
            return;
        }
        URI merchantLocation = queryResponse.identities.get(0).attributes.myLocation;

        logger.debug("Retrieving the identity of the accountant...");
        queryResponse = digitalMarketplaceClient.queryIdentitiesByAttribute("pseudonym", accountant);
        if (queryResponse.status != RequestStatus.Succeeded || queryResponse.identities.isEmpty()) {
            logger.error("Attempt to retrieve accountant identity information failed: {}", queryResponse);
            return;
        }
        URI accountantLocation = queryResponse.identities.get(0).attributes.myLocation;

        logger.debug("Retrieving the merchant's notary key...");
        char[] password = readPassword(merchant);
        File notaryFile = new File(dataDirectory, merchant + "-notary-key.json");
        if (!notaryFile.canRead()) {
            logger.error("The specified merchant does not exist: {}", merchant);
            return;
        }
        NotaryKey merchantKey = notary.deserializeNotaryKey(FileUtils.readFileToString(notaryFile), password);
        Arrays.fill(password, ' ');

        logger.debug("Minting the new tokens...");
        URI batchLocation = URI.create(digitalMarketplaceUri + "/batch/" + new Tag());
        List<DigitalToken> tokens = new ArrayList<>();
        for (int i = 1; i <= numberOfTokens; i++) {
            URI tokenLocation = URI.create(digitalMarketplaceUri + "/token/" + new Tag());
            DigitalToken token = tokenizationProvider.mintToken(tokenLocation, batchLocation, accountantLocation,
                    tokenType, Notarization.VALID_FOR_ONE_YEAR, merchantKey);
            tokens.add(token);
        }

        logger.info("Sending the digital accountant a request to certify the new tokens...");
        CertifyBatchRequest certifyRequest = new CertifyBatchRequest();
        certifyRequest.guarantorLocation = merchantLocation;
        certifyRequest.batchLocation = batchLocation;
        certifyRequest.tokens = tokens;
        CertifyBatchResponse certifyResponse = digitalMarketplaceClient.certifyBatch(certifyRequest);
        logger.info("certifyResponse: {}", certifyResponse);
    }

    private void retrieveBatch(Options options, String[] values) throws ParseException, IOException {
        logger.debug("Parsing the command line options...");
        options.addOption(batchOption);
        CommandLine commandLine = parse(options, values);
        String batchId = commandLine.getOptionValue("batch");
        String batchUri = digitalMarketplaceUri + "/batch/" + batchId;
        URI batchLocation = URI.create(batchUri);

        logger.info("Retrieving the batch of tokens...");
        RetrieveBatchResponse retrieveResponse = digitalMarketplaceClient.retrieveBatch(batchLocation);
        logger.info("retrieveResponse: {}", retrieveResponse);
    }

    private void retrieveToken(Options options, String[] values) throws ParseException, IOException {
        logger.debug("Parsing the command line options...");
        options.addOption(tokenOption);
        CommandLine commandLine = parse(options, values);
        String tokenId = commandLine.getOptionValue("token");
        String tokenUri = digitalMarketplaceUri + "/token/" + tokenId;
        URI tokenLocation = URI.create(tokenUri);

        logger.info("Retrieving the token...");
        RetrieveTokenResponse retrieveResponse = digitalMarketplaceClient.retrieveToken(tokenLocation);
        logger.info("retrieveResponse: {}", retrieveResponse);
    }

    private void transferToken(Options options, String[] values) throws ParseException, IOException {
        logger.debug("Parsing the command line options...");
        options.addOption(senderOption);
        options.addOption(receiverOption);
        options.addOption(tokenOption);
        CommandLine commandLine = parse(options, values);
        String sender = commandLine.getOptionValue("sender");
        String receiver = commandLine.getOptionValue("receiver");
        String tokenId = commandLine.getOptionValue("token");

        logger.debug("Retrieving the identity of the sender...");
        QueryIdentitiesResponse queryResponse = digitalMarketplaceClient.queryIdentitiesByAttribute("pseudonym",
                sender);
        if (queryResponse.status != RequestStatus.Succeeded || queryResponse.identities.isEmpty()) {
            logger.error("Attempt to retrieve sender identity information failed: {}", queryResponse);
            return;
        }
        URI senderLocation = queryResponse.identities.get(0).attributes.myLocation;

        logger.debug("Retrieving the identity of the receiver...");
        queryResponse = digitalMarketplaceClient.queryIdentitiesByAttribute("pseudonym", receiver);
        if (queryResponse.status != RequestStatus.Succeeded || queryResponse.identities.isEmpty()) {
            logger.error("Attempt to retrieve receiver identity information failed: {}", queryResponse);
            return;
        }
        URI receiverLocation = queryResponse.identities.get(0).attributes.myLocation;

        logger.debug("Retrieving the sender's notary key...");
        char[] password = readPassword(sender);
        File notaryFile = new File(dataDirectory, sender + "-notary-key.json");
        if (!notaryFile.canRead()) {
            logger.error("The specified sender does not exist: {}", sender);
            return;
        }
        NotaryKey senderKey = notary.deserializeNotaryKey(FileUtils.readFileToString(notaryFile), password);
        Arrays.fill(password, ' ');

        logger.debug("Retrieving the receiver's notary key...");
        password = readPassword(receiver);
        notaryFile = new File(dataDirectory, receiver + "-notary-key.json");
        if (!notaryFile.canRead()) {
            logger.error("The specified receiver does not exist:{} ", receiver);
            return;
        }
        NotaryKey receiverKey = notary.deserializeNotaryKey(FileUtils.readFileToString(notaryFile), password);
        Arrays.fill(password, ' ');

        logger.debug("Retrieving the token...");
        String tokenUri = digitalMarketplaceUri + "/token/" + tokenId;
        URI tokenLocation = URI.create(tokenUri);
        RetrieveTokenResponse retrieveTokenResponse = digitalMarketplaceClient.retrieveToken(tokenLocation);
        if (retrieveTokenResponse.status != RequestStatus.Succeeded) {
            logger.error("Attempt to retrieve token failed: {}", retrieveTokenResponse);
            return;
        }
        DigitalToken token = retrieveTokenResponse.token;

        logger.info("Transfering the token from the sender to the receiver...");
        URI transactionLocation = URI.create(digitalMarketplaceUri + "/transaction/" + new Tag());
        DigitalTransaction transaction = accountingProvider.initiateTransaction(transactionLocation, senderLocation,
                receiverLocation, "Token Transfer", token, senderKey);
        URI certificateLocation = transaction.senderSeal.attributes.verificationCitation.documentLocation;
        RetrieveCertificateResponse retrieveCertificateResponse = digitalMarketplaceClient
                .retrieveCertificate(certificateLocation);
        if (retrieveCertificateResponse.status != RequestStatus.Succeeded) {
            logger.error("Attempt to retrieve sender certificate failed: {}", retrieveCertificateResponse);
            return;
        }
        NotaryCertificate senderCertificate = retrieveCertificateResponse.certificate;
        accountingProvider.approveTransaction(transaction, token, senderCertificate, receiverKey);

        logger.info("Sending the digital accountant a request to certify the transaction...");
        RecordTransactionRequest recordRequest = new RecordTransactionRequest();
        recordRequest.transaction = transaction;
        RecordTransactionResponse recordResponse = digitalMarketplaceClient.recordTransaction(recordRequest);
        logger.info("recordResponse: {}", recordResponse);
    }

    private void retrieveTransaction(Options options, String[] values) throws ParseException, IOException {
        logger.debug("Parsing the command line options...");
        options.addOption(transactionOption);
        CommandLine commandLine = parse(options, values);
        String transactionId = commandLine.getOptionValue("transaction");

        logger.info("Retrieving the transaction...");
        String transactionUri = digitalMarketplaceUri + "/transaction/" + transactionId;
        URI transactionLocation = URI.create(transactionUri);
        RetrieveTransactionResponse retrieveResponse = digitalMarketplaceClient
                .retrieveTransaction(transactionLocation);
        logger.info("retrieveResponse: {}", retrieveResponse);
    }

    private void retrieveLedger(Options options, String[] values) throws ParseException, IOException {
        logger.debug("Parsing the command line options...");
        options.addOption(ledgerOption);
        CommandLine commandLine = parse(options, values);
        String ledgerId = commandLine.getOptionValue("ledger");

        logger.info("Retrieving the ledger...");
        String ledgerUri = digitalMarketplaceUri + "/ledger/" + ledgerId;
        URI ledgerLocation = URI.create(ledgerUri);
        RetrieveLedgerResponse retrieveResponse = digitalMarketplaceClient.retrieveLedger(ledgerLocation);
        logger.info("retrieveResponse: {}", retrieveResponse);
    }

    private void quit(Options options, String[] values) throws ParseException, IOException {
        done = true;
        if (console != null)
            writer.format("Goodbye!%n");
    }

}