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.

Original pull request: #254.
This commit is contained in:
Christoph Strobl
2014-12-04 10:21:24 +01:00
committed by Oliver Gierke
parent 7a3aff12a5
commit a0e42f5dfe
15 changed files with 1128 additions and 0 deletions

View File

@@ -0,0 +1,194 @@
/*
* 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.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.ServerSideJavaScript;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
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 {@link ServerSideJavaScript}.
*
* @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 register(ServerSideJavaScript script) {
Assert.notNull(script, "Script must not be null!");
CallableMongoScript callableScript = (script instanceof CallableMongoScript) ? (CallableMongoScript) script
: 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 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<Object>() {
@Override
public Object doInDB(DB db) throws MongoException, DataAccessException {
Assert.notNull(script.getCode(), "Script.code must not be null!");
return db.eval(script.getCode(), convertScriptArgs(args));
}
});
}
/*
* (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.hasText(scriptName, "ScriptName must not be null or empty!");
return mongoOperations.execute(new DbCallback<Object>() {
@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#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 Set<String> scriptNames() {
List<CallableMongoScript> scripts = (mongoOperations.findAll(CallableMongoScript.class, SCRIPT_COLLECTION_NAME));
if (CollectionUtils.isEmpty(scripts)) {
return Collections.emptySet();
}
Set<String> scriptNames = new HashSet<String>();
for (CallableMongoScript script : scripts) {
scriptNames.add(script.getName());
}
return scriptNames;
}
/**
* 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
*/
private String generateScriptName() {
return SCRIPT_NAME_PREFIX + randomUUID().toString().replaceAll("-", "");
}
private Object[] convertScriptArgs(Object... args) {
if (ObjectUtils.isEmpty(args)) {
return args;
}
List<Object> convertedValues = new ArrayList<Object>(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));
}
}

View File

@@ -284,6 +284,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.
* <p/>
@@ -994,4 +1002,5 @@ public interface MongoOperations {
* @return
*/
MongoConverter getConverter();
}

View File

@@ -540,6 +540,15 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
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> T findOne(Query query, Class<T> entityClass) {

View File

@@ -0,0 +1,76 @@
/*
* 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.util.Set;
import org.springframework.data.mongodb.core.script.CallableMongoScript;
import org.springframework.data.mongodb.core.script.ServerSideJavaScript;
import com.mongodb.DB;
/**
* Script operations on {@link com.mongodb.DB} level. Allows interaction with server side {@literal JavaScript}
* functions.
*
* @author Christoph Strobl
* @since 1.7
*/
public interface ScriptOperations {
/**
* 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 {@link CallableMongoScript} with name under which the {@literal JavaScript} function can be called.
*/
CallableMongoScript register(ServerSideJavaScript script);
/**
* 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(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);
/**
* Returns names of {@literal JavaScript} functions that can be called.
*
* @return empty {@link Set} if no scripts found.
*/
Set<String> scriptNames();
}

View File

@@ -44,6 +44,8 @@ import org.springframework.data.convert.WritingConverter;
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 class CustomConversions {
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());

View File

@@ -20,6 +20,7 @@ import java.math.BigInteger;
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;
@@ -27,8 +28,11 @@ import org.springframework.core.convert.converter.Converter;
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 +182,54 @@ abstract class MongoConverters {
return source == null ? null : source.getFormatted();
}
}
/**
* @author Christoph Strobl
* @since 1.7
*/
@ReadingConverter
public static enum DBObjectToCallableMongoScriptCoverter implements Converter<DBObject, CallableMongoScript> {
INSTANCE;
@Override
public CallableMongoScript convert(DBObject source) {
if (source == null) {
return null;
}
String id = source.get("_id").toString();
Object rawValue = source.get("value");
return new CallableMongoScript(id, ((Code) rawValue).getCode());
}
}
/**
* @author Christoph Strobl
* @since 1.7
*/
@WritingConverter
public static enum CallableMongoScriptToDBObjectConverter implements Converter<CallableMongoScript, DBObject> {
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", new Code(source.getCode()));
}
return builder.get();
}
}
}

View File

@@ -0,0 +1,80 @@
/*
* 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.springframework.data.annotation.Id;
import org.springframework.util.Assert;
/**
* 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 ServerSideJavaScript {
private final @Id String name;
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 {@literal 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, ServerSideJavaScript 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 String getCode() {
if (script == null) {
return null;
}
return script.getCode();
}
/**
* Get the name of the {@link CallableMongoScript} script.
*
* @return
*/
public String getName() {
return name;
}
}

View File

