public class SubclassOfTypeWithCustomComposedKeySpaceAnnotation
		extends TypeWithCustomComposedKeySpaceAnnotationUsingAliasFor {

	public SubclassOfTypeWithCustomComposedKeySpaceAnnotation(String name) {
		super(name);
	}

} @CustomKeySpaceAnnotationWithAliasFor(name = "aliased")
@Data
public class TypeWithCustomComposedKeySpaceAnnotationUsingAliasFor {

	@Id String id;
	String name;

	public TypeWithCustomComposedKeySpaceAnnotationUsingAliasFor(String name) {
		this.name = name;
	}
} public class KeyValueEvent<T> extends ApplicationEvent {

	private static final long serialVersionUID = -7128527253428193044L;

	private final String keyspace;

	protected KeyValueEvent(Object source, String keyspace) {

		super(source);
		this.keyspace = keyspace;
	}

	/**
	 * @return affected keyspace. Never {@literal null}.
	 */
	public String getKeyspace() {
		return keyspace;
	}

	@Override
	public String toString() {
		return "KeyValueEvent [keyspace=" + keyspace + ", source=" + getSource() + "]";
	}

	/**
	 * Create new {@link BeforeGetEvent}.
	 *
	 * @param id
	 * @param keyspace
	 * @param type
	 * @return
	 */
	public static <T> BeforeGetEvent<T> beforeGet(Object id, String keyspace, Class<T> type) {
		return new BeforeGetEvent<>(id, keyspace, type);
	}

	/**
	 * Create new {@link AfterGetEvent}.
	 *
	 * @param id
	 * @param keyspace
	 * @param type
	 * @param value
	 * @return
	 */
	public static <T> AfterGetEvent<T> afterGet(Object id, String keyspace, Class<T> type, @Nullable T value) {
		return new AfterGetEvent<>(id, keyspace, type, value);
	}

	/**
	 * Create new {@link BeforeInsertEvent}.
	 *
	 * @param id
	 * @param keyspace
	 * @param type
	 * @param value
	 * @return
	 */
	public static <T> BeforeInsertEvent<T> beforeInsert(Object id, String keyspace, Class<? extends T> type, T value) {
		return new BeforeInsertEvent<>(id, keyspace, type, value);
	}

	/**
	 * Create new {@link AfterInsertEvent}.
	 *
	 * @param id
	 * @param keyspace
	 * @param type
	 * @param value
	 * @return
	 */
	public static <T> AfterInsertEvent<T> afterInsert(Object id, String keyspace, Class<? extends T> type, T value) {
		return new AfterInsertEvent<>(id, keyspace, type, value);
	}

	/**
	 * Create new {@link BeforeUpdateEvent}.
	 *
	 * @param id
	 * @param keyspace
	 * @param type
	 * @param value
	 * @return
	 */
	public static <T> BeforeUpdateEvent<T> beforeUpdate(Object id, String keyspace, Class<? extends T> type, T value) {
		return new BeforeUpdateEvent<>(id, keyspace, type, value);
	}

	/**
	 * Create new {@link AfterUpdateEvent}.
	 *
	 * @param id
	 * @param keyspace
	 * @param type
	 * @param actualValue
	 * @param previousValue
	 * @return
	 */
	public static <T> AfterUpdateEvent<T> afterUpdate(Object id, String keyspace, Class<? extends T> type, T actualValue,
			@Nullable Object previousValue) {
		return new AfterUpdateEvent<>(id, keyspace, type, actualValue, previousValue);
	}

	/**
	 * Create new {@link BeforeDropKeySpaceEvent}.
	 *
	 * @param keyspace
	 * @param type
	 * @return
	 */
	public static <T> BeforeDropKeySpaceEvent<T> beforeDropKeySpace(String keyspace, Class<? extends T> type) {
		return new BeforeDropKeySpaceEvent<>(keyspace, type);
	}

	/**
	 * Create new {@link AfterDropKeySpaceEvent}.
	 *
	 * @param keyspace
	 * @param type
	 * @return
	 */
	public static <T> AfterDropKeySpaceEvent<T> afterDropKeySpace(String keyspace, Class<? extends T> type) {
		return new AfterDropKeySpaceEvent<>(keyspace, type);
	}

	/**
	 * Create new {@link BeforeDeleteEvent}.
	 *
	 * @param id
	 * @param keyspace
	 * @param type
	 * @return
	 */
	public static <T> BeforeDeleteEvent<T> beforeDelete(Object id, String keyspace, Class<? extends T> type) {
		return new BeforeDeleteEvent<>(id, keyspace, type);
	}

	/**
	 * Create new {@link AfterDeleteEvent}.
	 *
	 * @param id
	 * @param keyspace
	 * @param type
	 * @param value
	 * @return
	 */
	public static <T> AfterDeleteEvent<T> afterDelete(Object id, String keyspace, Class<? extends T> type,
			@Nullable T value) {
		return new AfterDeleteEvent<>(id, keyspace, type, value);
	}

	/**
	 * @author Christoph Strobl
	 * @param <T>
	 */
	@SuppressWarnings("serial")
	abstract static class KeyBasedEvent<T> extends KeyValueEvent<T> {

		private Object key;
		private Class<? extends T> type;

		KeyBasedEvent(Object key, String keyspace, Class<? extends T> type) {

			super(type, keyspace);
			this.key = key;
			this.type = type;
		}

		public Object getKey() {
			return key;
		}

		/*
		 * (non-Javadoc)
		 * @see java.util.EventObject#getSource()
		 */
		@Override
		public Object getSource() {
			return getKey();
		}

		/**
		 * Get the type of the element the {@link KeyValueEvent} refers to.
		 *
		 * @return
		 */
		public Class<? extends T> getType() {
			return type;
		}
	}

	/**
	 * @author Christoph Strobl
	 * @param <T>
	 */
	@SuppressWarnings("serial")
	abstract static class KeyBasedEventWithPayload<T> extends KeyBasedEvent<T> {

		private final @Nullable T payload;

		KeyBasedEventWithPayload(Object key, String keyspace, Class<? extends T> type, @Nullable T payload) {
			super(key, keyspace, type);
			this.payload = payload;
		}

		/**
		 * Get the value of the element the {@link KeyValueEvent} refers to. Can be {@literal null}.
		 *
		 * @return
		 */
		@Nullable
		public T getPayload() {
			return payload;
		}
	}

	/**
	 * {@link KeyValueEvent} raised before loading an object by its {@literal key}.
	 *
	 * @author Christoph Strobl
	 * @param <T>
	 */
	@SuppressWarnings("serial")
	public static class BeforeGetEvent<T> extends KeyBasedEvent<T> {

		protected BeforeGetEvent(Object key, String keyspace, Class<T> type) {
			super(key, keyspace, type);
		}

	}

	/**
	 * {@link KeyValueEvent} after loading an object by its {@literal key}.
	 *
	 * @author Christoph Strobl
	 * @param <T>
	 */
	@SuppressWarnings("serial")
	public static class AfterGetEvent<T> extends KeyBasedEventWithPayload<T> {

		protected AfterGetEvent(Object key, String keyspace, Class<T> type, @Nullable T payload) {
			super(key, keyspace, type, payload);
		}

	}

	/**
	 * {@link KeyValueEvent} before inserting an object by with a given {@literal key}.
	 *
	 * @author Christoph Strobl
	 * @param <T>
	 */
	@SuppressWarnings("serial")
	public static class BeforeInsertEvent<T> extends KeyBasedEventWithPayload<T> {

		public BeforeInsertEvent(Object key, String keyspace, Class<? extends T> type, @Nullable T payload) {
			super(key, keyspace, type, payload);

		}
	}

	/**
	 * {@link KeyValueEvent} after inserting an object by with a given {@literal key}.
	 *
	 * @author Christoph Strobl
	 * @param <T>
	 */
	@SuppressWarnings("serial")
	public static class AfterInsertEvent<T> extends KeyBasedEventWithPayload<T> {

		public AfterInsertEvent(Object key, String keyspace, Class<? extends T> type, @Nullable T payload) {
			super(key, keyspace, type, payload);
		}
	}

	/**
	 * {@link KeyValueEvent} before updating an object by with a given {@literal key}.
	 *
	 * @author Christoph Strobl
	 * @param <T>
	 */
	@SuppressWarnings("serial")
	public static class BeforeUpdateEvent<T> extends KeyBasedEventWithPayload<T> {

		public BeforeUpdateEvent(Object key, String keyspace, Class<? extends T> type, @Nullable T payload) {
			super(key, keyspace, type, payload);
		}
	}

	/**
	 * {@link KeyValueEvent} after updating an object by with a given {@literal key}.
	 *
	 * @author Christoph Strobl
	 * @param <T>
	 */
	@SuppressWarnings("serial")
	public static class AfterUpdateEvent<T> extends KeyBasedEventWithPayload<T> {

		private final @Nullable Object existing;

		public AfterUpdateEvent(Object key, String keyspace, Class<? extends T> type, T payload,
				@Nullable Object existing) {
			super(key, keyspace, type, payload);
			this.existing = existing;
		}

		/**
		 * Get the value before update. Can be {@literal null}.
		 *
		 * @return
		 */
		@Nullable
		public Object before() {
			return existing;
		}

		/**
		 * Get the current value.
		 *
		 * @return can be {@literal null}.
		 */
		@Nullable
		public T after() {
			return getPayload();
		}
	}

	/**
	 * {@link KeyValueEvent} before removing an object by with a given {@literal key}.
	 *
	 * @author Christoph Strobl
	 * @param <T>
	 */
	@SuppressWarnings("serial")
	public static class BeforeDeleteEvent<T> extends KeyBasedEvent<T> {

		public BeforeDeleteEvent(Object key, String keyspace, Class<? extends T> type) {
			super(key, keyspace, type);
		}
	}

	/**
	 * {@link KeyValueEvent} after removing an object by with a given {@literal key}.
	 *
	 * @author Christoph Strobl
	 * @param <T>
	 */
	@SuppressWarnings("serial")
	public static class AfterDeleteEvent<T> extends KeyBasedEventWithPayload<T> {

		public AfterDeleteEvent(Object key, String keyspace, Class<? extends T> type, @Nullable T payload) {
			super(key, keyspace, type, payload);
		}
	}

	/**
	 * {@link KeyValueEvent} before removing all elements in a given {@literal keyspace}.
	 *
	 * @author Christoph Strobl
	 * @param <T>
	 */
	@SuppressWarnings("serial")
	public static class BeforeDropKeySpaceEvent<T> extends KeyValueEvent<T> {

		public BeforeDropKeySpaceEvent(String keyspace, Class<? extends T> type) {
			super(type, keyspace);
		}

		@Override
		@SuppressWarnings("unchecked")
		public Class<T> getSource() {
			return (Class<T>) super.getSource();
		}

	}

	/**
	 * {@link KeyValueEvent} after removing all elements in a given {@literal keyspace}.
	 *
	 * @author Christoph Strobl
	 * @param <T>
	 */
	@SuppressWarnings("serial")
	public static class AfterDropKeySpaceEvent<T> extends KeyValueEvent<T> {

		public AfterDropKeySpaceEvent(String keyspace, Class<? extends T> type) {
			super(type, keyspace);
		}

		@Override
		@SuppressWarnings("unchecked")
		public Class<T> getSource() {
			return (Class<T>) super.getSource();
		}
	}
} @SuppressWarnings("serial")
public static class AfterDeleteEvent<T> extends KeyBasedEventWithPayload<T> {

