All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.resys.thena.docdb.spi.commits.HeadCommitBuilderDefault Maven / Gradle / Ivy

The newest version!
package io.resys.thena.docdb.spi.commits;

/*-
 * #%L
 * thena-docdb-api
 * %%
 * Copyright (C) 2021 Copyright 2021 ReSys OÜ
 * %%
 * Licensed 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.
 * #L%
 */

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import io.resys.thena.docdb.api.actions.CommitActions.CommitResult;
import io.resys.thena.docdb.api.actions.CommitActions.CommitStatus;
import io.resys.thena.docdb.api.actions.CommitActions.HeadCommitBuilder;
import io.resys.thena.docdb.api.actions.ImmutableCommitResult;
import io.resys.thena.docdb.api.actions.ObjectsActions;
import io.resys.thena.docdb.api.actions.ObjectsActions.ObjectsResult;
import io.resys.thena.docdb.api.actions.ObjectsActions.ObjectsStatus;
import io.resys.thena.docdb.api.actions.ObjectsActions.RefObjects;
import io.resys.thena.docdb.api.models.ImmutableMessage;
import io.resys.thena.docdb.spi.ClientState;
import io.resys.thena.docdb.spi.commits.CommitVisitor.CommitOutputStatus;
import io.resys.thena.docdb.spi.support.Identifiers;
import io.resys.thena.docdb.spi.support.RepoAssert;
import io.smallrye.mutiny.Uni;
import lombok.RequiredArgsConstructor;


@RequiredArgsConstructor
public class HeadCommitBuilderDefault implements HeadCommitBuilder {

  private final ClientState state;
  private final ObjectsActions objectsActions;
  private final Map appendBlobs = new HashMap<>();
  private final List deleteBlobs = new ArrayList<>();
  
  private String headGid;
  private String repoId;
  private String headName;
  private String author;
  private String message;
  private String parentCommit;
  private Boolean parentIsLatest;

  @Override
  public HeadCommitBuilder id(String headGid) {
    RepoAssert.isEmpty(headName, () -> "Can't defined id when head is defined!");
    this.headGid = headGid;
    return this;
  }
  @Override
  public HeadCommitBuilder head(String repoId, String headName) {
    RepoAssert.isEmpty(headGid, () -> "Can't defined head when id is defined!");
    RepoAssert.notEmpty(repoId, () -> "repoId can't be empty!");
    RepoAssert.notEmpty(headName, () -> "headName can't be empty!");
    RepoAssert.isName(headName, () -> "headName has invalid charecters!");
    this.repoId = repoId;
    this.headName = headName;
    return this;
  }
  @Override
  public HeadCommitBuilder append(String name, String blob) {
    RepoAssert.notEmpty(blob, () -> "blob can't be empty!");
    RepoAssert.notEmpty(name, () -> "name can't be empty!");
    RepoAssert.isTrue(!this.appendBlobs.containsKey(name), () -> "Blob with name: '" + name + "' is already defined!");
    RepoAssert.isTrue(!this.deleteBlobs.contains(name), () -> "Blob with name: '" + name + "' can't be appended because it's been marked for removal!");
    this.appendBlobs.put(name, blob);
    return this;
  }
  @Override
  public HeadCommitBuilder remove(String name) {
    RepoAssert.isTrue(!this.appendBlobs.containsKey(name), () -> "Blob with name: '" + name + "' can't be marked for removal because it's beed appended!");
    RepoAssert.isTrue(!this.deleteBlobs.contains(name), () -> "Blob with name: '" + name + "' is already marked for removal!");
    RepoAssert.notEmpty(name, () -> "name can't be empty!");
    this.deleteBlobs.add(name);
    return this;
  }
  @Override
  public HeadCommitBuilder author(String author) {
    RepoAssert.notEmpty(author, () -> "author can't be empty!");
    this.author = author;
    return this;
  }
  @Override
  public HeadCommitBuilder message(String message) {
    RepoAssert.notEmpty(message, () -> "message can't be empty!");
    this.message = message;
    return this;
  }
  @Override
  public HeadCommitBuilder parent(String parentCommit) {
    this.parentCommit = parentCommit;
    return this;
  }
  @Override
  public Uni build() {
    RepoAssert.notEmpty(author, () -> "author can't be empty!");
    RepoAssert.notEmpty(message, () -> "message can't be empty!");
    RepoAssert.isTrue(!appendBlobs.isEmpty() || !deleteBlobs.isEmpty(), () -> "Nothing to commit, no content!");
    
    if(this.headGid != null) {
      final var id = Identifiers.fromRepoHeadGid(this.headGid);
      this.repoId = id[0];
      this.headName = id[1];
    }
    RepoAssert.notEmpty(repoId, () -> "Can't resolve repoId!");
    RepoAssert.notEmpty(headName, () -> "Can't resolve headName!");
    
    final String gid = Identifiers.toRepoHeadGid(repoId, headName);
    
    return objectsActions.refState().repo(this.repoId).ref(headName).build()
        .onItem().transformToUni(state -> {
          final var result = validateState(state, gid);
          if(result != null) {
            return Uni.createFrom().item(result);
          }
          return createCommit(state, gid);
        });
  }
  
