/*
 * 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 org.apache.ambari.server.orm.dao;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.UUID;

import jakarta.persistence.EntityManager;

import org.apache.ambari.server.AmbariException;
import org.apache.ambari.server.H2DatabaseCleaner;
import org.apache.ambari.server.controller.AlertNoticeRequest;
import org.apache.ambari.server.controller.internal.AlertNoticeResourceProvider;
import org.apache.ambari.server.controller.internal.PageRequestImpl;
import org.apache.ambari.server.controller.internal.SortRequestImpl;
import org.apache.ambari.server.controller.spi.PageRequest.StartingPoint;
import org.apache.ambari.server.controller.spi.Predicate;
import org.apache.ambari.server.controller.spi.SortRequest;
import org.apache.ambari.server.controller.spi.SortRequest.Order;
import org.apache.ambari.server.controller.spi.SortRequestProperty;
import org.apache.ambari.server.controller.utilities.PredicateBuilder;
import org.apache.ambari.server.orm.AlertDaoHelper;
import org.apache.ambari.server.orm.GuiceJpaInitializer;
import org.apache.ambari.server.orm.InMemoryDefaultTestModule;
import org.apache.ambari.server.orm.OrmTestHelper;
import org.apache.ambari.server.orm.entities.AlertDefinitionEntity;
import org.apache.ambari.server.orm.entities.AlertGroupEntity;
import org.apache.ambari.server.orm.entities.AlertHistoryEntity;
import org.apache.ambari.server.orm.entities.AlertNoticeEntity;
import org.apache.ambari.server.orm.entities.AlertTargetEntity;
import org.apache.ambari.server.state.AlertState;
import org.apache.ambari.server.state.Cluster;
import org.apache.ambari.server.state.Clusters;
import org.apache.ambari.server.state.NotificationState;
import org.apache.ambari.server.state.ServiceComponentFactory;
import org.apache.ambari.server.state.ServiceComponentHostFactory;
import org.apache.ambari.server.state.ServiceFactory;
import org.apache.ambari.server.state.alert.Scope;
import org.apache.ambari.server.state.alert.SourceType;
import org.apache.ambari.server.utils.EventBusSynchronizer;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.persist.UnitOfWork;

/**
 * Tests {@link AlertDispatchDAO}.
 */
public class AlertDispatchDAOTest {

  private final static String HOSTNAME = "c6401.ambari.apache.org";

  private Clusters m_clusters;
  private Cluster m_cluster;
  private Injector m_injector;
  private AlertDispatchDAO m_dao;
  private AlertDefinitionDAO m_definitionDao;
  private AlertsDAO m_alertsDao;
  private OrmTestHelper m_helper;

  private ServiceFactory m_serviceFactory;
  private ServiceComponentFactory m_componentFactory;
  private ServiceComponentHostFactory m_schFactory;
  private AlertDaoHelper m_alertHelper;

  /**
   *
   */
  @Before
  public void setup() throws Exception {
    m_injector = Guice.createInjector(new InMemoryDefaultTestModule());
    m_injector.getInstance(GuiceJpaInitializer.class);
    m_injector.getInstance(UnitOfWork.class).begin();


    m_dao = m_injector.getInstance(AlertDispatchDAO.class);
    m_alertsDao = m_injector.getInstance(AlertsDAO.class);
    m_definitionDao = m_injector.getInstance(AlertDefinitionDAO.class);
    m_helper = m_injector.getInstance(OrmTestHelper.class);
    m_serviceFactory = m_injector.getInstance(ServiceFactory.class);
    m_componentFactory = m_injector.getInstance(ServiceComponentFactory.class);
    m_schFactory = m_injector.getInstance(ServiceComponentHostFactory.class);
    m_clusters = m_injector.getInstance(Clusters.class);
    m_alertHelper = m_injector.getInstance(AlertDaoHelper.class);


    // !!! need a synchronous op for testing
    EventBusSynchronizer.synchronizeAmbariEventPublisher(m_injector);

    m_cluster = m_clusters.getClusterById(m_helper.createCluster());
    m_helper.initializeClusterWithStack(m_cluster);
  }

