/* * 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.mapping; import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.expression.BeanFactoryAccessor; import org.springframework.context.expression.BeanFactoryResolver; import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.data.mapping.IdentifierAccessor; import org.springframework.data.mapping.TargetAwareIdentifierAccessor; import org.springframework.data.mapping.model.BasicPersistentEntity; import org.springframework.data.util.TypeInformation; 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 org.springframework.lang.Nullable; import org.springframework.util.StringUtils; import com.arangodb.entity.CollectionType; import com.arangodb.model.CollectionCreateOptions; import com.arangodb.springframework.annotation.Document; import com.arangodb.springframework.annotation.Edge; import com.arangodb.springframework.annotation.FulltextIndex; import com.arangodb.springframework.annotation.FulltextIndexes; import com.arangodb.springframework.annotation.GeoIndex; import com.arangodb.springframework.annotation.GeoIndexes; import com.arangodb.springframework.annotation.HashIndex; import com.arangodb.springframework.annotation.HashIndexes; import com.arangodb.springframework.annotation.PersistentIndex; import com.arangodb.springframework.annotation.PersistentIndexes; import com.arangodb.springframework.annotation.SkiplistIndex; import com.arangodb.springframework.annotation.SkiplistIndexes; /** * @author Mark Vollmary * @param <T> * */ public class DefaultArangoPersistentEntity<T> extends BasicPersistentEntity<T, ArangoPersistentProperty> implements ArangoPersistentEntity<T> { private static final SpelExpressionParser PARSER = new SpelExpressionParser(); private String collection; private final Expression expression; private final StandardEvaluationContext context; private ArangoPersistentProperty arangoIdProperty; private ArangoPersistentProperty revProperty; private final Collection<ArangoPersistentProperty> hashIndexedProperties; private final Collection<ArangoPersistentProperty> skiplistIndexedProperties; private final Collection<ArangoPersistentProperty> persistentIndexedProperties; private final Collection<ArangoPersistentProperty> geoIndexedProperties; private final Collection<ArangoPersistentProperty> fulltextIndexedProperties; private final CollectionCreateOptions collectionOptions; private final Map<Class<? extends Annotation>, Set<? extends Annotation>> repeatableAnnotationCache; public DefaultArangoPersistentEntity(final TypeInformation<T> information) { super(information); collection = StringUtils.uncapitalize(information.getType().getSimpleName()); context = new StandardEvaluationContext(); hashIndexedProperties = new ArrayList<>(); skiplistIndexedProperties = new ArrayList<>(); persistentIndexedProperties = new ArrayList<>(); geoIndexedProperties = new ArrayList<>(); fulltextIndexedProperties = new ArrayList<>(); repeatableAnnotationCache = new HashMap<>(); final Document document = findAnnotation(Document.class); final Edge edge = findAnnotation(Edge.class); if (edge != null) { collection = StringUtils.hasText(edge.value()) ? edge.value() : collection; collectionOptions = createCollectionOptions(edge); } else if (document != null) { collection = StringUtils.hasText(document.value()) ? document.value() : collection; collectionOptions = createCollectionOptions(document); } else { collectionOptions = new CollectionCreateOptions().type(CollectionType.DOCUMENT); } expression = PARSER.parseExpression(collection, ParserContext.TEMPLATE_EXPRESSION); } private static CollectionCreateOptions createCollectionOptions(final Document annotation) { final CollectionCreateOptions options = new CollectionCreateOptions().type(CollectionType.DOCUMENT) .waitForSync(annotation.waitForSync()).doCompact(annotation.doCompact()) .isVolatile(annotation.isVolatile()).isSystem(annotation.isSystem()); if (annotation.journalSize() > -1) { options.journalSize(annotation.journalSize()); } if (annotation.replicationFactor() > -1) { options.replicationFactor(annotation.replicationFactor()); } if (annotation.satellite()) { options.satellite(annotation.satellite()); } final String[] shardKeys = annotation.shardKeys(); if (shardKeys.length > 1 || (shardKeys.length > 0 && StringUtils.hasText(shardKeys[0]))) { options.shardKeys(shardKeys); } if (annotation.numberOfShards() > -1) { options.numberOfShards(annotation.numberOfShards()); } if (annotation.indexBuckets() > -1) { options.indexBuckets(annotation.indexBuckets()); } if (annotation.allowUserKeys()) { options.keyOptions(annotation.allowUserKeys(), annotation.keyType(), annotation.keyIncrement() > -1 ? annotation.keyIncrement() : null, annotation.keyOffset() > -1 ? annotation.keyOffset() : null); } return options; } private static CollectionCreateOptions createCollectionOptions(final Edge annotation) { final CollectionCreateOptions options = new CollectionCreateOptions().type(CollectionType.EDGES) .waitForSync(annotation.waitForSync()).doCompact(annotation.doCompact()) .isVolatile(annotation.isVolatile()).isSystem(annotation.isSystem()); if (annotation.journalSize() > -1) { options.journalSize(annotation.journalSize()); } if (annotation.replicationFactor() > -1) { options.replicationFactor(annotation.replicationFactor()); } final String[] shardKeys = annotation.shardKeys(); if (shardKeys.length > 0) { options.shardKeys(shardKeys); } if (annotation.numberOfShards() > -1) { options.numberOfShards(annotation.numberOfShards()); } if (annotation.indexBuckets() > -1) { options.indexBuckets(annotation.indexBuckets()); } if (annotation.allowUserKeys()) { options.keyOptions(annotation.allowUserKeys(), annotation.keyType(), annotation.keyIncrement() > -1 ? annotation.keyIncrement() : null, annotation.keyOffset() > -1 ? annotation.keyOffset() : null); } return options; } @Override public String getCollection() { return expression != null ? expression.getValue(context, String.class) : collection; } @Override public void setApplicationContext(final ApplicationContext applicationContext) throws BeansException { context.setRootObject(applicationContext); context.setBeanResolver(new BeanFactoryResolver(applicationContext)); context.addPropertyAccessor(new BeanFactoryAccessor()); } @Override public void addPersistentProperty(final ArangoPersistentProperty property) { super.addPersistentProperty(property); if (property.isArangoIdProperty()) { arangoIdProperty = property; } if (property.isRevProperty()) { revProperty = property; } property.getHashIndexed().ifPresent(i -> hashIndexedProperties.add(property)); property.getSkiplistIndexed().ifPresent(i -> skiplistIndexedProperties.add(property)); property.getPersistentIndexed().ifPresent(i -> persistentIndexedProperties.add(property)); property.getGeoIndexed().ifPresent(i -> geoIndexedProperties.add(property)); property.getFulltextIndexed().ifPresent(i -> fulltextIndexedProperties.add(property)); } @Override public Optional<ArangoPersistentProperty> getArangoIdProperty() { return Optional.ofNullable(arangoIdProperty); } @Override public Optional<ArangoPersistentProperty> getRevProperty() { return Optional.ofNullable(revProperty); } @Override public CollectionCreateOptions getCollectionOptions() { return collectionOptions; } @Override public Collection<HashIndex> getHashIndexes() { final Collection<HashIndex> indexes = getIndexes(HashIndex.class); Optional.ofNullable(findAnnotation(HashIndexes.class)).ifPresent(i -> indexes.addAll(Arrays.asList(i.value()))); return indexes; } @Override public Collection<SkiplistIndex> getSkiplistIndexes() { final Collection<SkiplistIndex> indexes = getIndexes(SkiplistIndex.class); Optional.ofNullable(findAnnotation(SkiplistIndexes.class)) .ifPresent(i -> indexes.addAll(Arrays.asList(i.value()))); return indexes; } @Override public Collection<PersistentIndex> getPersistentIndexes() { final Collection<PersistentIndex> indexes = getIndexes(PersistentIndex.class); Optional.ofNullable(findAnnotation(PersistentIndexes.class)) .ifPresent(i -> indexes.addAll(Arrays.asList(i.value()))); return indexes; } @Override public Collection<GeoIndex> getGeoIndexes() { final Collection<GeoIndex> indexes = getIndexes(GeoIndex.class); Optional.ofNullable(findAnnotation(GeoIndexes.class)).ifPresent(i -> indexes.addAll(Arrays.asList(i.value()))); return indexes; } @Override public Collection<FulltextIndex> getFulltextIndexes() { final Collection<FulltextIndex> indexes = getIndexes(FulltextIndex.class); Optional.ofNullable(findAnnotation(FulltextIndexes.class)) .ifPresent(i -> indexes.addAll(Arrays.asList(i.value()))); return indexes; } public <A extends Annotation> Collection<A> getIndexes(final Class<A> annotation) { final List<A> indexes = findAnnotations(annotation).stream().filter(a -> annotation.isInstance(a)) .map(a -> annotation.cast(a)).collect(Collectors.toList()); return indexes; } @Override public Collection<ArangoPersistentProperty> getHashIndexedProperties() { return hashIndexedProperties; } @Override public Collection<ArangoPersistentProperty> getSkiplistIndexedProperties() { return skiplistIndexedProperties; } @Override public Collection<ArangoPersistentProperty> getPersistentIndexedProperties() { return persistentIndexedProperties; } @Override public Collection<ArangoPersistentProperty> getGeoIndexedProperties() { return geoIndexedProperties; } @Override public Collection<ArangoPersistentProperty> getFulltextIndexedProperties() { return fulltextIndexedProperties; } @SuppressWarnings("unchecked") public <A extends Annotation> Set<A> findAnnotations(final Class<A> annotationType) { return (Set<A>) repeatableAnnotationCache.computeIfAbsent(annotationType, it -> AnnotatedElementUtils.findMergedRepeatableAnnotations(getType(), it)); } private static class AbsentAccessor extends TargetAwareIdentifierAccessor { public AbsentAccessor(final Object target) { super(target); } @Override @Nullable public Object getIdentifier() { return null; } } @Override public IdentifierAccessor getArangoIdAccessor(final Object bean) { return getArangoIdProperty().isPresent() ? new ArangoIdPropertyIdentifierAccessor(this, bean) : new AbsentAccessor(bean); } }