content: /*
   * 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 com.datastax.oss.driver.examples.mapper.killrvideo.user;
  
  import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.bindMarker;
  
  import com.datastax.oss.driver.api.core.CqlSession;
  import com.datastax.oss.driver.api.core.DefaultConsistencyLevel;
  import com.datastax.oss.driver.api.core.cql.BoundStatementBuilder;
  import com.datastax.oss.driver.api.core.cql.PreparedStatement;
  import com.datastax.oss.driver.api.core.cql.ResultSet;
  import com.datastax.oss.driver.api.mapper.MapperContext;
  import com.datastax.oss.driver.api.mapper.entity.EntityHelper;
  import com.datastax.oss.driver.api.mapper.entity.saving.NullSavingStrategy;
  import java.time.Instant;
  import java.util.Objects;
  import java.util.UUID;
  
  /**
   * Provides the implementation of {@link UserDao#create}.
   *
   * <p>Package-private visibility is sufficient, this will be called only from the generated DAO
   * implementation.
   */
  class CreateUserQueryProvider {
  
    private final CqlSession session;
    private final EntityHelper<User> userHelper;
    private final EntityHelper<UserCredentials> credentialsHelper;
    private final PreparedStatement preparedInsertCredentials;
    private final PreparedStatement preparedInsertUser;
    private final PreparedStatement preparedDeleteCredentials;
    private final PreparedStatement preparedDeleteUser;
  
    CreateUserQueryProvider(
        MapperContext context,
        EntityHelper<User> userHelper,
        EntityHelper<UserCredentials> credentialsHelper) {
  
      this.session = context.getSession();
  
      this.userHelper = userHelper;
      this.credentialsHelper = credentialsHelper;
  
      this.preparedInsertCredentials =
          session.prepare(credentialsHelper.insert().ifNotExists().asCql());
      this.preparedInsertUser = session.prepare(userHelper.insert().asCql());
      this.preparedDeleteCredentials =
          session.prepare(
              credentialsHelper
                  .deleteByPrimaryKey()
                  .ifColumn("userid")
                  .isEqualTo(bindMarker("userid"))
                  .builder()
                  .setConsistencyLevel(DefaultConsistencyLevel.ANY)
                  .build());
      this.preparedDeleteUser =
          session.prepare(
              userHelper
                  .deleteByPrimaryKey()
                  .ifExists()
                  .builder()
                  .setConsistencyLevel(DefaultConsistencyLevel.ANY)
                  .build());
    }
  
    boolean create(User user, char[] password) {
      Objects.requireNonNull(user.getUserid());
      Objects.requireNonNull(user.getEmail());
      if (user.getCreatedDate() == null) {
        user.setCreatedDate(Instant.now());
      }
  
      try {
        // Insert the user first: otherwise there would be a short window where we have credentials
        // without a corresponding user in the database, and this is considered an error state in
        // LoginQueryProvider
        insertUser(user);
        if (!insertCredentialsIfNotExists(user.getEmail(), password, user.getUserid())) {
          // email already exists
          session.execute(preparedDeleteUser.bind(user.getUserid()));
          return false;
        }
        return true;
      } catch (Exception insertException) {
        // Clean up and rethrow
        try {
          session.execute(preparedDeleteUser.bind(user.getUserid()));
        } catch (Exception e) {
          insertException.addSuppressed(e);
        }
        try {
          session.execute(preparedDeleteCredentials.bind(user.getEmail(), user.getUserid()));
        } catch (Exception e) {
          insertException.addSuppressed(e);
        }
        throw insertException;
      }
    }
  
    private boolean insertCredentialsIfNotExists(String email, char[] password, UUID userId) {
      String passwordHash = PasswordHashing.hash(Objects.requireNonNull(password));
      UserCredentials credentials =
          new UserCredentials(Objects.requireNonNull(email), passwordHash, userId);
      BoundStatementBuilder insertCredentials = preparedInsertCredentials.boundStatementBuilder();
      credentialsHelper.set(credentials, insertCredentials, NullSavingStrategy.DO_NOT_SET, false);
      ResultSet resultSet = session.execute(insertCredentials.build());
      return resultSet.wasApplied();
    }
  
    private void insertUser(User user) {
      BoundStatementBuilder insertUser = preparedInsertUser.boundStatementBuilder();
      userHelper.set(user, insertUser, NullSavingStrategy.DO_NOT_SET, false);
      session.execute(insertUser.build());
    }
  }
  