  /**
   * @throws Exception
   */
  @After
  public void teardown() throws Exception {
    m_injector.getInstance(UnitOfWork.class).end();
    H2DatabaseCleaner.clearDatabase(m_injector.getProvider(EntityManager.class).get());
  }

  private void initTestData() throws Exception {
    Set<AlertTargetEntity> targets = createTargets(1);

    for (int i = 0; i < 2; i++) {
      AlertGroupEntity group = new AlertGroupEntity();
      group.setDefault(false);
      group.setGroupName("Group Name " + i);
      group.setClusterId(m_cluster.getClusterId());
      for (AlertTargetEntity alertTarget : targets) {
        group.addAlertTarget(alertTarget);
      }

      m_dao.create(group);
    }
  }

  /**
   *
   */
  @Test
  public void testFindTargets() throws Exception {
    initTestData();
    // find all targets
    List<AlertTargetEntity> targets = m_dao.findAllTargets();
    assertNotNull(targets);
    assertEquals(1, targets.size());

    // find by ids
    List<Long> ids = new ArrayList<>();
    ids.add(targets.get(0).getTargetId());
    ids.add(99999L);

    targets = m_dao.findTargetsById(ids);
    assertEquals(1, targets.size());

    //find by name
    AlertTargetEntity target = targets.get(0);

    AlertTargetEntity actual = m_dao.findTargetByName(target.getTargetName());
    assertEquals(target, actual);
  }

  /**
   *
   */
  @Test
  public void testCreateAndFindAllGlobalTargets() throws Exception {
    List<AlertTargetEntity> targets = m_dao.findAllGlobalTargets();
    assertNotNull(targets);
    assertEquals(0, targets.size());

    AlertTargetEntity target  = m_helper.createGlobalAlertTarget();
    m_helper.createGlobalAlertTarget();
    m_helper.createGlobalAlertTarget();

    targets = m_dao.findAllGlobalTargets();
    assertTrue(target.isGlobal());
    assertEquals(3, targets.size());

    m_dao.findTargetByName(target.getTargetName());
    assertTrue( target.isGlobal() );
  }

  /**
   *
   */
  @Test
  public void testFindGroups() throws Exception {
    initTestData();
    // find all
    List<AlertGroupEntity> groups = m_dao.findAllGroups();
    assertNotNull(groups);
    assertEquals(2, groups.size());

    //find by name
    AlertGroupEntity group = groups.get(1);

    AlertGroupEntity actual = m_dao.findGroupByName(group.getClusterId(),
            group.getGroupName());

    assertEquals(group, actual);

    //find by id
    List<Long> ids = new ArrayList<>();
    ids.add(groups.get(0).getGroupId());
    ids.add(groups.get(1).getGroupId());
    ids.add(99999L);

    groups = m_dao.findGroupsById(ids);
    assertEquals(2, groups.size());

    // find default group
    for (AlertGroupEntity alertGroupEntity : groups) {
      assertFalse(alertGroupEntity.isDefault());
    }

    Cluster cluster = m_helper.buildNewCluster(m_clusters, m_serviceFactory,
            m_componentFactory, m_schFactory, HOSTNAME);

    AlertGroupEntity hdfsGroup = m_dao.findDefaultServiceGroup(
            cluster.getClusterId(), "HDFS");

    assertNotNull(hdfsGroup);
    assertTrue(hdfsGroup.isDefault());
  }

