From 56aed377e52694af0fc9fc03f41e4c0a921ca39d Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Thu, 4 Dec 2014 10:20:38 +0100 Subject: [PATCH 1/5] DATAMONGO-479 - Add support for calling functions. Prepare issue branch. --- pom.xml | 2 +- spring-data-mongodb-cross-store/pom.xml | 4 ++-- spring-data-mongodb-distribution/pom.xml | 2 +- spring-data-mongodb-log4j/pom.xml | 2 +- spring-data-mongodb/pom.xml | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pom.xml b/pom.xml index 93a4e6bf2a..1b2e9ec0f7 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-mongodb-parent - 1.7.0.BUILD-SNAPSHOT + 1.7.0.DATAMONGO-479-SNAPSHOT pom Spring Data MongoDB diff --git a/spring-data-mongodb-cross-store/pom.xml b/spring-data-mongodb-cross-store/pom.xml index c7610beee4..e5b1c5698a 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 - 1.7.0.BUILD-SNAPSHOT + 1.7.0.DATAMONGO-479-SNAPSHOT ../pom.xml @@ -48,7 +48,7 @@ org.springframework.data spring-data-mongodb - 1.7.0.BUILD-SNAPSHOT + 1.7.0.DATAMONGO-479-SNAPSHOT diff --git a/spring-data-mongodb-distribution/pom.xml b/spring-data-mongodb-distribution/pom.xml index 13110137b6..11cd8642a3 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 - 1.7.0.BUILD-SNAPSHOT + 1.7.0.DATAMONGO-479-SNAPSHOT ../pom.xml diff --git a/spring-data-mongodb-log4j/pom.xml b/spring-data-mongodb-log4j/pom.xml index 6ff09e4577..d2fbaba86c 100644 --- a/spring-data-mongodb-log4j/pom.xml +++ b/spring-data-mongodb-log4j/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-mongodb-parent - 1.7.0.BUILD-SNAPSHOT + 1.7.0.DATAMONGO-479-SNAPSHOT ../pom.xml diff --git a/spring-data-mongodb/pom.xml b/spring-data-mongodb/pom.xml index 7c2d818c42..58ad80e3ba 100644 --- a/spring-data-mongodb/pom.xml +++ b/spring-data-mongodb/pom.xml @@ -11,7 +11,7 @@ org.springframework.data spring-data-mongodb-parent - 1.7.0.BUILD-SNAPSHOT + 1.7.0.DATAMONGO-479-SNAPSHOT ../pom.xml From e04b2fa7e9b5c05feef64f471c165ad8721d93e9 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Thu, 4 Dec 2014 10:21:24 +0100 Subject: [PATCH 2/5] DATAMONGO-479 - Add support for calling functions. We added ScriptOperations to MongoTemplate. Those allow storage and execution of java script function directly on the MongoDB server instance. Having ScriptOperations in place builds the foundation for annotation driver support in repository layer. --- .../mongodb/core/DefaultScriptOperations.java | 152 +++++++++++++++++ .../data/mongodb/core/MongoOperations.java | 9 ++ .../data/mongodb/core/MongoTemplate.java | 9 ++ .../data/mongodb/core/ScriptOperations.java | 57 +++++++ .../core/convert/CustomConversions.java | 4 + .../mongodb/core/convert/MongoConverters.java | 57 +++++++ .../core/script/CallableMongoScript.java | 90 +++++++++++ .../core/script/ExecutableMongoScript.java | 51 ++++++ .../data/mongodb/core/script/MongoScript.java | 32 ++++ .../core/DefaultScriptOperationsTests.java | 153 ++++++++++++++++++ .../DefaultScriptOperationsUnitTests.java | 88 ++++++++++ .../CallableMongoScriptConvertsUnitTests.java | 144 +++++++++++++++++ .../script/CallableMongoScriptUnitTests.java | 67 ++++++++ .../ExecutableMongoScriptUnitTests.java | 81 ++++++++++ 14 files changed, 994 insertions(+) create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultScriptOperations.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ScriptOperations.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/script/CallableMongoScript.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/script/ExecutableMongoScript.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/script/MongoScript.java create mode 100644 spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultScriptOperationsTests.java create mode 100644 spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultScriptOperationsUnitTests.java create mode 100644 spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/CallableMongoScriptConvertsUnitTests.java create mode 100644 spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/script/CallableMongoScriptUnitTests.java create mode 100644 spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/script/ExecutableMongoScriptUnitTests.java diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultScriptOperations.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultScriptOperations.java new file mode 100644 index 0000000000..18615d3faf --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultScriptOperations.java @@ -0,0 +1,152 @@ +/* + * Copyright 2014 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 org.springframework.data.mongodb.core; + +import static java.util.UUID.*; +import static org.springframework.data.mongodb.core.query.Criteria.*; +import static org.springframework.data.mongodb.core.query.Query.*; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +import org.springframework.dao.DataAccessException; +import org.springframework.data.mongodb.core.script.CallableMongoScript; +import org.springframework.data.mongodb.core.script.MongoScript; +import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; + +import com.mongodb.DB; +import com.mongodb.MongoException; + +/** + * Default implementation of {@link ScriptOperations} capable of saving and executing simple {@link MongoScript}. + * + * @author Christoph Strobl + * @since 1.7 + */ +public class DefaultScriptOperations implements ScriptOperations { + + private static final String SCRIPT_COLLECTION_NAME = "system.js"; + private static final String SCRIPT_NAME_PREFIX = "func_"; + private final MongoOperations mongoOperations; + + /** + * Creates new {@link DefaultScriptOperations} using given {@link MongoOperations}. + * + * @param mongoOperations must not be {@literal null}. + */ + public DefaultScriptOperations(MongoOperations mongoOperations) { + + Assert.notNull(mongoOperations, "MongoOperations must not be null!"); + this.mongoOperations = mongoOperations; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.ScriptOperations#save(org.springframework.data.mongodb.core.script.MongoScript) + */ + @Override + public CallableMongoScript save(MongoScript script) { + + Assert.notNull(script, "Script must not be null!"); + CallableMongoScript callableScript = null; + + if (script instanceof CallableMongoScript) { + callableScript = (CallableMongoScript) script; + } else { + callableScript = new CallableMongoScript(generateScriptName(), script); + } + + mongoOperations.save(callableScript, SCRIPT_COLLECTION_NAME); + return callableScript; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.ScriptOperations#execute(org.springframework.data.mongodb.core.script.MongoScript, java.lang.Object[]) + */ + @Override + public Object execute(final MongoScript script, final Object... args) { + + Assert.notNull(script, "Script must not be null!"); + return mongoOperations.execute(new DbCallback() { + + @Override + public Object doInDB(DB db) throws MongoException, DataAccessException { + + if (script instanceof CallableMongoScript) { + + String evalString = ((CallableMongoScript) script).getName() + "(" + convertAndJoinScriptArgs(args) + ")"; + return db.eval(evalString); + } + + Assert.notNull(script.getCode(), "Script.code must not be null!"); + return db.eval(script.getCode().toString(), convertScriptArgs(args)); + } + }); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.ScriptOperations#load(java.io.Serializable) + */ + @Override + public CallableMongoScript load(Serializable name) { + + Assert.notNull(name, "Name must not be null!"); + return mongoOperations.findOne(query(where("name").is(name)), CallableMongoScript.class, SCRIPT_COLLECTION_NAME); + } + + /** + * Generate a valid name for the script. MongoDB requires a script id of type String. Calling scripts having ObjectId + * as id fails. Therefore we create a random UUID without {@code -} (as this won't work) an prefix the result with + * {@link #SCRIPT_NAME_PREFIX}. + * + * @return + */ + private String generateScriptName() { + return SCRIPT_NAME_PREFIX + randomUUID().toString().replaceAll("-", ""); + } + + private Object[] convertScriptArgs(Object... args) { + + if (ObjectUtils.isEmpty(args)) { + return args; + } + + List convertedValues = new ArrayList(args.length); + for (Object arg : args) { + if (arg instanceof String) { + convertedValues.add("'" + arg + "'"); + } else { + convertedValues.add(this.mongoOperations.getConverter().convertToMongoType(arg)); + } + } + return convertedValues.toArray(); + } + + private String convertAndJoinScriptArgs(Object... args) { + + if (ObjectUtils.isEmpty(args)) { + return ""; + } + + return StringUtils.arrayToCommaDelimitedString(convertScriptArgs(args)); + } + +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoOperations.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoOperations.java index 6e3261db4f..f35b8698fa 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoOperations.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoOperations.java @@ -268,6 +268,14 @@ public interface MongoOperations { */ IndexOperations indexOps(Class entityClass); + /** + * Returns the {@link ScriptOperations} that can be performed on {@link com.mongodb.DB} level. + * + * @return + * @since 1.7 + */ + ScriptOperations scriptOps(); + /** * Query for a list of objects of type T from the collection used by the entity class. *

@@ -978,4 +986,5 @@ T findAndModify(Query query, Update update, FindAndModifyOptions options, Cl * @return */ MongoConverter getConverter(); + } 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 04c140ace9..1238ff2729 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 @@ -513,6 +513,15 @@ public IndexOperations indexOps(Class entityClass) { return new DefaultIndexOperations(this, determineCollectionName(entityClass)); } + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.MongoOperations#scriptOps() + */ + @Override + public ScriptOperations scriptOps() { + return new DefaultScriptOperations(this); + } + // Find methods that take a Query to express the query and that return a single object. public T findOne(Query query, Class entityClass) { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ScriptOperations.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ScriptOperations.java new file mode 100644 index 0000000000..a980237b73 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ScriptOperations.java @@ -0,0 +1,57 @@ +/* + * Copyright 2014 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 org.springframework.data.mongodb.core; + +import java.io.Serializable; + +import org.springframework.data.mongodb.core.script.CallableMongoScript; +import org.springframework.data.mongodb.core.script.MongoScript; + +/** + * Script operations on {@link com.mongodb.DB} level. + * + * @author Christoph Strobl + * @since 1.7 + */ +public interface ScriptOperations { + + /** + * Saves given {@literal script} to currently used {@link com.mongodb.DB}. + * + * @param script must not be {@literal null}. + * @return + */ + CallableMongoScript save(MongoScript script); + + /** + * Executes the {@literal script} by either calling it via its {@literal _id} or directly sending it. + * + * @param script must not be {@literal null}. + * @param args arguments to pass on for script execution. + * @return the script evaluation result. + * @throws org.springframework.dao.DataAccessException + */ + Object execute(MongoScript script, Object... args); + + /** + * Retrieves the {@link CallableMongoScript} by its {@literal id}. + * + * @param name + * @return {@literal null} if not found. + */ + CallableMongoScript load(Serializable name); + +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/CustomConversions.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/CustomConversions.java index cd8c8866db..565bf76d0b 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/CustomConversions.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/CustomConversions.java @@ -44,6 +44,8 @@ import org.springframework.data.mapping.model.SimpleTypeHolder; import org.springframework.data.mongodb.core.convert.MongoConverters.BigDecimalToStringConverter; import org.springframework.data.mongodb.core.convert.MongoConverters.BigIntegerToStringConverter; +import org.springframework.data.mongodb.core.convert.MongoConverters.CallableMongoScriptToDBObjectConverter; +import org.springframework.data.mongodb.core.convert.MongoConverters.DBObjectToCallableMongoScriptCoverter; import org.springframework.data.mongodb.core.convert.MongoConverters.DBObjectToStringConverter; import org.springframework.data.mongodb.core.convert.MongoConverters.StringToBigDecimalConverter; import org.springframework.data.mongodb.core.convert.MongoConverters.StringToBigIntegerConverter; @@ -118,6 +120,8 @@ public CustomConversions(List converters) { toRegister.add(StringToURLConverter.INSTANCE); toRegister.add(DBObjectToStringConverter.INSTANCE); toRegister.add(TermToStringConverter.INSTANCE); + toRegister.add(CallableMongoScriptToDBObjectConverter.INSTANCE); + toRegister.add(DBObjectToCallableMongoScriptCoverter.INSTANCE); toRegister.addAll(JodaTimeConverters.getConvertersToRegister()); toRegister.addAll(GeoConverters.getConvertersToRegister()); diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoConverters.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoConverters.java index afabef2558..6618759181 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoConverters.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoConverters.java @@ -27,8 +27,11 @@ import org.springframework.data.convert.ReadingConverter; import org.springframework.data.convert.WritingConverter; import org.springframework.data.mongodb.core.query.Term; +import org.springframework.data.mongodb.core.script.CallableMongoScript; import org.springframework.util.StringUtils; +import com.mongodb.BasicDBObject; +import com.mongodb.BasicDBObjectBuilder; import com.mongodb.DBObject; /** @@ -178,4 +181,58 @@ public String convert(Term source) { return source == null ? null : source.getFormatted(); } } + + /** + * @author Christoph Strobl + * @since 1.7 + */ + @ReadingConverter + public static enum DBObjectToCallableMongoScriptCoverter implements Converter { + + INSTANCE; + + @Override + public CallableMongoScript convert(DBObject source) { + + if (source == null) { + return null; + } + + String id = source.get("_id").toString(); + Object rawValue = source.get("value"); + + if (rawValue != null) { + return new CallableMongoScript(id, rawValue.toString()); + } + + return new CallableMongoScript(id); + } + } + + /** + * @author Christoph Strobl + * @since 1.7 + */ + @WritingConverter + public static enum CallableMongoScriptToDBObjectConverter implements Converter { + + INSTANCE; + + @Override + public DBObject convert(CallableMongoScript source) { + + if (source == null) { + return new BasicDBObject(); + } + + BasicDBObjectBuilder builder = new BasicDBObjectBuilder(); + + builder.append("_id", source.getName()); + if (source.getCode() != null) { + builder.append("value", source.getCode()); + } + + return builder.get(); + } + } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/script/CallableMongoScript.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/script/CallableMongoScript.java new file mode 100644 index 0000000000..589bf1e315 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/script/CallableMongoScript.java @@ -0,0 +1,90 @@ +/* + * Copyright 2014 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 org.springframework.data.mongodb.core.script; + +import org.bson.types.Code; +import org.springframework.data.annotation.Id; +import org.springframework.util.Assert; + +/** + * A {@link MongoScript} implementation that allows calling the function by its {@literal id} once it has been saved to + * the {@link com.mongodb.DB} instance. + * + * @author Christoph Strobl + * @since 1.7 + */ +public class CallableMongoScript implements MongoScript { + + private final @Id String name; + private final MongoScript script; + + /** + * Creates new {@link CallableMongoScript} that uses the given {@literal id} to call the function on the server. + * + * @param name must not be {@literal null} or {@literal empty}. + */ + public CallableMongoScript(String name) { + this(name, (MongoScript) null); + } + + /** + * Creates new {@link CallableMongoScript} that can be saved to the {@link com.mongodb.DB} instance. + * + * @param name must not be {@literal null} or {@literal empty}. + * @param rawScript the {@link String} representation of the javascript function. Must not be {@literal null} or + * {@literal empty}. + */ + public CallableMongoScript(String name, String rawScript) { + this(name, new ExecutableMongoScript(rawScript)); + } + + /** + * Creates new {@link CallableMongoScript}. + * + * @param name must not be {@literal null} or {@literal empty}. + * @param script can be {@literal null}. + */ + public CallableMongoScript(String name, MongoScript script) { + + Assert.hasText(name, "Name must not be null or empty!"); + this.name = name; + this.script = script; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.script.MongoScript#getCode() + */ + @Override + public Code getCode() { + + if (script == null) { + return null; + } + + return script.getCode(); + } + + /** + * Get the id of the callable script. + * + * @return + */ + public String getName() { + return name; + } + +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/script/ExecutableMongoScript.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/script/ExecutableMongoScript.java new file mode 100644 index 0000000000..8a4fd4e53c --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/script/ExecutableMongoScript.java @@ -0,0 +1,51 @@ +/* + * Copyright 2014 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 org.springframework.data.mongodb.core.script; + +import org.bson.types.Code; +import org.springframework.util.Assert; + +/** + * {@link MongoScript} implementation that can be saved or directly executed. + * + * @author Christoph Strobl + * @since 1.7 + */ +public class ExecutableMongoScript implements MongoScript { + + private final String rawScript; + + /** + * Creates new {@link ExecutableMongoScript}. + * + * @param rawScript must not be {@literal null} or {@literal empty}. + */ + public ExecutableMongoScript(String rawScript) { + + Assert.hasText(rawScript, "RawScript must not be null or empty!"); + this.rawScript = rawScript; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.script.MongoScript#getCode() + */ + @Override + public Code getCode() { + return new Code(this.rawScript); + } + +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/script/MongoScript.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/script/MongoScript.java new file mode 100644 index 0000000000..8eb30ce255 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/script/MongoScript.java @@ -0,0 +1,32 @@ +/* + * Copyright 2014 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 org.springframework.data.mongodb.core.script; + +import org.bson.types.Code; + +/** + * @author Christoph Strobl + * @since 1.7 + */ +public interface MongoScript { + + /** + * Get the {@link Code} representation. + * + * @return {@literal null} when no code available. + */ + Code getCode(); +} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultScriptOperationsTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultScriptOperationsTests.java new file mode 100644 index 0000000000..173dd4dd84 --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultScriptOperationsTests.java @@ -0,0 +1,153 @@ +/* + * Copyright 2014 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 org.springframework.data.mongodb.core; + +import static org.hamcrest.core.Is.*; +import static org.junit.Assert.*; +import static org.junit.Assume.*; +import static org.springframework.data.mongodb.core.query.Criteria.*; +import static org.springframework.data.mongodb.core.query.Query.*; + +import org.hamcrest.core.Is; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.dao.DataAccessException; +import org.springframework.data.mongodb.config.AbstractMongoConfiguration; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.data.mongodb.core.script.CallableMongoScript; +import org.springframework.data.mongodb.core.script.ExecutableMongoScript; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import com.mongodb.BasicDBObject; +import com.mongodb.Mongo; +import com.mongodb.MongoClient; + +/** + * @author Christoph Strobl + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration +public class DefaultScriptOperationsTests { + + @Configuration + static class Config extends AbstractMongoConfiguration { + + @Override + protected String getDatabaseName() { + return "script-tests"; + } + + @Override + public Mongo mongo() throws Exception { + return new MongoClient(); + } + + } + + static final String SCRIPT_NAME = "echo"; + static final String JS_FUNCTION = "function(x) { return x; }"; + static final ExecutableMongoScript EXECUTABLE_SCRIPT = new ExecutableMongoScript(JS_FUNCTION); + static final CallableMongoScript CALLABLE_SCRIPT = new CallableMongoScript(SCRIPT_NAME, JS_FUNCTION); + + @Autowired MongoTemplate template; + DefaultScriptOperations scriptOps; + + @Before + public void setUp() { + + template.getCollection("system.js").remove(new BasicDBObject()); + this.scriptOps = new DefaultScriptOperations(template); + } + + /** + * @see DATAMONGO-479 + */ + @Test + public void executeShouldDirectlyRunExecutableMongoScript() { + + Object result = scriptOps.execute(EXECUTABLE_SCRIPT, 10); + + assertThat(result, Is. is(10D)); + } + + /** + * @see DATAMONGO-479 + */ + @Test(expected = DataAccessException.class) + public void executeThowsDataAccessExceptionWhenRunningCallableScriptThatHasNotBeenSavedBefore() { + scriptOps.execute(CALLABLE_SCRIPT, 10); + } + + /** + * @see DATAMONGO-479 + */ + @Test + public void saveShouldStoreCallableScriptCorrectly() { + + Query query = query(where("_id").is(SCRIPT_NAME)); + assumeThat(template.exists(query, "system.js"), is(false)); + + scriptOps.save(CALLABLE_SCRIPT); + + assumeThat(template.exists(query, "system.js"), is(true)); + } + + /** + * @see DATAMONGO-479 + */ + @Test + public void saveShouldStoreExecutableScriptCorrectly() { + + CallableMongoScript script = scriptOps.save(EXECUTABLE_SCRIPT); + + Query query = query(where("_id").is(script.getName())); + assumeThat(template.exists(query, "system.js"), is(true)); + } + + /** + * @see DATAMONGO-479 + */ + @Test + public void executeShouldRunCallableScriptThatHasBeenSavedBefore() { + + scriptOps.save(CALLABLE_SCRIPT); + + Query query = query(where("_id").is(SCRIPT_NAME)); + assumeThat(template.exists(query, "system.js"), is(true)); + + Object result = scriptOps.execute(CALLABLE_SCRIPT, 10); + + assertThat(result, Is. is(10D)); + } + + /** + * @see DATAMONGO-479 + */ + @Test + public void loadShouldFindScriptByItsName() { + + scriptOps.save(CALLABLE_SCRIPT); + + CallableMongoScript loaded = scriptOps.load(SCRIPT_NAME); + + assertThat(loaded.getName(), is(CALLABLE_SCRIPT.getName())); + assertThat(loaded.getCode(), is(CALLABLE_SCRIPT.getCode())); + } +} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultScriptOperationsUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultScriptOperationsUnitTests.java new file mode 100644 index 0000000000..f08604457b --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultScriptOperationsUnitTests.java @@ -0,0 +1,88 @@ +/* + * Copyright 2014 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 org.springframework.data.mongodb.core; + +import static org.hamcrest.core.IsNull.*; +import static org.mockito.Matchers.*; +import static org.mockito.Mockito.*; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import org.springframework.data.mongodb.core.script.CallableMongoScript; +import org.springframework.data.mongodb.core.script.ExecutableMongoScript; + +/** + * @author Christoph Strobl + * @since 1.7 + */ +@RunWith(MockitoJUnitRunner.class) +public class DefaultScriptOperationsUnitTests { + + DefaultScriptOperations scriptOps; + @Mock MongoOperations mongoOperationsMock; + + @Before + public void setUp() { + this.scriptOps = new DefaultScriptOperations(mongoOperationsMock); + } + + /** + * @see DATAMONGO-479 + */ + @Test(expected = IllegalArgumentException.class) + public void saveShouldThrowExceptionWhenCalledWithNullValue() { + scriptOps.save(null); + } + + /** + * @see DATAMONGO-479 + */ + @Test + public void saveShouldUseCorrectCollectionName() { + + scriptOps.save(new CallableMongoScript("foo", "function...")); + + verify(mongoOperationsMock, times(1)).save(any(CallableMongoScript.class), eq("system.js")); + } + + /** + * @see DATAMONGO-479 + */ + @Test + public void saveShouldGenerateScriptNameForExecutableMongoScripts() { + + scriptOps.save(new ExecutableMongoScript("function...")); + + ArgumentCaptor captor = ArgumentCaptor.forClass(CallableMongoScript.class); + + verify(mongoOperationsMock, times(1)).save(captor.capture(), eq("system.js")); + Assert.assertThat(captor.getValue().getName(), notNullValue()); + } + + /** + * @see DATAMONGO-479 + */ + @Test(expected = IllegalArgumentException.class) + public void executeShouldThrowExceptionWhenScriptIsNull() { + scriptOps.execute(null); + } + +} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/CallableMongoScriptConvertsUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/CallableMongoScriptConvertsUnitTests.java new file mode 100644 index 0000000000..f398cba317 --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/CallableMongoScriptConvertsUnitTests.java @@ -0,0 +1,144 @@ +/* + * Copyright 2014 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 org.springframework.data.mongodb.core.convert; + +import static org.hamcrest.core.Is.*; +import static org.hamcrest.core.IsEqual.*; +import static org.hamcrest.core.IsInstanceOf.*; +import static org.hamcrest.core.IsNull.*; +import static org.junit.Assert.*; + +import org.bson.types.Code; +import org.hamcrest.core.IsEqual; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Suite; +import org.junit.runners.Suite.SuiteClasses; +import org.springframework.data.mongodb.core.convert.CallableMongoScriptConvertsUnitTests.CallableMongoScriptToDboConverterUnitTests; +import org.springframework.data.mongodb.core.convert.MongoConverters.CallableMongoScriptToDBObjectConverter; +import org.springframework.data.mongodb.core.convert.MongoConverters.DBObjectToCallableMongoScriptCoverter; +import org.springframework.data.mongodb.core.script.CallableMongoScript; + +import com.mongodb.BasicDBObject; +import com.mongodb.BasicDBObjectBuilder; +import com.mongodb.DBObject; + +/** + * @author Christoph Strobl + */ +@RunWith(Suite.class) +@SuiteClasses({ CallableMongoScriptToDboConverterUnitTests.class }) +public class CallableMongoScriptConvertsUnitTests { + + static final String FUNCTION_NAME = "echo"; + static final String JS_FUNCTION = "function(x) { return x; }"; + static final CallableMongoScript ECHO_SCRIPT = new CallableMongoScript(FUNCTION_NAME, JS_FUNCTION); + static final DBObject FUNCTION = new BasicDBObjectBuilder().add("_id", FUNCTION_NAME) + .add("value", new Code(JS_FUNCTION)).get(); + + /** + * @author Christoph Strobl + */ + public static class CallableMongoScriptToDboConverterUnitTests { + + CallableMongoScriptToDBObjectConverter converter = CallableMongoScriptToDBObjectConverter.INSTANCE; + + /** + * @see DATAMONGO-479 + */ + @Test + public void convertShouldReturnEmptyDboWhenScriptIsNull() { + assertThat(converter.convert(null), IsEqual. equalTo(new BasicDBObject())); + } + + /** + * @see DATAMONGO-479 + */ + @Test + public void convertShouldConvertScriptNameCorreclty() { + + DBObject dbo = converter.convert(ECHO_SCRIPT); + + Object id = dbo.get("_id"); + assertThat(id, instanceOf(String.class)); + assertThat(id, IsEqual. equalTo(FUNCTION_NAME)); + } + + /** + * @see DATAMONGO-479 + */ + @Test + public void convertShouldConvertScriptCodeCorreclty() { + + DBObject dbo = converter.convert(ECHO_SCRIPT); + + Object code = dbo.get("value"); + assertThat(code, instanceOf(Code.class)); + assertThat(code.toString(), equalTo(JS_FUNCTION)); + } + + /** + * @see DATAMONGO-479 + */ + @Test + public void convertShouldNotAddValueWhenCodeIsNull() { + + DBObject dbo = converter.convert(new CallableMongoScript("named")); + + assertThat(dbo.containsField("value"), is(false)); + } + } + + /** + * @author Christoph Strobl + */ + public static class DboToCallableMongoScriptConverterUnitTests { + + DBObjectToCallableMongoScriptCoverter converter = DBObjectToCallableMongoScriptCoverter.INSTANCE; + + /** + * @see DATAMONGO-479 + */ + @Test + public void convertShouldReturnNullIfSourceIsNull() { + assertThat(converter.convert(null), nullValue()); + } + + /** + * @see DATAMONGO-479 + */ + @Test + public void convertShouldConvertIdCorreclty() { + + CallableMongoScript script = converter.convert(FUNCTION); + + assertThat(script.getName(), equalTo(FUNCTION_NAME)); + } + + /** + * @see DATAMONGO-479 + */ + @Test + public void convertShouldConvertScriptValueCorreclty() { + + CallableMongoScript script = converter.convert(FUNCTION); + + assertThat(script.getCode(), notNullValue()); + assertThat(script.getCode().toString(), equalTo(JS_FUNCTION)); + } + } + +} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/script/CallableMongoScriptUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/script/CallableMongoScriptUnitTests.java new file mode 100644 index 0000000000..27ae5ab5c9 --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/script/CallableMongoScriptUnitTests.java @@ -0,0 +1,67 @@ +/* + * Copyright 2014 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 org.springframework.data.mongodb.core.script; + +import static org.hamcrest.core.IsEqual.*; +import static org.hamcrest.core.IsNull.*; +import static org.junit.Assert.*; + +import org.junit.Test; + +/** + * @author Christoph Strobl + */ +public class CallableMongoScriptUnitTests { + + /** + * @see DATAMONGO-479 + */ + @Test(expected = IllegalArgumentException.class) + public void shouldThrowExceptionWhenScriptNameIsNull() { + new CallableMongoScript(null); + } + + /** + * @see DATAMONGO-479 + */ + @Test(expected = IllegalArgumentException.class) + public void shouldThrowExceptionWhenScriptNameIsEmptyString() { + new CallableMongoScript(""); + } + + /** + * @see DATAMONGO-479 + */ + @Test(expected = IllegalArgumentException.class) + public void shouldThrowExceptionWhenRawScriptIsEmptyString() { + new CallableMongoScript("foo", ""); + } + + /** + * @see DATAMONGO-479 + */ + @Test + public void getCodeShouldReturnCodeRepresentationOfRawScript() { + + String jsFunction = "function(x) { return x; }"; + + CallableMongoScript script = new CallableMongoScript("echo", jsFunction); + + assertThat(script.getCode(), notNullValue()); + assertThat(script.getCode().toString(), equalTo(jsFunction)); + } + +} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/script/ExecutableMongoScriptUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/script/ExecutableMongoScriptUnitTests.java new file mode 100644 index 0000000000..a8fda1fad1 --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/script/ExecutableMongoScriptUnitTests.java @@ -0,0 +1,81 @@ +/* + * Copyright 2014 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 org.springframework.data.mongodb.core.script; + +import static org.hamcrest.core.IsEqual.*; +import static org.hamcrest.core.IsNull.*; +import static org.junit.Assert.*; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.springframework.util.ObjectUtils; + +/** + * @author Christoph Strobl + */ +public class ExecutableMongoScriptUnitTests { + + public @Rule ExpectedException expectedException = ExpectedException.none(); + + /** + * @see DATAMONGO-479 + */ + @Test + public void constructorShouldThrowExceptionWhenRawScriptIsNull() { + + expectException(IllegalArgumentException.class, "must not be", "null"); + + new ExecutableMongoScript(null); + } + + /** + * @see DATAMONGO-479 + */ + @Test + public void constructorShouldThrowExceptionWhenRawScriptIsEmpty() { + + expectException(IllegalArgumentException.class, "must not be", "empty"); + + new ExecutableMongoScript(""); + } + + /** + * @see DATAMONGO-479 + */ + @Test + public void getCodeShouldReturnCodeRepresentationOfRawScript() { + + String jsFunction = "function(x) { return x; }"; + + ExecutableMongoScript script = new ExecutableMongoScript(jsFunction); + + assertThat(script.getCode(), notNullValue()); + assertThat(script.getCode().toString(), equalTo(jsFunction)); + } + + private void expectException(Class type, String... messageFragments) { + + expectedException.expect(IllegalArgumentException.class); + + if (!ObjectUtils.isEmpty(messageFragments)) { + for (String fragment : messageFragments) { + expectedException.expectMessage(fragment); + } + } + } + +} From e30aef95390b18569b9cec0e7b39365cc3bce03e Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Wed, 10 Dec 2014 16:01:07 +0100 Subject: [PATCH 3/5] DATAMONGO-479 - Add support for calling functions. Fix broken tests by avoiding initialization of MongoRepositories on package as this would trigger index creation which is likely to fail on older server versions. --- .../core/DefaultScriptOperationsTests.java | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultScriptOperationsTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultScriptOperationsTests.java index 173dd4dd84..307caeccd5 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultScriptOperationsTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultScriptOperationsTests.java @@ -26,9 +26,9 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.dao.DataAccessException; -import org.springframework.data.mongodb.config.AbstractMongoConfiguration; import org.springframework.data.mongodb.core.query.Query; import org.springframework.data.mongodb.core.script.CallableMongoScript; import org.springframework.data.mongodb.core.script.ExecutableMongoScript; @@ -47,18 +47,20 @@ public class DefaultScriptOperationsTests { @Configuration - static class Config extends AbstractMongoConfiguration { + static class Config { - @Override - protected String getDatabaseName() { - return "script-tests"; - } + private static final String DB_NAME = "script-tests"; - @Override + @Bean public Mongo mongo() throws Exception { return new MongoClient(); } + @Bean + public MongoTemplate template() throws Exception { + return new MongoTemplate(mongo(), DB_NAME); + } + } static final String SCRIPT_NAME = "echo"; From e132f8cd9fa9cd2edde18f4127ac189192aba863 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Wed, 7 Jan 2015 15:40:14 +0100 Subject: [PATCH 4/5] DATAMONGO-479 - Add support for calling functions. Use more explicit wording and add methods for checking existence of functions via name as well as listing names of available scripts. Updated javaDoc and reference documentation. --- .../mongodb/core/DefaultScriptOperations.java | 92 ++++++++++++++----- .../data/mongodb/core/ScriptOperations.java | 43 ++++++--- .../mongodb/core/convert/MongoConverters.java | 9 +- .../core/script/CallableMongoScript.java | 28 ++---- .../core/script/ExecutableMongoScript.java | 19 ++-- ...oScript.java => ServerSideJavaScript.java} | 8 +- .../core/DefaultScriptOperationsTests.java | 75 ++++++++++++--- .../DefaultScriptOperationsUnitTests.java | 38 +++++++- .../CallableMongoScriptConvertsUnitTests.java | 12 --- .../script/CallableMongoScriptUnitTests.java | 8 +- src/main/asciidoc/reference/mongodb.adoc | 19 ++++ 11 files changed, 241 insertions(+), 110 deletions(-) rename spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/script/{MongoScript.java => ServerSideJavaScript.java} (86%) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultScriptOperations.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultScriptOperations.java index 18615d3faf..44bf3a70c6 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultScriptOperations.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultScriptOperations.java @@ -19,14 +19,18 @@ import static org.springframework.data.mongodb.core.query.Criteria.*; import static org.springframework.data.mongodb.core.query.Query.*; -import java.io.Serializable; import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; import java.util.List; +import java.util.Set; +import org.bson.types.ObjectId; import org.springframework.dao.DataAccessException; import org.springframework.data.mongodb.core.script.CallableMongoScript; -import org.springframework.data.mongodb.core.script.MongoScript; +import org.springframework.data.mongodb.core.script.ServerSideJavaScript; import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; @@ -34,7 +38,7 @@ import com.mongodb.MongoException; /** - * Default implementation of {@link ScriptOperations} capable of saving and executing simple {@link MongoScript}. + * Default implementation of {@link ScriptOperations} capable of saving and executing {@link ServerSideJavaScript}. * * @author Christoph Strobl * @since 1.7 @@ -53,6 +57,7 @@ public class DefaultScriptOperations implements ScriptOperations { public DefaultScriptOperations(MongoOperations mongoOperations) { Assert.notNull(mongoOperations, "MongoOperations must not be null!"); + this.mongoOperations = mongoOperations; } @@ -61,17 +66,12 @@ public DefaultScriptOperations(MongoOperations mongoOperations) { * @see org.springframework.data.mongodb.core.ScriptOperations#save(org.springframework.data.mongodb.core.script.MongoScript) */ @Override - public CallableMongoScript save(MongoScript script) { + public CallableMongoScript register(ServerSideJavaScript script) { Assert.notNull(script, "Script must not be null!"); - CallableMongoScript callableScript = null; - - if (script instanceof CallableMongoScript) { - callableScript = (CallableMongoScript) script; - } else { - callableScript = new CallableMongoScript(generateScriptName(), script); - } + CallableMongoScript callableScript = (script instanceof CallableMongoScript) ? (CallableMongoScript) script + : new CallableMongoScript(generateScriptName(), script); mongoOperations.save(callableScript, SCRIPT_COLLECTION_NAME); return callableScript; } @@ -81,41 +81,83 @@ public CallableMongoScript save(MongoScript script) { * @see org.springframework.data.mongodb.core.ScriptOperations#execute(org.springframework.data.mongodb.core.script.MongoScript, java.lang.Object[]) */ @Override - public Object execute(final MongoScript script, final Object... args) { + public Object execute(final ServerSideJavaScript script, final Object... args) { Assert.notNull(script, "Script must not be null!"); + + if (script instanceof CallableMongoScript) { + return call(((CallableMongoScript) script).getName(), args); + } + return mongoOperations.execute(new DbCallback() { @Override public Object doInDB(DB db) throws MongoException, DataAccessException { - if (script instanceof CallableMongoScript) { + Assert.notNull(script.getCode(), "Script.code must not be null!"); + + return db.eval(script.getCode(), convertScriptArgs(args)); + } + }); + } - String evalString = ((CallableMongoScript) script).getName() + "(" + convertAndJoinScriptArgs(args) + ")"; - return db.eval(evalString); - } + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.ScriptOperations#call(java.lang.String, java.lang.Object[]) + */ + @Override + public Object call(final String scriptName, final Object... args) { - Assert.notNull(script.getCode(), "Script.code must not be null!"); - return db.eval(script.getCode().toString(), convertScriptArgs(args)); + Assert.hasText(scriptName, "ScriptName must not be null or empty!"); + + return mongoOperations.execute(new DbCallback() { + + @Override + public Object doInDB(DB db) throws MongoException, DataAccessException { + + String evalString = scriptName + "(" + convertAndJoinScriptArgs(args) + ")"; + return db.eval(evalString); } }); } /* * (non-Javadoc) - * @see org.springframework.data.mongodb.core.ScriptOperations#load(java.io.Serializable) + * @see org.springframework.data.mongodb.core.ScriptOperations#exists(java.lang.String) + */ + @Override + public Boolean exists(String scriptName) { + + Assert.hasText(scriptName, "ScriptName must not be null or empty!"); + + return mongoOperations.exists(query(where("name").is(scriptName)), CallableMongoScript.class, + SCRIPT_COLLECTION_NAME); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.ScriptOperations#scriptNames() */ @Override - public CallableMongoScript load(Serializable name) { + public Set scriptNames() { - Assert.notNull(name, "Name must not be null!"); - return mongoOperations.findOne(query(where("name").is(name)), CallableMongoScript.class, SCRIPT_COLLECTION_NAME); + List scripts = (mongoOperations.findAll(CallableMongoScript.class, SCRIPT_COLLECTION_NAME)); + + if (CollectionUtils.isEmpty(scripts)) { + return Collections.emptySet(); + } + + Set scriptNames = new HashSet(); + for (CallableMongoScript script : scripts) { + scriptNames.add(script.getName()); + } + return scriptNames; } /** - * Generate a valid name for the script. MongoDB requires a script id of type String. Calling scripts having ObjectId - * as id fails. Therefore we create a random UUID without {@code -} (as this won't work) an prefix the result with - * {@link #SCRIPT_NAME_PREFIX}. + * Generate a valid name for the {@literal JavaScript}. MongoDB requires an id of type String for scripts. Calling + * scripts having {@link ObjectId} as id fails. Therefore we create a random UUID without {@code -} (as this won't + * work) an prefix the result with {@link #SCRIPT_NAME_PREFIX}. * * @return */ diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ScriptOperations.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ScriptOperations.java index a980237b73..c4cd0c5822 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ScriptOperations.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ScriptOperations.java @@ -15,13 +15,16 @@ */ package org.springframework.data.mongodb.core; -import java.io.Serializable; +import java.util.Set; import org.springframework.data.mongodb.core.script.CallableMongoScript; -import org.springframework.data.mongodb.core.script.MongoScript; +import org.springframework.data.mongodb.core.script.ServerSideJavaScript; + +import com.mongodb.DB; /** - * Script operations on {@link com.mongodb.DB} level. + * Script operations on {@link com.mongodb.DB} level. Allows interaction with server side {@literal JavaScript} + * functions. * * @author Christoph Strobl * @since 1.7 @@ -29,29 +32,45 @@ public interface ScriptOperations { /** - * Saves given {@literal script} to currently used {@link com.mongodb.DB}. + * Store given {@literal script} to {@link com.mongodb.DB} so it can be called via its name. * * @param script must not be {@literal null}. - * @return + * @return {@link CallableMongoScript} with name under which the {@literal JavaScript} function can be called. */ - CallableMongoScript save(MongoScript script); + CallableMongoScript register(ServerSideJavaScript script); /** - * Executes the {@literal script} by either calling it via its {@literal _id} or directly sending it. + * Executes the {@literal script} by either calling it via its {@literal name} or directly sending it. * * @param script must not be {@literal null}. * @param args arguments to pass on for script execution. * @return the script evaluation result. * @throws org.springframework.dao.DataAccessException */ - Object execute(MongoScript script, Object... args); + Object execute(ServerSideJavaScript script, Object... args); + + /** + * Call the {@literal JavaScript} by its name. + * + * @param scriptName must not be {@literal null} or empty. + * @param args + * @return + */ + Object call(String scriptName, Object... args); + + /** + * Checks {@link DB} for existence of {@link ServerSideJavaScript} with given name. + * + * @param scriptName must not be {@literal null} or empty. + * @return false if no {@link ServerSideJavaScript} with given name exists. + */ + Boolean exists(String scriptName); /** - * Retrieves the {@link CallableMongoScript} by its {@literal id}. + * Returns names of {@literal JavaScript} functions that can be called. * - * @param name - * @return {@literal null} if not found. + * @return empty {@link Set} if no scripts found. */ - CallableMongoScript load(Serializable name); + Set scriptNames(); } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoConverters.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoConverters.java index 6618759181..b77f0d0294 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoConverters.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoConverters.java @@ -20,6 +20,7 @@ import java.net.MalformedURLException; import java.net.URL; +import org.bson.types.Code; import org.bson.types.ObjectId; import org.springframework.core.convert.ConversionFailedException; import org.springframework.core.convert.TypeDescriptor; @@ -201,11 +202,7 @@ public CallableMongoScript convert(DBObject source) { String id = source.get("_id").toString(); Object rawValue = source.get("value"); - if (rawValue != null) { - return new CallableMongoScript(id, rawValue.toString()); - } - - return new CallableMongoScript(id); + return new CallableMongoScript(id, rawValue.toString()); } } @@ -229,7 +226,7 @@ public DBObject convert(CallableMongoScript source) { builder.append("_id", source.getName()); if (source.getCode() != null) { - builder.append("value", source.getCode()); + builder.append("value", new Code(source.getCode())); } return builder.get(); diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/script/CallableMongoScript.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/script/CallableMongoScript.java index 589bf1e315..b2a7a1e1d5 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/script/CallableMongoScript.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/script/CallableMongoScript.java @@ -15,37 +15,27 @@ */ package org.springframework.data.mongodb.core.script; -import org.bson.types.Code; import org.springframework.data.annotation.Id; import org.springframework.util.Assert; /** - * A {@link MongoScript} implementation that allows calling the function by its {@literal id} once it has been saved to - * the {@link com.mongodb.DB} instance. + * A {@link ServerSideJavaScript} implementation that allows calling the function by its {@literal name} once it has + * been saved to the {@link com.mongodb.DB} instance. * * @author Christoph Strobl * @since 1.7 */ -public class CallableMongoScript implements MongoScript { +public class CallableMongoScript implements ServerSideJavaScript { private final @Id String name; - private final MongoScript script; - - /** - * Creates new {@link CallableMongoScript} that uses the given {@literal id} to call the function on the server. - * - * @param name must not be {@literal null} or {@literal empty}. - */ - public CallableMongoScript(String name) { - this(name, (MongoScript) null); - } + private final ServerSideJavaScript script; /** * Creates new {@link CallableMongoScript} that can be saved to the {@link com.mongodb.DB} instance. * * @param name must not be {@literal null} or {@literal empty}. - * @param rawScript the {@link String} representation of the javascript function. Must not be {@literal null} or - * {@literal empty}. + * @param rawScript the {@link String} representation of the {@literal JavaScript} function. Must not be + * {@literal null} or {@literal empty}. */ public CallableMongoScript(String name, String rawScript) { this(name, new ExecutableMongoScript(rawScript)); @@ -57,7 +47,7 @@ public CallableMongoScript(String name, String rawScript) { * @param name must not be {@literal null} or {@literal empty}. * @param script can be {@literal null}. */ - public CallableMongoScript(String name, MongoScript script) { + public CallableMongoScript(String name, ServerSideJavaScript script) { Assert.hasText(name, "Name must not be null or empty!"); this.name = name; @@ -69,7 +59,7 @@ public CallableMongoScript(String name, MongoScript script) { * @see org.springframework.data.mongodb.core.script.MongoScript#getCode() */ @Override - public Code getCode() { + public String getCode() { if (script == null) { return null; @@ -79,7 +69,7 @@ public Code getCode() { } /** - * Get the id of the callable script. + * Get the name of the {@link CallableMongoScript} script. * * @return */ diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/script/ExecutableMongoScript.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/script/ExecutableMongoScript.java index 8a4fd4e53c..8b0082048b 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/script/ExecutableMongoScript.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/script/ExecutableMongoScript.java @@ -15,28 +15,27 @@ */ package org.springframework.data.mongodb.core.script; -import org.bson.types.Code; import org.springframework.util.Assert; /** - * {@link MongoScript} implementation that can be saved or directly executed. + * {@link ServerSideJavaScript} implementation that can be saved or directly executed. * * @author Christoph Strobl * @since 1.7 */ -public class ExecutableMongoScript implements MongoScript { +public class ExecutableMongoScript implements ServerSideJavaScript { - private final String rawScript; + private final String code; /** * Creates new {@link ExecutableMongoScript}. * - * @param rawScript must not be {@literal null} or {@literal empty}. + * @param code must not be {@literal null} or {@literal empty}. */ - public ExecutableMongoScript(String rawScript) { + public ExecutableMongoScript(String code) { - Assert.hasText(rawScript, "RawScript must not be null or empty!"); - this.rawScript = rawScript; + Assert.hasText(code, "Code must not be null or empty!"); + this.code = code; } /* @@ -44,8 +43,8 @@ public ExecutableMongoScript(String rawScript) { * @see org.springframework.data.mongodb.core.script.MongoScript#getCode() */ @Override - public Code getCode() { - return new Code(this.rawScript); + public String getCode() { + return this.code; } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/script/MongoScript.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/script/ServerSideJavaScript.java similarity index 86% rename from spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/script/MongoScript.java rename to spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/script/ServerSideJavaScript.java index 8eb30ce255..564a8d93cc 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/script/MongoScript.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/script/ServerSideJavaScript.java @@ -15,18 +15,16 @@ */ package org.springframework.data.mongodb.core.script; -import org.bson.types.Code; - /** * @author Christoph Strobl * @since 1.7 */ -public interface MongoScript { +public interface ServerSideJavaScript { /** - * Get the {@link Code} representation. + * Get the {@link String} representation of the JavaScript code. * * @return {@literal null} when no code available. */ - Code getCode(); + String getCode(); } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultScriptOperationsTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultScriptOperationsTests.java index 307caeccd5..5d49ac3891 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultScriptOperationsTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultScriptOperationsTests.java @@ -15,7 +15,9 @@ */ package org.springframework.data.mongodb.core; +import static org.hamcrest.collection.IsEmptyCollection.*; import static org.hamcrest.core.Is.*; +import static org.hamcrest.core.IsCollectionContaining.*; import static org.junit.Assert.*; import static org.junit.Assume.*; import static org.springframework.data.mongodb.core.query.Criteria.*; @@ -29,6 +31,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.dao.DataAccessException; +import org.springframework.dao.UncategorizedDataAccessException; import org.springframework.data.mongodb.core.query.Query; import org.springframework.data.mongodb.core.script.CallableMongoScript; import org.springframework.data.mongodb.core.script.ExecutableMongoScript; @@ -63,6 +66,7 @@ public MongoTemplate template() throws Exception { } + static final String JAVASCRIPT_COLLECTION_NAME = "system.js"; static final String SCRIPT_NAME = "echo"; static final String JS_FUNCTION = "function(x) { return x; }"; static final ExecutableMongoScript EXECUTABLE_SCRIPT = new ExecutableMongoScript(JS_FUNCTION); @@ -74,7 +78,7 @@ public MongoTemplate template() throws Exception { @Before public void setUp() { - template.getCollection("system.js").remove(new BasicDBObject()); + template.getCollection(JAVASCRIPT_COLLECTION_NAME).remove(new BasicDBObject()); this.scriptOps = new DefaultScriptOperations(template); } @@ -104,11 +108,11 @@ public void executeThowsDataAccessExceptionWhenRunningCallableScriptThatHasNotBe public void saveShouldStoreCallableScriptCorrectly() { Query query = query(where("_id").is(SCRIPT_NAME)); - assumeThat(template.exists(query, "system.js"), is(false)); + assumeThat(template.exists(query, JAVASCRIPT_COLLECTION_NAME), is(false)); - scriptOps.save(CALLABLE_SCRIPT); + scriptOps.register(CALLABLE_SCRIPT); - assumeThat(template.exists(query, "system.js"), is(true)); + assumeThat(template.exists(query, JAVASCRIPT_COLLECTION_NAME), is(true)); } /** @@ -117,10 +121,10 @@ public void saveShouldStoreCallableScriptCorrectly() { @Test public void saveShouldStoreExecutableScriptCorrectly() { - CallableMongoScript script = scriptOps.save(EXECUTABLE_SCRIPT); + CallableMongoScript script = scriptOps.register(EXECUTABLE_SCRIPT); Query query = query(where("_id").is(script.getName())); - assumeThat(template.exists(query, "system.js"), is(true)); + assumeThat(template.exists(query, JAVASCRIPT_COLLECTION_NAME), is(true)); } /** @@ -129,10 +133,10 @@ public void saveShouldStoreExecutableScriptCorrectly() { @Test public void executeShouldRunCallableScriptThatHasBeenSavedBefore() { - scriptOps.save(CALLABLE_SCRIPT); + scriptOps.register(CALLABLE_SCRIPT); Query query = query(where("_id").is(SCRIPT_NAME)); - assumeThat(template.exists(query, "system.js"), is(true)); + assumeThat(template.exists(query, JAVASCRIPT_COLLECTION_NAME), is(true)); Object result = scriptOps.execute(CALLABLE_SCRIPT, 10); @@ -143,13 +147,58 @@ public void executeShouldRunCallableScriptThatHasBeenSavedBefore() { * @see DATAMONGO-479 */ @Test - public void loadShouldFindScriptByItsName() { + public void existsShouldReturnTrueIfScriptAvailableOnServer() { - scriptOps.save(CALLABLE_SCRIPT); + scriptOps.register(CALLABLE_SCRIPT); - CallableMongoScript loaded = scriptOps.load(SCRIPT_NAME); + assertThat(scriptOps.exists(SCRIPT_NAME), is(true)); + } + + /** + * @see DATAMONGO-479 + */ + @Test + public void existsShouldReturnFalseIfScriptNotAvailableOnServer() { + assertThat(scriptOps.exists(SCRIPT_NAME), is(false)); + } + + /** + * @see DATAMONGO-479 + */ + @Test + public void callShouldExecuteExistingScript() { + + scriptOps.register(CALLABLE_SCRIPT); + + Object result = scriptOps.call(SCRIPT_NAME, 10); + + assertThat(result, Is. is(10D)); + } - assertThat(loaded.getName(), is(CALLABLE_SCRIPT.getName())); - assertThat(loaded.getCode(), is(CALLABLE_SCRIPT.getCode())); + /** + * @see DATAMONGO-479 + */ + @Test(expected = UncategorizedDataAccessException.class) + public void callShouldThrowExceptionWhenCallingScriptThatDoesNotExist() { + scriptOps.call(SCRIPT_NAME, 10); + } + + /** + * @see DATAMONGO-479 + */ + @Test + public void scriptNamesShouldContainNameOfRegisteredScript() { + + scriptOps.register(CALLABLE_SCRIPT); + + assertThat(scriptOps.scriptNames(), hasItems("echo")); + } + + /** + * @see DATAMONGO-479 + */ + @Test + public void scriptNamesShouldReturnEmptySetWhenNoScriptRegistered() { + assertThat(scriptOps.scriptNames(), empty()); } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultScriptOperationsUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultScriptOperationsUnitTests.java index f08604457b..89e481eff4 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultScriptOperationsUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultScriptOperationsUnitTests.java @@ -49,7 +49,7 @@ public void setUp() { */ @Test(expected = IllegalArgumentException.class) public void saveShouldThrowExceptionWhenCalledWithNullValue() { - scriptOps.save(null); + scriptOps.register(null); } /** @@ -58,7 +58,7 @@ public void saveShouldThrowExceptionWhenCalledWithNullValue() { @Test public void saveShouldUseCorrectCollectionName() { - scriptOps.save(new CallableMongoScript("foo", "function...")); + scriptOps.register(new CallableMongoScript("foo", "function...")); verify(mongoOperationsMock, times(1)).save(any(CallableMongoScript.class), eq("system.js")); } @@ -69,7 +69,7 @@ public void saveShouldUseCorrectCollectionName() { @Test public void saveShouldGenerateScriptNameForExecutableMongoScripts() { - scriptOps.save(new ExecutableMongoScript("function...")); + scriptOps.register(new ExecutableMongoScript("function...")); ArgumentCaptor captor = ArgumentCaptor.forClass(CallableMongoScript.class); @@ -85,4 +85,36 @@ public void executeShouldThrowExceptionWhenScriptIsNull() { scriptOps.execute(null); } + /** + * @see DATAMONGO-479 + */ + @Test(expected = IllegalArgumentException.class) + public void existsShouldThrowExceptionWhenScriptNameIsNull() { + scriptOps.exists(null); + } + + /** + * @see DATAMONGO-479 + */ + @Test(expected = IllegalArgumentException.class) + public void existsShouldThrowExceptionWhenScriptNameIsEmpty() { + scriptOps.exists(""); + } + + /** + * @see DATAMONGO-479 + */ + @Test(expected = IllegalArgumentException.class) + public void callShouldThrowExceptionWhenScriptNameIsNull() { + scriptOps.call(null); + } + + /** + * @see DATAMONGO-479 + */ + @Test(expected = IllegalArgumentException.class) + public void callShouldThrowExceptionWhenScriptNameIsEmpty() { + scriptOps.call(""); + } + } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/CallableMongoScriptConvertsUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/CallableMongoScriptConvertsUnitTests.java index f398cba317..833f066e2c 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/CallableMongoScriptConvertsUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/CallableMongoScriptConvertsUnitTests.java @@ -15,7 +15,6 @@ */ package org.springframework.data.mongodb.core.convert; -import static org.hamcrest.core.Is.*; import static org.hamcrest.core.IsEqual.*; import static org.hamcrest.core.IsInstanceOf.*; import static org.hamcrest.core.IsNull.*; @@ -89,17 +88,6 @@ public void convertShouldConvertScriptCodeCorreclty() { assertThat(code, instanceOf(Code.class)); assertThat(code.toString(), equalTo(JS_FUNCTION)); } - - /** - * @see DATAMONGO-479 - */ - @Test - public void convertShouldNotAddValueWhenCodeIsNull() { - - DBObject dbo = converter.convert(new CallableMongoScript("named")); - - assertThat(dbo.containsField("value"), is(false)); - } } /** diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/script/CallableMongoScriptUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/script/CallableMongoScriptUnitTests.java index 27ae5ab5c9..aa45510b2a 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/script/CallableMongoScriptUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/script/CallableMongoScriptUnitTests.java @@ -16,7 +16,6 @@ package org.springframework.data.mongodb.core.script; import static org.hamcrest.core.IsEqual.*; -import static org.hamcrest.core.IsNull.*; import static org.junit.Assert.*; import org.junit.Test; @@ -31,7 +30,7 @@ public class CallableMongoScriptUnitTests { */ @Test(expected = IllegalArgumentException.class) public void shouldThrowExceptionWhenScriptNameIsNull() { - new CallableMongoScript(null); + new CallableMongoScript(null, "return 1;"); } /** @@ -39,7 +38,7 @@ public void shouldThrowExceptionWhenScriptNameIsNull() { */ @Test(expected = IllegalArgumentException.class) public void shouldThrowExceptionWhenScriptNameIsEmptyString() { - new CallableMongoScript(""); + new CallableMongoScript("", "return 1"); } /** @@ -60,8 +59,7 @@ public void getCodeShouldReturnCodeRepresentationOfRawScript() { CallableMongoScript script = new CallableMongoScript("echo", jsFunction); - assertThat(script.getCode(), notNullValue()); - assertThat(script.getCode().toString(), equalTo(jsFunction)); + assertThat(script.getCode(), equalTo(jsFunction)); } } diff --git a/src/main/asciidoc/reference/mongodb.adoc b/src/main/asciidoc/reference/mongodb.adoc index 6697716dbb..4376fd0d89 100644 --- a/src/main/asciidoc/reference/mongodb.adoc +++ b/src/main/asciidoc/reference/mongodb.adoc @@ -1325,6 +1325,25 @@ MapReduceResults results = mongoOperations.mapReduce(query, "jmr1", Note that you can specify additional limit and sort values as well on the query but not skip values. +[[mongo.server-side-scripts]] +== Script Operations + +MongoDB allows to execute JavaScript functions on the server by either directly sending the script or calling a stored one. `ScriptOperations` can be accessed via `MongoTemplate` and provides basic abstraction for `JavaScript` usage. + +=== Example Usage + +[source,java] +---- +ScriptOperations scriptOps = template.scriptOps(); + +ServerSideJavaScript echoScript = new ExecutableMongoScript("function(x) { return x; }"); +scriptOps.execute(echoScript, "directly execute script"); + +scriptOps.register(new CallableMongoScript("echo", echoScript)); +scriptOps.call("echo", "execute script via name"); +---- + + [[mongo.group]] == Group Operations From 7f86a05e01cf11a5962e7fbf7b03bbc5bed41b1e Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Tue, 3 Mar 2015 14:26:01 +0100 Subject: [PATCH 5/5] DATAMONGO-479 - Assert MongoDB Java driver generation 3 compatibility. Use getCode() instead of toString for converting o.b.t.Code objects. This works fine for MongoDB Java driver versions 2 and 3. --- .../data/mongodb/core/convert/MongoConverters.java | 2 +- .../core/convert/CallableMongoScriptConvertsUnitTests.java | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoConverters.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoConverters.java index b77f0d0294..7c5a19ed98 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoConverters.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoConverters.java @@ -202,7 +202,7 @@ public CallableMongoScript convert(DBObject source) { String id = source.get("_id").toString(); Object rawValue = source.get("value"); - return new CallableMongoScript(id, rawValue.toString()); + return new CallableMongoScript(id, ((Code) rawValue).getCode()); } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/CallableMongoScriptConvertsUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/CallableMongoScriptConvertsUnitTests.java index 833f066e2c..7d2fc80df7 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/CallableMongoScriptConvertsUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/CallableMongoScriptConvertsUnitTests.java @@ -27,6 +27,7 @@ import org.junit.runners.Suite; import org.junit.runners.Suite.SuiteClasses; import org.springframework.data.mongodb.core.convert.CallableMongoScriptConvertsUnitTests.CallableMongoScriptToDboConverterUnitTests; +import org.springframework.data.mongodb.core.convert.CallableMongoScriptConvertsUnitTests.DboToCallableMongoScriptConverterUnitTests; import org.springframework.data.mongodb.core.convert.MongoConverters.CallableMongoScriptToDBObjectConverter; import org.springframework.data.mongodb.core.convert.MongoConverters.DBObjectToCallableMongoScriptCoverter; import org.springframework.data.mongodb.core.script.CallableMongoScript; @@ -39,7 +40,7 @@ * @author Christoph Strobl */ @RunWith(Suite.class) -@SuiteClasses({ CallableMongoScriptToDboConverterUnitTests.class }) +@SuiteClasses({ CallableMongoScriptToDboConverterUnitTests.class, DboToCallableMongoScriptConverterUnitTests.class }) public class CallableMongoScriptConvertsUnitTests { static final String FUNCTION_NAME = "echo"; @@ -86,7 +87,7 @@ public void convertShouldConvertScriptCodeCorreclty() { Object code = dbo.get("value"); assertThat(code, instanceOf(Code.class)); - assertThat(code.toString(), equalTo(JS_FUNCTION)); + assertThat(code, equalTo((Object) new Code(JS_FUNCTION))); } } @@ -125,7 +126,7 @@ public void convertShouldConvertScriptValueCorreclty() { CallableMongoScript script = converter.convert(FUNCTION); assertThat(script.getCode(), notNullValue()); - assertThat(script.getCode().toString(), equalTo(JS_FUNCTION)); + assertThat(script.getCode(), equalTo(JS_FUNCTION)); } }