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.mapper;
  
  import static org.assertj.core.api.Assertions.assertThatCode;
  import static org.assertj.core.api.Assertions.assertThatThrownBy;
  
  import com.datastax.oss.driver.api.core.CqlIdentifier;
  import com.datastax.oss.driver.api.core.CqlSession;
  import com.datastax.oss.driver.api.core.config.DefaultDriverOption;
  import com.datastax.oss.driver.api.core.config.DriverConfigLoader;
  import com.datastax.oss.driver.api.core.cql.SimpleStatement;
  import com.datastax.oss.driver.api.mapper.MapperException;
  import com.datastax.oss.driver.api.mapper.annotations.Dao;
  import com.datastax.oss.driver.api.mapper.annotations.DaoFactory;
  import com.datastax.oss.driver.api.mapper.annotations.DaoKeyspace;
  import com.datastax.oss.driver.api.mapper.annotations.DefaultNullSavingStrategy;
  import com.datastax.oss.driver.api.mapper.annotations.Entity;
  import com.datastax.oss.driver.api.mapper.annotations.Mapper;
  import com.datastax.oss.driver.api.mapper.annotations.PartitionKey;
  import com.datastax.oss.driver.api.mapper.annotations.Select;
  import com.datastax.oss.driver.api.mapper.annotations.Update;
  import com.datastax.oss.driver.api.mapper.entity.saving.NullSavingStrategy;
  import com.datastax.oss.driver.api.testinfra.ccm.CcmRule;
  import com.datastax.oss.driver.api.testinfra.session.SessionRule;
  import com.datastax.oss.driver.api.testinfra.session.SessionUtils;
  import com.datastax.oss.driver.categories.ParallelizableTests;
  import java.util.Objects;
  import java.util.UUID;
  import org.junit.AfterClass;
  import org.junit.BeforeClass;
  import org.junit.ClassRule;
  import org.junit.Test;
  import org.junit.experimental.categories.Category;
  import org.junit.rules.RuleChain;
  import org.junit.rules.TestRule;
  
  @Category(ParallelizableTests.class)
  public class NullSavingStrategyIT {
  
    private static final CcmRule CCM_RULE = CcmRule.getInstance();
  
    private static final SessionRule<CqlSession> SESSION_RULE = SessionRule.builder(CCM_RULE).build();
  
    // JAVA-3076: V3 protocol calls that could trigger cassandra to issue client warnings appear to be
    // inherently unstable when used at the same time as V4+ protocol clients (common since this is
    // part of the parallelizable test suite).
    //
    // For this test we'll use latest protocol version for SessionRule set-up, which creates the
    // keyspace and could potentially result in warning about too many keyspaces, and then create a
    // new client for the tests to use, which they access via the static InventoryMapper instance
    // `mapper`.
    //
    // This additional client is created in the @BeforeClass method #setup() and guaranteed to be
    // closed in @AfterClass method #teardown().
    //
    // Note: The standard junit runner executes rules before class/test setup so the order of
    // execution will be CcmRule#before > SessionRule#before > NullSavingStrategyIT#setup, meaning
    // CCM_RULE/SESSION_RULE should be fully initialized by the time #setup() is invoked.
    private static CqlSession v3Session;
  
    @ClassRule
    public static final TestRule CHAIN = RuleChain.outerRule(CCM_RULE).around(SESSION_RULE);
  
    private static InventoryMapper mapper;
  
    @BeforeClass
    public static void setup() {
      // setup table for use in tests, this can use the default session
      SESSION_RULE
          .session()
          .execute(
              SimpleStatement.builder(
                      "CREATE TABLE product_simple(id uuid PRIMARY KEY, description text)")
                  .setExecutionProfile(SESSION_RULE.slowProfile())
                  .build());
  
      // Create V3 protocol session for use in tests, will be closed in #teardown()
      v3Session =
          SessionUtils.newSession(
              CCM_RULE,
              SESSION_RULE.keyspace(),
              DriverConfigLoader.programmaticBuilder()
                  .withString(DefaultDriverOption.PROTOCOL_VERSION, "V3")
                  .build());
  
      // Hand V3 session to InventoryMapper which the tests will use to perform db calls
      mapper = new NullSavingStrategyIT_InventoryMapperBuilder(v3Session).build();
    }
  
    @AfterClass
    public static void teardown() {
      // Close V3 session (SESSION_RULE will be closed separately by @ClassRule handling)
      if (v3Session != null) {
        v3Session.close();
      }
    }
  
    @Test
    public void should_throw_when_try_to_construct_dao_with_DO_NOT_SET_strategy_for_V3_protocol() {
      assertThatThrownBy(() -> mapper.productDao(SESSION_RULE.keyspace()))
          .isInstanceOf(MapperException.class)
          .hasMessage("You cannot use NullSavingStrategy.DO_NOT_SET for protocol version V3.");
    }
  
    @Test
    public void
        should_throw_when_try_to_construct_dao_with_DO_NOT_SET_implicit_strategy_for_V3_protocol() {
      assertThatThrownBy(() -> mapper.productDaoImplicit(SESSION_RULE.keyspace()))
          .isInstanceOf(MapperException.class)
          .hasMessage("You cannot use NullSavingStrategy.DO_NOT_SET for protocol version V3.");
    }
  
    @Test
    public void
        should_throw_when_try_to_construct_dao_with_DO_NOT_SET_strategy_set_globally_for_V3_protocol() {
      assertThatThrownBy(() -> mapper.productDaoDefault(SESSION_RULE.keyspace()))
          .isInstanceOf(MapperException.class)
          .hasMessage("You cannot use NullSavingStrategy.DO_NOT_SET for protocol version V3.");
    }
  
    @Test
    public void should_do_not_throw_when_construct_dao_with_global_level_SET_TO_NULL() {
      assertThatCode(() -> mapper.productDaoGlobalLevelSetToNull(SESSION_RULE.keyspace()))
          .doesNotThrowAnyException();
    }
  
    @Test
    public void should_do_not_throw_when_construct_dao_with_parent_interface_SET_TO_NULL() {
      assertThatCode(() -> mapper.productDaoSetToNullFromParentInterface(SESSION_RULE.keyspace()))
          .doesNotThrowAnyException();
    }
  
    @Test
    public void
        should_do_not_throw_when_construct_dao_with_global_level_DO_NOT_SET_and_local_override_to_SET_TO_NULL() {
      assertThatCode(() -> mapper.productDaoLocalOverride(SESSION_RULE.keyspace()))
          .doesNotThrowAnyException();
    }
  
    @Mapper
    public interface InventoryMapper {
      @DaoFactory
      ProductSimpleDao productDao(@DaoKeyspace CqlIdentifier keyspace);
  
      @DaoFactory
      ProductSimpleDaoDefault productDaoDefault(@DaoKeyspace CqlIdentifier keyspace);
  
      @DaoFactory
      ProductSimpleDaoImplicit productDaoImplicit(@DaoKeyspace CqlIdentifier keyspace);
  
      @DaoFactory
      ProductSimpleDaoGlobalLevelSetToNull productDaoGlobalLevelSetToNull(
          @DaoKeyspace CqlIdentifier keyspace);
  
      @DaoFactory
      ProductSimpleDaoSetToNullFromParentInterface productDaoSetToNullFromParentInterface(
          @DaoKeyspace CqlIdentifier keyspace);
  
      @DaoFactory
      ProductSimpleDaoGlobalLevelDoNotSetOverrideSetToNull productDaoLocalOverride(
          @DaoKeyspace CqlIdentifier keyspace);
    }
  
    @DefaultNullSavingStrategy(NullSavingStrategy.SET_TO_NULL)
    public interface SetToNull {}
  
    @Dao
    public interface ProductSimpleDao {
  
      @Update(nullSavingStrategy = NullSavingStrategy.DO_NOT_SET)
      void update(ProductSimple product);
  
      @Select
      ProductSimple findById(UUID productId);
    }
  
    @Dao
    public interface ProductSimpleDaoImplicit {
  
      @Update
      void update(ProductSimple product);
  
      @Select
      ProductSimple findById(UUID productId);
    }
  
    @Dao
    @DefaultNullSavingStrategy(NullSavingStrategy.DO_NOT_SET)
    public interface ProductSimpleDaoDefault extends SetToNull {
      @Update
      void update(ProductSimple product);
  
      @Select
      ProductSimple findById(UUID productId);
    }
  
    @Dao
    @DefaultNullSavingStrategy(NullSavingStrategy.SET_TO_NULL)
    public interface ProductSimpleDaoGlobalLevelSetToNull {
  
      @Update
      void update(ProductSimple product);
  
      @Select
      ProductSimple findById(UUID productId);
    }
  
    @Dao
    public interface ProductSimpleDaoSetToNullFromParentInterface
        extends ProductSimpleDaoImplicit, SetToNull {}
  
    @Dao
    @DefaultNullSavingStrategy(NullSavingStrategy.DO_NOT_SET)
    public interface ProductSimpleDaoGlobalLevelDoNotSetOverrideSetToNull {
  
      @Update(nullSavingStrategy = NullSavingStrategy.SET_TO_NULL)
      void update(ProductSimple product);
  
      @Select
      ProductSimple findById(UUID productId);
    }
  
    @Entity
    public static class ProductSimple {
      @PartitionKey private UUID id;
      private String description;
  
      public ProductSimple() {}
  
      public ProductSimple(UUID id, String description) {
        this.id = id;
        this.description = description;
      }
  
      public UUID getId() {
        return id;
      }
  
      public void setId(UUID id) {
        this.id = id;
      }
  
      public String getDescription() {
        return description;
      }
  
      public void setDescription(String description) {
        this.description = description;
      }
  
      @Override
      public boolean equals(Object other) {
  
        if (this == other) {
          return true;
        } else if (other instanceof ProductSimple) {
          ProductSimple that = (ProductSimple) other;
          return Objects.equals(this.id, that.id)
              && Objects.equals(this.description, that.description);
        } else {
          return false;
        }
      }
  
      @Override
      public int hashCode() {
        return Objects.hash(id, description);
      }
  
      @Override
      public String toString() {
        return "ProductSimple{" + "id=" + id + ", description='" + description + '\'' + '}';
      }
    }
  }
  