  /**
   *
   */
  @Test
  public void testCreateUpdateRemoveGroup() throws Exception {
    // create group
    AlertTargetEntity target = m_helper.createAlertTarget();
    Set<AlertTargetEntity> targets = new HashSet<>();
    targets.add(target);

    AlertGroupEntity group = m_helper.createAlertGroup(
        m_cluster.getClusterId(), targets);
    AlertGroupEntity actual = m_dao.findGroupById(group.getGroupId());
    assertNotNull(group);

    assertEquals(group.getGroupName(), actual.getGroupName());
    assertEquals(group.isDefault(), actual.isDefault());
    assertEquals(group.getAlertTargets(), actual.getAlertTargets());
    assertEquals(group.getAlertDefinitions(), actual.getAlertDefinitions());

    // update group
    AlertGroupEntity group1 = m_helper.createAlertGroup(
            m_cluster.getClusterId(), null);

    String groupName = group1.getGroupName();

    group1 = m_dao.findGroupById(group1.getGroupId());
    group1.setGroupName(groupName + "FOO");
    group1.setDefault(true);

    m_dao.merge(group1);
    group = m_dao.findGroupById(group1.getGroupId());

    assertEquals(groupName + "FOO", group1.getGroupName());
    assertEquals(true, group1.isDefault());
    assertEquals(0, group1.getAlertDefinitions().size());
    assertEquals(0, group1.getAlertTargets().size());

    group1.addAlertTarget(target);
    m_dao.merge(group);

    group1 = m_dao.findGroupById(group1.getGroupId());
    assertEquals(targets, group1.getAlertTargets());

    // delete group
    m_dao.remove(group);
    group = m_dao.findGroupById(group.getGroupId());
    assertNull(group);

    target = m_dao.findTargetById(target.getTargetId());
    assertNotNull(target);
    assertEquals(1, m_dao.findAllTargets().size());
  }

  /**
   *
   */
  @Test
  public void testCreateAndRemoveTarget() throws Exception {
    // create target
    int targetCount = m_dao.findAllTargets().size();

    AlertTargetEntity target = m_helper.createAlertTarget();
    Set<AlertTargetEntity> targets = new HashSet<>();
    targets.add(target);

    AlertGroupEntity group = m_helper.createAlertGroup(
        m_cluster.getClusterId(), targets);

    AlertTargetEntity actual = m_dao.findTargetById(target.getTargetId());
    assertNotNull(actual);

    assertEquals(target.getTargetName(), actual.getTargetName());
    assertEquals(target.getDescription(), actual.getDescription());
    assertEquals(target.getNotificationType(), actual.getNotificationType());
    assertEquals(target.getProperties(), actual.getProperties());
    assertEquals(false, actual.isGlobal());

    assertNotNull(actual.getAlertGroups());
    Iterator<AlertGroupEntity> iterator = actual.getAlertGroups().iterator();
    AlertGroupEntity actualGroup = iterator.next();

    assertEquals(group, actualGroup);

    assertEquals(targetCount + 1, m_dao.findAllTargets().size());

    // remove target
    m_dao.remove(target);

    target = m_dao.findTargetById(target.getTargetId());
    assertNull(target);

  }

  /**
   *
   */
  @Test
  public void testGlobalTargetAssociations() throws Exception {
    AlertGroupEntity group = m_helper.createAlertGroup(
        m_cluster.getClusterId(), null);

    group = m_dao.findGroupById(group.getGroupId());
    assertNotNull(group);
    assertEquals(0, group.getAlertTargets().size());

    AlertTargetEntity target = m_helper.createGlobalAlertTarget();
    assertTrue(target.isGlobal());

    group = m_dao.findGroupById(group.getGroupId());
    assertNotNull(group);
    assertEquals(1, group.getAlertTargets().size());

    List<AlertGroupEntity> groups = m_dao.findAllGroups();
    target = m_dao.findTargetById(target.getTargetId());
    assertEquals(groups.size(), target.getAlertGroups().size());

    m_dao.remove(target);

    group = m_dao.findGroupById(group.getGroupId());
    assertNotNull(group);
    assertEquals(0, group.getAlertTargets().size());
  }

  /**
   * Tests that a newly created group is correctly associated with all global
   * targets.
   */
  @Test
  public void testGlobalTargetAssociatedWithNewGroup() throws Exception {
    AlertTargetEntity target1 = m_helper.createGlobalAlertTarget();
    AlertTargetEntity target2 = m_helper.createGlobalAlertTarget();
    assertTrue(target1.isGlobal());
    assertTrue(target2.isGlobal());

    AlertGroupEntity group = m_helper.createAlertGroup(
        m_cluster.getClusterId(), null);

    group = m_dao.findGroupById(group.getGroupId());
    assertNotNull(group);
    assertEquals(2, group.getAlertTargets().size());

    Iterator<AlertTargetEntity> iterator = group.getAlertTargets().iterator();
    AlertTargetEntity groupTarget1 = iterator.next();
    AlertTargetEntity groupTarget2 = iterator.next();

    assertTrue(groupTarget1.isGlobal());
    assertTrue(groupTarget2.isGlobal());
  }