	public AfterDeleteEvent(Object key, String keyspace, Class<? extends T> type, @Nullable T payload) {
		super(key, keyspace, type, payload);
	}
} @SuppressWarnings("serial")
public static class AfterDropKeySpaceEvent<T> extends KeyValueEvent<T> {

	public AfterDropKeySpaceEvent(String keyspace, Class<? extends T> type) {
		super(type, keyspace);
	}

	@Override
	@SuppressWarnings("unchecked")
	public Class<T> getSource() {
		return (Class<T>) super.getSource();
	}
} @SuppressWarnings("serial")
public static class AfterGetEvent<T> extends KeyBasedEventWithPayload<T> {

	protected AfterGetEvent(Object key, String keyspace, Class<T> type, @Nullable T payload) {
		super(key, keyspace, type, payload);
	}

} @SuppressWarnings("serial")
public static class AfterInsertEvent<T> extends KeyBasedEventWithPayload<T> {

	public AfterInsertEvent(Object key, String keyspace, Class<? extends T> type, @Nullable T payload) {
		super(key, keyspace, type, payload);
	}
} @SuppressWarnings("serial")
public static class AfterUpdateEvent<T> extends KeyBasedEventWithPayload<T> {

	private final @Nullable Object existing;

	public AfterUpdateEvent(Object key, String keyspace, Class<? extends T> type, T payload,
			@Nullable Object existing) {
		super(key, keyspace, type, payload);
		this.existing = existing;
	}

