diff --git a/pom.xml b/pom.xml
index 61eb8ba165..36229264c4 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
org.springframework.data
spring-data-mongodb-parent
- 2.0.0.BUILD-SNAPSHOT
+ 2.0.0.DATAMONGO-1682-SNAPSHOT
pom
Spring Data MongoDB
diff --git a/spring-data-mongodb-benchmarks/pom.xml b/spring-data-mongodb-benchmarks/pom.xml
index 494925fb63..748c301901 100644
--- a/spring-data-mongodb-benchmarks/pom.xml
+++ b/spring-data-mongodb-benchmarks/pom.xml
@@ -7,7 +7,7 @@
org.springframework.data
spring-data-mongodb-parent
- 2.0.0.BUILD-SNAPSHOT
+ 2.0.0.DATAMONGO-1682-SNAPSHOT
../pom.xml
diff --git a/spring-data-mongodb-cross-store/pom.xml b/spring-data-mongodb-cross-store/pom.xml
index 4a49168713..963601a3b7 100644
--- a/spring-data-mongodb-cross-store/pom.xml
+++ b/spring-data-mongodb-cross-store/pom.xml
@@ -6,7 +6,7 @@
org.springframework.data
spring-data-mongodb-parent
- 2.0.0.BUILD-SNAPSHOT
+ 2.0.0.DATAMONGO-1682-SNAPSHOT
../pom.xml
@@ -48,7 +48,7 @@
org.springframework.data
spring-data-mongodb
- 2.0.0.BUILD-SNAPSHOT
+ 2.0.0.DATAMONGO-1682-SNAPSHOT
diff --git a/spring-data-mongodb-distribution/pom.xml b/spring-data-mongodb-distribution/pom.xml
index 750ed23aa8..4c1fc7ad39 100644
--- a/spring-data-mongodb-distribution/pom.xml
+++ b/spring-data-mongodb-distribution/pom.xml
@@ -13,7 +13,7 @@
org.springframework.data
spring-data-mongodb-parent
- 2.0.0.BUILD-SNAPSHOT
+ 2.0.0.DATAMONGO-1682-SNAPSHOT
../pom.xml
diff --git a/spring-data-mongodb/pom.xml b/spring-data-mongodb/pom.xml
index 5e935eb8d8..d4def9ebae 100644
--- a/spring-data-mongodb/pom.xml
+++ b/spring-data-mongodb/pom.xml
@@ -11,7 +11,7 @@
org.springframework.data
spring-data-mongodb-parent
- 2.0.0.BUILD-SNAPSHOT
+ 2.0.0.DATAMONGO-1682-SNAPSHOT
../pom.xml
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultIndexOperations.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultIndexOperations.java
index 7b46efd80c..51c0a31d05 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultIndexOperations.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultIndexOperations.java
@@ -23,8 +23,8 @@
import org.bson.Document;
import org.springframework.dao.DataAccessException;
-import org.springframework.data.mongodb.core.convert.QueryMapper;
import org.springframework.data.mongodb.MongoDbFactory;
+import org.springframework.data.mongodb.core.convert.QueryMapper;
import org.springframework.data.mongodb.core.index.IndexDefinition;
import org.springframework.data.mongodb.core.index.IndexInfo;
import org.springframework.data.mongodb.core.index.IndexOperations;
@@ -38,7 +38,7 @@
/**
* Default implementation of {@link IndexOperations}.
- *
+ *
* @author Mark Pollack
* @author Oliver Gierke
* @author Komi Innocent
@@ -56,7 +56,7 @@ public class DefaultIndexOperations implements IndexOperations {
/**
* Creates a new {@link DefaultIndexOperations}.
- *
+ *
* @param mongoDbFactory must not be {@literal null}.
* @param collectionName must not be {@literal null}.
* @param queryMapper must not be {@literal null}.
@@ -98,24 +98,22 @@ public String ensureIndex(final IndexDefinition indexDefinition) {
Document indexOptions = indexDefinition.getIndexOptions();
- if (indexOptions != null) {
-
- IndexOptions ops = IndexConverters.indexDefinitionToIndexOptionsConverter().convert(indexDefinition);
+ if (indexOptions == null) {
+ return collection.createIndex(indexDefinition.getIndexKeys());
+ }
- if (indexOptions.containsKey(PARTIAL_FILTER_EXPRESSION_KEY)) {
+ IndexOptions ops = IndexConverters.indexDefinitionToIndexOptionsConverter().convert(indexDefinition);
- Assert.isInstanceOf(Document.class, indexOptions.get(PARTIAL_FILTER_EXPRESSION_KEY));
+ if (indexOptions.containsKey(PARTIAL_FILTER_EXPRESSION_KEY)) {
- ops.partialFilterExpression( mapper.getMappedObject(
- (Document) indexOptions.get(PARTIAL_FILTER_EXPRESSION_KEY), lookupPersistentEntity(type, collectionName)));
- }
+ Assert.isInstanceOf(Document.class, indexOptions.get(PARTIAL_FILTER_EXPRESSION_KEY));
- return collection.createIndex(indexDefinition.getIndexKeys(), ops);
+ ops.partialFilterExpression(mapper.getMappedObject((Document) indexOptions.get(PARTIAL_FILTER_EXPRESSION_KEY),
+ lookupPersistentEntity(type, collectionName)));
}
- return collection.createIndex(indexDefinition.getIndexKeys());
- }
- );
+ return collection.createIndex(indexDefinition.getIndexKeys(), ops);
+ });
}
private MongoPersistentEntity> lookupPersistentEntity(Class> entityType, String collection) {
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultReactiveIndexOperations.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultReactiveIndexOperations.java
index 0ba81a33f7..431dd73c5e 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultReactiveIndexOperations.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultReactiveIndexOperations.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2016 the original author or authors.
+ * Copyright 2016-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.
@@ -15,41 +15,73 @@
*/
package org.springframework.data.mongodb.core;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.util.Collection;
+import java.util.Optional;
+
import org.bson.Document;
+import org.springframework.data.mongodb.core.convert.QueryMapper;
import org.springframework.data.mongodb.core.index.IndexDefinition;
import org.springframework.data.mongodb.core.index.IndexInfo;
-import org.springframework.data.mongodb.core.index.IndexOperations;
+import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
import org.springframework.util.Assert;
-import com.mongodb.reactivestreams.client.ListIndexesPublisher;
-
-import reactor.core.publisher.Flux;
-import reactor.core.publisher.Mono;
+import com.mongodb.client.model.IndexOptions;
/**
- * Default implementation of {@link IndexOperations}.
+ * Default implementation of {@link ReactiveIndexOperations}.
*
* @author Mark Paluch
- * @since 1.11
+ * @author Christoph Strobl
+ * @since 2.0
*/
public class DefaultReactiveIndexOperations implements ReactiveIndexOperations {
+ private static final String PARTIAL_FILTER_EXPRESSION_KEY = "partialFilterExpression";
+
private final ReactiveMongoOperations mongoOperations;
private final String collectionName;
+ private final QueryMapper queryMapper;
+ private final Optional> type;
+
+ /**
+ * Creates a new {@link DefaultReactiveIndexOperations}.
+ *
+ * @param mongoOperations must not be {@literal null}.
+ * @param collectionName must not be {@literal null}.
+ * @param queryMapper must not be {@literal null}.
+ */
+ public DefaultReactiveIndexOperations(ReactiveMongoOperations mongoOperations, String collectionName,
+ QueryMapper queryMapper) {
+ this(mongoOperations, collectionName, queryMapper, Optional.empty());
+ }
/**
* Creates a new {@link DefaultReactiveIndexOperations}.
*
* @param mongoOperations must not be {@literal null}.
* @param collectionName must not be {@literal null}.
+ * @param queryMapper must not be {@literal null}.
+ * @param type used for mapping potential partial index filter expression, must not be {@literal null}.
*/
- public DefaultReactiveIndexOperations(ReactiveMongoOperations mongoOperations, String collectionName) {
+ public DefaultReactiveIndexOperations(ReactiveMongoOperations mongoOperations, String collectionName,
+ QueryMapper queryMapper, Class> type) {
+ this(mongoOperations, collectionName, queryMapper, Optional.of(type));
+ }
+
+ private DefaultReactiveIndexOperations(ReactiveMongoOperations mongoOperations, String collectionName,
+ QueryMapper queryMapper, Optional> type) {
Assert.notNull(mongoOperations, "ReactiveMongoOperations must not be null!");
Assert.notNull(collectionName, "Collection must not be null!");
+ Assert.notNull(queryMapper, "QueryMapper must not be null!");
this.mongoOperations = mongoOperations;
this.collectionName = collectionName;
+ this.queryMapper = queryMapper;
+ this.type = type;
}
/* (non-Javadoc)
@@ -57,28 +89,48 @@ public DefaultReactiveIndexOperations(ReactiveMongoOperations mongoOperations, S
*/
public Mono ensureIndex(final IndexDefinition indexDefinition) {
- return mongoOperations.execute(collectionName, (ReactiveCollectionCallback) collection -> {
+ return mongoOperations.execute(collectionName, collection -> {
Document indexOptions = indexDefinition.getIndexOptions();
- if (indexOptions != null) {
- return collection.createIndex(indexDefinition.getIndexKeys(),
- IndexConverters.indexDefinitionToIndexOptionsConverter().convert(indexDefinition));
+ if (indexOptions == null) {
+ return collection.createIndex(indexDefinition.getIndexKeys());
}
- return collection.createIndex(indexDefinition.getIndexKeys());
+ IndexOptions ops = IndexConverters.indexDefinitionToIndexOptionsConverter().convert(indexDefinition);
+
+ if (indexOptions.containsKey(PARTIAL_FILTER_EXPRESSION_KEY)) {
+
+ Assert.isInstanceOf(Document.class, indexOptions.get(PARTIAL_FILTER_EXPRESSION_KEY));
+
+ MongoPersistentEntity> entity = type
+ .map(val -> (MongoPersistentEntity) queryMapper.getMappingContext().getRequiredPersistentEntity(val))
+ .orElseGet(() -> lookupPersistentEntity(collectionName));
+
+ ops = ops.partialFilterExpression(
+ queryMapper.getMappedObject(indexOptions.get(PARTIAL_FILTER_EXPRESSION_KEY, Document.class), entity));
+ }
+
+ return collection.createIndex(indexDefinition.getIndexKeys(), ops);
+
}).next();
}
+ private MongoPersistentEntity> lookupPersistentEntity(String collection) {
+
+ Collection extends MongoPersistentEntity>> entities = queryMapper.getMappingContext().getPersistentEntities();
+
+ return entities.stream() //
+ .filter(entity -> entity.getCollection().equals(collection)) //
+ .findFirst() //
+ .orElse(null);
+ }
+
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.ReactiveIndexOperations#dropIndex(java.lang.String)
*/
public Mono dropIndex(final String name) {
-
- return mongoOperations.execute(collectionName, collection -> {
-
- return Mono.from(collection.dropIndex(name));
- }).flatMap(success -> Mono.empty()).next();
+ return mongoOperations.execute(collectionName, collection -> collection.dropIndex(name)).then();
}
/* (non-Javadoc)
@@ -93,11 +145,7 @@ public Mono dropAllIndexes() {
*/
public Flux getIndexInfo() {
- return mongoOperations.execute(collectionName, collection -> {
-
- ListIndexesPublisher indexesPublisher = collection.listIndexes(Document.class);
-
- return Flux.from(indexesPublisher).map(IndexConverters.documentToIndexInfoConverter()::convert);
- });
+ return mongoOperations.execute(collectionName, collection -> collection.listIndexes(Document.class)) //
+ .map(IndexConverters.documentToIndexInfoConverter()::convert);
}
}
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java
index d1e34e095b..8c27f268a9 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java
@@ -548,7 +548,8 @@ public IndexOperations indexOps(String collectionName) {
}
public IndexOperations indexOps(Class> entityClass) {
- return new DefaultIndexOperations(getMongoDbFactory(), determineCollectionName(entityClass), queryMapper);
+ return new DefaultIndexOperations(getMongoDbFactory(), determineCollectionName(entityClass), queryMapper,
+ entityClass);
}
public BulkOperations bulkOps(BulkMode bulkMode, String collectionName) {
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveIndexOperations.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveIndexOperations.java
index 88c89d2c3c..c375c07971 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveIndexOperations.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveIndexOperations.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2016 the original author or authors.
+ * Copyright 2016-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.
@@ -15,8 +15,6 @@
*/
package org.springframework.data.mongodb.core;
-import java.util.List;
-
import org.springframework.data.mongodb.core.index.IndexDefinition;
import org.springframework.data.mongodb.core.index.IndexInfo;
@@ -58,5 +56,4 @@ public interface ReactiveIndexOperations {
* @return index information on the collection
*/
Flux getIndexInfo();
-
}
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java
index 9ed9ae3f01..8cdcb90b28 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java
@@ -302,14 +302,15 @@ public MongoConverter getConverter() {
* @see org.springframework.data.mongodb.core.ReactiveMongoOperations#reactiveIndexOps(java.lang.String)
*/
public ReactiveIndexOperations indexOps(String collectionName) {
- return new DefaultReactiveIndexOperations(this, collectionName);
+ return new DefaultReactiveIndexOperations(this, collectionName, this.queryMapper);
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.ReactiveMongoOperations#reactiveIndexOps(java.lang.Class)
*/
public ReactiveIndexOperations indexOps(Class> entityClass) {
- return new DefaultReactiveIndexOperations(this, determineCollectionName(entityClass));
+ return new DefaultReactiveIndexOperations(this, determineCollectionName(entityClass), this.queryMapper,
+ entityClass);
}
public String getCollectionName(Class> entityClass) {
@@ -1915,8 +1916,7 @@ String determineCollectionName(Class> entityClass) {
"No class parameter provided, entity collection can't be determined!");
}
- MongoPersistentEntity> entity = mappingContext.getRequiredPersistentEntity(entityClass);
- return entity.getCollection();
+ return mappingContext.getRequiredPersistentEntity(entityClass).getCollection();
}
private static MappingMongoConverter getDefaultMongoConverter() {
diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultReactiveIndexOperationsTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultReactiveIndexOperationsTests.java
index 1dbff6cf40..effbf4ef27 100644
--- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultReactiveIndexOperationsTests.java
+++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultReactiveIndexOperationsTests.java
@@ -16,11 +16,14 @@
package org.springframework.data.mongodb.core;
import static org.assertj.core.api.Assertions.*;
-import static org.hamcrest.core.Is.*;
import static org.junit.Assume.*;
+import static org.springframework.data.mongodb.core.index.PartialIndexFilter.*;
+import static org.springframework.data.mongodb.core.query.Criteria.*;
import reactor.test.StepVerifier;
+import java.util.function.Predicate;
+
import org.bson.Document;
import org.junit.Before;
import org.junit.Test;
@@ -31,9 +34,11 @@
import org.springframework.data.mongodb.config.AbstractReactiveMongoConfiguration;
import org.springframework.data.mongodb.core.query.Collation;
import org.springframework.data.mongodb.core.query.Collation.CaseFirst;
-import org.springframework.data.mongodb.core.DefaultIndexOperationsIntegrationTests.DefaultIndexOperationsIntegrationTestsSample;
+import org.springframework.data.mongodb.core.convert.QueryMapper;
import org.springframework.data.mongodb.core.index.Index;
import org.springframework.data.mongodb.core.index.IndexDefinition;
+import org.springframework.data.mongodb.core.index.IndexInfo;
+import org.springframework.data.mongodb.core.mapping.Field;
import org.springframework.data.util.Version;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@@ -64,6 +69,7 @@ protected String getDatabaseName() {
}
}
+ private static final Version THREE_DOT_TWO = new Version(3, 2);
private static final Version THREE_DOT_FOUR = new Version(3, 4);
private static Version mongoVersion;
@@ -79,8 +85,10 @@ public void setUp() {
String collectionName = this.template.getCollectionName(DefaultIndexOperationsIntegrationTestsSample.class);
this.collection = this.template.getMongoDatabase().getCollection(collectionName, Document.class);
- this.collection.dropIndexes();
- this.indexOps = new DefaultReactiveIndexOperations(template, collectionName);
+ this.indexOps = new DefaultReactiveIndexOperations(template, collectionName,
+ new QueryMapper(template.getConverter()));
+
+ StepVerifier.create(this.collection.dropIndexes()).expectNextCount(1).verifyComplete();
}
private void queryMongoVersionIfNecessary() {
@@ -94,7 +102,7 @@ private void queryMongoVersionIfNecessary() {
@Test // DATAMONGO-1518
public void shouldCreateIndexWithCollationCorrectly() {
- assumeThat(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_FOUR), is(true));
+ assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_FOUR));
IndexDefinition id = new Index().named("with-collation").on("xyz", Direction.ASC)
.collation(Collation.of("de_AT").caseFirst(CaseFirst.off()));
@@ -111,7 +119,7 @@ public void shouldCreateIndexWithCollationCorrectly() {
.append("normalization", false) //
.append("backwards", false);
- StepVerifier.create(indexOps.getIndexInfo().filter(val -> val.getName().equals("with-collation")))
+ StepVerifier.create(indexOps.getIndexInfo().filter(this.indexByName("with-collation"))) //
.consumeNextWith(indexInfo -> {
assertThat(indexInfo.getCollation()).isPresent();
@@ -125,4 +133,93 @@ public void shouldCreateIndexWithCollationCorrectly() {
.verifyComplete();
}
+ @Test // DATAMONGO-1682
+ public void shouldApplyPartialFilterCorrectly() {
+
+ assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_TWO));
+
+ IndexDefinition id = new Index().named("partial-with-criteria").on("k3y", Direction.ASC)
+ .partial(of(where("q-t-y").gte(10)));
+
+ indexOps.ensureIndex(id).subscribe();
+
+ StepVerifier.create(indexOps.getIndexInfo().filter(this.indexByName("partial-with-criteria"))) //
+ .consumeNextWith(indexInfo -> {
+ assertThat(indexInfo.getPartialFilterExpression()).isEqualTo("{ \"q-t-y\" : { \"$gte\" : 10 } }");
+ }) //
+ .verifyComplete();
+ }
+
+ @Test // DATAMONGO-1682
+ public void shouldApplyPartialFilterWithMappedPropertyCorrectly() {
+
+ assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_TWO));
+
+ IndexDefinition id = new Index().named("partial-with-mapped-criteria").on("k3y", Direction.ASC)
+ .partial(of(where("quantity").gte(10)));
+
+ indexOps.ensureIndex(id).subscribe();
+
+ StepVerifier.create(indexOps.getIndexInfo().filter(this.indexByName("partial-with-mapped-criteria"))) //
+ .consumeNextWith(indexInfo -> {
+ assertThat(indexInfo.getPartialFilterExpression()).isEqualTo("{ \"qty\" : { \"$gte\" : 10 } }");
+ }).verifyComplete();
+ }
+
+ @Test // DATAMONGO-1682
+ public void shouldApplyPartialDBOFilterCorrectly() {
+
+ assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_TWO));
+
+ IndexDefinition id = new Index().named("partial-with-dbo").on("k3y", Direction.ASC)
+ .partial(of(new org.bson.Document("qty", new org.bson.Document("$gte", 10))));
+
+ indexOps.ensureIndex(id).subscribe();
+
+ StepVerifier.create(indexOps.getIndexInfo().filter(this.indexByName("partial-with-dbo"))) //
+ .consumeNextWith(indexInfo -> {
+ assertThat(indexInfo.getPartialFilterExpression()).isEqualTo("{ \"qty\" : { \"$gte\" : 10 } }");
+ }) //
+ .verifyComplete();
+
+ }
+
+ @Test // DATAMONGO-1682
+ public void shouldFavorExplicitMappingHintViaClass() {
+
+ assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_TWO));
+
+ IndexDefinition id = new Index().named("partial-with-inheritance").on("k3y", Direction.ASC)
+ .partial(of(where("age").gte(10)));
+
+ indexOps = new DefaultReactiveIndexOperations(template,
+ this.template.getCollectionName(DefaultIndexOperationsIntegrationTestsSample.class),
+ new QueryMapper(template.getConverter()), MappingToSameCollection.class);
+
+ indexOps.ensureIndex(id).subscribe();
+
+ StepVerifier.create(indexOps.getIndexInfo().filter(this.indexByName("partial-with-inheritance"))) //
+ .consumeNextWith(indexInfo -> {
+ assertThat(indexInfo.getPartialFilterExpression()).isEqualTo("{ \"a_g_e\" : { \"$gte\" : 10 } }");
+ }) //
+ .verifyComplete();
+ }
+
+ Predicate indexByName(String name) {
+ return indexInfo -> indexInfo.getName().equals(name);
+ }
+
+ @org.springframework.data.mongodb.core.mapping.Document(collection = "default-index-operations-tests")
+ static class DefaultIndexOperationsIntegrationTestsSample {
+
+ @Field("qty") Integer quantity;
+ }
+
+ @org.springframework.data.mongodb.core.mapping.Document(collection = "default-index-operations-tests")
+ static class MappingToSameCollection
+ extends DefaultIndexOperationsIntegrationTests.DefaultIndexOperationsIntegrationTestsSample {
+
+ @Field("a_g_e") Integer age;
+ }
+
}