  /**
  *
  */
  @Test
  public void testDeleteTargetWithNotices() throws Exception {
    AlertTargetEntity target = m_helper.createAlertTarget();

    List<AlertDefinitionEntity> definitions = createDefinitions();
    AlertDefinitionEntity definition = definitions.get(0);

    AlertHistoryEntity history = new AlertHistoryEntity();
    history.setServiceName(definition.getServiceName());
    history.setClusterId(m_cluster.getClusterId());
    history.setAlertDefinition(definition);
    history.setAlertLabel("Label");
    history.setAlertState(AlertState.OK);
    history.setAlertText("Alert Text");
    history.setAlertTimestamp(System.currentTimeMillis());
    m_alertsDao.create(history);

    AlertNoticeEntity notice = new AlertNoticeEntity();
    notice.setUuid(UUID.randomUUID().toString());
    notice.setAlertTarget(target);
    notice.setAlertHistory(history);
    notice.setNotifyState(NotificationState.PENDING);
    m_dao.create(notice);

    notice = m_dao.findNoticeById(notice.getNotificationId());
    assertEquals(target.getTargetId(), notice.getAlertTarget().getTargetId());

    //new org.apache.derby.tools.dblook(new String[]{"-d", Configuration.JDBC_IN_MEMORY_URL, "-verbose", "-o", "/tmp/1.ddl"});

    target = m_dao.findTargetById(target.getTargetId());
    m_dao.remove(target);
    notice = m_dao.findNoticeById(notice.getNotificationId());
    assertNull(notice);
  }

  /**
   *
   */
  @Test
  public void testDeleteAssociatedTarget() throws Exception {
    AlertTargetEntity target = m_helper.createAlertTarget();
    Set<AlertTargetEntity> targets = new HashSet<>();
    targets.add(target);

    AlertGroupEntity group = m_helper.createAlertGroup(
        m_cluster.getClusterId(), targets);
    assertEquals(1, group.getAlertTargets().size());

    target = m_dao.findTargetById(target.getTargetId());
    m_dao.refresh(target);

    assertNotNull(target);
    assertEquals(1, target.getAlertGroups().size());

    m_dao.remove(target);
    target = m_dao.findTargetById(target.getTargetId());
    assertNull(target);

    group = m_dao.findGroupById(group.getGroupId());
    assertNotNull(group);

    assertEquals(0, group.getAlertTargets().size());
  }

  /**
   * Tests finding groups by a definition ID that they are associatd with.
   *
   * @throws Exception
   */
  @Test
  public void testFindGroupsByDefinition() throws Exception {
    List<AlertDefinitionEntity> definitions = createDefinitions();
    AlertGroupEntity group = m_helper.createAlertGroup(
        m_cluster.getClusterId(), null);

    group = m_dao.findGroupById(group.getGroupId());
    assertNotNull(group);

    for (AlertDefinitionEntity definition : definitions) {
      group.addAlertDefinition(definition);
    }

    m_dao.merge(group);

    group = m_dao.findGroupByName(m_cluster.getClusterId(), group.getGroupName());
    assertEquals(definitions.size(), group.getAlertDefinitions().size());

    // assert that the definition is now part of 2 groups (the default group
    // and the newly associated group from above)
    for (AlertDefinitionEntity definition : definitions) {
      List<AlertGroupEntity> groups = m_dao.findGroupsByDefinition(definition);
      assertEquals(2, groups.size());
    }
  }

