/* * DISCLAIMER * * Copyright 2017 ArangoDB GmbH, Cologne, Germany * * 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. * * Copyright holder is ArangoDB GmbH, Cologne, Germany */ package com.arangodb.springframework.core.template; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; import java.util.stream.StreamSupport; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.expression.BeanFactoryAccessor; import org.springframework.context.expression.BeanFactoryResolver; import org.springframework.dao.DataAccessException; import org.springframework.dao.support.PersistenceExceptionTranslator; import org.springframework.data.domain.Persistable; import org.springframework.data.mapping.PersistentPropertyAccessor; import org.springframework.expression.Expression; import org.springframework.expression.ParserContext; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.support.StandardEvaluationContext; import com.arangodb.ArangoCollection; import com.arangodb.ArangoCursor; import com.arangodb.ArangoDB; import com.arangodb.ArangoDBException; import com.arangodb.ArangoDatabase; import com.arangodb.entity.ArangoDBVersion; import com.arangodb.entity.DocumentEntity; import com.arangodb.entity.MultiDocumentEntity; import com.arangodb.entity.UserEntity; import com.arangodb.model.AqlQueryOptions; import com.arangodb.model.CollectionCreateOptions; import com.arangodb.model.DocumentCreateOptions; import com.arangodb.model.DocumentDeleteOptions; import com.arangodb.model.DocumentReadOptions; import com.arangodb.model.DocumentReplaceOptions; import com.arangodb.model.DocumentUpdateOptions; import com.arangodb.model.FulltextIndexOptions; import com.arangodb.model.GeoIndexOptions; import com.arangodb.model.HashIndexOptions; import com.arangodb.model.PersistentIndexOptions; import com.arangodb.model.SkiplistIndexOptions; import com.arangodb.springframework.annotation.FulltextIndex; import com.arangodb.springframework.annotation.GeoIndex; import com.arangodb.springframework.annotation.HashIndex; import com.arangodb.springframework.annotation.PersistentIndex; import com.arangodb.springframework.annotation.SkiplistIndex; import com.arangodb.springframework.core.ArangoOperations; import com.arangodb.springframework.core.CollectionOperations; import com.arangodb.springframework.core.UserOperations; import com.arangodb.springframework.core.convert.ArangoConverter; import com.arangodb.springframework.core.mapping.ArangoPersistentEntity; import com.arangodb.springframework.core.mapping.ArangoPersistentProperty; import com.arangodb.springframework.core.mapping.event.AfterDeleteEvent; import com.arangodb.springframework.core.mapping.event.AfterLoadEvent; import com.arangodb.springframework.core.mapping.event.AfterSaveEvent; import com.arangodb.springframework.core.mapping.event.ArangoMappingEvent; import com.arangodb.springframework.core.mapping.event.BeforeDeleteEvent; import com.arangodb.springframework.core.mapping.event.BeforeSaveEvent; import com.arangodb.springframework.core.template.DefaultUserOperation.CollectionCallback; import com.arangodb.springframework.core.util.ArangoExceptionTranslator; import com.arangodb.springframework.core.util.MetadataUtils; import com.arangodb.util.MapBuilder; import com.arangodb.velocypack.VPackSlice; /** * @author Mark Vollmary * @author Christian Lechner * @author Reşat SABIQ */ public class ArangoTemplate implements ArangoOperations, CollectionCallback, ApplicationContextAware { private static final SpelExpressionParser PARSER = new SpelExpressionParser(); private volatile ArangoDBVersion version; private final PersistenceExceptionTranslator exceptionTranslator; private final ArangoConverter converter; private final ArangoDB arango; private final String databaseName; private final Expression databaseExpression; private final Map<String, ArangoDatabase> databaseCache; private final Map<CollectionCacheKey, CollectionCacheValue> collectionCache; private final StandardEvaluationContext context; private ApplicationEventPublisher eventPublisher; public ArangoTemplate(final ArangoDB arango, final String database) { this(arango, database, null); } public ArangoTemplate(final ArangoDB arango, final String database, final ArangoConverter converter) { this(arango, database, converter, new ArangoExceptionTranslator()); } public ArangoTemplate(final ArangoDB arango, final String database, final ArangoConverter converter, final PersistenceExceptionTranslator exceptionTranslator) { super(); this.arango = arango._setCursorInitializer(new ArangoCursorInitializer(converter)); this.databaseName = database; this.databaseExpression = PARSER.parseExpression(databaseName, ParserContext.TEMPLATE_EXPRESSION); this.converter = converter; this.exceptionTranslator = exceptionTranslator; this.context = new StandardEvaluationContext(); // set concurrency level to 1 as writes are very rare compared to reads collectionCache = new ConcurrentHashMap<>(8, 0.9f, 1); databaseCache = new ConcurrentHashMap<>(8, 0.9f, 1); version = null; } private ArangoDatabase db() { final String key = databaseExpression != null ? databaseExpression.getValue(context, String.class) : databaseName; return databaseCache.computeIfAbsent(key, name -> { final ArangoDatabase db = arango.db(name); if (!db.exists()) { db.create(); } return db; }); } private DataAccessException translateExceptionIfPossible(final RuntimeException exception) { return exceptionTranslator.translateExceptionIfPossible(exception); } private ArangoCollection _collection(final String name) { return _collection(name, null, null); } private ArangoCollection _collection(final Class<?> entityClass) { return _collection(entityClass, null); } private ArangoCollection _collection(final Class<?> entityClass, final Object id) { final ArangoPersistentEntity<?> persistentEntity = converter.getMappingContext() .getRequiredPersistentEntity(entityClass); final String name = determineCollectionFromId(id).orElse(persistentEntity.getCollection()); return _collection(name, persistentEntity, persistentEntity.getCollectionOptions()); } private ArangoCollection _collection( final String name, final ArangoPersistentEntity<?> persistentEntity, final CollectionCreateOptions options) { final ArangoDatabase db = db(); final Class<?> entityClass = persistentEntity != null ? persistentEntity.getType() : null; final CollectionCacheValue value = collectionCache.computeIfAbsent(new CollectionCacheKey(db.name(), name), key -> { final ArangoCollection collection = db.collection(name); if (!collection.exists()) { collection.create(options); } return new CollectionCacheValue(collection); }); final Collection<Class<?>> entities = value.getEntities(); final ArangoCollection collection = value.getCollection(); if (persistentEntity != null && !entities.contains(entityClass)) { value.addEntityClass(entityClass); ensureCollectionIndexes(collection(collection), persistentEntity); } return collection; } private static void ensureCollectionIndexes( final CollectionOperations collection, final ArangoPersistentEntity<?> persistentEntity) { persistentEntity.getHashIndexes().stream().forEach(index -> ensureHashIndex(collection, index)); persistentEntity.getHashIndexedProperties().stream().forEach(p -> ensureHashIndex(collection, p)); persistentEntity.getSkiplistIndexes().stream().forEach(index -> ensureSkiplistIndex(collection, index)); persistentEntity.getSkiplistIndexedProperties().stream().forEach(p -> ensureSkiplistIndex(collection, p)); persistentEntity.getPersistentIndexes().stream().forEach(index -> ensurePersistentIndex(collection, index)); persistentEntity.getPersistentIndexedProperties().stream().forEach(p -> ensurePersistentIndex(collection, p)); persistentEntity.getGeoIndexes().stream().forEach(index -> ensureGeoIndex(collection, index)); persistentEntity.getGeoIndexedProperties().stream().forEach(p -> ensureGeoIndex(collection, p)); persistentEntity.getFulltextIndexes().stream().forEach(index -> ensureFulltextIndex(collection, index)); persistentEntity.getFulltextIndexedProperties().stream().forEach(p -> ensureFulltextIndex(collection, p)); } private static void ensureHashIndex(final CollectionOperations collection, final HashIndex annotation) { collection.ensureHashIndex(Arrays.asList(annotation.fields()), new HashIndexOptions() .unique(annotation.unique()).sparse(annotation.sparse()).deduplicate(annotation.deduplicate())); } private static void ensureHashIndex(final CollectionOperations collection, final ArangoPersistentProperty value) { final HashIndexOptions options = new HashIndexOptions(); value.getHashIndexed() .ifPresent(i -> options.unique(i.unique()).sparse(i.sparse()).deduplicate(i.deduplicate())); collection.ensureHashIndex(Collections.singleton(value.getFieldName()), options); } private static void ensureSkiplistIndex(final CollectionOperations collection, final SkiplistIndex annotation) { collection.ensureSkiplistIndex(Arrays.asList(annotation.fields()), new SkiplistIndexOptions() .unique(annotation.unique()).sparse(annotation.sparse()).deduplicate(annotation.deduplicate())); } private static void ensureSkiplistIndex( final CollectionOperations collection, final ArangoPersistentProperty value) { final SkiplistIndexOptions options = new SkiplistIndexOptions(); value.getSkiplistIndexed() .ifPresent(i -> options.unique(i.unique()).sparse(i.sparse()).deduplicate(i.deduplicate())); collection.ensureSkiplistIndex(Collections.singleton(value.getFieldName()), options); } private static void ensurePersistentIndex(final CollectionOperations collection, final PersistentIndex annotation) { collection.ensurePersistentIndex(Arrays.asList(annotation.fields()), new PersistentIndexOptions().unique(annotation.unique()).sparse(annotation.sparse())); } private static void ensurePersistentIndex( final CollectionOperations collection, final ArangoPersistentProperty value) { final PersistentIndexOptions options = new PersistentIndexOptions(); value.getPersistentIndexed().ifPresent(i -> options.unique(i.unique()).sparse(i.sparse())); collection.ensurePersistentIndex(Collections.singleton(value.getFieldName()), options); } private static void ensureGeoIndex(final CollectionOperations collection, final GeoIndex annotation) { collection.ensureGeoIndex(Arrays.asList(annotation.fields()), new GeoIndexOptions().geoJson(annotation.geoJson())); } private static void ensureGeoIndex(final CollectionOperations collection, final ArangoPersistentProperty value) { final GeoIndexOptions options = new GeoIndexOptions(); value.getGeoIndexed().ifPresent(i -> options.geoJson(i.geoJson())); collection.ensureGeoIndex(Collections.singleton(value.getFieldName()), options); } private static void ensureFulltextIndex(final CollectionOperations collection, final FulltextIndex annotation) { collection.ensureFulltextIndex(Collections.singleton(annotation.field()), new FulltextIndexOptions().minLength(annotation.minLength() > -1 ? annotation.minLength() : null)); } private static void ensureFulltextIndex( final CollectionOperations collection, final ArangoPersistentProperty value) { final FulltextIndexOptions options = new FulltextIndexOptions(); value.getFulltextIndexed().ifPresent(i -> options.minLength(i.minLength() > -1 ? i.minLength() : null)); collection.ensureFulltextIndex(Collections.singleton(value.getFieldName()), options); } private Optional<String> determineCollectionFromId(final Object id) { return id != null ? Optional.ofNullable(MetadataUtils.determineCollectionFromId(converter.convertId(id))) : Optional.empty(); } private String determineDocumentKeyFromId(final Object id) { return MetadataUtils.determineDocumentKeyFromId(converter.convertId(id)); } private VPackSlice toVPack(final Object source) { return converter.write(source); } private Collection<VPackSlice> toVPackCollection(final Iterable<?> values) { final Collection<VPackSlice> vpacks = new ArrayList<>(); for (final Object value : values) { vpacks.add(toVPack(value)); } return vpacks; } private <T> T fromVPack(final Class<T> entityClass, final VPackSlice source) { final T result = converter.read(entityClass, source); if (result != null) { potentiallyEmitEvent(new AfterLoadEvent<>(result)); } return result; } @Override public ArangoDB driver() { return arango; } @Override public ArangoDBVersion getVersion() throws DataAccessException { try { if (version == null) { version = db().getVersion(); } return version; } catch (final ArangoDBException e) { throw translateExceptionIfPossible(e); } } @Override public <T> ArangoCursor<T> query(final String query, final Class<T> entityClass) throws DataAccessException { return query(query, null, null, entityClass); } @Override public <T> ArangoCursor<T> query(final String query, final Map<String, Object> bindVars, final Class<T> entityClass) throws DataAccessException { return query(query, bindVars, null, entityClass); } @Override public <T> ArangoCursor<T> query(final String query, final AqlQueryOptions options, final Class<T> entityClass) throws DataAccessException { return query(query, null, options, entityClass); } @Override public <T> ArangoCursor<T> query( final String query, final Map<String, Object> bindVars, final AqlQueryOptions options, final Class<T> entityClass) throws DataAccessException { return db().query(query, bindVars == null ? null : prepareBindVars(bindVars), options, entityClass); } private Map<String, Object> prepareBindVars(final Map<String, Object> bindVars) { final Map<String, Object> prepared = new HashMap<>(bindVars.size()); for (final Entry<String, Object> entry : bindVars.entrySet()) { if (entry.getKey().startsWith("@") && entry.getValue() instanceof Class) { prepared.put(entry.getKey(), _collection((Class<?>) entry.getValue()).name()); } else { prepared.put(entry.getKey(), toVPack(entry.getValue())); } } return prepared; } @Override public MultiDocumentEntity<? extends DocumentEntity> delete( final Iterable<Object> values, final Class<?> entityClass, final DocumentDeleteOptions options) throws DataAccessException { potentiallyEmitBeforeDeleteEvent(values, entityClass); MultiDocumentEntity<? extends DocumentEntity> result; try { result = _collection(entityClass).deleteDocuments(toVPackCollection(values), entityClass, options); } catch (final ArangoDBException e) { throw translateExceptionIfPossible(e); } potentiallyEmitAfterDeleteEvent(values, entityClass, result); return result; } @Override public MultiDocumentEntity<? extends DocumentEntity> delete( final Iterable<Object> values, final Class<?> entityClass) throws DataAccessException { return delete(values, entityClass, new DocumentDeleteOptions()); } @Override public DocumentEntity delete(final Object id, final Class<?> entityClass, final DocumentDeleteOptions options) throws DataAccessException { potentiallyEmitEvent(new BeforeDeleteEvent<>(id, entityClass)); final DocumentEntity result; try { result = _collection(entityClass, id).deleteDocument(determineDocumentKeyFromId(id), entityClass, options); } catch (final ArangoDBException e) { throw translateExceptionIfPossible(e); } potentiallyEmitEvent(new AfterDeleteEvent<>(id, entityClass)); return result; } @Override public DocumentEntity delete(final Object id, final Class<?> entityClass) throws DataAccessException { return delete(id, entityClass, new DocumentDeleteOptions()); } @Override public <T> MultiDocumentEntity<? extends DocumentEntity> update( final Iterable<T> values, final Class<T> entityClass, final DocumentUpdateOptions options) throws DataAccessException { potentiallyEmitBeforeSaveEvent(values); final MultiDocumentEntity<? extends DocumentEntity> result; try { result = _collection(entityClass).updateDocuments(toVPackCollection(values), options); } catch (final ArangoDBException e) { throw translateExceptionIfPossible(e); } updateDBFields(values, result); potentiallyEmitAfterSaveEvent(values, result); return result; } @Override public <T> MultiDocumentEntity<? extends DocumentEntity> update( final Iterable<T> values, final Class<T> entityClass) throws DataAccessException { return update(values, entityClass, new DocumentUpdateOptions()); } @Override public DocumentEntity update(final Object id, final Object value, final DocumentUpdateOptions options) throws DataAccessException { potentiallyEmitEvent(new BeforeSaveEvent<>(value)); final DocumentEntity result; try { result = _collection(value.getClass(), id).updateDocument(determineDocumentKeyFromId(id), toVPack(value), options); } catch (final ArangoDBException e) { throw translateExceptionIfPossible(e); } updateDBFields(value, result); potentiallyEmitEvent(new AfterSaveEvent<>(value)); return result; } @Override public DocumentEntity update(final Object id, final Object value) throws DataAccessException { return update(id, value, new DocumentUpdateOptions()); } @Override public <T> MultiDocumentEntity<? extends DocumentEntity> replace( final Iterable<T> values, final Class<T> entityClass, final DocumentReplaceOptions options) throws DataAccessException { potentiallyEmitBeforeSaveEvent(values); final MultiDocumentEntity<? extends DocumentEntity> result; try { result = _collection(entityClass).replaceDocuments(toVPackCollection(values), options); } catch (final ArangoDBException e) { throw translateExceptionIfPossible(e); } updateDBFields(values, result); potentiallyEmitAfterSaveEvent(values, result); return result; } @Override public <T> MultiDocumentEntity<? extends DocumentEntity> replace( final Iterable<T> values, final Class<T> entityClass) throws DataAccessException { return replace(values, entityClass, new DocumentReplaceOptions()); } @Override public DocumentEntity replace(final Object id, final Object value, final DocumentReplaceOptions options) throws DataAccessException { potentiallyEmitEvent(new BeforeSaveEvent<>(value)); final DocumentEntity result; try { result = _collection(value.getClass(), id).replaceDocument(determineDocumentKeyFromId(id), toVPack(value), options); } catch (final ArangoDBException e) { throw translateExceptionIfPossible(e); } updateDBFields(value, result); potentiallyEmitEvent(new AfterSaveEvent<>(value)); return result; } @Override public DocumentEntity replace(final Object id, final Object value) throws DataAccessException { return replace(id, value, new DocumentReplaceOptions()); } @Override public <T> Optional<T> find(final Object id, final Class<T> entityClass, final DocumentReadOptions options) throws DataAccessException { try { final VPackSlice doc = _collection(entityClass, id).getDocument(determineDocumentKeyFromId(id), VPackSlice.class, options); return Optional.ofNullable(fromVPack(entityClass, doc)); } catch (final ArangoDBException e) { throw translateExceptionIfPossible(e); } } @Override public <T> Optional<T> find(final Object id, final Class<T> entityClass) throws DataAccessException { return find(id, entityClass, new DocumentReadOptions()); } @Override public <T> Iterable<T> findAll(final Class<T> entityClass) throws DataAccessException { final String query = "FOR entity IN @@col RETURN entity"; final Map<String, Object> bindVars = new MapBuilder().put("@col", entityClass).get(); return new Iterable<T>() { @Override public Iterator<T> iterator() { return query(query, bindVars, null, entityClass); } }; } @Override public <T> Iterable<T> find(final Iterable<? extends Object> ids, final Class<T> entityClass) throws DataAccessException { try { final Collection<String> keys = new ArrayList<>(); ids.forEach(id -> keys.add(determineDocumentKeyFromId(id))); final MultiDocumentEntity<VPackSlice> docs = _collection(entityClass).getDocuments(keys, VPackSlice.class); return docs.getDocuments().stream().map(doc -> fromVPack(entityClass, doc)).collect(Collectors.toList()); } catch (final ArangoDBException e) { throw translateExceptionIfPossible(e); } } @Override public <T> MultiDocumentEntity<? extends DocumentEntity> insert( final Iterable<T> values, final Class<T> entityClass, final DocumentCreateOptions options) throws DataAccessException { potentiallyEmitBeforeSaveEvent(values); final MultiDocumentEntity<? extends DocumentEntity> result; try { result = _collection(entityClass).insertDocuments(toVPackCollection(values), options); } catch (final ArangoDBException e) { throw translateExceptionIfPossible(e); } updateDBFields(values, result); potentiallyEmitAfterSaveEvent(values, result); return result; } @Override public <T> MultiDocumentEntity<? extends DocumentEntity> insert( final Iterable<T> values, final Class<T> entityClass) throws DataAccessException { return insert(values, entityClass, new DocumentCreateOptions()); } @Override public DocumentEntity insert(final Object value, final DocumentCreateOptions options) throws DataAccessException { potentiallyEmitEvent(new BeforeSaveEvent<>(value)); final DocumentEntity result; try { result = _collection(value.getClass()).insertDocument(toVPack(value), options); } catch (final ArangoDBException e) { throw exceptionTranslator.translateExceptionIfPossible(e); } updateDBFields(value, result); potentiallyEmitEvent(new AfterSaveEvent<>(value)); return result; } @Override public DocumentEntity insert(final Object value) throws DataAccessException { return insert(value, new DocumentCreateOptions()); } @Override public DocumentEntity insert(final String collectionName, final Object value, final DocumentCreateOptions options) throws DataAccessException { potentiallyEmitEvent(new BeforeSaveEvent<>(value)); final DocumentEntity result; try { result = _collection(collectionName).insertDocument(toVPack(value), options); } catch (final ArangoDBException e) { throw exceptionTranslator.translateExceptionIfPossible(e); } updateDBFields(value, result); potentiallyEmitEvent(new AfterSaveEvent<>(value)); return result; } @Override public DocumentEntity insert(final String collectionName, final Object value) throws DataAccessException { return insert(collectionName, value, new DocumentCreateOptions()); } private Object getDocumentKey(final ArangoPersistentEntity<?> entity, final Object value) { Object id = entity.getIdentifierAccessor(value).getIdentifier(); if (id == null) { final Object docId = entity.getArangoIdAccessor(value).getIdentifier(); if (docId != null) { id = MetadataUtils.determineDocumentKeyFromId((String) docId); } } return id; } @Override public <T> void upsert(final T value, final UpsertStrategy strategy) throws DataAccessException { final Class<? extends Object> entityClass = value.getClass(); final ArangoPersistentEntity<?> entity = getConverter().getMappingContext().getPersistentEntity(entityClass); final Object id = getDocumentKey(entity, value); if (id != null && (!(value instanceof Persistable) || !Persistable.class.cast(value).isNew())) { switch (strategy) { case UPDATE: update(id.toString(), value); break; case REPLACE: default: replace(id.toString(), value); break; } return; } insert(value); } @SuppressWarnings("unchecked") @Override public <T> void upsert(final Iterable<T> value, final UpsertStrategy strategy) throws DataAccessException { final Optional<T> first = StreamSupport.stream(value.spliterator(), false).findFirst(); if (!first.isPresent()) { return; } final Class<T> entityClass = (Class<T>) first.get().getClass(); final ArangoPersistentEntity<?> entity = getConverter().getMappingContext().getPersistentEntity(entityClass); final Collection<T> withId = new ArrayList<>(); final Collection<T> withoutId = new ArrayList<>(); for (final T e : value) { final Object id = getDocumentKey(entity, e); if (id != null && (!(e instanceof Persistable) || !Persistable.class.cast(e).isNew())) { withId.add(e); continue; } withoutId.add(e); } if (!withoutId.isEmpty()) { insert(withoutId, entityClass); } if (!withId.isEmpty()) { switch (strategy) { case UPDATE: update(withId, entityClass); break; case REPLACE: default: replace(withId, entityClass); break; } } } @Override public <T> void repsert(final T value) throws DataAccessException { insert(value, new DocumentCreateOptions().overwrite(true)); } @Override public <T> void repsert(final Iterable<T> value, final Class<T> entityClass) throws DataAccessException { insert(value, entityClass, new DocumentCreateOptions().overwrite(true)); } private <T> void updateDBFields(final Iterable<T> values, final MultiDocumentEntity<? extends DocumentEntity> res) { final Iterator<T> valueIterator = values.iterator(); if (res.getErrors().isEmpty()) { final Iterator<? extends DocumentEntity> documentIterator = res.getDocuments().iterator(); for (; valueIterator.hasNext() && documentIterator.hasNext();) { updateDBFields(valueIterator.next(), documentIterator.next()); } } else { final Iterator<Object> documentIterator = res.getDocumentsAndErrors().iterator(); for (; valueIterator.hasNext() && documentIterator.hasNext();) { final Object nextDoc = documentIterator.next(); final Object nextValue = valueIterator.next(); if (nextDoc instanceof DocumentEntity) { updateDBFields(nextValue, (DocumentEntity) nextDoc); } } } } private void updateDBFields(final Object value, final DocumentEntity documentEntity) { final ArangoPersistentEntity<?> entity = converter.getMappingContext().getPersistentEntity(value.getClass()); final PersistentPropertyAccessor<?> accessor = entity.getPropertyAccessor(value); final ArangoPersistentProperty idProperty = entity.getIdProperty(); if (idProperty != null && !idProperty.isImmutable()) { accessor.setProperty(idProperty, documentEntity.getKey()); } entity.getArangoIdProperty().filter(arangoId -> !arangoId.isImmutable()) .ifPresent(arangoId -> accessor.setProperty(arangoId, documentEntity.getId())); entity.getRevProperty().filter(rev -> !rev.isImmutable()) .ifPresent(rev -> accessor.setProperty(rev, documentEntity.getRev())); } @Override public boolean exists(final Object id, final Class<?> entityClass) throws DataAccessException { try { return _collection(entityClass).documentExists(determineDocumentKeyFromId(id)); } catch (final ArangoDBException e) { throw translateExceptionIfPossible(e); } } @Override public void dropDatabase() throws DataAccessException { final ArangoDatabase db = db(); try { db.drop(); } catch (final ArangoDBException e) { throw translateExceptionIfPossible(e); } databaseCache.remove(db.name()); collectionCache.keySet().stream().filter(key -> key.getDb().equals(db.name())) .forEach(key -> collectionCache.remove(key)); } @Override public CollectionOperations collection(final Class<?> entityClass) throws DataAccessException { return collection(_collection(entityClass)); } @Override public CollectionOperations collection(final String name) throws DataAccessException { return collection(_collection(name)); } @Override public CollectionOperations collection(final String name, final CollectionCreateOptions options) throws DataAccessException { return collection(_collection(name, null, options)); } private CollectionOperations collection(final ArangoCollection collection) { return new DefaultCollectionOperations(collection, collectionCache, exceptionTranslator); } @Override public UserOperations user(final String username) { return new DefaultUserOperation(db(), username, exceptionTranslator, this); } @Override public Iterable<UserEntity> getUsers() throws DataAccessException { try { return arango.getUsers(); } catch (final ArangoDBException e) { throw translateExceptionIfPossible(e); } } @Override public ArangoConverter getConverter() { return this.converter; } @Override public void setApplicationContext(final ApplicationContext applicationContext) throws BeansException { context.setRootObject(applicationContext); context.setBeanResolver(new BeanFactoryResolver(applicationContext)); context.addPropertyAccessor(new BeanFactoryAccessor()); eventPublisher = applicationContext; arango._setCursorInitializer(new ArangoCursorInitializer(converter, applicationContext)); } private void potentiallyEmitEvent(final ArangoMappingEvent<?> event) { if (eventPublisher != null) { eventPublisher.publishEvent(event); } } private void potentiallyEmitBeforeSaveEvent(final Iterable<?> values) { for (final Object value : values) { potentiallyEmitEvent(new BeforeSaveEvent<>(value)); } } private void potentiallyEmitAfterSaveEvent( final Iterable<?> values, final MultiDocumentEntity<? extends DocumentEntity> result) { final Iterator<?> valueIterator = values.iterator(); final Iterator<?> documentIterator = result.getDocumentsAndErrors().iterator(); while (valueIterator.hasNext() && documentIterator.hasNext()) { final Object nextDoc = documentIterator.next(); final Object nextValue = valueIterator.next(); if (nextDoc instanceof DocumentEntity) { potentiallyEmitEvent(new AfterSaveEvent<>(nextValue)); } } } private void potentiallyEmitBeforeDeleteEvent(final Iterable<?> values, final Class<?> type) { for (final Object value : values) { potentiallyEmitEvent(new BeforeDeleteEvent<>(value, type)); } } private void potentiallyEmitAfterDeleteEvent( final Iterable<?> values, final Class<?> entityClass, final MultiDocumentEntity<? extends DocumentEntity> result) { final Iterator<?> valueIterator = values.iterator(); final Iterator<?> documentIterator = result.getDocumentsAndErrors().iterator(); while (valueIterator.hasNext() && documentIterator.hasNext()) { final Object nextDoc = documentIterator.next(); final Object nextValue = valueIterator.next(); if (nextDoc instanceof DocumentEntity) { potentiallyEmitEvent(new AfterDeleteEvent<>(nextValue, entityClass)); } } } }