Router.java

/*
 * @copyright defined in LICENSE.txt
 */

package ship.build.web;

import static hera.util.ExceptionUtils.getStackTraceOf;
import static hera.util.ObjectUtils.nvl;
import static org.slf4j.LoggerFactory.getLogger;
import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR;

import java.io.IOException;
import java.util.List;
import javax.annotation.PostConstruct;
import javax.inject.Inject;
import lombok.Setter;
import org.slf4j.Logger;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.WebRequest;
import ship.build.web.exception.HttpException;
import ship.build.web.exception.ResourceNotFoundException;
import ship.build.web.model.BuildDetails;
import ship.build.web.model.BuildSummary;
import ship.build.web.model.ContractInput;
import ship.build.web.model.DeploymentResult;
import ship.build.web.model.ExecutionResult;
import ship.build.web.model.QueryResult;
import ship.build.web.model.ServiceError;
import ship.build.web.service.BuildService;
import ship.build.web.service.ContractService;
import ship.build.web.service.LiveUpdateService;

@RestController
@ControllerAdvice
public class Router {

  protected final transient Logger logger = getLogger(getClass());

  @Inject
  @Setter
  protected BuildService buildService;

  @Inject
  @Setter
  protected ContractService contractService;

  @Inject
  @Setter
  protected LiveUpdateService liveUpdateService;

  @PostConstruct
  public void initialize() {
    buildService.addListener(liveUpdateService::notifyChange);
  }

  @GetMapping("contract")
  public DeploymentResult getLatestContract() {
    return contractService.getLatestContractInformation();
  }

  @GetMapping("builds")
  public List<BuildSummary> getLatestBuilds() {
    return buildService.list(null, 5);
  }

  @GetMapping("build/{uuid}")
  public BuildDetails getBuild(@PathVariable("uuid") final String buildUuid) {
    return buildService.get(buildUuid).orElseThrow(ResourceNotFoundException::new);
  }

  /**
   * Deploy result of build with {@code buildUuid}.
   *
   * @param buildUuid build uuid
   *
   * @return deployment result
   *
   * @throws Exception Fail to deploy
   */
  @PostMapping("build/{uuid}/deploy")
  public DeploymentResult deploy(@PathVariable("uuid") final String buildUuid) throws Exception {
    final BuildDetails buildDetails = buildService.get(buildUuid)
        .orElseThrow(() -> new ResourceNotFoundException(buildUuid + " not found"));
    return contractService.deploy(buildDetails);
  }

  @PostMapping(value = "contract/{tx}/{function}")
  public ExecutionResult execute(
      @PathVariable("tx") final String contractTransactionHash,
      @PathVariable("function") final String functionName,
      @RequestBody final ContractInput contractInput) throws IOException {
    final String[] arguments = contractInput.getArguments();
    return contractService.tryExecute(contractTransactionHash, functionName, arguments);
  }

  /**
   * Query contract.
   *
   * @param contractTransactionHash transaction hash
   * @param functionName            function'name to call
   * @param arguments               argument for function
   *
   * @return function's result
   */
  @GetMapping(value = "contract/{tx}/{function}")
  public QueryResult query(
      @PathVariable("tx") final String contractTransactionHash,
      @PathVariable("function") final String functionName,
      @RequestParam(value = "arguments", required = false) final String[] arguments
  ) {
    logger.trace("Transaction Hash: {}, Function: {}, Arguments: {}",
        contractTransactionHash, functionName, arguments);
    final String[] safeArguments = nvl(arguments, new String[0]);
    return contractService.tryQuery(contractTransactionHash, functionName, safeArguments);
  }

  @ExceptionHandler(value = { HttpException.class })
  protected ResponseEntity handleHttpException(final HttpException ex, final WebRequest request) {
    logger.warn("Unexpected exception:", ex);
    return ResponseEntity.status(ex.getStatusCode()).body(handleThrowable(ex, request));
  }

  @ExceptionHandler(value = { Throwable.class })
  @ResponseStatus(INTERNAL_SERVER_ERROR)
  @ResponseBody
  protected Object handleThrowable(Throwable ex, WebRequest request) {
    logger.warn("Unexpected exception:", ex);
    return new ServiceError(ex.getMessage(), getStackTraceOf(ex));
  }
}