23
23
import org .springframework .data .mongodb .core .aggregation .ExposedFields .ExposedField ;
24
24
import org .springframework .data .mongodb .core .aggregation .ExposedFields .FieldReference ;
25
25
import org .springframework .data .mongodb .core .aggregation .ProjectionOperation .ProjectionOperationBuilder .FieldProjection ;
26
+ import org .springframework .data .mongodb .core .aggregation .ProjectionOperation .ProjectionOperationBuilder .OperationProjection ;
26
27
import org .springframework .util .Assert ;
27
28
28
29
import com .mongodb .BasicDBObject ;
@@ -72,7 +73,7 @@ protected ProjectionOperation and(Projection projection) {
72
73
* @return
73
74
*/
74
75
public ProjectionOperationBuilder and (String name ) {
75
- return new ProjectionOperationBuilder (name , this );
76
+ return new ProjectionOperationBuilder (name , this , null );
76
77
}
77
78
78
79
/**
@@ -125,6 +126,16 @@ protected ExposedFields getFields() {
125
126
return fields ;
126
127
}
127
128
129
+ /**
130
+ * Removes the given projectionOperation from the list of current projection operations. Needed in order to support
131
+ * aliasing.
132
+ *
133
+ * @param projectionOperation
134
+ */
135
+ private void remove (OperationProjection projectionOperation ) {
136
+ this .projections .remove (projectionOperation );
137
+ }
138
+
128
139
/*
129
140
* (non-Javadoc)
130
141
* @see org.springframework.data.mongodb.core.aggregation.AggregationOperation#toDBObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
@@ -146,25 +157,28 @@ public DBObject toDBObject(AggregationOperationContext context) {
146
157
*
147
158
* @author Oliver Gierke
148
159
*/
149
- public static class ProjectionOperationBuilder {
160
+ public static class ProjectionOperationBuilder implements AggregationOperation {
150
161
151
162
private final String name ;
152
163
private final ProjectionOperation operation ;
164
+ private final OperationProjection previousProjection ;
153
165
154
166
/**
155
167
* Creates a new {@link ProjectionOperationBuilder} for the field with the given name on top of the given
156
168
* {@link ProjectionOperation}.
157
169
*
158
170
* @param name must not be {@literal null} or empty.
159
171
* @param operation must not be {@literal null}.
172
+ * @param previousProjection the previous operation projection, may be {@literal null}.
160
173
*/
161
- public ProjectionOperationBuilder (String name , ProjectionOperation operation ) {
174
+ public ProjectionOperationBuilder (String name , ProjectionOperation operation , OperationProjection previousProjection ) {
162
175
163
176
Assert .hasText (name , "Field name must not be null or empty!" );
164
177
Assert .notNull (operation , "ProjectionOperation must not be null!" );
165
178
166
179
this .name = name ;
167
180
this .operation = operation ;
181
+ this .previousProjection = previousProjection ;
168
182
}
169
183
170
184
/**
@@ -189,14 +203,88 @@ public ProjectionOperation nested(Fields fields) {
189
203
return this .operation .and (new NestedFieldProjection (name , fields ));
190
204
}
191
205
192
- public ProjectionOperation plus (Number number ) {
206
+ /**
207
+ * Allows to specify an alias for the previous projection operation.
208
+ *
209
+ * @param string
210
+ * @return
211
+ */
212
+ public ProjectionOperation as (String alias ) {
213
+
214
+ Assert .notNull (this .previousProjection , "previousProjection must not be null!" );
215
+ this .operation .remove (this .previousProjection );
216
+ return this .operation .and (previousProjection .withAlias (alias ));
217
+ }
218
+
219
+ /**
220
+ * Generates an {@code $add} expression that adds the given number to the previously mentioned field.
221
+ *
222
+ * @param number
223
+ * @return
224
+ */
225
+ public ProjectionOperationBuilder plus (Number number ) {
226
+
193
227
Assert .notNull (number , "Number must not be null!" );
194
228
return project ("add" , number );
195
229
}
196
230
197
- public ProjectionOperation minus (Number number ) {
231
+ /**
232
+ * Generates an {@code $subtract} expression that subtracts the given number to the previously mentioned field.
233
+ *
234
+ * @param number
235
+ * @return
236
+ */
237
+ public ProjectionOperationBuilder minus (Number number ) {
238
+
198
239
Assert .notNull (number , "Number must not be null!" );
199
- return project ("substract" , number );
240
+ return project ("subtract" , number );
241
+ }
242
+
243
+ /**
244
+ * Generates an {@code $multiply} expression that multiplies the given number with the previously mentioned field.
245
+ *
246
+ * @param number
247
+ * @return
248
+ */
249
+ public ProjectionOperationBuilder multiply (Number number ) {
250
+
251
+ Assert .notNull (number , "Number must not be null!" );
252
+ return project ("multiply" , number );
253
+ }
254
+
255
+ /**
256
+ * Generates an {@code $divide} expression that divides the previously mentioned field by the given number.
257
+ *
258
+ * @param number
259
+ * @return
260
+ */
261
+ public ProjectionOperationBuilder divide (Number number ) {
262
+
263
+ Assert .notNull (number , "Number must not be null!" );
264
+ Assert .isTrue (Math .abs (number .intValue ()) != 0 , "Number must not be zero!" );
265
+ return project ("divide" , number );
266
+ }
267
+
268
+ /**
269
+ * Generates an {@code $mod} expression that divides the previously mentioned field by the given number and returns
270
+ * the remainder.
271
+ *
272
+ * @param number
273
+ * @return
274
+ */
275
+ public ProjectionOperationBuilder mod (Number number ) {
276
+
277
+ Assert .notNull (number , "Number must not be null!" );
278
+ Assert .isTrue (Math .abs (number .intValue ()) != 0 , "Number must not be zero!" );
279
+ return project ("mod" , number );
280
+ }
281
+
282
+ /* (non-Javadoc)
283
+ * @see org.springframework.data.mongodb.core.aggregation.AggregationOperation#toDBObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
284
+ */
285
+ @ Override
286
+ public DBObject toDBObject (AggregationOperationContext context ) {
287
+ return this .operation .toDBObject (context );
200
288
}
201
289
202
290
/**
@@ -206,8 +294,9 @@ public ProjectionOperation minus(Number number) {
206
294
* @param values the values to be set for the projection operation.
207
295
* @return
208
296
*/
209
- public ProjectionOperation project (String operation , Object ... values ) {
210
- return this .operation .and (new OperationProjection (name , operation , values ));
297
+ public ProjectionOperationBuilder project (String operation , Object ... values ) {
298
+ OperationProjection projectionOperation = new OperationProjection (name , operation , name , values );
299
+ return new ProjectionOperationBuilder (name , this .operation .and (projectionOperation ), projectionOperation );
211
300
}
212
301
213
302
/**
@@ -308,21 +397,27 @@ static class OperationProjection extends Projection {
308
397
private final String operation ;
309
398
private final List <Object > values ;
310
399
400
+ private final String aliasName ;
401
+
311
402
/**
312
403
* Creates a new {@link OperationProjection} for the given field.
313
404
*
314
405
* @param name the name of the field to add the operation projection for, must not be {@literal null} or empty.
406
+ * @param aliasName the name of the field alias to write that will hold the operation projection, must not be
407
+ * {@literal null} or empty.
315
408
* @param operation the actual operation key, must not be {@literal null} or empty.
316
409
* @param values the values to pass into the operation, must not be {@literal null}.
317
410
*/
318
- public OperationProjection (String name , String operation , Object ... values ) {
411
+ public OperationProjection (String name , String operation , String aliasName , Object [] values ) {
319
412
320
413
super (Fields .field (name ));
321
414
415
+ Assert .hasText (aliasName , "aliasName must not be null or empty!" );
322
416
Assert .hasText (operation , "Operation must not be null or empty!" );
323
417
Assert .notNull (values , "Values must not be null!" );
324
418
325
419
this .name = name ;
420
+ this .aliasName = aliasName ;
326
421
this .operation = operation ;
327
422
this .values = Arrays .asList (values );
328
423
}
@@ -335,21 +430,32 @@ public OperationProjection(String name, String operation, Object... values) {
335
430
public DBObject toDBObject (AggregationOperationContext context ) {
336
431
337
432
List <Object > values = buildReferences (context );
338
- DBObject inner = new BasicDBObject (operation , values . size () == 1 ? values . get ( 0 ) : values .toArray ());
433
+ DBObject inner = new BasicDBObject ("$" + operation , values .toArray ());
339
434
340
- return new BasicDBObject (name , inner );
435
+ return new BasicDBObject (this . aliasName , inner );
341
436
}
342
437
343
438
private List <Object > buildReferences (AggregationOperationContext context ) {
344
439
345
440
List <Object > result = new ArrayList <Object >(values .size ());
441
+ result .add (context .getReference (this .name ).toString ());
346
442
347
443
for (Object element : values ) {
348
444
result .add (element instanceof Field ? context .getReference ((Field ) element ).toString () : element );
349
445
}
350
446
351
447
return result ;
352
448
}
449
+
450
+ /**
451
+ * Creates a new instance of this {@link OperationProjection} with the given alias.
452
+ *
453
+ * @param aliasName the aliasName to set
454
+ * @return
455
+ */
456
+ public OperationProjection withAlias (String aliasName ) {
457
+ return new OperationProjection (name , operation , aliasName , values .toArray ());
458
+ }
353
459
}
354
460
355
461
static class NestedFieldProjection extends Projection {
@@ -398,4 +504,5 @@ public ExposedField getField() {
398
504
399
505
public abstract DBObject toDBObject (AggregationOperationContext context );
400
506
}
507
+
401
508
}
0 commit comments