From 50cb4b9bba221ad87ad49a388d94f7ebb0a01402 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Thu, 5 Mar 2015 17:40:18 +0100 Subject: [PATCH] #58 - Add sample for usage of server-side JavaScript. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use a server-side script to simulate putIfAbsent(…) as contracted by the Map interface. The operation will be atomic since MongoDB will put a lock on database until the script is finished. --- .../springdata/mongodb/customer/Customer.java | 7 +- .../mongodb/customer/CustomerRepository.java | 7 +- .../advanced/ServersideScriptTests.java | 116 ++++++++++++++++++ 3 files changed, 121 insertions(+), 9 deletions(-) create mode 100644 mongodb/example/src/test/java/example/springdata/mongodb/advanced/ServersideScriptTests.java diff --git a/mongodb/example/src/main/java/example/springdata/mongodb/customer/Customer.java b/mongodb/example/src/main/java/example/springdata/mongodb/customer/Customer.java index 0fac3e4c..60bd1748 100644 --- a/mongodb/example/src/main/java/example/springdata/mongodb/customer/Customer.java +++ b/mongodb/example/src/main/java/example/springdata/mongodb/customer/Customer.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 the original author or authors. + * Copyright 2014-2015 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. @@ -17,7 +17,6 @@ package example.springdata.mongodb.customer; import lombok.Data; -import org.bson.types.ObjectId; import org.springframework.data.mongodb.core.mapping.Document; import org.springframework.util.Assert; @@ -30,9 +29,7 @@ import org.springframework.util.Assert; @Document public class Customer { - private ObjectId id; - private String firstname, lastname; - + private String id, firstname, lastname; private Address address; /** diff --git a/mongodb/example/src/main/java/example/springdata/mongodb/customer/CustomerRepository.java b/mongodb/example/src/main/java/example/springdata/mongodb/customer/CustomerRepository.java index 1fe4ee6d..a8d3332f 100644 --- a/mongodb/example/src/main/java/example/springdata/mongodb/customer/CustomerRepository.java +++ b/mongodb/example/src/main/java/example/springdata/mongodb/customer/CustomerRepository.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 the original author or authors. + * Copyright 2014-2015 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. @@ -17,7 +17,6 @@ package example.springdata.mongodb.customer; import java.util.List; -import org.bson.types.ObjectId; import org.springframework.data.domain.Sort; import org.springframework.data.geo.Distance; import org.springframework.data.geo.GeoResults; @@ -29,7 +28,7 @@ import org.springframework.data.repository.CrudRepository; * * @author Oliver Gierke */ -public interface CustomerRepository extends CrudRepository { +public interface CustomerRepository extends CrudRepository { /** * Derived query using dynamic sort information. @@ -41,7 +40,7 @@ public interface CustomerRepository extends CrudRepository { List findByLastname(String lastname, Sort sort); /** - * Showcase for a repository query using geo-spatial functionality. + * Show case for a repository query using geo-spatial functionality. * * @param point * @param distance diff --git a/mongodb/example/src/test/java/example/springdata/mongodb/advanced/ServersideScriptTests.java b/mongodb/example/src/test/java/example/springdata/mongodb/advanced/ServersideScriptTests.java new file mode 100644 index 00000000..7e4192c0 --- /dev/null +++ b/mongodb/example/src/test/java/example/springdata/mongodb/advanced/ServersideScriptTests.java @@ -0,0 +1,116 @@ +/* + * Copyright 2015 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. + * 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. + */ +package example.springdata.mongodb.advanced; + +import static org.hamcrest.core.Is.*; +import static org.hamcrest.core.IsNull.*; +import static org.junit.Assert.*; + +import example.springdata.mongodb.customer.Customer; +import example.springdata.mongodb.util.RequiresMongoDB; + +import java.util.Map; + +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.data.mongodb.core.MongoOperations; +import org.springframework.data.mongodb.core.script.ExecutableMongoScript; +import org.springframework.data.mongodb.core.script.NamedMongoScript; +import org.springframework.data.util.Version; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import com.mongodb.BasicDBObject; +import com.mongodb.DBObject; + +/** + * @author Christoph Strobl + */ +@RunWith(SpringJUnit4ClassRunner.class) +@SpringApplicationConfiguration(classes = ApplicationConfiguration.class) +public class ServersideScriptTests { + + @ClassRule public static RequiresMongoDB mongodbAvailable = RequiresMongoDB.atLeast(new Version(2, 6)); + + @Autowired AdvancedRepository repository; + @Autowired MongoOperations operations; + + @Before + public void setUp() { + + if (!operations.collectionExists(Customer.class)) { + operations.createCollection(Customer.class); + } + + // just make sure we remove everything properly + operations.getCollection("system.js").remove(new BasicDBObject()); + repository.deleteAll(); + } + + /** + * Store and call an arbitrary JavaScript function (in this case a simple echo script) via its name. + */ + @Test + public void saveAndCallScriptViaName() { + + operations.scriptOps() + .register(new NamedMongoScript("echoScript", new ExecutableMongoScript("function(x) { return x; }"))); + + Object o = operations.scriptOps().call("echoScript", "Hello echo...!"); + assertThat(o, is((Object) "Hello echo...!")); + } + + /** + * Use a script execution to create an atomic put-if-absent operation that fulfills the contract of + * {@link Map#putIfAbsent(Object, Object)} + */ + @Test + public void complexScriptExecutionSimulatingPutIfAbsent() { + + Customer ned = new Customer("Ned", "Stark"); + ned.setId("ned-stark"); + + // #1: on first insert null has to be returned + assertThat(operations.scriptOps().execute(createExecutablePutIfAbsentScript(ned)), nullValue()); + + // #2: change the firstname and put the object again, we expect a return value. + ned.setFirstname("Eddard"); + assertThat(operations.scriptOps().execute(createExecutablePutIfAbsentScript(ned)), notNullValue()); + + // #3: make sure the entity has not been altered by #2 + assertThat(repository.findOne(ned.getId()).getFirstname(), is("Ned")); + assertThat(repository.count(), is(1L)); + } + + private ExecutableMongoScript createExecutablePutIfAbsentScript(Customer customer) { + + String collectionName = operations.getCollectionName(Customer.class); + Object id = operations.getConverter().getMappingContext().getPersistentEntity(Customer.class) + .getIdentifierAccessor(customer).getIdentifier(); + + DBObject dbo = new BasicDBObject(); + operations.getConverter().write(customer, dbo); + + String scriptString = String.format( + "object = db.%1$s.findOne('{\"_id\": \"%2$s\"}'); if (object == null) { db.%1s.insert(%3$s); return null; } else { return object; }", + collectionName, id, dbo); + + return new ExecutableMongoScript(scriptString); + } +}