	/**
	 * Get the value before update. Can be {@literal null}.
	 *
	 * @return
	 */
	@Nullable
	public Object before() {
		return existing;
	}

	/**
	 * Get the current value.
	 *
	 * @return can be {@literal null}.
	 */
	@Nullable
	public T after() {
		return getPayload();
	}
} @SuppressWarnings("serial")
public static class BeforeDeleteEvent<T> extends KeyBasedEvent<T> {

	public BeforeDeleteEvent(Object key, String keyspace, Class<? extends T> type) {
		super(key, keyspace, type);
	}
} @SuppressWarnings("serial")
public static class BeforeGetEvent<T> extends KeyBasedEvent<T> {

	protected BeforeGetEvent(Object key, String keyspace, Class<T> type) {
		super(key, keyspace, type);
	}

} @SuppressWarnings("serial")
public static class BeforeInsertEvent<T> extends KeyBasedEventWithPayload<T> {

	public BeforeInsertEvent(Object key, String keyspace, Class<? extends T> type, @Nullable T payload) {
		super(key, keyspace, type, payload);

	}
} @SuppressWarnings("serial")
public static class BeforeUpdateEvent<T> extends KeyBasedEventWithPayload<T> {

	public BeforeUpdateEvent(Object key, String keyspace, Class<? extends T> type, @Nullable T payload) {
		super(key, keyspace, type, payload);
	}
} public class KeyValueQuery<T> {

	private Sort sort = Sort.unsorted();
	private long offset = -1;
	private int rows = -1;
	private final @Nullable T criteria;

	/**
	 * Creates new instance of {@link KeyValueQuery}.
	 */
	public KeyValueQuery() {
		this((T) null);
	}

	/**
	 * Creates new instance of {@link KeyValueQuery} with given criteria.
	 *
	 * @param criteria can be {@literal null}.
	 */
	public KeyValueQuery(@Nullable T criteria) {
		this.criteria = criteria;
	}

	/**
	 * Creates new instance of {@link KeyValueQuery} with given criteria and {@link Sort}.
	 *
	 * @param criteria can be {@literal null}.
	 * @param sort must not be {@literal null}.
	 * @since 2.4
	 */
	public KeyValueQuery(@Nullable T criteria, Sort sort) {
		this.criteria = criteria;
		setSort(sort);
	}

	/**
	 * Creates new instance of {@link KeyValueQuery} with given {@link Sort}.
	 *
	 * @param sort must not be {@literal null}.
	 */
	public KeyValueQuery(Sort sort) {
		this();
		setSort(sort);
	}

	/**
	 * Get the criteria object.
	 *
	 * @return
	 * @since 2.0
	 */
	@Nullable
	public T getCriteria() {
		return criteria;
	}

	/**
	 * Get {@link Sort}.
	 *
	 * @return
	 */
	public Sort getSort() {
		return sort;
	}

	/**
	 * Number of elements to skip.
	 *
	 * @return negative value if not set.
	 */
	public long getOffset() {
		return this.offset;
	}

	/**
	 * Number of elements to read.
	 *
	 * @return negative value if not set.
	 */
	public int getRows() {
		return this.rows;
	}

	/**
	 * Set the number of elements to skip.
	 *
	 * @param offset use negative value for none.
	 */
	public void setOffset(long offset) {
		this.offset = offset;
	}

	/**
	 * Set the number of elements to read.
	 *
	 * @param rows use negative value for all.
	 */
	public void setRows(int rows) {
		this.rows = rows;
	}

	/**
	 * Set {@link Sort} to be applied.
	 *
	 * @param sort
	 */
	public void setSort(Sort sort) {

		Assert.notNull(sort, "Sort must not be null!");

		this.sort = sort;
	}

	/**
	 * Add given {@link Sort}.
	 *
	 * @param sort must not be {@literal null}.
	 * @return
	 */
	public KeyValueQuery<T> orderBy(Sort sort) {

		Assert.notNull(sort, "Sort must not be null!");

		if (this.sort.isSorted()) {
			this.sort = this.sort.and(sort);
		} else {
			this.sort = sort;
		}

		return this;
	}

	/**
	 * @see KeyValueQuery#setOffset(long)
	 * @param offset
	 * @return
	 */
	public KeyValueQuery<T> skip(long offset) {

		setOffset(offset);

		return this;
	}

	/**
	 * @see KeyValueQuery#setRows(int)
	 * @param rows
	 * @return
	 */
	public KeyValueQuery<T> limit(int rows) {
		setRows(rows);
		return this;
	}

}
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.annotation.Id;
import org.springframework.data.keyvalue.SubclassOfTypeWithCustomComposedKeySpaceAnnotation;
import org.springframework.data.keyvalue.TypeWithCustomComposedKeySpaceAnnotationUsingAliasFor;
import org.springframework.data.keyvalue.core.event.KeyValueEvent;
import org.springframework.data.keyvalue.core.event.KeyValueEvent.AfterDeleteEvent;
import org.springframework.data.keyvalue.core.event.KeyValueEvent.AfterDropKeySpaceEvent;
import org.springframework.data.keyvalue.core.event.KeyValueEvent.AfterGetEvent;
import org.springframework.data.keyvalue.core.event.KeyValueEvent.AfterInsertEvent;
import org.springframework.data.keyvalue.core.event.KeyValueEvent.AfterUpdateEvent;
import org.springframework.data.keyvalue.core.event.KeyValueEvent.BeforeDeleteEvent;
import org.springframework.data.keyvalue.core.event.KeyValueEvent.BeforeGetEvent;
import org.springframework.data.keyvalue.core.event.KeyValueEvent.BeforeInsertEvent;
import org.springframework.data.keyvalue.core.event.KeyValueEvent.BeforeUpdateEvent;
import org.springframework.data.keyvalue.core.query.KeyValueQuery;

		template.setEventTypesToPublish(Collections.emptySet());

		template.insert("1", FOO_ONE);

		verifyNoInteractions(publisherMock);
	}

	@Test // DATAKV-104
	void shouldPublishEventsByDefault() {

		template.insert("1", FOO_ONE);

		verify(publisherMock, atLeastOnce()).publishEvent(any());
	}

	@Test // DATAKV-91, DATAKV-104

	void shouldNotPublishEventWhenNotExplicitlySetForPublication() {

		setEventsToPublish(BeforeDeleteEvent.class);

		template.insert("1", FOO_ONE);

		verifyNoInteractions(publisherMock);
	}

	@Test // DATAKV-91, DATAKV-104, DATAKV-187
	@SuppressWarnings({ "rawtypes" })
	void shouldPublishBeforeInsertEventCorrectly() {

		setEventsToPublish(BeforeInsertEvent.class);

		template.insert("1", FOO_ONE);

		ArgumentCaptor<BeforeInsertEvent> captor = ArgumentCaptor.forClass(BeforeInsertEvent.class);

		verify(publisherMock, times(1)).publishEvent(captor.capture());
		verifyNoMoreInteractions(publisherMock);

		assertThat(captor.getValue().getKey()).isEqualTo("1");
		assertThat(captor.getValue().getKeyspace()).isEqualTo(Foo.class.getName());
		assertThat(captor.getValue().getPayload()).isEqualTo(FOO_ONE);
	}

	@Test // DATAKV-91, DATAKV-104, DATAKV-187
	@SuppressWarnings({ "rawtypes" })
	void shouldPublishAfterInsertEventCorrectly() {

		setEventsToPublish(AfterInsertEvent.class);

		template.insert("1", FOO_ONE);

		ArgumentCaptor<AfterInsertEvent> captor = ArgumentCaptor.forClass(AfterInsertEvent.class);

		verify(publisherMock, times(1)).publishEvent(captor.capture());
		verifyNoMoreInteractions(publisherMock);

		assertThat(captor.getValue().getKey()).isEqualTo("1");
		assertThat(captor.getValue().getKeyspace()).isEqualTo(Foo.class.getName());
		assertThat(captor.getValue().getPayload()).isEqualTo(FOO_ONE);
	}

	@Test // DATAKV-91, DATAKV-104, DATAKV-187
	@SuppressWarnings({ "rawtypes" })
	void shouldPublishBeforeUpdateEventCorrectly() {

		setEventsToPublish(BeforeUpdateEvent.class);

		template.update("1", FOO_ONE);

		ArgumentCaptor<BeforeUpdateEvent> captor = ArgumentCaptor.forClass(BeforeUpdateEvent.class);

		verify(publisherMock, times(1)).publishEvent(captor.capture());
		verifyNoMoreInteractions(publisherMock);

		assertThat(captor.getValue().getKey()).isEqualTo("1");
		assertThat(captor.getValue().getKeyspace()).isEqualTo(Foo.class.getName());
		assertThat(captor.getValue().getPayload()).isEqualTo(FOO_ONE);
	}

	@Test // DATAKV-91, DATAKV-104, DATAKV-187
	@SuppressWarnings({ "rawtypes" })
	void shouldPublishAfterUpdateEventCorrectly() {

		setEventsToPublish(AfterUpdateEvent.class);

		template.update("1", FOO_ONE);

		ArgumentCaptor<AfterUpdateEvent> captor = ArgumentCaptor.forClass(AfterUpdateEvent.class);

		verify(publisherMock, times(1)).publishEvent(captor.capture());
		verifyNoMoreInteractions(publisherMock);

		assertThat(captor.getValue().getKey()).isEqualTo("1");
		assertThat(captor.getValue().getKeyspace()).isEqualTo(Foo.class.getName());
		assertThat(captor.getValue().getPayload()).isEqualTo(FOO_ONE);
	}

	@Test // DATAKV-91, DATAKV-104, DATAKV-187
	@SuppressWarnings({ "rawtypes" })
	void shouldPublishBeforeDeleteEventCorrectly() {

		setEventsToPublish(BeforeDeleteEvent.class);

		template.delete("1", FOO_ONE.getClass());

		ArgumentCaptor<BeforeDeleteEvent> captor = ArgumentCaptor.forClass(BeforeDeleteEvent.class);

		verify(publisherMock, times(1)).publishEvent(captor.capture());
		verifyNoMoreInteractions(publisherMock);

		assertThat(captor.getValue().getKey()).isEqualTo("1");
		assertThat(captor.getValue().getKeyspace()).isEqualTo(Foo.class.getName());
	}

	@Test // DATAKV-91, DATAKV-104, DATAKV-187
	@SuppressWarnings({ "rawtypes" })
	void shouldPublishAfterDeleteEventCorrectly() {

		setEventsToPublish(AfterDeleteEvent.class);