  /**
   * Tests finding groups by a definition ID that they are associatd with in
   * order to get any targets associated with that group. This exercises the
   * bi-directional
   *
   * @throws Exception
   */
  @Test
  public void testFindTargetsViaGroupsByDefinition() throws Exception {
    initTestData();
    List<AlertDefinitionEntity> definitions = createDefinitions();
    AlertGroupEntity group = m_helper.createAlertGroup(
        m_cluster.getClusterId(), null);

    group = m_dao.findGroupById(group.getGroupId());
    assertNotNull(group);

    AlertDefinitionEntity definition = definitions.get(0);
    group.addAlertDefinition(definition);

    m_dao.merge(group);

    List<AlertTargetEntity> targets = m_dao.findAllTargets();
    AlertTargetEntity target = targets.get(0);
    Set<AlertTargetEntity> setTargets = Collections.singleton(target);

    group.setAlertTargets(setTargets);
    m_dao.merge(group);

    List<AlertGroupEntity> groups = m_dao.findGroupsByDefinition(definition);
    assertEquals(2, groups.size());

    group = groups.get(groups.indexOf(group));
    assertEquals(1, group.getAlertTargets().size());
    assertEquals(target.getTargetId(),
        group.getAlertTargets().iterator().next().getTargetId());
  }

  /**
   * @throws Exception
   */
  @Test
  public void testFindNoticeByUuid() throws Exception {
    List<AlertDefinitionEntity> definitions = createDefinitions();
    AlertDefinitionEntity definition = definitions.get(0);

    AlertHistoryEntity history = new AlertHistoryEntity();
    history.setServiceName(definition.getServiceName());
    history.setClusterId(m_cluster.getClusterId());
    history.setAlertDefinition(definition);
    history.setAlertLabel("Label");
    history.setAlertState(AlertState.OK);
    history.setAlertText("Alert Text");
    history.setAlertTimestamp(System.currentTimeMillis());
    m_alertsDao.create(history);

    AlertTargetEntity target = m_helper.createAlertTarget();

    AlertNoticeEntity notice = new AlertNoticeEntity();
    notice.setUuid(UUID.randomUUID().toString());
    notice.setAlertTarget(target);
    notice.setAlertHistory(history);
    notice.setNotifyState(NotificationState.PENDING);
    m_dao.create(notice);

    AlertNoticeEntity actual = m_dao.findNoticeByUuid(notice.getUuid());
    assertEquals(notice.getNotificationId(), actual.getNotificationId());
    assertNull(m_dao.findNoticeByUuid("DEADBEEF"));
  }


  /**
   * Tests that the Ambari {@link Predicate} can be converted and submitted to
   * JPA correctly to return a restricted result set.
   *
   * @throws Exception
   */
  @Test
  public void testAlertNoticePredicate() throws Exception {
    m_helper.addHost(m_clusters, m_cluster, HOSTNAME);
    m_helper.installHdfsService(m_cluster, m_serviceFactory, m_componentFactory, m_schFactory, HOSTNAME);
    m_helper.installYarnService(m_cluster, m_serviceFactory, m_componentFactory, m_schFactory, HOSTNAME);

    m_alertHelper.populateData(m_cluster);

    Predicate clusterPredicate = null;
    Predicate hdfsPredicate = null;
    Predicate yarnPredicate = null;
    Predicate adminPredicate = null;
    Predicate adminOrOperatorPredicate = null;
    Predicate pendingPredicate = null;
    Predicate noticeIdPredicate = null;

    clusterPredicate = new PredicateBuilder().property(
        AlertNoticeResourceProvider.ALERT_NOTICE_CLUSTER_NAME).equals("c1").toPredicate();

    hdfsPredicate = new PredicateBuilder().property(
        AlertNoticeResourceProvider.ALERT_NOTICE_SERVICE_NAME).equals("HDFS").toPredicate();

    yarnPredicate = new PredicateBuilder().property(
        AlertNoticeResourceProvider.ALERT_NOTICE_SERVICE_NAME).equals("YARN").toPredicate();

    adminPredicate = new PredicateBuilder().property(
        AlertNoticeResourceProvider.ALERT_NOTICE_TARGET_NAME).equals(
        "Administrators").toPredicate();

    adminOrOperatorPredicate = new PredicateBuilder().property(
        AlertNoticeResourceProvider.ALERT_NOTICE_TARGET_NAME).equals(
        "Administrators").or().property(
        AlertNoticeResourceProvider.ALERT_NOTICE_TARGET_NAME).equals(
        "Operators").toPredicate();

    pendingPredicate = new PredicateBuilder().property(
        AlertNoticeResourceProvider.ALERT_NOTICE_STATE).equals(
        NotificationState.PENDING.name()).toPredicate();

    AlertNoticeRequest request = new AlertNoticeRequest();
    request.Predicate = clusterPredicate;

    List<AlertNoticeEntity> notices = m_dao.findAllNotices(request);
    assertEquals(3, notices.size());

    request.Predicate = hdfsPredicate;
    notices = m_dao.findAllNotices(request);
    assertEquals(2, notices.size());

    request.Predicate = yarnPredicate;
    notices = m_dao.findAllNotices(request);
    assertEquals(1, notices.size());

    request.Predicate = adminPredicate;
    notices = m_dao.findAllNotices(request);
    assertEquals(2, notices.size());

    request.Predicate = adminOrOperatorPredicate;
    notices = m_dao.findAllNotices(request);
    assertEquals(3, notices.size());

    request.Predicate = pendingPredicate;
    notices = m_dao.findAllNotices(request);
    assertEquals(1, notices.size());

    noticeIdPredicate = new PredicateBuilder().property(
        AlertNoticeResourceProvider.ALERT_NOTICE_ID).equals(
        notices.get(0).getNotificationId()).toPredicate();

    request.Predicate = noticeIdPredicate;
    notices = m_dao.findAllNotices(request);
    assertEquals(1, notices.size());
  }