@@ -0,0 +1,50 @@
/*
* 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.springframework.util.Assert;
/**
* {@link ServerSideJavaScript} implementation that can be saved or directly executed.
*
* @author Christoph Strobl
* @since 1.7
*/
public class ExecutableMongoScript implements ServerSideJavaScript {
private final String code;
/**
* Creates new {@link ExecutableMongoScript}.
*
* @param code must not be {@literal null} or {@literal empty}.
*/
public ExecutableMongoScript(String code) {
Assert.hasText(code, "Code must not be null or empty!");
this.code = code;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.script.MongoScript#getCode()
*/
@Override
public String getCode() {
return this.code;
}
}

View File

@@ -0,0 +1,30 @@
/*
* 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;
/**
* @author Christoph Strobl
* @since 1.7
*/
public interface ServerSideJavaScript {
/**
* Get the {@link String} representation of the JavaScript code.
*
* @return {@literal null} when no code available.
*/
String getCode();
}

View File

@@ -0,0 +1,204 @@
/*
* 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.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.*;
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.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;
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 {
private static final String DB_NAME = "script-tests";
@Bean
public Mongo mongo() throws Exception {
return new MongoClient();
}
@Bean
public MongoTemplate template() throws Exception {
return new MongoTemplate(mongo(), DB_NAME);
}
}
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);
static final CallableMongoScript CALLABLE_SCRIPT = new CallableMongoScript(SCRIPT_NAME, JS_FUNCTION);
@Autowired MongoTemplate template;
DefaultScriptOperations scriptOps;
@Before
public void setUp() {
template.getCollection(JAVASCRIPT_COLLECTION_NAME).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.<Object> 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, JAVASCRIPT_COLLECTION_NAME), is(false));
scriptOps.register(CALLABLE_SCRIPT);
assumeThat(template.exists(query, JAVASCRIPT_COLLECTION_NAME), is(true));
}
/**
* @see DATAMONGO-479
*/
@Test
public void saveShouldStoreExecutableScriptCorrectly() {
CallableMongoScript script = scriptOps.register(EXECUTABLE_SCRIPT);
Query query = query(where("_id").is(script.getName()));
assumeThat(template.exists(query, JAVASCRIPT_COLLECTION_NAME), is(true));
}
/**
* @see DATAMONGO-479
*/
@Test
public void executeShouldRunCallableScriptThatHasBeenSavedBefore() {
scriptOps.register(CALLABLE_SCRIPT);
Query query = query(where("_id").is(SCRIPT_NAME));
assumeThat(template.exists(query, JAVASCRIPT_COLLECTION_NAME), is(true));
Object result = scriptOps.execute(CALLABLE_SCRIPT, 10);
assertThat(result, Is.<Object> is(10D));
}
/**
* @see DATAMONGO-479
*/
@Test
public void existsShouldReturnTrueIfScriptAvailableOnServer() {
scriptOps.register(CALLABLE_SCRIPT);
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.<Object> is(10D));
}
/**
* @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());
}
}

View File

@@ -0,0 +1,120 @@
/*
* 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.register(null);
}
/**
* @see DATAMONGO-479
*/
@Test
public void saveShouldUseCorrectCollectionName() {
scriptOps.register(new CallableMongoScript("foo", "function..."));
verify(mongoOperationsMock, times(1)).save(any(CallableMongoScript.class), eq("system.js"));
}
/**
* @see DATAMONGO-479
*/
@Test
public void saveShouldGenerateScriptNameForExecutableMongoScripts() {
scriptOps.register(new ExecutableMongoScript("function..."));
ArgumentCaptor<CallableMongoScript> 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);
}
/**
* @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("");
}
}

View File

@@ -0,0 +1,133 @@
/*
* 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.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.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;
import com.mongodb.BasicDBObject;
import com.mongodb.BasicDBObjectBuilder;
import com.mongodb.DBObject;
/**
* @author Christoph Strobl
*/
@RunWith(Suite.class)
@SuiteClasses({ CallableMongoScriptToDboConverterUnitTests.class, DboToCallableMongoScriptConverterUnitTests.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.<DBObject> 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.<Object> 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, equalTo((Object) new Code(JS_FUNCTION)));
}
}
/**
* @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(), equalTo(JS_FUNCTION));
}
}
}

View File

@@ -0,0 +1,65 @@
/*
* 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.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, "return 1;");
}
/**
* @see DATAMONGO-479
*/
@Test(expected = IllegalArgumentException.class)
public void shouldThrowExceptionWhenScriptNameIsEmptyString() {
new CallableMongoScript("", "return 1");
}
/**
* @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(), equalTo(jsFunction));
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -1325,6 +1325,25 @@ MapReduceResults<ValueObject> 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