DATAMONGO-2188 - Deprecate auto-index creation & introduce configuration to disable it.
Auto-index creation can now be disabled by setting MongoMappingContext.setAutoIndexCreation(false). This configuration prevents automatic index creation on application startup and during access to entities. Original Pull Request: #636
This commit is contained in:
committed by
Christoph Strobl
parent
8f3dacd55e
commit
33fa79b29f
@@ -246,10 +246,14 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
mappingContext = this.mongoConverter.getMappingContext();
|
||||
// We create indexes based on mapping events
|
||||
if (mappingContext instanceof MongoMappingContext) {
|
||||
indexCreator = new MongoPersistentEntityIndexCreator((MongoMappingContext) mappingContext, this);
|
||||
eventPublisher = new MongoMappingEventPublisher(indexCreator);
|
||||
if (mappingContext instanceof ApplicationEventPublisherAware) {
|
||||
((ApplicationEventPublisherAware) mappingContext).setApplicationEventPublisher(eventPublisher);
|
||||
|
||||
MongoMappingContext mappingContext = (MongoMappingContext) this.mappingContext;
|
||||
|
||||
if (mappingContext.isAutoIndexCreation()) {
|
||||
|
||||
indexCreator = new MongoPersistentEntityIndexCreator(mappingContext, this);
|
||||
eventPublisher = new MongoMappingEventPublisher(indexCreator);
|
||||
mappingContext.setApplicationEventPublisher(eventPublisher);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1582,7 +1586,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
query.getCollation().map(Collation::toMongoCollation).ifPresent(opts::collation);
|
||||
}
|
||||
|
||||
Document updateObj = update instanceof MappedUpdate ? update.getUpdateObject() : updateMapper.getMappedObject(update.getUpdateObject(), entity);
|
||||
Document updateObj = update instanceof MappedUpdate ? update.getUpdateObject()
|
||||
: updateMapper.getMappedObject(update.getUpdateObject(), entity);
|
||||
|
||||
if (multi && update.isIsolated() && !queryObj.containsKey("$isolated")) {
|
||||
queryObj.put("$isolated", 1);
|
||||
@@ -1617,7 +1622,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
});
|
||||
}
|
||||
|
||||
private void increaseVersionForUpdateIfNecessary(@Nullable MongoPersistentEntity<?> persistentEntity, UpdateDefinition update) {
|
||||
private void increaseVersionForUpdateIfNecessary(@Nullable MongoPersistentEntity<?> persistentEntity,
|
||||
UpdateDefinition update) {
|
||||
|
||||
if (persistentEntity != null && persistentEntity.hasVersionProperty()) {
|
||||
String versionFieldName = persistentEntity.getRequiredVersionProperty().getFieldName();
|
||||
|
||||
@@ -232,12 +232,15 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
if (this.mappingContext instanceof MongoMappingContext) {
|
||||
|
||||
MongoMappingContext mongoMappingContext = (MongoMappingContext) this.mappingContext;
|
||||
this.indexCreator = new ReactiveMongoPersistentEntityIndexCreator(mongoMappingContext, this::indexOps);
|
||||
this.eventPublisher = new MongoMappingEventPublisher(this.indexCreatorListener);
|
||||
|
||||
mongoMappingContext.setApplicationEventPublisher(this.eventPublisher);
|
||||
this.mappingContext.getPersistentEntities()
|
||||
.forEach(entity -> onCheckForIndexes(entity, subscriptionExceptionHandler));
|
||||
if (mongoMappingContext.isAutoIndexCreation()) {
|
||||
this.indexCreator = new ReactiveMongoPersistentEntityIndexCreator(mongoMappingContext, this::indexOps);
|
||||
this.eventPublisher = new MongoMappingEventPublisher(this.indexCreatorListener);
|
||||
|
||||
mongoMappingContext.setApplicationEventPublisher(this.eventPublisher);
|
||||
this.mappingContext.getPersistentEntities()
|
||||
.forEach(entity -> onCheckForIndexes(entity, subscriptionExceptionHandler));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -55,7 +55,8 @@ public @interface CompoundIndex {
|
||||
|
||||
/**
|
||||
* @return
|
||||
* @see <a href="https://docs.mongodb.org/manual/core/index-unique/">https://docs.mongodb.org/manual/core/index-unique/</a>
|
||||
* @see <a href=
|
||||
* "https://docs.mongodb.org/manual/core/index-unique/">https://docs.mongodb.org/manual/core/index-unique/</a>
|
||||
*/
|
||||
boolean unique() default false;
|
||||
|
||||
@@ -63,13 +64,15 @@ public @interface CompoundIndex {
|
||||
* If set to true index will skip over any document that is missing the indexed field.
|
||||
*
|
||||
* @return
|
||||
* @see <a href="https://docs.mongodb.org/manual/core/index-sparse/">https://docs.mongodb.org/manual/core/index-sparse/</a>
|
||||
* @see <a href=
|
||||
* "https://docs.mongodb.org/manual/core/index-sparse/">https://docs.mongodb.org/manual/core/index-sparse/</a>
|
||||
*/
|
||||
boolean sparse() default false;
|
||||
|
||||
/**
|
||||
* @return
|
||||
* @see <a href="https://docs.mongodb.org/manual/core/index-creation/#index-creation-duplicate-dropping">https://docs.mongodb.org/manual/core/index-creation/#index-creation-duplicate-dropping</a>
|
||||
* @see <a href=
|
||||
* "https://docs.mongodb.org/manual/core/index-creation/#index-creation-duplicate-dropping">https://docs.mongodb.org/manual/core/index-creation/#index-creation-duplicate-dropping</a>
|
||||
* @deprecated since 2.1. No longer supported by MongoDB as of server version 3.0.
|
||||
*/
|
||||
@Deprecated
|
||||
@@ -131,7 +134,8 @@ public @interface CompoundIndex {
|
||||
* If {@literal true} the index will be created in the background.
|
||||
*
|
||||
* @return
|
||||
* @see <a href="https://docs.mongodb.org/manual/core/indexes/#background-construction">https://docs.mongodb.org/manual/core/indexes/#background-construction</a>
|
||||
* @see <a href=
|
||||
* "https://docs.mongodb.org/manual/core/indexes/#background-construction">https://docs.mongodb.org/manual/core/indexes/#background-construction</a>
|
||||
*/
|
||||
boolean background() default false;
|
||||
|
||||
|
||||
@@ -28,8 +28,8 @@ import org.springframework.data.util.TypeInformation;
|
||||
interface IndexResolver {
|
||||
|
||||
/**
|
||||
* Find and create {@link IndexDefinition}s for properties of given {@link TypeInformation}. {@link IndexDefinition}s are created
|
||||
* for properties and types with {@link Indexed}, {@link CompoundIndexes} or {@link GeoSpatialIndexed}.
|
||||
* Find and create {@link IndexDefinition}s for properties of given {@link TypeInformation}. {@link IndexDefinition}s
|
||||
* are created for properties and types with {@link Indexed}, {@link CompoundIndexes} or {@link GeoSpatialIndexed}.
|
||||
*
|
||||
* @param typeInformation
|
||||
* @return Empty {@link Iterable} in case no {@link IndexDefinition} could be resolved for type.
|
||||
|
||||
@@ -31,7 +31,7 @@ import java.lang.annotation.Target;
|
||||
* @author Christoph Strobl
|
||||
* @author Jordi Llach
|
||||
*/
|
||||
@Target({ElementType.ANNOTATION_TYPE, ElementType.FIELD})
|
||||
@Target({ ElementType.ANNOTATION_TYPE, ElementType.FIELD })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface Indexed {
|
||||
|
||||
@@ -39,7 +39,8 @@ public @interface Indexed {
|
||||
* If set to true reject all documents that contain a duplicate value for the indexed field.
|
||||
*
|
||||
* @return
|
||||
* @see <a href="https://docs.mongodb.org/manual/core/index-unique/">https://docs.mongodb.org/manual/core/index-unique/</a>
|
||||
* @see <a href=
|
||||
* "https://docs.mongodb.org/manual/core/index-unique/">https://docs.mongodb.org/manual/core/index-unique/</a>
|
||||
*/
|
||||
boolean unique() default false;
|
||||
|
||||
@@ -49,13 +50,15 @@ public @interface Indexed {
|
||||
* If set to true index will skip over any document that is missing the indexed field.
|
||||
*
|
||||
* @return
|
||||
* @see <a href="https://docs.mongodb.org/manual/core/index-sparse/">https://docs.mongodb.org/manual/core/index-sparse/</a>
|
||||
* @see <a href=
|
||||
* "https://docs.mongodb.org/manual/core/index-sparse/">https://docs.mongodb.org/manual/core/index-sparse/</a>
|
||||
*/
|
||||
boolean sparse() default false;
|
||||
|
||||
/**
|
||||
* @return
|
||||
* @see <a href="https://docs.mongodb.org/manual/core/index-creation/#index-creation-duplicate-dropping">https://docs.mongodb.org/manual/core/index-creation/#index-creation-duplicate-dropping</a>
|
||||
* @see <a href=
|
||||
* "https://docs.mongodb.org/manual/core/index-creation/#index-creation-duplicate-dropping">https://docs.mongodb.org/manual/core/index-creation/#index-creation-duplicate-dropping</a>
|
||||
* @deprecated since 2.1. No longer supported by MongoDB as of server version 3.0.
|
||||
*/
|
||||
@Deprecated
|
||||
@@ -115,7 +118,8 @@ public @interface Indexed {
|
||||
* If {@literal true} the index will be created in the background.
|
||||
*
|
||||
* @return
|
||||
* @see <a href="https://docs.mongodb.org/manual/core/indexes/#background-construction">https://docs.mongodb.org/manual/core/indexes/#background-construction</a>
|
||||
* @see <a href=
|
||||
* "https://docs.mongodb.org/manual/core/indexes/#background-construction">https://docs.mongodb.org/manual/core/indexes/#background-construction</a>
|
||||
*/
|
||||
boolean background() default false;
|
||||
|
||||
@@ -123,7 +127,8 @@ public @interface Indexed {
|
||||
* Configures the number of seconds after which the collection should expire. Defaults to -1 for no expiry.
|
||||
*
|
||||
* @return
|
||||
* @see <a href="https://docs.mongodb.org/manual/tutorial/expire-data/">https://docs.mongodb.org/manual/tutorial/expire-data/</a>
|
||||
* @see <a href=
|
||||
* "https://docs.mongodb.org/manual/tutorial/expire-data/">https://docs.mongodb.org/manual/tutorial/expire-data/</a>
|
||||
*/
|
||||
int expireAfterSeconds() default -1;
|
||||
}
|
||||
|
||||
@@ -36,7 +36,9 @@ import org.springframework.util.Assert;
|
||||
* @author Jon Brisbin
|
||||
* @author Oliver Gierke
|
||||
* @author Mark Paluch
|
||||
* @deprecated since 2.2. Use {@link IndexOperations} to define and create indexes.
|
||||
*/
|
||||
@Deprecated
|
||||
public class MongoMappingEventPublisher implements ApplicationEventPublisher {
|
||||
|
||||
private final ApplicationListener<MappingContextEvent<?, ?>> indexCreator;
|
||||
|
||||
@@ -63,10 +63,12 @@ public class MongoPersistentEntityIndexCreator implements ApplicationListener<Ma
|
||||
/**
|
||||
* Creates a new {@link MongoPersistentEntityIndexCreator} for the given {@link MongoMappingContext} and
|
||||
* {@link MongoDbFactory}.
|
||||
* @param mappingContext must not be {@literal null}.
|
||||
*
|
||||
* @param mappingContext must not be {@literal null}.
|
||||
* @param indexOperationsProvider must not be {@literal null}.
|
||||
*/
|
||||
public MongoPersistentEntityIndexCreator(MongoMappingContext mappingContext, IndexOperationsProvider indexOperationsProvider) {
|
||||
public MongoPersistentEntityIndexCreator(MongoMappingContext mappingContext,
|
||||
IndexOperationsProvider indexOperationsProvider) {
|
||||
this(mappingContext, indexOperationsProvider, new MongoPersistentEntityIndexResolver(mappingContext));
|
||||
}
|
||||
|
||||
@@ -78,8 +80,8 @@ public class MongoPersistentEntityIndexCreator implements ApplicationListener<Ma
|
||||
* @param mongoDbFactory must not be {@literal null}.
|
||||
* @param indexResolver must not be {@literal null}.
|
||||
*/
|
||||
public MongoPersistentEntityIndexCreator(MongoMappingContext mappingContext, IndexOperationsProvider indexOperationsProvider,
|
||||
IndexResolver indexResolver) {
|
||||
public MongoPersistentEntityIndexCreator(MongoMappingContext mappingContext,
|
||||
IndexOperationsProvider indexOperationsProvider, IndexResolver indexResolver) {
|
||||
|
||||
Assert.notNull(mappingContext, "MongoMappingContext must not be null!");
|
||||
Assert.notNull(indexOperationsProvider, "IndexOperationsProvider must not be null!");
|
||||
@@ -146,8 +148,8 @@ public class MongoPersistentEntityIndexCreator implements ApplicationListener<Ma
|
||||
|
||||
} catch (UncategorizedMongoDbException ex) {
|
||||
|
||||
if (ex.getCause() instanceof MongoException &&
|
||||
MongoDbErrorCodes.isDataIntegrityViolationCode(((MongoException) ex.getCause()).getCode())) {
|
||||
if (ex.getCause() instanceof MongoException
|
||||
&& MongoDbErrorCodes.isDataIntegrityViolationCode(((MongoException) ex.getCause()).getCode())) {
|
||||
|
||||
IndexInfo existingIndex = fetchIndexInformation(indexDefinition);
|
||||
String message = "Cannot create index for '%s' in collection '%s' with keys '%s' and options '%s'.";
|
||||
|
||||
@@ -43,6 +43,7 @@ public class MongoMappingContext extends AbstractMappingContext<BasicMongoPersis
|
||||
|
||||
private FieldNamingStrategy fieldNamingStrategy = DEFAULT_NAMING_STRATEGY;
|
||||
private @Nullable ApplicationContext context;
|
||||
private boolean autoIndexCreation = true;
|
||||
|
||||
/**
|
||||
* Creates a new {@link MongoMappingContext}.
|
||||
@@ -101,4 +102,28 @@ public class MongoMappingContext extends AbstractMappingContext<BasicMongoPersis
|
||||
|
||||
this.context = applicationContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether auto-index creation is enabled or disabled. Please note that auto-index creation is deprecated.
|
||||
* Index creation should happen at a well-defined time that is ideally controlled by the application itself.
|
||||
*
|
||||
* @return {@literal true} when auto-index creation is enabled; {@literal false} otherwise.
|
||||
* @since 2.2
|
||||
* @see org.springframework.data.mongodb.core.index.Indexed
|
||||
*/
|
||||
public boolean isAutoIndexCreation() {
|
||||
return autoIndexCreation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables/disables auto-index creation. Please note that auto-index creation is deprecated. Index creation should
|
||||
* happen at a well-defined time that is ideally controlled by the application its
|
||||
*
|
||||
* @param autoCreateIndexes {@literal true} to enable auto-index creation. Enabled by default.
|
||||
* @since 2.2
|
||||
* @see org.springframework.data.mongodb.core.index.Indexed
|
||||
*/
|
||||
public void setAutoIndexCreation(boolean autoCreateIndexes) {
|
||||
this.autoIndexCreation = autoCreateIndexes;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,10 @@ import org.springframework.util.Assert;
|
||||
* @author Oliver Gierke
|
||||
* @author Mark Paluch
|
||||
* @author Christoph Strobl
|
||||
* @deprecated since 2.2. Use {@link org.springframework.data.mongodb.core.index.IndexOperations} to define and create
|
||||
* indexes.
|
||||
*/
|
||||
@Deprecated
|
||||
class IndexEnsuringQueryCreationListener implements QueryCreationListener<PartTreeMongoQuery> {
|
||||
|
||||
private static final Set<Type> GEOSPATIAL_TYPES = new HashSet<Type>(Arrays.asList(Type.NEAR, Type.WITHIN));
|
||||
|
||||
@@ -35,7 +35,7 @@ public class MongoRepositoryFactoryBean<T extends Repository<S, ID>, S, ID exten
|
||||
extends RepositoryFactoryBeanSupport<T, S, ID> {
|
||||
|
||||
private @Nullable MongoOperations operations;
|
||||
private boolean createIndexesForQueryMethods = false;
|
||||
@Deprecated private boolean createIndexesForQueryMethods = false;
|
||||
private boolean mappingContextConfigured = false;
|
||||
|
||||
/**
|
||||
@@ -60,7 +60,10 @@ public class MongoRepositoryFactoryBean<T extends Repository<S, ID>, S, ID exten
|
||||
* Configures whether to automatically create indexes for the properties referenced in a query method.
|
||||
*
|
||||
* @param createIndexesForQueryMethods the createIndexesForQueryMethods to set
|
||||
* @deprecated since 2.2. Use {@link org.springframework.data.mongodb.core.index.IndexOperations} to define and create
|
||||
* indexes.
|
||||
*/
|
||||
@Deprecated
|
||||
public void setCreateIndexesForQueryMethods(boolean createIndexesForQueryMethods) {
|
||||
this.createIndexesForQueryMethods = createIndexesForQueryMethods;
|
||||
}
|
||||
|
||||
@@ -40,7 +40,8 @@ public class ReactiveMongoRepositoryFactoryBean<T extends Repository<S, ID>, S,
|
||||
extends RepositoryFactoryBeanSupport<T, S, ID> {
|
||||
|
||||
private @Nullable ReactiveMongoOperations operations;
|
||||
private boolean createIndexesForQueryMethods = false;
|
||||
|
||||
@Deprecated private boolean createIndexesForQueryMethods = false;
|
||||
private boolean mappingContextConfigured = false;
|
||||
|
||||
/**
|
||||
@@ -65,7 +66,10 @@ public class ReactiveMongoRepositoryFactoryBean<T extends Repository<S, ID>, S,
|
||||
* Configures whether to automatically create indexes for the properties referenced in a query method.
|
||||
*
|
||||
* @param createIndexesForQueryMethods the createIndexesForQueryMethods to set
|
||||
* @deprecated since 2.2. Use {@link org.springframework.data.mongodb.core.index.IndexOperations} to define and create
|
||||
* indexes.
|
||||
*/
|
||||
@Deprecated
|
||||
public void setCreateIndexesForQueryMethods(boolean createIndexesForQueryMethods) {
|
||||
this.createIndexesForQueryMethods = createIndexesForQueryMethods;
|
||||
}
|
||||
|
||||
@@ -30,25 +30,26 @@ import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.data.mongodb.MongoCollectionUtils;
|
||||
import org.springframework.data.mongodb.MongoDbFactory;
|
||||
import org.springframework.data.mongodb.core.CollectionCallback;
|
||||
import org.springframework.data.mongodb.core.MongoOperations;
|
||||
import org.springframework.data.mongodb.core.MongoTemplate;
|
||||
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
|
||||
import org.springframework.data.mongodb.core.convert.NoOpDbRefResolver;
|
||||
import org.springframework.data.mongodb.core.mapping.Document;
|
||||
import org.springframework.data.mongodb.core.mapping.Field;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
import com.mongodb.MongoException;
|
||||
import com.mongodb.client.MongoCollection;
|
||||
|
||||
/**
|
||||
* Integration tests for index handling.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Christoph Strobl
|
||||
* @author Jordi Llach
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration("classpath:infrastructure.xml")
|
||||
@@ -72,6 +73,21 @@ public class IndexingIntegrationTests {
|
||||
assertThat(hasIndex("_firstname", IndexedPerson.class), is(true));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-237
|
||||
@DirtiesContext
|
||||
public void shouldNotCreateIndexOnIndexingDisabled() {
|
||||
|
||||
MongoMappingContext context = new MongoMappingContext();
|
||||
context.setAutoIndexCreation(false);
|
||||
|
||||
MongoTemplate template = new MongoTemplate(mongoDbFactory,
|
||||
new MappingMongoConverter(NoOpDbRefResolver.INSTANCE, context));
|
||||
|
||||
template.getConverter().getMappingContext().getPersistentEntity(IndexedPerson.class);
|
||||
|
||||
assertThat(hasIndex("_firstname", MongoCollectionUtils.getPreferredCollectionName(IndexedPerson.class)), is(false));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1163
|
||||
@DirtiesContext
|
||||
public void createsIndexFromMetaAnnotation() {
|
||||
@@ -101,22 +117,30 @@ public class IndexingIntegrationTests {
|
||||
* @param entityType
|
||||
* @return
|
||||
*/
|
||||
private boolean hasIndex(final String indexName, Class<?> entityType) {
|
||||
private boolean hasIndex(String indexName, Class<?> entityType) {
|
||||
return hasIndex(indexName, operations.getCollectionName(entityType));
|
||||
}
|
||||
|
||||
return operations.execute(entityType, new CollectionCallback<Boolean>() {
|
||||
public Boolean doInCollection(MongoCollection<org.bson.Document> collection)
|
||||
throws MongoException, DataAccessException {
|
||||
/**
|
||||
* Returns whether an index with the given name exists for the given collection.
|
||||
*
|
||||
* @param indexName
|
||||
* @param collectionName
|
||||
* @return
|
||||
*/
|
||||
private boolean hasIndex(String indexName, String collectionName) {
|
||||
|
||||
List<org.bson.Document> indexes = new ArrayList<org.bson.Document>();
|
||||
collection.listIndexes(org.bson.Document.class).into(indexes);
|
||||
return operations.execute(collectionName, collection -> {
|
||||
|
||||
for (org.bson.Document indexInfo : indexes) {
|
||||
if (indexName.equals(indexInfo.get("name"))) {
|
||||
return true;
|
||||
}
|
||||
List<org.bson.Document> indexes = new ArrayList<>();
|
||||
collection.listIndexes(org.bson.Document.class).into(indexes);
|
||||
|
||||
for (org.bson.Document indexInfo : indexes) {
|
||||
if (indexName.equals(indexInfo.get("name"))) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -378,7 +378,7 @@ public class Person {
|
||||
|
||||
IMPORTANT: The `@Id` annotation tells the mapper which property you want to use for the MongoDB `_id` property, and the `@Indexed` annotation tells the mapping framework to call `createIndex(…)` on that property of your document, making searches faster.
|
||||
|
||||
IMPORTANT: Automatic index creation is only done for types annotated with `@Document`.
|
||||
IMPORTANT: Automatic index creation is deprecated since version 2.2 because controlling the actual time of index creation is rather difficult. Index creation can be part of the application startup, happen during runtime or an out of band process. Therefore, Spring Data MongoDB backs off entirely and recommends index creation to happen either out of band or as part of the application startup using `IndexOperations`. Automatic index creation is still available and is is only done for types annotated with `@Document`.
|
||||
|
||||
[[mapping-usage-annotations]]
|
||||
=== Mapping Annotation Overview
|
||||
|
||||
Reference in New Issue
Block a user