  /**
   * Tests that JPA does the pagination work for us.
   *
   * @throws Exception
   */
  @Test
  public void testAlertNoticePagination() throws Exception {
    m_helper.addHost(m_clusters, m_cluster, HOSTNAME);
    m_helper.installHdfsService(m_cluster, m_serviceFactory, m_componentFactory, m_schFactory, HOSTNAME);
    m_helper.installYarnService(m_cluster, m_serviceFactory, m_componentFactory, m_schFactory, HOSTNAME);

    m_alertHelper.populateData(m_cluster);

    AlertNoticeRequest request = new AlertNoticeRequest();
    request.Pagination = null;

    // get back all 3
    List<AlertNoticeEntity> notices = m_dao.findAllNotices(request);
    assertEquals(3, notices.size());

    // only the first 2
    request.Pagination = new PageRequestImpl(StartingPoint.Beginning, 2, 0,
        null, null);

    notices = m_dao.findAllNotices(request);
    assertEquals(2, notices.size());

    // the 2nd and 3rd
    request.Pagination = new PageRequestImpl(StartingPoint.Beginning, 1, 2,
        null, null);

    notices = m_dao.findAllNotices(request);
    assertEquals(1, notices.size());

    // none b/c we're out of index
    request.Pagination = new PageRequestImpl(StartingPoint.Beginning, 1, 3,
        null, null);

    notices = m_dao.findAllNotices(request);
    assertEquals(0, notices.size());
  }

  /**
   * Tests that JPA does the sorting work for us.
   *
   * @throws Exception
   */
  @Test
  public void testAlertNoticeSorting() throws Exception {
    m_helper.addHost(m_clusters, m_cluster, HOSTNAME);
    m_helper.installHdfsService(m_cluster, m_serviceFactory, m_componentFactory, m_schFactory, HOSTNAME);
    m_helper.installYarnService(m_cluster, m_serviceFactory, m_componentFactory, m_schFactory, HOSTNAME);

    m_alertHelper.populateData(m_cluster);

    List<SortRequestProperty> sortProperties = new ArrayList<>();
    SortRequest sortRequest = new SortRequestImpl(sortProperties);

    AlertNoticeRequest request = new AlertNoticeRequest();
    request.Sort = sortRequest;

    Predicate clusterPredicate = new PredicateBuilder().property(
        AlertNoticeResourceProvider.ALERT_NOTICE_CLUSTER_NAME).equals("c1").toPredicate();

    request.Predicate = clusterPredicate;

    sortProperties.add(new SortRequestProperty(
        AlertNoticeResourceProvider.ALERT_NOTICE_ID, Order.ASC));

    // get back all 3
    List<AlertNoticeEntity> notices = m_dao.findAllNotices(request);
    assertEquals(3, notices.size());

    // assert sorting ASC
    long lastId = 0L;
    for (AlertNoticeEntity notice : notices) {
      if (lastId == 0L) {
        lastId = notice.getNotificationId();
        continue;
      }

      long currentId = notice.getNotificationId();
      assertTrue(lastId < currentId);
      lastId = currentId;
    }

    // clear and do DESC
    sortProperties.clear();
    sortProperties.add(new SortRequestProperty(
        AlertNoticeResourceProvider.ALERT_NOTICE_ID, Order.DESC));

    notices = m_dao.findAllNotices(request);
    assertEquals(3, notices.size());

    // assert sorting DESC
    lastId = 0L;
    for (AlertNoticeEntity notice : notices) {
      if (lastId == 0L) {
        lastId = notice.getNotificationId();
        continue;
      }

      long currentId = notice.getNotificationId();
      assertTrue(lastId > currentId);
      lastId = currentId;
    }
  }