  private CommitResult validateState(ObjectsResult state, String gid) {
    if(state.getStatus() == ObjectsStatus.ERROR) {
      return ImmutableCommitResult.builder()
          .gid(gid)
          .addAllMessages(state.getMessages())
          .status(CommitStatus.ERROR)
          .build();
    }
    
    
    // Unknown parent
    if(state.getObjects() == null && parentCommit != null) {
      return (CommitResult) ImmutableCommitResult.builder()
          .gid(gid)
          .addMessages(ImmutableMessage.builder()
              .text(new StringBuilder()
                  .append("Commit to head: '").append(headName).append("'")
                  .append(" is rejected.")
                  .append(" Your head is: '").append(parentCommit).append("')")
                  .append(" but remote has no head.").append("'!")
                  .toString())
              .build())
          .status(CommitStatus.ERROR)
          .build();
      
    }
    
    
    // No parent commit defined for existing head
    if(state.getObjects() != null && parentCommit == null && !Boolean.TRUE.equals(this.parentIsLatest)) {
      return (CommitResult) ImmutableCommitResult.builder()
          .gid(gid)
          .addMessages(ImmutableMessage.builder()
              .text(new StringBuilder()
                  .append("Parent commit can only be undefined for the first commit!")
                  .append(" Parent commit for")
                  .append(" head: '").append(headName).append("'")
                  .append(" is: '").append(state.getObjects().getRef().getCommit()).append("'!")
                  .toString())
              .build())
          .status(CommitStatus.ERROR)
          .build();
    }
    
    // Wrong parent commit
    if(state.getObjects() != null && parentCommit != null && 
        !parentCommit.equals(state.getObjects().getRef().getCommit()) &&
        !Boolean.TRUE.equals(this.parentIsLatest)) {
      
      final var text = new StringBuilder()
        .append("Commit to head: '").append(headName).append("'")
        .append(" is rejected.")
        .append(" Your head is: '").append(parentCommit).append("')")
        .append(" but remote is: '").append(state.getObjects().getRef().getCommit()).append("'!")
        .toString();
      
      return ImmutableCommitResult.builder()
          .gid(gid)
          .addMessages(ImmutableMessage.builder().text(text).build())
          .status(CommitStatus.ERROR)
          .build();
    }

    return null;
  }
  
  private Uni createCommit(ObjectsResult state, String gid) {
    final var toBeSaved = new CommitVisitor().visit(
        ImmutableCommitInput.builder()
          .commitAuthor(this.author)
          .commitMessage(this.message)
          .ref(headName)
          .append(appendBlobs)
          .remove(deleteBlobs)
          .repo(state.getRepo())
          .parent(Optional.ofNullable(state.getObjects()))
          .build());
    return new CommitSaveVisitor(this.state.withRepo(state.getRepo())).visit(toBeSaved)
        .onItem().transform(saved -> (CommitResult) ImmutableCommitResult.builder()
          .gid(gid)
          .commit(saved.getCommit())
          .addAllMessages(saved.getMessages())
          .addMessages(saved.getLog())
          .status(visitStatus(saved.getStatus()))
          .build());
  }
  
  private CommitStatus visitStatus(CommitOutputStatus src) {
    if(src == CommitOutputStatus.OK) {
      return CommitStatus.OK;
    } else if(src == CommitOutputStatus.CONFLICT) {
      return CommitStatus.CONFLICT;
    }
    return CommitStatus.ERROR;
    
  }
  @Override
  public HeadCommitBuilder parentIsLatest() {
    this.parentIsLatest = true;
    return this;
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy