/* * Copyright 2014-2017 the original author or authors. * * 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. */ package org.springframework.data.keyvalue.core; import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.Set; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisherAware; import org.springframework.dao.DataAccessException; import org.springframework.dao.DuplicateKeyException; import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.dao.support.PersistenceExceptionTranslator; import org.springframework.data.domain.Sort; import org.springframework.data.keyvalue.core.event.KeyValueEvent; import org.springframework.data.keyvalue.core.mapping.KeyValuePersistentEntity; import org.springframework.data.keyvalue.core.mapping.KeyValuePersistentProperty; import org.springframework.data.keyvalue.core.mapping.context.KeyValueMappingContext; import org.springframework.data.keyvalue.core.query.KeyValueQuery; import org.springframework.data.mapping.context.MappingContext; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.CollectionUtils; import org.springframework.util.ObjectUtils; /** * Basic implementation of {@link KeyValueOperations}. * * @author Christoph Strobl * @author Oliver Gierke * @author Thomas Darimont */ public class KeyValueTemplate implements KeyValueOperations, ApplicationEventPublisherAware { private static final PersistenceExceptionTranslator DEFAULT_PERSISTENCE_EXCEPTION_TRANSLATOR = new KeyValuePersistenceExceptionTranslator(); private final KeyValueAdapter adapter; private final MappingContext<? extends KeyValuePersistentEntity<?, ?>, ? extends KeyValuePersistentProperty<?>> mappingContext; private final IdentifierGenerator identifierGenerator; private PersistenceExceptionTranslator exceptionTranslator = DEFAULT_PERSISTENCE_EXCEPTION_TRANSLATOR; private ApplicationEventPublisher eventPublisher; private boolean publishEvents = true; private @SuppressWarnings("rawtypes") Set<Class<? extends KeyValueEvent>> eventTypesToPublish = Collections .emptySet(); /** * Create new {@link KeyValueTemplate} using the given {@link KeyValueAdapter} with a default * {@link KeyValueMappingContext}. * * @param adapter must not be {@literal null}. */ public KeyValueTemplate(KeyValueAdapter adapter) { this(adapter, new KeyValueMappingContext()); } /** * Create new {@link KeyValueTemplate} using the given {@link KeyValueAdapter} and {@link MappingContext}. * * @param adapter must not be {@literal null}. * @param mappingContext must not be {@literal null}. */ public KeyValueTemplate(KeyValueAdapter adapter, MappingContext<? extends KeyValuePersistentEntity<?, ?>, ? extends KeyValuePersistentProperty<?>> mappingContext) { Assert.notNull(adapter, "Adapter must not be null!"); Assert.notNull(mappingContext, "MappingContext must not be null!"); this.adapter = adapter; this.mappingContext = mappingContext; this.identifierGenerator = DefaultIdentifierGenerator.INSTANCE; } /** * Set the {@link PersistenceExceptionTranslator} used for converting {@link RuntimeException}. * * @param exceptionTranslator must not be {@literal null}. */ public void setExceptionTranslator(PersistenceExceptionTranslator exceptionTranslator) { Assert.notNull(exceptionTranslator, "ExceptionTranslator must not be null."); this.exceptionTranslator = exceptionTranslator; } /** * Define the event types to publish via {@link ApplicationEventPublisher}. * * @param eventTypesToPublish use {@literal null} or {@link Collections#emptySet()} to stop publishing. */ @SuppressWarnings("rawtypes") public void setEventTypesToPublish(Set<Class<? extends KeyValueEvent>> eventTypesToPublish) { if (CollectionUtils.isEmpty(eventTypesToPublish)) { this.publishEvents = false; } else { this.publishEvents = true; this.eventTypesToPublish = Collections.unmodifiableSet(eventTypesToPublish); } } /* * (non-Javadoc) * @see org.springframework.context.ApplicationEventPublisherAware#setApplicationEventPublisher(org.springframework.context.ApplicationEventPublisher) */ @Override public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { this.eventPublisher = applicationEventPublisher; } /* * (non-Javadoc) * @see org.springframework.data.keyvalue.core.KeyValueOperations#insert(java.lang.Object) */ @Override public <T> T insert(T objectToInsert) { KeyValuePersistentEntity<?, ?> entity = getKeyValuePersistentEntity(objectToInsert); GeneratingIdAccessor generatingIdAccessor = new GeneratingIdAccessor(entity.getPropertyAccessor(objectToInsert), entity.getIdProperty() .orElseThrow(() -> new IllegalArgumentException("Unable to extract 'id' for object to be deleted")), identifierGenerator); Object id = generatingIdAccessor.getOrGenerateIdentifier(); insert((Serializable) id, objectToInsert); return objectToInsert; } private KeyValuePersistentEntity<?, ?> getKeyValuePersistentEntity(Object objectToInsert) { return this.mappingContext.getPersistentEntity(ClassUtils.getUserClass(objectToInsert)) .orElseThrow(() -> new IllegalArgumentException( String.format("Unable to find PersistentEntity for %s", ObjectUtils.nullSafeClassName(objectToInsert)))); } /* * (non-Javadoc) * @see org.springframework.data.keyvalue.core.KeyValueOperations#insert(java.io.Serializable, java.lang.Object) */ @Override public void insert(final Serializable id, final Object objectToInsert) { Assert.notNull(id, "Id for object to be inserted must not be null!"); Assert.notNull(objectToInsert, "Object to be inserted must not be null!"); final String keyspace = resolveKeySpace(objectToInsert.getClass()); potentiallyPublishEvent(KeyValueEvent.beforeInsert(id, keyspace, objectToInsert.getClass(), objectToInsert)); execute(new KeyValueCallback<Void>() { @Override public Void doInKeyValue(KeyValueAdapter adapter) { if (adapter.contains(id, keyspace)) { throw new DuplicateKeyException( String.format("Cannot insert existing object with id %s!. Please use update.", id)); } adapter.put(id, objectToInsert, keyspace); return null; } }); potentiallyPublishEvent(KeyValueEvent.afterInsert(id, keyspace, objectToInsert.getClass(), objectToInsert)); } /* * (non-Javadoc) * @see org.springframework.data.keyvalue.core.KeyValueOperations#update(java.lang.Object) */ @SuppressWarnings("rawtypes") @Override public void update(Object objectToUpdate) { KeyValuePersistentEntity<?, ?> entity = getKeyValuePersistentEntity(objectToUpdate); if (!entity.hasIdProperty()) { throw new InvalidDataAccessApiUsageException( String.format("Cannot determine id for type %s", ClassUtils.getUserClass(objectToUpdate))); } update((Serializable) entity.getIdentifierAccessor(objectToUpdate).getIdentifier().get(), objectToUpdate); } /* * (non-Javadoc) * @see org.springframework.data.keyvalue.core.KeyValueOperations#update(java.io.Serializable, java.lang.Object) */ @Override public void update(final Serializable id, final Object objectToUpdate) { Assert.notNull(id, "Id for object to be inserted must not be null!"); Assert.notNull(objectToUpdate, "Object to be updated must not be null!"); final String keyspace = resolveKeySpace(objectToUpdate.getClass()); potentiallyPublishEvent(KeyValueEvent.beforeUpdate(id, keyspace, objectToUpdate.getClass(), objectToUpdate)); Object existing = execute(new KeyValueCallback<Object>() { @Override public Object doInKeyValue(KeyValueAdapter adapter) { return adapter.put(id, objectToUpdate, keyspace); } }); potentiallyPublishEvent( KeyValueEvent.afterUpdate(id, keyspace, objectToUpdate.getClass(), objectToUpdate, existing)); } /* * (non-Javadoc) * @see org.springframework.data.keyvalue.core.KeyValueOperations#findAllOf(java.lang.Class) */ @Override public <T> Iterable<T> findAll(final Class<T> type) { Assert.notNull(type, "Type to fetch must not be null!"); return execute(new KeyValueCallback<Iterable<T>>() { @SuppressWarnings("unchecked") @Override public Iterable<T> doInKeyValue(KeyValueAdapter adapter) { Iterable<?> values = adapter.getAllOf(resolveKeySpace(type)); if (values == null) { return Collections.emptySet(); } ArrayList<T> filtered = new ArrayList<>(); for (Object candidate : values) { if (typeCheck(type, candidate)) { filtered.add((T) candidate); } } return filtered; } }); } /* * (non-Javadoc) * @see org.springframework.data.keyvalue.core.KeyValueOperations#findById(java.io.Serializable, java.lang.Class) */ @Override public <T> Optional<T> findById(final Serializable id, final Class<T> type) { Assert.notNull(id, "Id for object to be inserted must not be null!"); Assert.notNull(type, "Type to fetch must not be null!"); final String keyspace = resolveKeySpace(type); potentiallyPublishEvent(KeyValueEvent.beforeGet(id, keyspace, type)); T result = execute(new KeyValueCallback<T>() { @SuppressWarnings("unchecked") @Override public T doInKeyValue(KeyValueAdapter adapter) { Object result = adapter.get(id, keyspace, type); if (result == null || typeCheck(type, result)) { return (T) result; } return null; } }); potentiallyPublishEvent(KeyValueEvent.afterGet(id, keyspace, type, result)); return Optional.ofNullable(result); } /* * (non-Javadoc) * @see org.springframework.data.keyvalue.core.KeyValueOperations#delete(java.lang.Class) */ @Override public void delete(final Class<?> type) { Assert.notNull(type, "Type to delete must not be null!"); final String keyspace = resolveKeySpace(type); potentiallyPublishEvent(KeyValueEvent.beforeDropKeySpace(keyspace, type)); execute(new KeyValueCallback<Void>() { @Override public Void doInKeyValue(KeyValueAdapter adapter) { adapter.deleteAllOf(keyspace); return null; } }); potentiallyPublishEvent(KeyValueEvent.afterDropKeySpace(keyspace, type)); } /* * (non-Javadoc) * @see org.springframework.data.keyvalue.core.KeyValueOperations#delete(java.lang.Object) */ @SuppressWarnings({ "unchecked", "rawtypes" }) @Override public <T> T delete(T objectToDelete) { Class<T> type = (Class<T>) ClassUtils.getUserClass(objectToDelete); KeyValuePersistentEntity<?, ?> entity = getKeyValuePersistentEntity(objectToDelete); return delete((Serializable) entity.getIdentifierAccessor(objectToDelete).getIdentifier() .orElseThrow(() -> new IllegalArgumentException("Unable to extract 'id' for object to be deleted")), type); } /* * (non-Javadoc) * @see org.springframework.data.keyvalue.core.KeyValueOperations#delete(java.io.Serializable, java.lang.Class) */ @Override public <T> T delete(final Serializable id, final Class<T> type) { Assert.notNull(id, "Id for object to be deleted must not be null!"); Assert.notNull(type, "Type to delete must not be null!"); final String keyspace = resolveKeySpace(type); potentiallyPublishEvent(KeyValueEvent.beforeDelete(id, keyspace, type)); T result = execute(new KeyValueCallback<T>() { @Override public T doInKeyValue(KeyValueAdapter adapter) { return (T) adapter.delete(id, keyspace, type); } }); potentiallyPublishEvent(KeyValueEvent.afterDelete(id, keyspace, type, result)); return result; } /* * (non-Javadoc) * @see org.springframework.data.keyvalue.core.KeyValueOperations#count(java.lang.Class) */ @Override public long count(Class<?> type) { Assert.notNull(type, "Type for count must not be null!"); return adapter.count(resolveKeySpace(type)); } /* * (non-Javadoc) * @see org.springframework.data.keyvalue.core.KeyValueOperations#execute(org.springframework.data.keyvalue.core.KeyValueCallback) */ @Override public <T> T execute(KeyValueCallback<T> action) { Assert.notNull(action, "KeyValueCallback must not be null!"); try { return action.doInKeyValue(this.adapter); } catch (RuntimeException e) { throw resolveExceptionIfPossible(e); } } /* * (non-Javadoc) * @see org.springframework.data.keyvalue.core.KeyValueOperations#find(org.springframework.data.keyvalue.core.query.KeyValueQuery, java.lang.Class) */ @Override public <T> Iterable<T> find(final KeyValueQuery<?> query, final Class<T> type) { return execute(new KeyValueCallback<Iterable<T>>() { @SuppressWarnings("unchecked") @Override public Iterable<T> doInKeyValue(KeyValueAdapter adapter) { Iterable<?> result = adapter.find(query, resolveKeySpace(type), type); if (result == null) { return Collections.emptySet(); } List<T> filtered = new ArrayList<>(); for (Object candidate : result) { if (typeCheck(type, candidate)) { filtered.add((T) candidate); } } return filtered; } }); } /* * (non-Javadoc) * @see org.springframework.data.keyvalue.core.KeyValueOperations#findAllOf(org.springframework.data.domain.Sort, java.lang.Class) */ @SuppressWarnings("rawtypes") @Override public <T> Iterable<T> findAll(Sort sort, Class<T> type) { return find(new KeyValueQuery(sort), type); } /* * (non-Javadoc) * @see org.springframework.data.keyvalue.core.KeyValueOperations#findInRange(long, int, java.lang.Class) */ @SuppressWarnings("rawtypes") @Override public <T> Iterable<T> findInRange(long offset, int rows, Class<T> type) { return find(new KeyValueQuery().skip(offset).limit(rows), type); } /* * (non-Javadoc) * @see org.springframework.data.keyvalue.core.KeyValueOperations#findInRange(long, int, org.springframework.data.domain.Sort, java.lang.Class) */ @SuppressWarnings("rawtypes") @Override public <T> Iterable<T> findInRange(long offset, int rows, Sort sort, Class<T> type) { return find(new KeyValueQuery(sort).skip(offset).limit(rows), type); } /* * (non-Javadoc) * @see org.springframework.data.keyvalue.core.KeyValueOperations#count(org.springframework.data.keyvalue.core.query.KeyValueQuery, java.lang.Class) */ @Override public long count(final KeyValueQuery<?> query, final Class<?> type) { return execute(new KeyValueCallback<Long>() { @Override public Long doInKeyValue(KeyValueAdapter adapter) { return adapter.count(query, resolveKeySpace(type)); } }); } /* * (non-Javadoc) * @see org.springframework.data.keyvalue.core.KeyValueOperations#getMappingContext() */ @Override public MappingContext<?, ?> getMappingContext() { return this.mappingContext; } /* * (non-Javadoc) * @see org.springframework.beans.factory.DisposableBean#destroy() */ @Override public void destroy() throws Exception { this.adapter.clear(); } private String resolveKeySpace(Class<?> type) { return this.mappingContext.getPersistentEntity(type).get().getKeySpace(); } private RuntimeException resolveExceptionIfPossible(RuntimeException e) { DataAccessException translatedException = exceptionTranslator.translateExceptionIfPossible(e); return translatedException != null ? translatedException : e; } @SuppressWarnings("rawtypes") private void potentiallyPublishEvent(KeyValueEvent event) { if (eventPublisher == null) { return; } if (publishEvents && (eventTypesToPublish.isEmpty() || eventTypesToPublish.contains(event.getClass()))) { eventPublisher.publishEvent(event); } } private static boolean typeCheck(Class<?> requiredType, Object candidate) { return candidate == null ? true : ClassUtils.isAssignable(requiredType, candidate.getClass()); } }