  /**
   * Tests that when creating a new {@link AlertDefinitionEntity}, if the group
   * for its service does not exist, then it will be created.
   */
  @Test
  public void testDefaultGroupAutomaticCreation() throws Exception {
    m_helper.addHost(m_clusters, m_cluster, HOSTNAME);
    m_helper.installHdfsService(m_cluster, m_serviceFactory, m_componentFactory, m_schFactory, HOSTNAME);
    //m_helper.installYarnService(m_cluster, m_serviceFactory, m_componentFactory, m_schFactory, HOSTNAME);

    AlertGroupEntity hdfsGroup = m_dao.findDefaultServiceGroup(
            m_cluster.getClusterId(), "HDFS");

    // remove the HDFS default group
    m_dao.remove(hdfsGroup);
    hdfsGroup = m_dao.findDefaultServiceGroup(m_cluster.getClusterId(), "HDFS");
    assertNull(hdfsGroup);

    AlertDefinitionEntity datanodeProcess = new AlertDefinitionEntity();
    datanodeProcess.setClusterId(m_cluster.getClusterId());
    datanodeProcess.setDefinitionName("datanode_process");
    datanodeProcess.setServiceName("HDFS");
    datanodeProcess.setComponentName("DATANODE");
    datanodeProcess.setHash(UUID.randomUUID().toString());
    datanodeProcess.setScheduleInterval(60);
    datanodeProcess.setScope(Scope.SERVICE);
    datanodeProcess.setSource("{\"type\" : \"SCRIPT\"}");
    datanodeProcess.setSourceType(SourceType.SCRIPT);
    m_definitionDao.create(datanodeProcess);

    // the group should be created and should be default
    hdfsGroup = m_dao.findDefaultServiceGroup(m_cluster.getClusterId(), "HDFS");
    assertNotNull(hdfsGroup);
    assertTrue(hdfsGroup.isDefault());
  }

  /**
   * Tests that when creating a new {@link AlertDefinitionEntity}, if the group
   * for its service does not exist, then it will not be created if the service
   * is invalid.
   */
  @Test(expected = AmbariException.class)
  public void testDefaultGroupInvalidServiceNoCreation() throws Exception {
    initTestData();
    m_helper.addHost(m_clusters, m_cluster, HOSTNAME);
    m_helper.installHdfsService(m_cluster, m_serviceFactory, m_componentFactory, m_schFactory, HOSTNAME);
    //m_helper.installYarnService(m_cluster, m_serviceFactory, m_componentFactory, m_schFactory, HOSTNAME);

    assertEquals(3, m_dao.findAllGroups().size());

    // create a definition with an invalid service
    AlertDefinitionEntity datanodeProcess = new AlertDefinitionEntity();
    datanodeProcess.setClusterId(m_cluster.getClusterId());
    datanodeProcess.setDefinitionName("datanode_process");
    datanodeProcess.setServiceName("INVALID");
    datanodeProcess.setComponentName("DATANODE");
    datanodeProcess.setHash(UUID.randomUUID().toString());
    datanodeProcess.setScheduleInterval(60);
    datanodeProcess.setScope(Scope.SERVICE);
    datanodeProcess.setSource("{\"type\" : \"SCRIPT\"}");
    datanodeProcess.setSourceType(SourceType.SCRIPT);

    try {
      m_definitionDao.create(datanodeProcess);
    } finally {
      // assert no group was added
      assertEquals(3, m_dao.findAllGroups().size());
    }
  }

  /**
   * @return
   */
  private List<AlertDefinitionEntity> createDefinitions() throws Exception {
    // add a host to the cluster
    m_helper.addHost(m_clusters, m_cluster, HOSTNAME);

    // install YARN (which doesn't have any alerts defined in the test JSON)
    // so that the definitions get created correctly
    m_helper.installYarnService(m_cluster, m_serviceFactory,
        m_componentFactory, m_schFactory, HOSTNAME);

    List<AlertDefinitionEntity> alertDefinitionEntities = new ArrayList<>();

    for (int i = 0; i < 2; i++) {
      AlertDefinitionEntity definition = new AlertDefinitionEntity();
      definition.setDefinitionName("Alert Definition " + i);
      definition.setServiceName("YARN");
      definition.setComponentName(null);
      definition.setClusterId(m_cluster.getClusterId());
      definition.setHash(UUID.randomUUID().toString());
      definition.setScheduleInterval(60);
      definition.setScope(Scope.SERVICE);
      definition.setSource("{\"type\" : \"SCRIPT\"}");
      definition.setSourceType(SourceType.SCRIPT);
      m_definitionDao.create(definition);
      alertDefinitionEntities.add(definition);
    }

    return alertDefinitionEntities;
  }

  /**
   * @return
   * @throws Exception
   */
  private Set<AlertTargetEntity> createTargets(int numberOfTargets) throws Exception {
    Set<AlertTargetEntity> targets = new HashSet<>();
    for (int i = 0; i < numberOfTargets; i++) {
      AlertTargetEntity target = new AlertTargetEntity();
      target.setDescription("Target Description " + i);
      target.setNotificationType("EMAIL");
      target.setProperties("Target Properties " + i);
      target.setTargetName("Target Name " + i);
      m_dao.create(target);
      targets.add(target);
    }

    return targets;
  }

  /**
   *
   */
  @Test
  public void testGroupDefinitions() throws Exception {
    List<AlertDefinitionEntity> definitions = createDefinitions();

    AlertGroupEntity group = m_helper.createAlertGroup(
            m_cluster.getClusterId(), null);

    group = m_dao.findGroupById(group.getGroupId());
    assertNotNull(group);

    for (AlertDefinitionEntity definition : definitions) {
      group.addAlertDefinition(definition);
    }

    m_dao.merge(group);

    group = m_dao.findGroupByName(m_cluster.getClusterId(), group.getGroupName());
    assertEquals(definitions.size(), group.getAlertDefinitions().size());

    for (AlertDefinitionEntity definition : definitions) {
      assertTrue(group.getAlertDefinitions().contains(definition));
    }

    m_definitionDao.refresh(definitions.get(0));
    m_definitionDao.remove(definitions.get(0));
    definitions.remove(0);

    group = m_dao.findGroupByName(m_cluster.getClusterId(), group.getGroupName());
    assertEquals(definitions.size(), group.getAlertDefinitions().size());

    for (AlertDefinitionEntity definition : definitions) {
      assertTrue(group.getAlertDefinitions().contains(definition));
    }
  }

  /**
   * Tests that updating JPA associations concurrently doesn't lead to Concu
   *
   * @throws Exception
   */
  @Test
  public void testConcurrentGroupModification() throws Exception {
    createDefinitions();

    AlertGroupEntity group = m_helper.createAlertGroup(m_cluster.getClusterId(), null);
    final Set<AlertTargetEntity> targets = createTargets(100);

    group.setAlertTargets(targets);
    group = m_dao.merge(group);

    final class AlertGroupWriterThread extends Thread {
      private AlertGroupEntity group;

      /**
       * {@inheritDoc}
       */
      @Override
      public void run() {
        for (int i = 0; i < 1000; i++) {
          group.setAlertTargets(new HashSet<>(targets));
        }
      }
    }

    List<Thread> threads = new ArrayList<>();
    for (int i = 0; i < 5; i++) {
      AlertGroupWriterThread thread = new AlertGroupWriterThread();
      threads.add(thread);

      thread.group = group;
      thread.start();
    }

    for (Thread thread : threads) {
      thread.join();
    }
  }
}
