├── .classpath ├── .gitignore ├── .project ├── Application.cfc ├── README.textile ├── build.xml ├── core ├── Balisong.cfc ├── DefaultFactory.cfc ├── ExpressionBuilder.cfc ├── JavaloaderFactory.cfc ├── MapReduceResult.cfc ├── Mongo.cfc ├── MongoConfig.cfc ├── MongoUtil.cfc ├── SearchBuilder.cfc └── SearchResult.cfc ├── examples ├── PeopleList │ ├── Application.cfc │ ├── detail.cfm │ ├── index.cfm │ └── load.cfm ├── aggregation │ ├── group.cfm │ ├── load.cfm │ └── mapReduce.cfm ├── benchmark.cfm ├── geospatial │ ├── geo.cfm │ ├── geo.json │ └── load.cfm ├── gettingstarted.cfm └── initMongo.cfm ├── java ├── .gitignore └── src │ ├── com │ └── mongodb │ │ ├── CFBasicDBObject.java │ │ └── CFBasicDBObjectBuilder.java │ └── net │ └── marcesher │ ├── CFStrictTyper.java │ ├── NoTyper.java │ ├── TypedStruct.java │ └── Typer.java ├── lib ├── cfmongodb.jar ├── javaloader │ ├── .settings │ │ └── org.eclipse.core.resources.prefs │ ├── JavaLoader.cfc │ ├── JavaProxy.cfc │ ├── lib │ │ ├── classloader-20100119110136.jar │ │ └── classloader-src.zip │ ├── licence.txt │ ├── readme.txt │ └── tags │ │ └── directory.cfm ├── mongo-2.4.jar └── mxunit-ant.jar └── test ├── .gitignore ├── AuthenticationTest.cfc ├── BaseTestCase.cfc ├── HttpAntRunner.cfc ├── IncludeExamplesTest.cfc ├── MongoTest.cfc ├── RemoteFacade.cfc ├── run.cfm ├── scratch.cfm └── temp.cfm /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /settings.xml 2 | /settings.xml 3 | /settings.xml 4 | /deploy 5 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | cfmongodb 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | 15 | com.adobe.ide.coldfusion.projectNature 16 | org.eclipse.jdt.core.javanature 17 | org.cfeclipse.cfml.CFENature 18 | 19 | 20 | -------------------------------------------------------------------------------- /Application.cfc: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /README.textile: -------------------------------------------------------------------------------- 1 | h1. CFMongoDB 2 | 3 | CFMongoDB is both partial wrapper for the MongoDB Java driver and a document-struct mapper for ColdFusion. It attempts to remove the need for constant javacasting in your CFML when working with MongoDB. Additionally, there's a simple DSL which provides ColdFusion developers the ability to easily search MongoDB document collections. 4 | 5 | CFMongoDB works with Adobe ColdFusion 9.0.1+ and Railo 3.2+ 6 | 7 | h2. Some Code 8 | 9 | One of the most appealing aspects is that data can be created as a ColdFusion structure and persisted almost verbatim. Example: 10 | 11 |
12 | 
13 | 
14 | //save
15 | col = 'my_collection':
16 | my_struct = {
17 |   name = 'Orc #getTickCount()#'
18 |   foo = 'bar'
19 |   bar = 123
20 |   'tags'=[ 'cool', 'distributed', 'fast' ]
21 | };
22 | 
23 | mongo.save(my_struct, col);
24 | 
25 | //query
26 | result = mongo.query(col).startsWith('name','Orc').search(limit=20);
27 | writeOutput("Found #results.size()# of #results.totalCount()# Orcs");
28 | 
29 | //use the native mongo cursor. it is case sensitive!
30 | cursor = result.asCursor();
31 | while( cursor.hasNext() ){
32 |   thisOrc = cursor.next();
33 |   writeOutput(" name = #thisOrc['name'] 
"); 34 | } 35 | 36 | //use a ColdFusion array of structs. this is not case sensitive 37 | orcs = result.asArray(); 38 | for(orc in orcs){ 39 | writeOutput(" name = #orc.name#
"); 40 | } 41 | 42 |
43 |
44 | 45 | h2. More Examples 46 | 47 | See examples/gettingstarted.cfm to start. 48 | 49 | Additional examples are in the various subdirectories in examples/ 50 | 51 | h2. The Wiki 52 | 53 | Check out the wiki for additional info: "http://wiki.github.com/marcesher/cfmongodb/":http://wiki.github.com/marcesher/cfmongodb/ 54 | 55 | h2. Getting Help 56 | 57 | We have a Google group: "http://groups.google.com/group/cfmongodb":http://groups.google.com/group/cfmongodb 58 | 59 | Please limit conversations to MongoDB and ColdFusion. General MongoDB questions are best asked on the MongoDB group at "http://groups.google.com/group/mongodb-user":http://groups.google.com/group/mongodb-user 60 | 61 | h2. Posting Issues 62 | 63 | Post issues to the github issue tracker for the project. Better: post fixes. Best: post fixes with unit tests. 64 | 65 | h2. Getting Involved 66 | 67 | Collaboration is welcome. Fork -- Commit -- Request a pull. For bug fixes and feature additions, commits with unit tests are much more likely to be accepted. 68 | 69 | Code well. 70 | 71 | -------------------------------------------------------------------------------- /build.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /core/Balisong.cfc: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | public numeric function count(string predicate, any collection){ 6 | return _count(predicate,collection,0); 7 | } 8 | 9 | public any function _count(string predicate, any collection, numeric accumulator){ 10 | collection = ensureArray( collection ); 11 | for(item in collection){ 12 | if( isArray(item) ) accumulator = _count(predicate, item, accumulator); 13 | else if(refindnocase(predicate,item)) ++accumulator; 14 | } 15 | return accumulator; 16 | } 17 | 18 | 19 | public any function filter(string predicate, any collection){ 20 | if( isArray(collection) ) return _filterArray(predicate,collection, [] ); 21 | if( isStruct(collection) ) return _filterStruct(predicate,collection, {} ); 22 | return collection; //Hmmm ... just echo? perhaps an exception would be better. 23 | } 24 | 25 | //slightly different implementation for arrays 26 | public any function _filterArray(string predicate, array collection, array accumulator){ 27 | for(item in collection){ 28 | if( isCollection(item) ) arrayAppend(accumulator, filter(predicate, item, accumulator) ); 29 | else if(refindnocase(predicate, item)) arrayAppend(accumulator, item); 30 | } 31 | return accumulator; 32 | } 33 | 34 | //slightly different implementation for structs 35 | public any function _filterStruct(string predicate, struct collection, struct accumulator){ 36 | for(item in collection){ 37 | if( isCollection(collection[item]) ) structInsert(accumulator, item, filter(predicate, collection[item], accumulator) ); 38 | else if (refindnocase(predicate, collection[item])) structInsert(accumulator, item, collection[item]); 39 | } 40 | return accumulator; 41 | } 42 | 43 | 44 | //returns true if all elements in the collection that 45 | //match the predicate 46 | public boolean function all(string predicate, any collection){ 47 | collection = ensureArray( collection ); 48 | for(item in collection){ 49 | if( isArray(item) && any(predicate,item) ) return true; 50 | else if (!refindnocase(predicate,item)>0) return false; 51 | } 52 | return true; 53 | } 54 | 55 | 56 | //returns true if any element is found in the collection that 57 | //matches the predicate 58 | public boolean function any(string predicate, any collection){ 59 | collection = ensureArray( collection ); 60 | for(item in collection){ 61 | if( isArray(item) && any(predicate,item) ) return true; 62 | else if (refindnocase(predicate,item)>0) return true; 63 | } 64 | return false; 65 | } 66 | 67 | 68 | //returns true if an element exists in the collection 69 | public boolean function exists(any target, any collection){ 70 | collection = ensureArray( collection ); 71 | return arrayfindnocase( collection, target ); 72 | } 73 | 74 | 75 | public boolean function isList(any l){ 76 | return (isSimplevalue(l) && listLen(l)); 77 | } 78 | 79 | public any function ensureArray(any l){ 80 | if(isList(l)) return listToArray( duplicate(l)); 81 | return duplicate(l); 82 | } 83 | 84 | public any function clone(any o){ 85 | return duplicate(o); 86 | } 87 | 88 | public function foldRight(array a, string op, numeric start){ 89 | var accumulator = start; 90 | //same as reducing but uses a custom accumulator 91 | return reduce(a,op,accumulator,'right'); 92 | } 93 | 94 | 95 | 96 | public function foldLeft(array a, string op, numeric start){ 97 | var accumulator = start; 98 | //same as reducing but uses a custom accumulator 99 | return reduce(a,op,accumulator,'left'); 100 | } 101 | 102 | 103 | 104 | public function reduceRight(array a, string op){ 105 | var accumulator = (op == '+')? 0 : 1; 106 | return _reduceRight(a,op,accumulator); 107 | } 108 | 109 | 110 | public function _reduceRight(array a, string op, numeric accumulator){ 111 | var i = 1; 112 | for(i=a.size(); i > 0; i--){ 113 | if( isArray(a[i]) ) accumulator = _reduceRight(a[i], op, accumulator); 114 | else accumulator = evaluate(accumulator & op & a[i]); 115 | } 116 | return accumulator; 117 | } 118 | 119 | 120 | 121 | public function reduce(array a, string op='+', numeric accumulator="0", string direction="left" ){ 122 | return (direction=='left')?_reduceLeft(a,op,accumulator):_reduceRight(a,op,accumulator); 123 | } 124 | 125 | 126 | public function reduceLeft(array a, string op='+' ){ 127 | var accumulator = (op == '+')? 0 : 1; 128 | return _reduceLeft(a, op, accumulator ); 129 | } 130 | 131 | 132 | private function _reduceLeft(array a, string op, numeric accumulator){ 133 | var i = 1; 134 | for(i=1; i <= arrayLen(a); i++){ 135 | if( isArray(a[i]) ) accumulator = _reduceLeft(a[i], op, accumulator); 136 | //else if( isStruct(a[i]) ) continue; 137 | else if (isNumeric(a[i])) accumulator = evaluate(accumulator & op & a[i]); 138 | } 139 | return accumulator; 140 | } 141 | 142 | 143 | public function flatten(array a){ 144 | return _flatten(a, []); 145 | } 146 | 147 | 148 | public function _flatten(array a, array accumulator){ 149 | var item = chr(0); 150 | for(item in a){ 151 | if( isArray(item) ) accumulator = _flatten( item, accumulator ); 152 | else arrayAppend( accumulator, item); 153 | } 154 | return accumulator; 155 | } 156 | 157 | 158 | //concatonates N collections together. all args should be same type 159 | public function concat(){ 160 | var c = ( isArray(arguments[1]) ) ? [] : {}; 161 | var i = ''; 162 | var j = ''; 163 | for(i in arguments) 164 | for (j in arguments[i]) 165 | (isArray(c)) ? arrayAppend(c, j) : structInsert(c,j,arguments[i][j]); 166 | return c; 167 | } 168 | 169 | 170 | // increments each numeric value in the array. if the value is not numeric, it just returns 171 | // the value. 172 | public function inc(any a){ 173 | return apply(_inc,a); //would be nice to have a lambda or closure here 174 | }// 175 | 176 | 177 | public function _inc(any val){ 178 | if( isNumeric(val) ) return ++val; 179 | return val; 180 | }// 181 | 182 | 183 | // executes the given function on each element of the list and returns a new list 184 | // this is semantically equivallent to map, but the syntax and name may be more intuitive 185 | // to English speakers ... apply a function to and array... vs. map array to function 186 | public function apply(any func, any a){ 187 | if (isStruct(a)) return _applyStruct(func, a); 188 | else return _applyListOrArray(func, a); 189 | }// 190 | 191 | 192 | private function isCollection(any coll){ 193 | return ( isStruct(coll) || isArray(coll) ); 194 | } 195 | 196 | 197 | private function _applyStruct(any func, any col){ 198 | var item = chr(0); 199 | var _s = {}; 200 | var _a = []; 201 | for(item in col){ 202 | if( isStruct(col[item]) ) structInsert(_s, item, apply(func, col[item]) ); 203 | else if( isArray(col[item]) ) { arrayAppend(_a, apply(func, col[item]) ); structInsert(_s,item,_a); } 204 | else structInsert( _s, item, func(col[item]) ); 205 | } 206 | return _s; 207 | }// 208 | 209 | 210 | 211 | private function _applyListOrArray(any func, any a){ 212 | var item = chr(0); 213 | var _a = []; 214 | var _s = {}; 215 | a = ensureArray(a); 216 | for(item in a){ 217 | if( isArray(item)) arrayAppend(_a, apply(func, item) ); 218 | else if( isStruct(item) ) {structInsert(_s, 'item', apply(func, item) ); arrayAppend(_a, _s); } 219 | else arrayAppend( _a, func(item) ); 220 | } 221 | return _a; 222 | }// 223 | 224 | 225 | 226 | // executes the given function on each element of the list 227 | public function foreach(a, func){ 228 | var item = chr(0); 229 | for(item in a){ 230 | if( isArray(item) ) apply(func, item) ; 231 | else func(item); 232 | } 233 | }// 234 | 235 | 236 | 237 | 238 | -------------------------------------------------------------------------------- /core/DefaultFactory.cfc: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /core/ExpressionBuilder.cfc: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | /*--------------------------------------------------------------------- 5 | 6 | DSL for MongoDB searches: 7 | 8 | mongo.expressionBuilder(). 9 | 10 | results = mongo.startsWith('name','foo'). //string 11 | endsWith('title','bar'). //string 12 | exists('field','value'). //string 13 | regex('field','value'). //string 14 | eq('field','value'). //numeric 15 | lt('field','value'). //numeric 16 | gt('field','value'). //numeric 17 | gte('field','value'). //numeric 18 | lte('field','value'). //numeric 19 | in('field','value'). //array 20 | nin('field','value'). //array 21 | mod('field','value'). //numeric 22 | size('field','value'). //numeric 23 | after('field','value'). //date 24 | before('field','value'). //date 25 | search('title,author,date', limit, start); 26 | 27 | search(keys=[keys_to_return],limit=num,start=num); 28 | 29 | -------------------------------------------------------------------------------------*/ 30 | 31 | builder = ''; 32 | pattern = ''; 33 | collection = ''; 34 | 35 | function init(string coll, any db){ 36 | builder = createObject('java', 'com.mongodb.CFBasicDBObjectBuilder').start(); 37 | pattern = createObject('java', 'java.util.regex.Pattern'); 38 | collection = db.getCollection(coll); 39 | } 40 | 41 | 42 | function startsWith(element, val){ 43 | var regex = '^' & val; 44 | builder.add( element, pattern.compile(regex) ); 45 | return this; 46 | } 47 | 48 | function endsWith(element, val){ 49 | var regex = val & '$'; 50 | builder.add( element, pattern.compile(regex) ); 51 | return this; 52 | } 53 | 54 | 55 | function exists(element, val){ 56 | var regex = '.*' & val & '.*'; 57 | builder.add( element, pattern.compile(regex) ); 58 | return this; 59 | } 60 | 61 | 62 | function regex(element, val){ 63 | var regex = val; 64 | builder.add( element, pattern.compile(regex) ); 65 | return this; 66 | } 67 | 68 | 69 | function builder(){ 70 | return builder; 71 | } 72 | 73 | function start(){ 74 | builder.start(); 75 | return this; 76 | } 77 | 78 | function get(){ 79 | return builder.get(); 80 | } 81 | 82 | 83 | //May need at least some exception handling 84 | function where( js_expression ){ 85 | builder.add( '$where', js_expression ); 86 | return this; 87 | } 88 | 89 | function inArray(element, val){ 90 | builder.add( element, val ); 91 | return this; 92 | } 93 | 94 | //vals should be list or array 95 | function $in(element,vals){ 96 | if(isArray(vals)) return addArrayCriteria(element,vals,'$in'); 97 | return addArrayCriteria(element, listToArray(vals),'$in'); 98 | } 99 | 100 | function $nin(element,vals){ 101 | if(isArray(vals)) return addArrayCriteria(element,vals,'$nin'); 102 | return addArrayCriteria(element, listToArray(vals),'$nin'); 103 | } 104 | 105 | 106 | function $eq(element,val){ 107 | builder.add( element, val ); 108 | return this; 109 | } 110 | 111 | 112 | function $ne(element,val){ 113 | return addNumericCriteria(element,val,'$ne'); 114 | } 115 | 116 | 117 | function $lt(element,val){ 118 | return addNumericCriteria(element,val,'$lt'); 119 | } 120 | 121 | 122 | function $lte(element,val){ 123 | return addNumericCriteria(element,val,'$lte'); 124 | } 125 | 126 | 127 | function $gt(element,val){ 128 | return addNumericCriteria(element,val,'$gt'); 129 | } 130 | 131 | 132 | function $gte(element,val){ 133 | return addNumericCriteria(element,val,'$gte'); 134 | } 135 | 136 | function listToStruct(list){ 137 | var item = ''; 138 | var s = {}; 139 | var i = 1; 140 | for(i; i lte listlen(list); i++) { 141 | s.put(listgetat(list,i),1); 142 | } 143 | return s; 144 | } 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | var key_exp = listToStruct(arguments.keys); 156 | var _keys = mongoUtil.newDBObject().init(key_exp); 157 | var search_results = []; 158 | var criteria = get(); 159 | var q = mongoUtil.newDBObject().init(criteria); 160 | search_results = collection.find(q,_keys).limit(limit); 161 | return search_results.toArray(); 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | var exp = {}; 172 | var date = parseDateTime(val); 173 | exp['$lte'] = date; 174 | builder.add( element, exp ); 175 | return this; 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | var exp = {}; 185 | var date = parseDateTime(val); 186 | exp['$gte'] = date; 187 | builder.add( element, exp ); 188 | return this; 189 | 190 | 191 | 192 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | var exp = {}; 206 | exp[type] = val; 207 | builder.add( element, exp ); 208 | return this; 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | var exp = {}; 218 | exp[type] = val; 219 | builder.add( element, exp ); 220 | return this; 221 | 222 | 223 | 224 | 225 | -------------------------------------------------------------------------------- /core/JavaloaderFactory.cfc: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /core/MapReduceResult.cfc: -------------------------------------------------------------------------------- 1 | component accessors="true"{ 2 | 3 | property name="dbCommand"; 4 | property name="commandResult"; 5 | property name="searchResult"; 6 | 7 | /** 8 | * Initializes and the result object. 9 | 10 | Use getdbCommand() to see all the parameters passed to mapReduce 11 | 12 | Use getCommandResult() to see a struct representation of the mapReduce result. This includes counts and timing information 13 | */ 14 | function init( dbCommand, commandResult, searchResult, mongoUtil ){ 15 | variables.mongoUtil = arguments.mongoUtil; 16 | variables.searchResult = arguments.searchResult; 17 | variables.dbCommand = mongoUtil.toCF(arguments.dbCommand); 18 | variables.commandResult = mongoUtil.toCF(arguments.commandResult); 19 | return this; 20 | } 21 | 22 | /** 23 | * Returns the name of the resultant MapReduce collection 24 | */ 25 | function getMapReduceCollectionName(){ 26 | return commandResult.result; 27 | } 28 | 29 | /** 30 | * The fastest return type... returns the case-sensitive cursor which you'd iterate over with 31 | while(cursor.hasNext()) {cursor.next();} 32 | 33 | Note: you can use the cursor object to get full access to the full API at http://api.mongodb.org/java 34 | */ 35 | function asCursor(){ 36 | return searchResult.asCursor(); 37 | } 38 | 39 | /** 40 | * Converts all cursor elements into a ColdFusion structure and returns them as an array of structs. 41 | */ 42 | function asArray(){ 43 | return searchResult.asArray(); 44 | } 45 | 46 | /** 47 | * The number of elements in the current SearchResult, after limit and skip are applied. 48 | Note that skip and limit would only be relevant when performing additional searches against the temporary 49 | MapReduce collection and setting that SeachResult into the MapReduceResult via mapReduceResult.setSearchResult( searchResult ); 50 | */ 51 | function size(){ 52 | return searchResult.size(); 53 | } 54 | 55 | /** 56 | * The total number of elements in the SearchResult. 57 | */ 58 | function totalCount(){ 59 | return searchResult.totalCount(); 60 | } 61 | 62 | /** 63 | * The criteria used for the query. 64 | Note that a query other than '{}' would only be returned when performing additional searches against the temporary 65 | MapReduce collection and setting that SeachResult into the MapReduceResult via mapReduceResult.setSearchResult( searchResult ); 66 | 67 | Use getQuery().toString() to get a copy/paste string for the Mongo shell 68 | */ 69 | function getQuery(){ 70 | return searchResult.getQuery(); 71 | } 72 | 73 | /** 74 | * The sort used for the query. 75 | Note that a sort other than '{}' would only be returned when performing additional searches against the temporary 76 | MapReduce collection and setting that SeachResult into the MapReduceResult via mapReduceResult.setSearchResult( searchResult ); 77 | 78 | Use getSort().toString() to get a copy/paste string for the Mongo shell 79 | */ 80 | function getSort(){ 81 | return searchResult.getSort(); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /core/Mongo.cfc: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | /** 10 | * You can init CFMongoDB in two ways: 11 | 1) drop the included jars into your CF's lib path (restart CF) 12 | 2) use Mark Mandel's javaloader (included). You needn't restart CF) 13 | 14 | --1: putting the jars into CF's lib path 15 | mongoConfig = createObject('component','cfmongodb.core.MongoConfig').init(dbName="mongorocks"); 16 | mongo = createObject('component','cfmongodb.core.Mongo').init(mongoConfig); 17 | 18 | --2: using javaloader 19 | javaloaderFactory = createObject('component','cfmongodb.core.JavaloaderFactory').init(); 20 | mongoConfig = createObject('component','cfmongodb.core.MongoConfig').init(dbName="mongorocks", mongoFactory=javaloaderFactory); 21 | mongo = createObject('component','cfmongodb.core.Mongo').init(mongoConfig); 22 | 23 | Note that authentication credentials, if set in MongoConfig, will be used to authenticate against the database. 24 | * 25 | */ 26 | function init(MongoConfig="#createObject('MongoConfig')#"){ 27 | setMongoConfig(arguments.MongoConfig); 28 | setMongoFactory(mongoConfig.getMongoFactory()); 29 | variables.mongo = mongofactory.getObject("com.mongodb.Mongo"); 30 | 31 | if( arrayLen( mongoConfig.getServers() ) GT 1 ){ 32 | variables.mongo.init(variables.mongoConfig.getServers()); 33 | } else { 34 | var _server = mongoConfig.getServers()[1]; 35 | writeDump(_server); 36 | writeoutput(_server.getHost()); writeOutput(_server.getPort()); 37 | variables.mongo.init( _server.getHost(), _server.getPort() ); 38 | } 39 | 40 | mongoUtil = new MongoUtil(mongoFactory); 41 | 42 | // Check for authentication, and if we have details set call it once on this database instance 43 | if ( isAuthenticationRequired() ) { 44 | var authResult = authenticate(mongoConfig.getAuthDetails().username, mongoConfig.getAuthDetails().password); 45 | if(not authResult.authenticated and structIsEmpty(authResult.error) ) { 46 | throw( message="Error authenticating against MongoDB Database", type="AuthenticationFailedException" ); 47 | } else if( not authResult.authenticated ) { 48 | throw(object=authResult.error); 49 | } 50 | } 51 | 52 | return this; 53 | } 54 | 55 | /** 56 | * Authenticates connection/db with given name and password 57 | 58 | Typical usage: 59 | mongoConfig.init(...); 60 | mongoConfig.setAuthDetails( username, password ); 61 | mongo = new Mongo(mongoConfig); 62 | 63 | If you set credentials to mongoConfig, Mongo.cfc will use those credentials to authenticate upon initialization. 64 | If authentication fails, an error will be thrown 65 | * 66 | */ 67 | struct function authenticate( string username, string password ){ 68 | var result = {authenticated = false, error={}}; 69 | try{ 70 | result.authenticated = getMongoDB( variables.mongoConfig ).authenticate( arguments.username, arguments.password.toCharArray() ); 71 | } 72 | catch( any e ){ 73 | result.error = e; 74 | } 75 | return result; 76 | } 77 | 78 | /** 79 | * Attempts to determine whether mongod is running in auth mode 80 | */ 81 | boolean function isAuthenticationRequired(){ 82 | try{ 83 | getIndexes("foo"); 84 | return false; 85 | }catch(any e){ 86 | return true; 87 | } 88 | } 89 | 90 | /** 91 | * Adds a user to the database 92 | */ 93 | function addUser( string username, string password) { 94 | getMongoDB( variables.mongoConfig ).addUser(arguments.username, arguments.password.toCharArray()); 95 | return this; 96 | } 97 | 98 | /** 99 | * Drops the database currently specified in MongoConfig 100 | */ 101 | function dropDatabase() { 102 | variables.mongo.dropDatabase(variables.mongoConfig.getDBName()); 103 | return this; 104 | } 105 | 106 | 107 | /** 108 | * Closes the underlying mongodb object. Once closed, you cannot perform additional mongo operations and you'll need to init a new mongo. 109 | Best practice is to close mongo in your Application.cfc's onApplicationStop() method. Something like: 110 | getBeanFactory().getBean("mongo").close(); 111 | or 112 | application.mongo.close() 113 | 114 | depending on how you're initializing and making mongo available to your app 115 | 116 | NOTE: If you do not close your mongo object, you WILL leak connections! 117 | */ 118 | function close(){ 119 | try{ 120 | variables.mongo.close(); 121 | }catch(any e){ 122 | //the error that this throws *appears* to be harmless. 123 | writeLog("Error closing Mongo: " & e.message); 124 | } 125 | return this; 126 | } 127 | 128 | /** 129 | * Returns the last error for the current connection. 130 | */ 131 | function getLastError() 132 | { 133 | return getMongoDB().getLastError(); 134 | } 135 | 136 | /** 137 | * For simple mongo _id searches, use findById(), like so: 138 | 139 | byID = mongo.findById( url.personId, collection ); 140 | */ 141 | function findById( id, string collectionName, mongoConfig="" ){ 142 | var collection = getMongoDBCollection(collectionName, mongoConfig); 143 | var result = collection.findOne( mongoUtil.newIDCriteriaObject(id) ); 144 | return mongoUtil.toCF( result ); 145 | } 146 | 147 | /** 148 | * Run a query against MongoDB. 149 | Query returns a SearchBuilder object, which you'll call functions on. 150 | Finally, you'll use various "execution" functions on the SearchBuilder to get a SearchResult object, 151 | which provides useful functions for working with your results. 152 | 153 | kidSearch = mongo.query( collection ).between("KIDS.AGE", 2, 30).search(); 154 | writeDump( kidSearch.asArray() ); 155 | 156 | See gettingstarted.cfm for many examples 157 | */ 158 | function query(string collectionName, mongoConfig=""){ 159 | var db = getMongoDB(mongoConfig); 160 | return new SearchBuilder(collectionName,db,mongoUtil); 161 | } 162 | 163 | /** 164 | * Runs mongodb's distinct() command. Returns an array of distinct values 165 | * 166 | distinctAges = mongo.distinct( "KIDS.AGE", "people" ); 167 | */ 168 | function distinct(string key, string collectionName, mongoConfig=""){ 169 | return getMongoDBCollection(collectionName, mongoConfig).distinct( key ); 170 | } 171 | 172 | /** 173 | * So important we need to provide top level access to it and make it as easy to use as possible. 174 | 175 | FindAndModify is critical for queue-like operations. Its atomicity removes the traditional need to synchronize higher-level methods to ensure queue elements only get processed once. 176 | 177 | http://www.mongodb.org/display/DOCS/findandmodify+Command 178 | 179 | This function assumes you are using this to *apply* additional changes to the "found" document. If you wish to overwrite, pass overwriteExisting=true. One bristles at the thought 180 | 181 | */ 182 | function findAndModify(struct query, struct fields, any sort, boolean remove=false, struct update, boolean returnNew=true, boolean upsert=false, boolean applySet=true, string collectionName, mongoConfig=""){ 183 | // Confirm our complex defaults exist; need this chunk of muck because CFBuilder 1 breaks with complex datatypes in defaults 184 | local.argumentDefaults = {sort={"_id"=1},fields={}}; 185 | for(local.k in local.argumentDefaults) 186 | { 187 | if (!structKeyExists(arguments, local.k)) 188 | { 189 | arguments[local.k] = local.argumentDefaults[local.k]; 190 | } 191 | } 192 | 193 | var collection = getMongoDBCollection(collectionName, mongoConfig); 194 | //must apply $set, otherwise old struct is overwritten 195 | if( applySet ){ 196 | update = { "$set" = mongoUtil.toMongo(update) }; 197 | } 198 | if( not isStruct( sort ) ){ 199 | sort = mongoUtil.createOrderedDBObject(sort); 200 | } else { 201 | sort = mongoUtil.toMongo( sort ); 202 | } 203 | 204 | var updated = collection.findAndModify( 205 | mongoUtil.toMongo(query), 206 | mongoUtil.toMongo(fields), 207 | sort, 208 | remove, 209 | mongoUtil.toMongo(update), 210 | returnNew, 211 | upsert 212 | ); 213 | if( isNull(updated) ) return {}; 214 | 215 | return mongoUtil.toCF(updated); 216 | } 217 | 218 | /** 219 | * Executes Mongo's group() command. Returns an array of structs. 220 | 221 | usage, including optional 'query': 222 | 223 | result = mongo.group( "tasks", "STATUS,OWNER", {TOTAL=0}, "function(obj,agg){ agg.TOTAL++; }, {SOMENUM = {"$gt" = 5}}" ); 224 | 225 | See examples/aggregation/group.cfm for detail 226 | */ 227 | function group( collectionName, keys, initial, reduce, query, keyf="", finalize="" ){ 228 | 229 | if (!structKeyExists(arguments, 'query')) 230 | { 231 | arguments.query = {}; 232 | } 233 | 234 | 235 | var collection = getMongoDBCollection(collectionName); 236 | var dbCommand = 237 | { "group" = 238 | {"ns" = collectionName, 239 | "key" = mongoUtil.createOrderedDBObject(keys), 240 | "cond" = query, 241 | "initial" = initial, 242 | "$reduce" = trim(reduce), 243 | "finalize" = trim(finalize) 244 | } 245 | }; 246 | if( len(trim(keyf)) ){ 247 | structDelete(dbCommand.group,"key"); 248 | dbCommand.group["$keyf"] = trim(keyf); 249 | } 250 | var result = getMongoDB().command( mongoUtil.toMongo(dbCommand) ); 251 | return result["retval"]; 252 | } 253 | 254 | /** 255 | * Executes Mongo's mapReduce command. Returns a MapReduceResult object 256 | 257 | basic usage: 258 | 259 | result = mongo.mapReduce( collectionName="tasks", map=map, reduce=reduce ); 260 | 261 | 262 | See examples/aggregation/mapReduce for detail 263 | */ 264 | function mapReduce( collectionName, map, reduce, query, sort, limit="0", out="", keeptemp="false", finalize="", scope, verbose="true", outType="normal" ){ 265 | 266 | // Confirm our complex defaults exist; need this hunk of muck because CFBuilder 1 breaks with complex datatypes as defaults 267 | local.argumentDefaults = { 268 | query={} 269 | ,sort={} 270 | ,scope={} 271 | }; 272 | for(local.k in local.argumentDefaults) 273 | { 274 | if (!structKeyExists(arguments, local.k)) 275 | { 276 | arguments[local.k] = local.argumentDefaults[local.k]; 277 | } 278 | } 279 | 280 | var dbCommand = mongoUtil.createOrderedDBObject( 281 | [ 282 | {"mapreduce"=collectionName}, {"map"=trim(map)}, {"reduce"=trim(reduce)} 283 | , {"query"=query}, {"sort"=sort}, {"limit"=limit}, {"keeptemp"=keeptemp}, {"finalize"=trim(finalize)}, {"scope"=scope}, {"verbose"=verbose}, {"outType"=outType} 284 | ] ); 285 | if( trim(arguments.out) neq "" ){ 286 | dbCommand.append( "out", arguments.out ); 287 | } 288 | var commandResult = getMongoDB().command( dbCommand ); 289 | var searchResult = this.query( commandResult["result"] ).search(); 290 | var mapReduceResult = createObject("component", "MapReduceResult").init(dbCommand, commandResult, searchResult, mongoUtil); 291 | return mapReduceResult; 292 | } 293 | 294 | /** 295 | * Saves a struct into the collection; Returns the newly-saved Document's _id; populates the struct with that _id 296 | 297 | person = {name="bill", badmofo=true}; 298 | mongo.save( person, "coolpeople" ); 299 | */ 300 | function save(struct doc, string collectionName, mongoConfig=""){ 301 | var collection = getMongoDBCollection(collectionName, mongoConfig); 302 | if( structKeyExists(doc, "_id") ){ 303 | update( doc = doc, collectionName = collectionName, mongoConfig = mongoConfig); 304 | } else { 305 | var dbObject = mongoUtil.toMongo(doc); 306 | collection.insert([dbObject]); 307 | doc["_id"] = dbObject.get("_id"); 308 | } 309 | return doc["_id"]; 310 | } 311 | 312 | /** 313 | * Saves an array of structs into the collection. Can also save an array of pre-created CFBasicDBObjects 314 | 315 | people = [{name="bill", badmofo=true}, {name="marc", badmofo=true}]; 316 | mongo.saveAll( people, "coolpeople" ); 317 | */ 318 | function saveAll(array docs, string collectionName, mongoConfig=""){ 319 | if( arrayIsEmpty(docs) ) return docs; 320 | 321 | var collection = getMongoDBCollection(collectionName, mongoConfig); 322 | var i = 1; 323 | if( getMetadata(docs[1]).getCanonicalName() eq "com.mongodb.CFBasicDBObject" ){ 324 | collection.insert(docs); 325 | } else { 326 | var total = arrayLen(docs); 327 | var allDocs = []; 328 | for(i=1; i LTE total; i++){ 329 | arrayAppend( allDocs, mongoUtil.toMongo(docs[i]) ); 330 | } 331 | collection.insert(allDocs); 332 | } 333 | return docs; 334 | } 335 | 336 | /** 337 | * Updates a document in the collection. 338 | 339 | The "doc" argument will either be an existing Mongo document to be updated based on its _id, or it will be a document that will be "applied" to any documents that match the "query" argument 340 | 341 | To update a single existing document, simply pass that document and update() will update the document by its _id: 342 | person = person.findById(url.id); 343 | person.something = "something else"; 344 | mongo.update( person, "people" ); 345 | 346 | To update a document by a criteria query and have the "doc" argument applied to a single found instance: 347 | update = {STATUS = "running"}; 348 | query = {STATUS = "pending"}; 349 | mongo.update( update, "tasks", query ); 350 | 351 | To update multiple documents by a criteria query and have the "doc" argument applied to all matching instances, pass multi=true 352 | mongo.update( update, "tasks", query, false, true ) 353 | 354 | Pass upsert=true to create a document if no documents are found that match the query criteria 355 | */ 356 | function update(doc, collectionName, query, upsert=false, multi=false, applySet=true, mongoConfig=""){ 357 | 358 | if (!structKeyExists(arguments, 'query')) 359 | { 360 | arguments.query = {}; 361 | } 362 | 363 | var collection = getMongoDBCollection(collectionName, mongoConfig); 364 | 365 | if( structIsEmpty(query) ){ 366 | query = mongoUtil.newIDCriteriaObject(doc['_id'].toString()); 367 | var dbo = mongoUtil.toMongo(doc); 368 | } else{ 369 | query = mongoUtil.toMongo(query); 370 | var keys = structkeyList(doc); 371 | if( applySet ){ 372 | doc = { "$set" = mongoUtil.toMongo(doc) }; 373 | } 374 | 375 | } 376 | var dbo = mongoUtil.toMongo(doc); 377 | collection.update( query, dbo, upsert, multi ); 378 | } 379 | 380 | /** 381 | * Remove one or more documents from the collection. 382 | 383 | If the document has an "_id", this will remove that single document by its _id. 384 | 385 | Otherwise, "doc" is treated as a "criteria" object. For example, if doc is {STATUS="complete"}, then all documents matching that criteria would be removed. 386 | 387 | pass an empty struct to remove everything from the collection: mongo.remove({}, collection); 388 | */ 389 | function remove(doc, collectionName, mongoConfig=""){ 390 | var collection = getMongoDBCollection(collectionName, mongoConfig); 391 | if( structKeyExists(doc, "_id") ){ 392 | return removeById( doc["_id"], collectionName, mongoConfig ); 393 | } 394 | var dbo = mongoUtil.toMongo(doc); 395 | var writeResult = collection.remove( dbo ); 396 | return writeResult; 397 | } 398 | 399 | /** 400 | * Convenience for removing a document from the collection by the String representation of its ObjectId 401 | 402 | mongo.removeById(url.id, somecollection); 403 | */ 404 | function removeById( id, collectionName, mongoConfig="" ){ 405 | var collection = getMongoDBCollection(collectionName, mongoConfig); 406 | return collection.remove( mongoUtil.newIDCriteriaObject(id) ); 407 | } 408 | 409 | 410 | /** 411 | * The array of fields can either be 412 | a) an array of field names. The sort direction will be "1" 413 | b) an array of structs in the form of fieldname=direction. Eg: 414 | 415 | [ 416 | {lastname=1}, 417 | {dob=-1} 418 | ] 419 | 420 | */ 421 | public array function ensureIndex(array fields, collectionName, unique=false, mongoConfig=""){ 422 | var collection = getMongoDBCollection(collectionName, mongoConfig); 423 | var pos = 1; 424 | var doc = {}; 425 | var indexName = ""; 426 | var fieldName = ""; 427 | 428 | for( pos = 1; pos LTE arrayLen(fields); pos++ ){ 429 | if( isSimpleValue(fields[pos]) ){ 430 | fieldName = fields[pos]; 431 | doc[ fieldName ] = 1; 432 | } else { 433 | fieldName = structKeyList(fields[pos]); 434 | doc[ fieldName ] = fields[pos][fieldName]; 435 | } 436 | indexName = listAppend( indexName, fieldName, "_"); 437 | } 438 | 439 | var dbo = mongoUtil.toMongo( doc ); 440 | collection.ensureIndex( dbo, "_#indexName#_", unique ); 441 | 442 | return getIndexes(collectionName, mongoConfig); 443 | } 444 | 445 | /** 446 | * Ensures a "2d" index on a single field. If another 2d index exists on the same collection, this will error 447 | */ 448 | public array function ensureGeoIndex(field, collectionName, min="", max="", mongoConfig=""){ 449 | var collection = getMongoDBCollection(collectionName, mongoConfig); 450 | var doc = { "#arguments.field#" = "2d" }; 451 | var options = {}; 452 | if( isNumeric(arguments.min) and isNumeric(arguments.max) ){ 453 | options = {"min" = arguments.min, "max" = arguments.max}; 454 | } 455 | //need to do this bit of getObject ugliness b/c the CFBasicDBObject will convert "2d" to a double. whoops. 456 | collection.ensureIndex( mongoUtil.getMongoFactory().getObject("com.mongodb.BasicDBObject").init(doc), mongoUtil.toMongo(options) ); 457 | return getIndexes( collectionName, mongoConfig ); 458 | } 459 | 460 | 461 | /** 462 | * Returns an array with information about all of the indexes for the collection 463 | */ 464 | public array function getIndexes(collectionName, mongoConfig=""){ 465 | var collection = getMongoDBCollection(collectionName, mongoConfig); 466 | var indexes = collection.getIndexInfo().toArray(); 467 | return indexes; 468 | } 469 | 470 | /** 471 | * Drops all indexes from the collection 472 | */ 473 | public array function dropIndexes(collectionName, mongoConfig=""){ 474 | getMongoDBCollection( collectionName, mongoConfig ).dropIndexes(); 475 | return getIndexes( collectionName, mongoConfig ); 476 | } 477 | 478 | /** 479 | * Decide whether to use the MongoConfig in the variables scope, the one being passed around as arguments, or create a new one 480 | */ 481 | function getMongoConfig(mongoConfig=""){ 482 | if(isSimpleValue(arguments.mongoConfig)){ 483 | mongoConfig = variables.mongoConfig; 484 | } 485 | return mongoConfig; 486 | } 487 | 488 | /** 489 | * Get the underlying Java driver's Mongo object 490 | */ 491 | function getMongo(mongoConfig=""){ 492 | return variables.mongo; 493 | } 494 | 495 | /** 496 | * Get the underlying Java driver's DB object 497 | */ 498 | function getMongoDB(mongoConfig=""){ 499 | var jMongo = getMongo(mongoConfig); 500 | return jMongo.getDb(getMongoConfig(mongoConfig).getDefaults().dbName); 501 | } 502 | 503 | /** 504 | * Get the underlying Java driver's DBCollection object for the given collection 505 | */ 506 | function getMongoDBCollection(collectionName="", mongoConfig=""){ 507 | var jMongoDB = getMongoDB(mongoConfig); 508 | return jMongoDB.getCollection(collectionName); 509 | } 510 | 511 | 512 | 513 | 514 | -------------------------------------------------------------------------------- /core/MongoConfig.cfc: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | variables.environment = "local"; 9 | variables.conf = {}; 10 | 11 | 12 | /** 13 | * Constructor 14 | * @hosts Defaults to [{serverName='localhost',serverPort='27017'}] 15 | */ 16 | public function init(Array hosts, dbName='default_db', MongoFactory="#createObject('DefaultFactory')#"){ 17 | 18 | if (!structKeyExists(arguments, 'hosts') || arrayIsEmpty(arguments.hosts)) { 19 | arguments.hosts = [{serverName='localhost',serverPort='27017'}]; 20 | } 21 | 22 | variables.mongoFactory = arguments.mongoFactory; 23 | establishHostInfo(); 24 | 25 | variables.conf = { dbname = dbName, servers = mongoFactory.getObject('java.util.ArrayList').init(), auth={username="",password=""} }; 26 | 27 | var item = ""; 28 | for(item in arguments.hosts){ 29 | addServer( item.serverName, item.serverPort ); 30 | } 31 | 32 | //main entry point for environment-aware configuration; subclasses should do their work in here 33 | environment = configureEnvironment(); 34 | 35 | return this; 36 | } 37 | 38 | public function addServer(serverName, serverPort){ 39 | var sa = mongoFactory.getObject("com.mongodb.ServerAddress").init( serverName, serverPort ); 40 | variables.conf.servers.add( sa ); 41 | return this; 42 | } 43 | 44 | public function removeAllServers(){ 45 | variables.conf.servers.clear(); 46 | return this; 47 | } 48 | 49 | public function setAuthDetails(username, password) { 50 | structAppend(variables.conf.auth, arguments); 51 | return this; 52 | } 53 | 54 | public struct function getAuthDetails() { 55 | return variables.conf.auth; 56 | } 57 | 58 | public function establishHostInfo(){ 59 | // environment decisions can often be made from this information 60 | var inetAddress = createObject( "java", "java.net.InetAddress"); 61 | variables.hostAddress = inetAddress.getLocalHost().getHostAddress(); 62 | variables.hostName = inetAddress.getLocalHost().getHostName(); 63 | return this; 64 | } 65 | 66 | /** 67 | * Main extension point: do whatever it takes to decide environment; 68 | * set environment-specific defaults by overriding the environment-specific 69 | * structure keyed on the environment name you decide 70 | */ 71 | public string function configureEnvironment(){ 72 | //overriding classes could do all manner of interesting things here... read config from properties file, etc. 73 | return "local"; 74 | } 75 | 76 | public string function getDBName(){ return getDefaults().dbName; } 77 | 78 | public Array function getServers(){return getDefaults().servers; } 79 | 80 | public struct function getDefaults(){ return conf; } 81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /core/MongoUtil.cfc: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | /** 8 | * initialize the MongoUtil. Pass an instance of JavaLoaderFactory to bypass the default MongoFactory 9 | Using a JavaLoaderFactory lets you use the libs provided with cfmongodb without adding them to your 10 | path and restarting CF 11 | */ 12 | function init(mongoFactory=""){ 13 | if(isSimpleValue(mongoFactory)){ 14 | arguments.mongoFactory = createObject("component", "DefaultFactory"); 15 | } 16 | variables.mongoFactory = arguments.mongoFactory; 17 | variables.dboFactory = mongoFactory.getObject('com.mongodb.CFBasicDBObject'); 18 | variables.typerClass = getTyperClass(); 19 | variables.typer = mongoFactory.getObject(typerClass).getInstance(); 20 | } 21 | 22 | /** 23 | * Returns a passthrough typer for Railo b/c it knows that 1 is 1 and not "1"; returns the CFStrictTyper otherwise 24 | */ 25 | public function getTyperClass(){ 26 | if( server.coldfusion.productname eq "Railo") return "net.marcesher.NoTyper"; 27 | return "net.marcesher.CFStrictTyper"; 28 | } 29 | 30 | /** 31 | * Create a new instance of the CFBasicDBObject. You use these anywhere the Mongo Java driver takes a DBObject 32 | */ 33 | function newDBObject(){ 34 | return dboFactory.newInstance(variables.typer); 35 | } 36 | 37 | /** 38 | * Converts a ColdFusion structure to a CFBasicDBobject, which the Java drivers can use 39 | */ 40 | function toMongo(any data){ 41 | //for now, assume it's a struct to DBO conversion 42 | var dbo = newDBObject(); 43 | dbo.putAll( data ); 44 | return dbo; 45 | } 46 | 47 | /** 48 | * Converts a Mongo DBObject to a ColdFusion structure 49 | */ 50 | function toCF(BasicDBObject){ 51 | var s = {}; 52 | s.putAll(BasicDBObject); 53 | return s; 54 | } 55 | 56 | /** 57 | * Convenience for turning a string _id into a Mongo ObjectId object 58 | */ 59 | function newObjectIDFromID(String id){ 60 | if( not isSimpleValue( id ) ) return id; 61 | return mongoFactory.getObject("org.bson.types.ObjectId").init(id); 62 | } 63 | 64 | /** 65 | * Convenience for creating a new criteria object based on a string _id 66 | */ 67 | function newIDCriteriaObject(String id){ 68 | return newDBObject().put("_id",newObjectIDFromID(id)); 69 | } 70 | 71 | /** 72 | * Creates a Mongo CFBasicDBObject whose order matches the order of the keyValues argument 73 | keyValues can be: 74 | 1) a string in k,k format: "STATUS,TS". This will set the value for each key to "1". Useful for creating Mongo's 'all true' structs, like the "keys" argument to group() 75 | 2) a string in k=v format: STATUS=1,TS=-1 76 | 3) an array of strings in k=v format: ["STATUS=1","TS=-1"] 77 | 4) an array of structs (often necessary when creating "command" objects for passing to db.command()): 78 | createOrderedDBObject( [ {"mapreduce"="tasks"}, {"map"=map}, {"reduce"=reduce} ] ) 79 | */ 80 | function createOrderedDBObject( keyValues ){ 81 | var dbo = newDBObject(); 82 | var kv = ""; 83 | if( isSimpleValue(keyValues) ){ 84 | keyValues = listToArray(keyValues); 85 | } 86 | for(kv in keyValues){ 87 | if( isSimpleValue( kv ) ){ 88 | var key = listFirst(kv, "="); 89 | var value = find("=",kv) ? listRest(kv, "=") : 1; 90 | } else { 91 | var key = structKeyList(kv); 92 | var value = kv[key]; 93 | } 94 | 95 | dbo.append( key, value ); 96 | } 97 | return dbo; 98 | } 99 | 100 | /** 101 | * Extracts the timestamp from the Doc's ObjectId. This represents the time the document was added to MongoDB 102 | */ 103 | function getDateFromDoc( doc ){ 104 | var ts = doc["_id"].getTime(); 105 | return createObject("java", "java.util.Date").init(ts); 106 | } 107 | 108 | -------------------------------------------------------------------------------- /core/SearchBuilder.cfc: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | /*--------------------------------------------------------------------- 5 | 6 | DSL for MongoDB searches: 7 | 8 | mongo.query('collection_name'). 9 | 10 | results = mongo.startsWith('name','foo'). //string 11 | endsWith('title','bar'). //string 12 | like('field','value'). //string 13 | regex('field','value'). //string 14 | eq('field','value'). //numeric 15 | lt('field','value'). //numeric 16 | gt('field','value'). //numeric 17 | gte('field','value'). //numeric 18 | lte('field','value'). //numeric 19 | in('field','value'). //array 20 | nin('field','value'). //array 21 | mod('field','value'). //numeric 22 | size('field','value'). //numeric 23 | after('field','value'). //date 24 | before('field','value'). //date 25 | search('title,author,date', limit, start); 26 | 27 | search(keys=[keys_to_return],limit=num,start=num); 28 | 29 | -------------------------------------------------------------------------------------*/ 30 | 31 | builder = ''; 32 | pattern = ''; 33 | collection = ''; 34 | mongoFactory = ''; 35 | mongoUtil = ''; 36 | 37 | function init(string coll, any db, any mongoUtil){ 38 | variables.mongoUtil = arguments.mongoUtil; 39 | variables.mongoFactory = arguments.mongoUtil.getMongoFactory(); 40 | builder = mongoFactory.getObject('com.mongodb.CFBasicDBObjectBuilder'); 41 | pattern = createObject('java', 'java.util.regex.Pattern'); 42 | collection = db.getCollection(coll); 43 | } 44 | 45 | function builder(){ 46 | return builder; 47 | } 48 | 49 | function start(){ 50 | builder.start(); 51 | return this; 52 | } 53 | 54 | function add( key, value ){ 55 | builder.add( key, value ); 56 | return this; 57 | } 58 | 59 | function get(){ 60 | return builder.get(); 61 | } 62 | 63 | function startsWith(element, val){ 64 | var regex = '^' & val; 65 | builder.add( element, pattern.compile(regex) ); 66 | return this; 67 | } 68 | 69 | function endsWith(element, val){ 70 | var regex = val & '$'; 71 | builder.add( element, pattern.compile(regex) ); 72 | return this; 73 | } 74 | 75 | 76 | function like(element, val){ 77 | var regex = '.*' & val & '.*'; 78 | builder.add( element, pattern.compile(regex) ); 79 | return this; 80 | } 81 | 82 | 83 | function regex(element, val){ 84 | var regex = val; 85 | builder.add( element, pattern.compile(regex) ); 86 | return this; 87 | } 88 | 89 | 90 | //May need at least some exception handling 91 | function where( js_expression ){ 92 | builder.add( '$where', js_expression ); 93 | return this; 94 | } 95 | 96 | function inArray(element, val){ 97 | builder.add( element, val ); 98 | return this; 99 | } 100 | 101 | 102 | //vals should be list or array 103 | function $in(element,vals){ 104 | if(isArray(vals)) return addArrayCriteria(element,vals,'$in'); 105 | return addArrayCriteria(element, listToArray(vals),'$in'); 106 | } 107 | 108 | function $nin(element,vals){ 109 | if(isArray(vals)) return addArrayCriteria(element,vals,'$nin'); 110 | return addArrayCriteria(element, listToArray(vals),'$nin'); 111 | } 112 | 113 | 114 | function $eq(element,val){ 115 | builder.add( element,val ); 116 | return this; 117 | } 118 | 119 | 120 | function $ne(element,val){ 121 | return addNumericCriteria(element,val,'$ne'); 122 | } 123 | 124 | 125 | function $lt(element,val){ 126 | return addNumericCriteria(element,val,'$lt'); 127 | } 128 | 129 | 130 | function $lte(element,val){ 131 | return addNumericCriteria(element,val,'$lte'); 132 | } 133 | 134 | 135 | function $gt(element,val){ 136 | return addNumericCriteria(element,val,'$gt'); 137 | } 138 | 139 | 140 | function $gte(element,val){ 141 | return addNumericCriteria(element,val,'$gte'); 142 | } 143 | 144 | function $exists(element, exists=true){ 145 | var criteria = {"$exists" = javacast("boolean",exists)}; 146 | builder.add( element, criteria ); 147 | return this; 148 | } 149 | 150 | function between(element, lower, upper){ 151 | var criteria = {"$gte" = lower, "$lte" = upper}; 152 | builder.add( element, criteria ); 153 | return this; 154 | } 155 | 156 | function betweenExclusive(element, lower, upper){ 157 | var criteria = {"$gt" = lower, "$lt" = upper}; 158 | builder.add( element, criteria ); 159 | return this; 160 | } 161 | 162 | function listToStruct(list){ 163 | var item = ''; 164 | var s = {}; 165 | var i = 1; 166 | for(i; i lte listlen(list); i++) { 167 | s.put(listgetat(list,i),1); 168 | } 169 | return s; 170 | } 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | var key_exp = listToStruct(arguments.keys); 183 | var _keys = mongoUtil.toMongo(key_exp); 184 | var search_results = []; 185 | var criteria = get(); 186 | if( isSimpleValue(sort) ) { 187 | sort = mongoUtil.createOrderedDBObject( sort ); 188 | } else { 189 | sort = mongoUtil.toMongo(sort); 190 | } 191 | search_results = collection.find(criteria, _keys).limit(limit).skip(skip).sort(sort); 192 | 193 | return createObject("component", "SearchResult").init( search_results, sort, mongoUtil ); 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | var exp = {}; 209 | var date = parseDateTime(val); 210 | exp['$lte'] = date; 211 | builder.add( element, exp ); 212 | return this; 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | var exp = {}; 222 | var date = parseDateTime(val); 223 | exp['$gte'] = date; 224 | builder.add( element, exp ); 225 | return this; 226 | 227 | 228 | 229 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | var exp = {}; 243 | exp[type] = val; 244 | builder.append( element, exp ); 245 | return this; 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | var exp = {}; 255 | exp[type] = val; 256 | builder.add( element, exp ); 257 | return this; 258 | 259 | 260 | 261 | -------------------------------------------------------------------------------- /core/SearchResult.cfc: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | mongoCursor = ""; 5 | query = ""; 6 | mongoUtil = ""; 7 | 8 | documents = ""; 9 | count = ""; 10 | tCount = ""; 11 | 12 | function init ( mongoCursor, sort, mongoUtil ){ 13 | structAppend( variables, arguments ); 14 | query = mongoCursor.getQuery(); 15 | return this; 16 | } 17 | 18 | /** 19 | * The fastest return type... returns the case-sensitive cursor which you'd iterate over with 20 | while(cursor.hasNext()) {cursor.next();} 21 | 22 | Note: you can use the cursor object to get full access to the full API at http://api.mongodb.org/java 23 | */ 24 | function asCursor(){ 25 | return mongoCursor; 26 | } 27 | 28 | /** 29 | * Converts all cursor elements into a ColdFusion structure and returns them as an array of structs. 30 | */ 31 | function asArray(){ 32 | if( isSimpleValue(documents) ){ 33 | documents = []; 34 | while(mongoCursor.hasNext()){ 35 | var doc = mongoUtil.toCF( mongoCursor.next() ); 36 | arrayAppend( documents, doc ); 37 | } 38 | } 39 | return documents; 40 | } 41 | 42 | /** 43 | * The number of elements in the result, after limit and skip are applied 44 | */ 45 | function size(){ 46 | if( count eq "" ){ 47 | //designed to reduce calls to mongo... mongoCursor.size() will additionally query the database, and arrayLen() is faster 48 | if( isArray( documents ) ){ 49 | count = arrayLen( documents ); 50 | } else { 51 | count = mongoCursor.size(); 52 | } 53 | } 54 | return count; 55 | } 56 | 57 | /** 58 | * The total number of elements for the query, before limit and skip are applied 59 | */ 60 | function totalCount(){ 61 | if( variables.tCount eq "" ){ 62 | variables.tCount = mongoCursor.count(); 63 | } 64 | return variables.tCount; 65 | } 66 | 67 | /** 68 | * Mongo's native explain command. Useful for debugging and performance analysis 69 | */ 70 | function explain(){ 71 | return mongoCursor.explain(); 72 | } 73 | 74 | /** 75 | * The criteria used for the query. Use getQuery().toString() to get a copy/paste string for the Mongo shell 76 | */ 77 | function getQuery(){ 78 | return query; 79 | } 80 | 81 | /** 82 | * The sort used for the query. use getSort().toString() to get a copy/paste string for the Mongo shell 83 | */ 84 | function getSort(){ 85 | return sort; 86 | } 87 | 88 | 89 | -------------------------------------------------------------------------------- /examples/PeopleList/Application.cfc: -------------------------------------------------------------------------------- 1 | component{ 2 | this.name = "cfmongodb_people"; 3 | 4 | 5 | function onApplicationStart(){ 6 | variables.dbname = "cfmongodb_examples"; 7 | include "../initMongo.cfm"; 8 | application.mongo = mongo; 9 | application.collection = "people_list"; 10 | 11 | include "load.cfm"; 12 | } 13 | 14 | function onRequestStart(){ 15 | if( structKeyExists(url, "reload") ){ 16 | applicationStop(); 17 | } 18 | } 19 | 20 | function onApplicationStop(){ 21 | application.mongo.close(); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /examples/PeopleList/detail.cfm: -------------------------------------------------------------------------------- 1 | 2 | 3 |

Person #url.id#

4 | 5 | -------------------------------------------------------------------------------- /examples/PeopleList/index.cfm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |

Showing #url.skip+1# through #results.size()+url.skip# of #results.totalCount()# People

18 | 19 | 20 | 21 | 22 |

23 | 24 | 25 | Previous 26 | 27 | 28 | 29 | 30 | 31 | Next 32 | 33 | 34 |

35 |
36 |
37 | 38 | #navlinks# 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 67 | 68 | 69 | 70 |
N Name Spouse Kids
#person.counter# #person.name# #person.spouse# 59 | 60 | 61 | #kid.name# | 62 | 63 | 64 | None 65 | 66 |
71 | 72 | #navlinks# 73 | 74 |
75 | 76 | -------------------------------------------------------------------------------- /examples/PeopleList/load.cfm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | mongo = application.mongo; 5 | collection = application.collection; 6 | 7 | counters = mongo.distinct( "COUNTER",collection ); 8 | arraySort( counters, "numeric", "desc" ); 9 | 10 | if( NOT arrayIsEmpty(counters) ){ 11 | nextNum = counters[1] + 1; 12 | } else { 13 | nextNum = 1; 14 | } 15 | 16 | coolPeople = []; 17 | for( i = nextNum; i LTE nextNum + 50; i++ ){ 18 | DOC = 19 | { 20 | NAME = "Cool Person #i#", 21 | SPOUSE = "Cool Spouse #i#", 22 | KIDS = [ 23 | {NAME="kid #i#", age=randRange(1,80), HAIR="strawberry", DESCRIPTION="fun" }, 24 | {NAME="kid #i+1#", age=randRange(1,80), HAIR="raven", DESCRIPTION="joyful" } 25 | ], 26 | BIKE = "Specialized", 27 | TS = now(), 28 | COUNTER = i 29 | }; 30 | arrayAppend( coolPeople, doc ); 31 | } 32 | 33 | mongo.saveAll( coolPeople, collection ); 34 | -------------------------------------------------------------------------------- /examples/aggregation/group.cfm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | collectionName = "tasks"; 10 | 11 | reduce = " 12 | function(obj,agg) { 13 | agg.TOTAL++; 14 | 15 | if( obj.COMPLETETS ){ 16 | agg.TOTALTIMETOCOMPLETE += ( obj.COMPLETETS.getTime() - obj.ADDEDTS.getTime() ); 17 | } 18 | 19 | if( obj.STARTTS ){ 20 | //it's been started... calculate from the STARTTS 21 | agg.TOTALPENDINGTIME += ( obj.STARTTS.getTime() - obj.ADDEDTS.getTime() ); 22 | } else { 23 | //it's not yet started... calculate from now 24 | agg.TOTALPENDINGTIME += ( new Date().getTime() - obj.ADDEDTS.getTime() ); 25 | } 26 | //agg.VALS.push(obj); //uncomment this to see all the objects that came through here; warning, this will add a lot of time to the writeDump 27 | } 28 | "; 29 | 30 | finalize = " 31 | function(out){ 32 | out.AVGPENDINGTIME = out.TOTALPENDINGTIME/out.TOTAL; 33 | } 34 | "; 35 | 36 | //This is how to do it with CFMongoDB. Note that the order of arguments is slightly different from the Java Driver's version 37 | newStatusGroups = mongo.group( 38 | collectionName=collectionName, 39 | keys="STATUS,OWNER", 40 | initial={TOTAL=0, TOTALTIMETOCOMPLETE=0, TOTALPENDINGTIME=0, VALS=[]}, 41 | reduce=reduce, 42 | finalize=finalize 43 | ); 44 | 45 | 46 | 47 | //This is how to do it with Java. Note use of mongoUtil to properly format the arguments 48 | collection = mongo.getMongoDBCollection(collectionName); 49 | mongoUtil = mongo.getMongoUtil(); 50 | keys = mongoUtil.createOrderedDBObject( [{STATUS=true}, {OWNER=true}] ); 51 | emptyCondition = mongoUtil.toMongo({}); 52 | initial = mongoUtil.toMongo( {TOTAL=0, TOTALTIMETOCOMPLETE=0, TOTALPENDINGTIME=0, VALS=[]} ); 53 | 54 | statusGroups = collection.group( 55 | keys, 56 | emptyCondition, 57 | initial, 58 | reduce 59 | ); 60 | 61 | mongo.close(); 62 | 63 | 64 | 65 | 66 |

CFMongoDB group() Demo

67 |

This shows how to use CFMongoDB's group() function

68 |

The data are randomly generated in load.cfm.

69 | 70 |

Group #group.STATUS#, OWNER: #group.OWNER#

71 | Total: #group.TOTAL#
72 | Avg Pending Time: #group.AVGPENDINGTIME/1000# seconds
73 | Total Time to Complete: 74 | 75 | 76 | #(group.TOTALTIMETOCOMPLETE/1000)/group.TOTAL#
77 | 78 | not yet complete 79 |
80 |
81 | 82 | 83 | 84 |
85 | 86 |

Raw group() Demo

87 |

This shows how to use MongoDB's group() function directly, without going through cfmongodb. Note that it uses mongoUtil to ensure proper datatype conversion

88 |

The data are randomly generated in load.cfm.

89 | 90 |

Group #group.STATUS#, OWNER: #group.OWNER#

91 | Total: #group.TOTAL#
92 | Avg Pending Time: #(group.TOTALPENDINGTIME/1000)/group.TOTAL# seconds
93 | Total Time to Complete: 94 | 95 | 96 | #(group.TOTALTIMETOCOMPLETE/1000)/group.TOTAL#
97 | 98 | not yet complete 99 |
100 |
101 | 102 | 103 |
104 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /examples/aggregation/load.cfm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | collection = "tasks"; 5 | 6 | total = mongo.query(collection).count(); 7 | badRecords = mongo.query(collection).$exists("ADDEDTS",false).count(); 8 | if(NOT total OR forceLoad OR badRecords){ 9 | mongo.remove({},collection); 10 | nextNum = 1; 11 | 12 | pending = []; 13 | for( i = nextNum; i <= randRange( nextNum+1, nextNum+100 ); i++ ){ 14 | arrayAppend( pending, { N=i, OWNER='', DATA="Some data goes here #i#", STATUS="P", ADDEDTS=now() } ); 15 | } 16 | 17 | running = []; 18 | nextNum = i; 19 | for( i = nextNum; i <= randRange( nextNum+1, nextNum+100 ); i++ ){ 20 | owner = i mod 2 ? "localhost" : "someOtherServer"; 21 | arrayAppend( running, { N=i, OWNER=owner, DATA="Some data goes here #i#", STATUS="R", ADDEDTS=dateAdd("d",-randRange(0,50),now()), STARTTS=now() } ); 22 | } 23 | 24 | paused = []; 25 | nextNum = i; 26 | for( i = nextNum; i <= randRange( nextNum+1, nextNum+100 ); i++ ){ 27 | arrayAppend( paused, { N=i, OWNER='', DATA="Some data goes here #i#", STATUS="U", ADDEDTS=dateAdd("d",-randRange(0,50),now()) } ); 28 | } 29 | 30 | completed = []; 31 | nextNum = i; 32 | for( i = nextNum; i <= randRange( nextNum+1, nextNum+100 ); i++ ){ 33 | owner = i mod 2 ? "localhost" : "someOtherServer"; 34 | arrayAppend( paused, { N=i, OWNER=owner, DATA="Some data goes here #i#", STATUS="C", ADDEDTS=dateAdd("d",-randRange(1,50),now()), STARTTS=dateAdd("n",-randRange(0,500),now()), COMPLETETS=now() } ); 35 | } 36 | 37 | 38 | mongo.saveAll( pending, collection ); 39 | mongo.saveAll( running, collection ); 40 | mongo.saveAll( paused, collection ); 41 | mongo.saveAll( completed, collection ); 42 | } 43 | mongo.close(); 44 | -------------------------------------------------------------------------------- /examples/aggregation/mapReduce.cfm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | u = mongo.getMongoUtil(); 10 | 11 | map = " 12 | function(){ 13 | emit(this.STATUS, 1); 14 | } 15 | "; 16 | 17 | reduce = " 18 | function(key, emits){ 19 | var total = 0; 20 | for( var i in emits ){ 21 | total += emits[i]; 22 | } 23 | return total; 24 | } 25 | "; 26 | 27 | //does nothing... here simply to show how you would pass a finalize function using dbCommand, which you couldn't do with the basic Java mapReduce signature 28 | finalize = "function( key, value ){ return value }"; 29 | 30 | //#1 use CFMongoDB 31 | result = mongo.mapReduce( collectionName="tasks", map=map, reduce=reduce ); 32 | 33 | writeOutput("

CFMongoDB mongo.mapReduce()

"); 34 | writeOutput("

The MapReduceResult object. Expand it for all the goodies

"); 35 | writeDump(var=result, label="mongo.cfc mapReduceResult", expand="false"); 36 | writeOutput("

MapReduceResult.asArray()

"); 37 | writeDump(var=result.asArray(), label="mapReduceResult.asArray() over collection #result.getMapReduceCollectionName()#"); 38 | writeOutput("
"); 39 | 40 | //#1.5: perform additional queries against the MapReduce collection 41 | filteredResult = mongo.query( result.getMapReduceCollectionName() ).$gt("value",10).search(limit=2,sort="value=-1"); 42 | result.setSearchResult( filteredResult ); 43 | 44 | writeOutput("

Perform additional searches on the CFMongoDB MapReduce result

"); 45 | writeOutput("

The query against the new MapReduce collection

"); 46 | writeDump( var=result.getQuery() ); 47 | writeOutput("

The results, limiting to 2 and sorting by 'value' desc

"); 48 | writeDump( var=result.asArray() ); 49 | 50 | writeOutput("
"); 51 | 52 | //#2: try it using a command instead of the driver's mapReduce function 53 | //have to use the "ordered" stuff here because if we do straight struct creation, CF will order them 54 | //indeterminantly. MongoDB, for whatever reason, uses the first key as the command name (as opposed to "command" = "mapreduce", which would be infinitely more sensible) 55 | dbCommand = u.createOrderedDBObject( [ {"mapreduce"="tasks"}, {"map"=map}, {"reduce"=reduce}, {"finalize"=finalize}, {"verbose" = true} ] ); 56 | result = mongo.getMongoDB().command( dbCommand ); 57 | //now use a normal cfmongodb query to search the tmp collection created by mapreduce 58 | searchResult = mongo.query(result["result"]).search(); 59 | 60 | writeOutput("

Java getMongoDB().command()

"); 61 | writeOutput("

The command object

"); 62 | writeDump(var=dbCommand, expand=false); 63 | 64 | writeOutput("

The result of .command()

"); 65 | writeDump(var=result, expand=false); 66 | writeOutput("

Passing the result's temp collection name through CFMongoDB.query().search()

"); 67 | writeDump(var=searchResult.asArray(), expand=false); 68 | writeOutput("
"); 69 | 70 | //#3: now use the java driver's minimal signature 71 | jResult = mongo.getMongoDBCollection("tasks").mapReduce(map, reduce, javacast("null",""),javacast("null","")); 72 | //use a little trick... fill up a SearchResult object with this M/R's cursor 73 | mrSearchResult = createObject("cfmongodb.core.SearchResult").init(jResult.results(),{},u); 74 | writeOutput("

Java Driver's built-in, minimal mapReduce

"); 75 | writeOutput("

The Java driver's MapReduceOutput object

"); 76 | writeDump(var=jResult, expand=false); 77 | writeOutput("

Passing that object's results() function to a new cfmongodb SearchResult object

"); 78 | writeDump(var=mrSearchResult.asArray(), expand=false); 79 | writeOutput("
"); 80 | 81 | //#4: now use the java driver's smaller but more flexible signature, which takes a Command, letting you pass in all the stuff that you could pass from the shell 82 | jResult2 = mongo.getMongoDBCollection("tasks").mapReduce( dbCommand ); 83 | mrSearchResult = createObject("cfmongodb.core.SearchResult").init(jResult2.results(),{},u); 84 | writeOutput("

Java Driver's built-in, full mapReduce, which takes a BasicDBObject command

"); 85 | writeOutput("

The Java driver's MapReduceOutput object

"); 86 | writedump(jResult2); 87 | writeOutput("

Passing that object's results() function to a new cfmongodb SearchResult object

"); 88 | writeDump(mrSearchResult.asArray()); 89 |
90 | 91 | 92 | -------------------------------------------------------------------------------- /examples/benchmark.cfm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | serverName = server.coldfusion.productname & "_" & server.coldfusion.productversion; 9 | 10 | //we'll create/use a 'people' collection 11 | collection = "datadump"; 12 | metricColl = "metrics"; 13 | 14 | dataCreateStart = getTickCount(); 15 | coolPeople = []; 16 | totalDocs = 1000; 17 | for( i = 1; i LTE totalDocs; i++ ){ 18 | DOC = 19 | { 20 | NAME = "Cool Dude #i#", 21 | WIFE = "Smokin hot wife #i#", 22 | KIDS = [ 23 | {NAME="kid #i#", age=randRange(1,80), hair="strawberry", description="fun" }, 24 | {NAME="kid #i+1#", age=randRange(1,80), hair="raven", description="joyful" } 25 | ], 26 | BIKE = "Specialized", 27 | TS = now(), 28 | COUNTER = i, 29 | MONGOROCKS = true, 30 | PRODUCT = serverName 31 | }; 32 | arrayAppend( coolPeople, doc ); 33 | } 34 | dataCreateTotal = getTickCount() - dataCreateStart; 35 | 36 | saveStart = getTickCount(); 37 | 38 | mongo.saveAll( coolPeople, collection ); 39 | 40 | saveTotal = getTickCount() - saveStart; 41 | 42 | stat = { DATATOTAL=dataCreateTotal, SAVETOTAL=saveTotal, COUNT=totalDocs, SAVETYPE='structs', USEJL=url.useJavaLoader, PRODUCT=serverName }; 43 | mongo.save( stat, metricColl ); 44 | 45 | 46 | dataCreateStart = getTickCount(); 47 | coolPeople = []; 48 | for( i = 1; i LTE totalDocs; i++ ){ 49 | DOC = 50 | { 51 | NAME = "Cool Dude #i#", 52 | WIFE = "Smokin hot wife #i#", 53 | KIDS = [ 54 | {NAME="kid #i#", age=randRange(1,80), hair="strawberry", description="fun" }, 55 | {NAME="kid #i+1#", age=randRange(1,80), hair="raven", description="joyful" } 56 | ], 57 | BIKE = "Specialized", 58 | TS = now(), 59 | COUNTER = i, 60 | MONGOROCKS = true, 61 | PRODUCT = serverName 62 | }; 63 | arrayAppend( coolPeople, mongoUtil.toMongo(doc) ); 64 | } 65 | 66 | dataCreateTotal = getTickCount() - dataCreateStart; 67 | 68 | saveStart = getTickCount(); 69 | 70 | mongo.saveAll( coolPeople, collection ); 71 | 72 | saveTotal = getTickCount() - saveStart; 73 | 74 | stat = { DATATOTAL=dataCreateTotal, SAVETOTAL=saveTotal, COUNT=totalDocs, SAVETYPE='dbos', USEJL=url.useJavaLoader, PRODUCT=serverName }; 75 | mongo.save( stat, metricColl ); 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /examples/geospatial/geo.cfm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | collection = "geoexamples"; 9 | 10 | try { 11 | //only need to do this once, but here for illustration 12 | indexes = mongo.ensureGeoIndex("LOC", collection); 13 | writeDump(indexes); 14 | 15 | //as of this writing, you can perform geo queries like so: 16 | nearResult = mongo.query( collection ).add( "LOC", {"$near" = [38,-85]} ).search(limit=10); 17 | writeDump( var = nearResult.asArray(), label = "$near result" ); 18 | } 19 | catch(Any e){ 20 | writeDump(e); 21 | } 22 | 23 | mongo.close(); 24 | 25 | -------------------------------------------------------------------------------- /examples/geospatial/load.cfm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | collection = "geoexamples"; 6 | total = mongo.query( collection ).count(); 7 | 8 | if( total eq 0 OR forceLoad ){ 9 | mongo.remove( {}, collection ); 10 | rows = deserializeJson( fileRead(expandPath('geo.json')) ); 11 | mongo.saveAll( rows, collection ); 12 | } 13 | 14 | mongo.close(); 15 | 16 | -------------------------------------------------------------------------------- /examples/gettingstarted.cfm: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | if( url.useJavaLoader ){ 16 | //the fastest way to try out cfmongodb is using Mark Mandel's javaloader, which we ship with cfmongodb. Thanks Mark! 17 | //http://javaloader.riaforge.org 18 | 19 | //use the cfmongodb javaloader factory 20 | javaloaderFactory = createObject('component','cfmongodb.core.JavaloaderFactory').init(); 21 | 22 | //create a default MongoConfig instance; in your real apps, you'll create an object that extends MongoConfig and put your environment specific config in there 23 | //here we initialize it with a db named 'mongorocks' 24 | mongoConfig = createObject('component','cfmongodb.core.MongoConfig').init(dbName="mongorocks", mongoFactory=javaloaderFactory); 25 | } 26 | else 27 | { 28 | mongoConfig = createObject('component','cfmongodb.core.MongoConfig').init(dbName="mongorocks"); 29 | } 30 | 31 | //initialize the core cfmongodb Mongo object 32 | mongo = createObject('component','cfmongodb.core.Mongo').init(mongoConfig); 33 | 34 | //we'll create/use a 'people' collection 35 | collection = "people"; 36 | 37 | //clear out the collection so we always start fresh, for demo purposes 38 | mongo.remove({},collection); 39 | 40 | 41 | //here's how to insert one document 42 | doc = 43 | { 44 | NAME = "Marc", 45 | SPOUSE = "Heather", 46 | KIDS = [ 47 | {NAME="Alexis", AGE=7, HAIR="blonde", DESCRIPTION="crazy" }, 48 | {NAME="Sidney", AGE=2, HAIR="dirty blonde", DESCRIPTION="ornery" } 49 | ], 50 | BIKE = "Felt", 51 | LOVESSQL = true, 52 | LOVESMONGO = true, 53 | TS = now(), 54 | COUNTER = 1 55 | }; 56 | 57 | mongo.save( doc, collection ); 58 | 59 | writeDump( var=doc, label="Saved document", expand="false" ); 60 | 61 | /* 62 | * VERY IMPORTANT: ColdFusion will automatically uppercase struct keys if you do not quote them. Consequently, the document will be stored 63 | * in MongoDB with upper case keys. Below, where we search, we MUST use uppercase keys. 64 | * 65 | * at the shell, mongo.find({name:'Marc'}) != mongo.find({NAME: 'Marc'}) 66 | */ 67 | 68 | 69 | //here's how to insert multiple documents 70 | coolPeople = []; 71 | for( i = 1; i LTE 5; i++ ){ 72 | DOC = 73 | { 74 | NAME = "Cool Person #i#", 75 | SPOUSE = "Cool Spouse #i#", 76 | KIDS = [ 77 | {NAME="kid #i#", age=randRange(1,80), HAIR="strawberry", DESCRIPTION="fun" }, 78 | {NAME="kid #i+1#", age=randRange(1,80), HAIR="raven", DESCRIPTION="joyful" } 79 | ], 80 | BIKE = "Specialized", 81 | TS = now(), 82 | COUNTER = i 83 | }; 84 | arrayAppend( coolPeople, doc ); 85 | } 86 | 87 | mongo.saveAll( coolPeople, collection ); 88 | 89 | //find Marc 90 | marc = mongo.query( collection ).$eq("NAME", "Marc").search(); 91 | showResult( marc, "Marcs" ); 92 | 93 | 94 | //find riders of Specialized bikes 95 | specialized = mongo.query( collection ).$eq("BIKE", "Specialized").search(); 96 | showResult( specialized, "Specialized riders" ); 97 | 98 | //find the 3rd and 4th Specialized bike riders, sorted by "ts" descending 99 | specialized = mongo.query( collection ).$eq("BIKE", "Specialized").search( skip=2, limit=2, sort="TS=-1" ); 100 | showResult( specialized, "Specialized riders, skipping to 3, limiting to 2, sorting by ts desc (skip is 0-based!)" ); 101 | 102 | //find riders with counter between 1 and 3, sorted by "ts" descending 103 | specialized = mongo.query( collection ) 104 | .$eq("BIKE", "Specialized") 105 | .between("COUNTER", 1, 3) 106 | .search( sort="TS=-1" ); 107 | showResult( specialized, "Specialized riders, COUNTER between 1 and 3" ); 108 | 109 | //find riders with counter between 1 and 3 Exclusive, sorted by "ts" descending 110 | specialized = mongo.query( collection ) 111 | .$eq("BIKE", "Specialized") 112 | .betweenExclusive("COUNTER", 1, 3) 113 | .search( sort="TS=-1" ); 114 | showResult( specialized, "Specialized riders, COUNTER between 1 and 3 Exclusive" ); 115 | 116 | //find people with kids aged between 2 and 30 117 | kidSearch = mongo.query( collection ).between("KIDS.AGE", 2, 30).search(keys="NAME,COUNTER,KIDS", sort="COUNTER=-1"); 118 | showResult( kidSearch, "People with kids aged between 2 and 30" ); 119 | 120 | 121 | //find a document by ObjectID... note that it returns the document, NOT a SearchResult object; here, we'll "spoof" what your app would do if the id were in the URL scope 122 | url.personId = specialized.asArray()[1]["_id"].toString(); 123 | 124 | byID = mongo.findById( url.personId, collection ); 125 | writeOutput("

Find by ID

"); 126 | writeDump(var=byID, label="Find by ID: #url.personID#", expand="false"); 127 | 128 | //using count(), SearchResult.totalCount(), and SearchResult.size() 129 | totalPeople = mongo.query( collection ).between("KIDS.AGE", 2, 100).count(); 130 | searchResult = mongo.query( collection ).between("KIDS.AGE", 2, 100).search(limit=3);//using limit to show difference between SearchResult.size() and totalCount() 131 | alsoTotalPeople = searchResult.totalCount(); //equivalent to query().count()! 132 | sizeWithLimit = searchResult.size(); 133 | writeOutput("

How to count things

"); 134 | writeOutput("Total People with kids aged between 2 and 100: #totalPeople#; Also Total People, fetched via the SearchResult object, which does not care about limit and skip: #alsoTotalPeople#
"); 135 | writeOutput("SearchResult.size() will respect the skip and limit arguments: #sizeWithLimit#
"); 136 | 137 | //using distinct() to return an array of unique values 138 | kidAges = mongo.distinct( "KIDS.AGE", collection ); 139 | writeOutput("

Distinct values

"); 140 | writeDump(var=kidAges, label="Distinct kid ages", expand="false"); 141 | 142 | 143 | 144 | //here's how to update. You'll generally do two kinds of updating: 145 | // 1) updating a single pre-fetched document... this is the most common. It's a find/modify/resave 146 | // 2) updating one or more documents based on criteria. You almost always need to use a $set in this situation!!! 147 | 148 | //updating a single pre-fetched document 149 | person = mongo.query( collection ).search(limit="1").asArray()[1]; 150 | person.FAVORITECIGAR = "H. Upmann Cubano"; 151 | person.MODTS = now(); 152 | arrayAppend( person.KIDS, {NAME = "Pauly", AGE = 0} ); 153 | mongo.update( person, collection ); 154 | 155 | writeOutput("

Updated Person

"); 156 | writeDump( var=person, label="updated person", expand="false"); 157 | 158 | //updating a single document. by default it'll wrap the "doc" arg in "$set" as a convenience 159 | person = {NAME = "Ima PHP dev", AGE=12}; 160 | mongo.save( person, collection ); 161 | 162 | mongo.update( doc={NAME = "Ima CF Dev", HAPPY = true}, query= {NAME = "Ima PHP dev"}, collectionName = collection ); 163 | afterUpdate = mongo.findById( person["_id"], collection ); 164 | 165 | writeOutput("

Updated person by criteria

"); 166 | writeDump(var = person, label="Original", expand=false); 167 | writeDump(var = afterUpdate, label = "After update", expand=false); 168 | 169 | //updating a single document based on criteria and overwriting instead of updating 170 | person = {NAME = "Ima PHP dev", AGE=12}; 171 | mongo.save( person, collection ); 172 | 173 | mongo.update( doc={NAME = "Ima CF Dev", HAPPY = true}, query= {NAME = "Ima PHP dev"}, applySet = false, collectionName = collection ); 174 | afterUpdate = mongo.findById( person["_id"], collection ); 175 | 176 | writeOutput("

Updated person by criteria with applySet=false. Notice it OVERWROTE the entire document

"); 177 | writeDump(var = person, label="Original", expand=false); 178 | writeDump(var = afterUpdate, label = "After update without using $set", expand=false); 179 | 180 | 181 | //updating multiple documents 182 | mongo.saveAll( 183 | [{NAME = "EmoHipster", AGE=16}, 184 | {NAME = "EmoHipster", AGE=15}, 185 | {NAME = "EmoHipster", AGE=18}], 186 | collection 187 | ); 188 | 189 | update = {NAME = "Oldster", AGE=76, REALIZED="tempus fugit"}; 190 | query = {NAME = "EmoHipster"}; 191 | 192 | mongo.update( doc = update, query = query, 193 | multi=true, 194 | collectionName = collection ); 195 | 196 | oldsters = mongo.query( collection ).$eq("NAME","Oldster").search().asArray(); 197 | 198 | writeOutput("

Updating multiple documents

"); 199 | writeDump( var=oldsters, label="Even EmoHipsters get old some day", expand="false"); 200 | 201 | //perform an $inc update 202 | cast = [{NAME = "Wesley", LIFELEFT=50, TORTUREMACHINE=true}, 203 | {NAME = "Spaniard", LIFELEFT=42, TORTUREMACHINE=false}, 204 | {NAME = "Giant", LIFELEFT=6, TORTUREMACHINE=false}, 205 | {NAME = "Poor Forest Walker", LIFELEFT=60, TORTUREMACHINE=true}]; 206 | 207 | mongo.saveAll( cast, collection ); 208 | 209 | suckLifeOut = {"$inc" = {LIFELEFT = -1}}; 210 | victims = {TORTUREMACHINE = true}; 211 | mongo.update( doc = suckLifeOut, query = victims, multi = true, collectionName = collection ); 212 | 213 | rugenVictims = mongo.query( collection ).$eq("TORTUREMACHINE", true).search().asArray(); 214 | 215 | writeOutput("

Atomically incrementing with $inc

"); 216 | writeDump( var = cast, label="Before the movie started", expand=false); 217 | writeDump( var = rugenVictims, label="Instead of sucking water, I'm sucking life", expand=false); 218 | 219 | 220 | //Upserting 221 | doc = { 222 | NAME = "Marc", 223 | BIKE = "Felt", 224 | JOYFUL = true 225 | }; 226 | mongo.save(doc = doc, collectionName = collection); 227 | 228 | writeOutput("

Upserted document after saving initially

"); 229 | writeDump( var = doc, label = "Upserted doc: #doc['_id'].toString()#", expand = false); 230 | 231 | doc.WANTSSTOGIE = true; 232 | mongo.save(doc = doc, collectionName = collection); 233 | 234 | writeOutput("

Upserted document after updating

"); 235 | writeDump( var = doc, label = "Upserted doc: #doc['_id'].toString()#", expand = false); 236 | 237 | 238 | 239 | 240 | 241 | //findAndModify: Great for Queuing! 242 | //insert docs into a work queue; find the first 'pending' one and modify it to 'running' 243 | mongo.remove( {}, "tasks" ); 244 | jobs = [ 245 | {STATUS = 'P', N = 1, DATA = 'Let it be'}, 246 | {STATUS = 'P', N = 2, DATA = 'Hey Jude!'}, 247 | {STATUS = 'P', N = 3, DATA = 'Ebony and Ivory'}, 248 | {STATUS = 'P', N = 4, DATA = 'Bang your head'} 249 | ]; 250 | mongo.saveAll( jobs, "tasks" ); 251 | 252 | query = {STATUS = 'P'}; 253 | update = {STATUS = 'R', started = now(), owner = cgi.server_name}; 254 | 255 | nowScheduled = mongo.findAndModify( query = query, update = update, 256 | sort = "N", collectionName = "tasks" ); 257 | 258 | writeOutput("

findAndModify()

"); 259 | writeDump(var=nowScheduled, label="findAndModify", expand="false"); 260 | 261 | writeOutput("

Indexes

"); 262 | //here's how to add indexes onto collections for faster querying 263 | mongo.ensureIndex( ["NAME"], collection ); 264 | mongo.ensureIndex( ["BIKE"], collection ); 265 | mongo.ensureIndex( ["KIDS.AGE"], collection ); 266 | writeDump(var=mongo.getIndexes(collection), label="Indexes", expand="false"); 267 | 268 | 269 | 270 | //show how you get timestamp creation on all documents, for free, when using the default ObjectID 271 | mongoUtil = mongo.getMongoUtil(); 272 | all = mongo.query( collection ).search().asArray(); 273 | first = all[1]; 274 | last = all[ arrayLen(all) ]; 275 | writeOutput("

Timestamps from Doc

"); 276 | writeOutput("Timestamp on first doc: #first['_id'].getTime()# = #mongoUtil.getDateFromDoc(first)#
"); 277 | writeOutput("Timestamp on last doc: #last['_id'].getTime()# = #mongoUtil.getDateFromDoc(last)#
"); 278 | 279 | //close the Mongo instance. Very important! 280 | mongo.close(); 281 | 282 | 283 | function showResult( searchResult, label ){ 284 | writeOutput("

#label#

"); 285 | writeDump( var=searchResult.asArray(), label=label & '(Result from MongoDB)', expand="false" ); 286 | writeOutput( "
Total #label# in this result, accounting for skip and limit: " & searchResult.size() ); 287 | writeOutput( "
Total #label#, ignoring skip and limit: " & searchResult.totalCount() ); 288 | writeOutput( "
Query: " & searchResult.getQuery().toString() ); 289 | writeOutput( "
Sort: " & searchResult.getSort().toString() & "
"); 290 | } 291 | 292 |
293 | -------------------------------------------------------------------------------- /examples/initMongo.cfm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | if( url.useJavaLoader ){ 10 | //the fastest way to try out cfmongodb is using Mark Mandel's javaloader, which we ship with cfmongodb. Thanks Mark! 11 | //http://javaloader.riaforge.org 12 | 13 | //use the cfmongodb javaloader factory 14 | javaloaderFactory = createObject('component','cfmongodb.core.JavaloaderFactory').init(); 15 | 16 | //create a default MongoConfig instance; in your real apps, you'll create an object that extends MongoConfig and put your environment specific config in there 17 | //here we initialize it with a db named 'mongorocks' 18 | mongoConfig = createObject('component','cfmongodb.core.MongoConfig').init(dbName=variables.dbName, mongoFactory=javaloaderFactory); 19 | } 20 | else 21 | { 22 | mongoConfig = createObject('component','cfmongodb.core.MongoConfig').init(dbName=variables.dbName); 23 | } 24 | 25 | //initialize the core cfmongodb Mongo object 26 | mongo = createObject('component','cfmongodb.core.Mongo').init(mongoConfig); 27 | 28 | -------------------------------------------------------------------------------- /java/.gitignore: -------------------------------------------------------------------------------- 1 | /bin 2 | -------------------------------------------------------------------------------- /java/src/com/mongodb/CFBasicDBObject.java: -------------------------------------------------------------------------------- 1 | package com.mongodb; 2 | 3 | import java.util.Map; 4 | 5 | import net.marcesher.CFStrictTyper; 6 | import net.marcesher.Typer; 7 | 8 | public class CFBasicDBObject extends com.mongodb.BasicDBObject{ 9 | 10 | private static final long serialVersionUID = 1L; 11 | private Typer typer = CFStrictTyper.getInstance(); 12 | 13 | public static BasicDBObject newInstance(){ 14 | return new CFBasicDBObject(); 15 | } 16 | public static BasicDBObject newInstance(Typer typer){ 17 | return new CFBasicDBObject(typer); 18 | } 19 | 20 | public CFBasicDBObject(){ 21 | } 22 | 23 | //in the event that someone wishes to write and inject their own typer object 24 | public CFBasicDBObject(Typer typer){ 25 | this.typer = typer; 26 | } 27 | 28 | public CFBasicDBObject(String key, Object value){ 29 | put(key, value); 30 | } 31 | 32 | public CFBasicDBObject(CFBasicDBObject other){ 33 | putAll((Map)other); 34 | } 35 | 36 | public CFBasicDBObject(Map map) { 37 | putAll(map); 38 | } 39 | 40 | public Object put(String key, Object val){ 41 | super.put(key, typer.toJavaType(val)); 42 | return this; 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /java/src/com/mongodb/CFBasicDBObjectBuilder.java: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 4 | * NOTE: I copied this source from the java api version 2.2 and replaced "BasicDBObject" with "CFBasicDBObject" 5 | * 6 | * 7 | * 8 | * Copyright (C) 2008 10gen Inc. 9 | * 10 | * Licensed under the Apache License, Version 2.0 (the "License"); 11 | * you may not use this file except in compliance with the License. 12 | * You may obtain a copy of the License at 13 | * 14 | * http://www.apache.org/licenses/LICENSE-2.0 15 | * 16 | * Unless required by applicable law or agreed to in writing, software 17 | * distributed under the License is distributed on an "AS IS" BASIS, 18 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | * See the License for the specific language governing permissions and 20 | * limitations under the License. 21 | */ 22 | 23 | package com.mongodb; 24 | 25 | import java.util.*; 26 | 27 | /** 28 | * utility for building objects 29 | * example: 30 | * BasicDBObjectBuilder.start().add( "name" , "eliot" ).add( "number" , 17 ).get() 31 | */ 32 | public class CFBasicDBObjectBuilder extends BasicDBObjectBuilder { 33 | 34 | public static BasicDBObjectBuilder newInstance(){ 35 | return new CFBasicDBObjectBuilder(); 36 | } 37 | 38 | public CFBasicDBObjectBuilder(){ 39 | _stack = new LinkedList(); 40 | _stack.add( new CFBasicDBObject() ); 41 | } 42 | 43 | public static BasicDBObjectBuilder start(){ 44 | return newInstance(); 45 | } 46 | 47 | public static BasicDBObjectBuilder start( String k , Object val ){ 48 | return (newInstance()).add( k , val ); 49 | } 50 | 51 | /** 52 | * Creates an object builder from an existing map. 53 | * @param m map to use 54 | * @return the new builder 55 | */ 56 | public static BasicDBObjectBuilder start(Map m){ 57 | BasicDBObjectBuilder b = newInstance(); 58 | Iterator i = m.entrySet().iterator(); 59 | while (i.hasNext()) { 60 | Map.Entry entry = i.next(); 61 | b.add(entry.getKey().toString(), entry.getValue()); 62 | } 63 | return b; 64 | } 65 | 66 | /** 67 | * @return returns itself so you can chain .append( "a" , 1 ).add( "b" , 1 ) 68 | */ 69 | public CFBasicDBObjectBuilder append( String key , Object val ){ 70 | _cur().put( key , val ); 71 | return this; 72 | } 73 | 74 | 75 | /** 76 | * @return returns itself so you can chain .add( "a" , 1 ).add( "b" , 1 ) 77 | */ 78 | public CFBasicDBObjectBuilder add( String key , Object val ){ 79 | _cur().put( key , val ); 80 | return this; 81 | } 82 | 83 | public CFBasicDBObjectBuilder push( String key ){ 84 | CFBasicDBObject o = new CFBasicDBObject(); 85 | _cur().put( key , o ); 86 | _stack.addLast( o ); 87 | return this; 88 | } 89 | 90 | public CFBasicDBObjectBuilder pop(){ 91 | if ( _stack.size() <= 1 ) 92 | throw new IllegalArgumentException( "can't pop last element" ); 93 | _stack.removeLast(); 94 | return this; 95 | } 96 | 97 | public DBObject get(){ 98 | return _stack.getFirst(); 99 | } 100 | 101 | private DBObject _cur(){ 102 | return _stack.getLast(); 103 | } 104 | 105 | private final LinkedList _stack; 106 | 107 | } 108 | -------------------------------------------------------------------------------- /java/src/net/marcesher/CFStrictTyper.java: -------------------------------------------------------------------------------- 1 | package net.marcesher; 2 | 3 | import java.util.Iterator; 4 | import java.util.List; 5 | import java.util.Map; 6 | import java.util.Vector; 7 | 8 | 9 | public class CFStrictTyper implements Typer { 10 | 11 | private final static Typer instance = new CFStrictTyper(); 12 | 13 | public static Typer getInstance(){ 14 | return instance; 15 | } 16 | 17 | private CFStrictTyper(){} 18 | 19 | /* (non-Javadoc) 20 | * @see com.mongodb.Typer#toJavaType(java.lang.Object) 21 | */ 22 | @Override 23 | public Object toJavaType(Object value){ 24 | if( value == null ) return ""; 25 | 26 | if(value instanceof java.lang.String){ 27 | return handleSimpleValue(value); 28 | } else if ( value instanceof List ){ 29 | return handleArray(value); 30 | } else if( value instanceof Map ){ 31 | return handleMap(value); 32 | } 33 | 34 | return value; 35 | } 36 | 37 | public Object handleSimpleValue(Object value) { 38 | String stringValue = (java.lang.String) value; 39 | String stringValueLowerCase = stringValue.toLowerCase(); 40 | 41 | //CF booleans 42 | if( stringValueLowerCase.equals("false") ) return false; 43 | if( stringValueLowerCase.equals("true") ) return true; 44 | 45 | //CF numbers 46 | //my testing showed that it was faster to let these fall through rather than check for alpha characters via string.matches() and then parse the numbers. 47 | try { 48 | return Integer.parseInt(stringValue); 49 | } catch (Exception e) { 50 | //nothing; it's not an int 51 | } 52 | 53 | try { 54 | return Long.parseLong(stringValue); 55 | } catch (Exception e){ 56 | //nothing; it's not a long 57 | } 58 | 59 | try { 60 | return Float.parseFloat(stringValue); 61 | } catch (Exception e) { 62 | //nothing; it's not a float 63 | } 64 | return value; 65 | } 66 | 67 | public Object handleArray(Object value) { 68 | try { 69 | List array = (List) value; 70 | Vector newArray = new Vector(); 71 | for (Iterator iterator = array.iterator(); iterator.hasNext();) { 72 | newArray.add( toJavaType((Object) iterator.next()) ); 73 | } 74 | return newArray; 75 | } catch (Exception e) { 76 | System.out.println("Exception creating DBObject from Array: " +e.toString()); 77 | return value; 78 | } 79 | } 80 | 81 | public Object handleMap(Object value) { 82 | try { 83 | Map map = (Map) value; 84 | Map ts = new TypedStruct( map, instance ); 85 | return ts ; 86 | } catch (Exception e) { 87 | System.out.println("Exception creating DBObject from Map: " + e.toString()); 88 | return value; 89 | } 90 | } 91 | 92 | } 93 | -------------------------------------------------------------------------------- /java/src/net/marcesher/NoTyper.java: -------------------------------------------------------------------------------- 1 | package net.marcesher; 2 | 3 | public class NoTyper implements Typer { 4 | 5 | private final static Typer instance = new NoTyper(); 6 | 7 | public static Typer getInstance(){ 8 | return instance; 9 | } 10 | 11 | @Override 12 | public Object toJavaType(Object val) { 13 | return val; 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /java/src/net/marcesher/TypedStruct.java: -------------------------------------------------------------------------------- 1 | package net.marcesher; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | 7 | public class TypedStruct extends HashMap { 8 | 9 | private Typer typer = CFStrictTyper.getInstance(); 10 | private static final long serialVersionUID = 1L; 11 | 12 | //factory methods. This way, you can create an instance of a TypedStruct, and then use that instance to get new instances. This improves performance when working with javaloader, because you only need to have javaloader create a single instance of TypedStruct, and from there you can use that instance to create new TypedStructs 13 | public TypedStruct newInstance(){ 14 | return new TypedStruct(); 15 | } 16 | 17 | public TypedStruct newInstance(Map other){ 18 | return new TypedStruct(other); 19 | } 20 | 21 | public TypedStruct newInstance(Typer typer){ 22 | return new TypedStruct(typer); 23 | } 24 | 25 | public TypedStruct newInstance(Map other, Typer typer){ 26 | return new TypedStruct(other, typer); 27 | } 28 | 29 | //Constructors 30 | public TypedStruct(){} 31 | 32 | public TypedStruct(Map other){ 33 | putAll(other); 34 | } 35 | 36 | public TypedStruct(Typer typer){ 37 | this.typer = typer; 38 | } 39 | 40 | public TypedStruct(Map other, Typer typer){ 41 | this.typer = typer; 42 | putAll(other); 43 | } 44 | 45 | //Overrides 46 | public Object put(Object key, Object val){ 47 | super.put(key, typer.toJavaType(val)); 48 | return this; 49 | } 50 | 51 | public Object append(String key, Object val){ 52 | return put(key, val); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /java/src/net/marcesher/Typer.java: -------------------------------------------------------------------------------- 1 | package net.marcesher; 2 | 3 | public interface Typer { 4 | 5 | public abstract Object toJavaType(Object val); 6 | 7 | } -------------------------------------------------------------------------------- /lib/cfmongodb.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/virtix/cfmongodb/214c58592d402bb4a31fb9cbf125e2d53360482c/lib/cfmongodb.jar -------------------------------------------------------------------------------- /lib/javaloader/.settings/org.eclipse.core.resources.prefs: -------------------------------------------------------------------------------- 1 | #Wed Sep 22 16:48:25 EDT 2010 2 | eclipse.preferences.version=1 3 | encoding/=UTF-8 4 | -------------------------------------------------------------------------------- /lib/javaloader/JavaLoader.cfc: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | 16 | instance = StructNew(); 17 | instance.static.uuid = "A0608BEC-0AEB-B46A-0E1E1EC5F3CE7C9C"; 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | initUseJavaProxyCFC(); 34 | 35 | if(arguments.loadColdFusionClassPath) 36 | { 37 | //arguments.parentClassLoader = createObject("java", "java.lang.Thread").currentThread().getContextClassLoader(); 38 | //can't use above, as doesn't work in some... things 39 | 40 | arguments.parentClassLoader = getPageContext().getClass().getClassLoader(); 41 | 42 | //arguments.parentClassLoader = createObject("java", "java.lang.ClassLoader").getSystemClassLoader(); 43 | //can't use the above, it doesn't have the CF stuff in it. 44 | } 45 | 46 | setClassLoadPaths(arguments.loadPaths); 47 | setParentClassLoader(arguments.parentClassLoader); 48 | 49 | ensureNetworkClassLoaderOnServerScope(); 50 | 51 | loadClasses(); 52 | 53 | if(structKeyExists(arguments, "sourceDirectories") AND ArrayLen(arguments.sourceDirectories)) 54 | { 55 | setJavaCompiler(createObject("component", "JavaCompiler").init(arguments.compileDirectory)); 56 | setSourceDirectories(arguments.sourceDirectories); 57 | setCompileDirectory(arguments.compileDirectory); 58 | 59 | setTrustedSource(arguments.trustedSource); 60 | 61 | compileSource(); 62 | 63 | setSourceLastModified(calculateSourceLastModified()); 64 | 65 | //do the method switching for non-trusted source 66 | if(NOT arguments.trustedSource) 67 | { 68 | variables.createWithoutCheck = variables.create; 69 | 70 | StructDelete(this, "create"); 71 | StructDelete(variables, "create"); 72 | 73 | this.create = variables.createWithSourceCheck; 74 | } 75 | } 76 | 77 | return this; 78 | 79 | 80 | 81 | 82 | 83 | 84 | try 85 | { 86 | //do this in one line just for speed. 87 | return createJavaProxy(getURLClassLoader().loadClass(arguments.className)); 88 | } 89 | catch(java.lang.ClassNotFoundException exc) 90 | { 91 | throwException("javaloader.ClassNotFoundException", "The requested class could not be found.", "The requested class '#arguments.className#' could not be found in the loaded jars/directories."); 92 | } 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | var dateLastModified = calculateSourceLastModified(); 112 | 113 | /* 114 | If the source has changed in any way, recompile and load 115 | */ 116 | if(dateCompare(dateLastModified, getSourceLastModified()) eq 1) 117 | { 118 | loadClasses(); 119 | compileSource(); 120 | } 121 | 122 | //if all the comilation goes according to plan, set the date last modified 123 | setSourceLastModified(dateLastModified); 124 | 125 | return createWithoutCheck(argumentCollection=arguments); 126 | 127 | 128 | 129 | 130 | 131 | var iterator = getClassLoadPaths().iterator(); 132 | var file = 0; 133 | var classLoader = 0; 134 | var networkClassLoaderClass = 0; 135 | var networkClassLoaderProxy = 0; 136 | 137 | networkClassLoaderClass = getServerURLClassLoader().loadClass("com.compoundtheory.classloader.NetworkClassLoader"); 138 | 139 | networkClassLoaderProxy = createJavaProxy(networkClassLoaderClass); 140 | 141 | if(isObject(getParentClassLoader())) 142 | { 143 | classLoader = networkClassLoaderProxy.init(getParentClassLoader()); 144 | } 145 | else 146 | { 147 | classLoader = networkClassLoaderProxy.init(); 148 | } 149 | 150 | while(iterator.hasNext()) 151 | { 152 | file = createObject("java", "java.io.File").init(iterator.next()); 153 | if(NOT file.exists()) 154 | { 155 | throwException("javaloader.PathNotFoundException", "The path you have specified could not be found", file.getAbsolutePath() & " does not exist"); 156 | } 157 | 158 | classLoader.addUrl(file.toURL()); 159 | } 160 | 161 | setURLClassLoader(classLoader); 162 | 163 | 164 | 165 | 166 | 167 | var dir = 0; 168 | var path = 0; 169 | 170 | var paths = 0; 171 | var file = 0; 172 | var counter = 1; 173 | var len = 0; 174 | var directories = 0; 175 | 176 | //do check to see if the compiled jar is already there 177 | var jarName = calculateJarName(getSourceDirectories()); 178 | var jar = getCompileDirectory() & "/" & jarName; 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | //first we copy the source to our tmp dir 199 | directories = getSourceDirectories(); 200 | len = arraylen(directories); 201 | for(; counter lte len; counter = counter + 1) 202 | { 203 | dir = directories[counter]; 204 | directoryCopy(dir, path); 205 | } 206 | 207 | //then we compile it, and grab that jar 208 | 209 | paths = ArrayNew(1); //have to write it this way so CF7 compiles 210 | ArrayAppend(paths, path); 211 | 212 | jar = getJavaCompiler().compile(paths, getURLClassLoader(), jarName); 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | var file = hash(arrayToList(arguments.directoryArray)) & ".jar"; 244 | 245 | return file; 246 | 247 | 248 | 249 | 250 | 251 | var lastModified = createDate(1900, 1, 1); 252 | var dir = 0; 253 | var qLastModified = 0; 254 | var directories = getSourceDirectories(); 255 | var len = arraylen(directories); 256 | var counter = 0; 257 | 258 | 259 | 260 | 261 | 262 | 266 | 267 | //it's possible there are no source files. 268 | if(qLastModified.recordCount) 269 | { 270 | //get the latest date modified 271 | if(dateCompare(lastModified, qlastModified.dateLastModified) eq -1) 272 | { 273 | /* 274 | This is here, because cfdirectory only ever gives you minute accurate modified 275 | date, which is not good enough. 276 | */ 277 | lastModified = createObject("java", "java.util.Date").init(createObject("java", "java.io.File").init(qLastModified.directory & "/" & qLastModified.name).lastModified()); 278 | } 279 | } 280 | else 281 | { 282 | lastModified = Now(); 283 | } 284 | 285 | 286 | 287 | 288 | 289 | 290 | 293 | 294 | var Class = createObject("java", "java.lang.Class"); 295 | var Array = createObject("java", "java.lang.reflect.Array"); 296 | var jars = queryJars(); 297 | var iterator = jars.iterator(); 298 | var file = 0; 299 | var urls = Array.newInstance(Class.forName("java.net.URL"), ArrayLen(jars)); 300 | var counter = 0; 301 | var urlClassLoader = 0; 302 | var key = instance.static.uuid & "." & getVersion(); 303 | 304 | 305 | 306 | 307 | 308 | if(NOT StructKeyExists(server, key)) 309 | { 310 | while(iterator.hasNext()) 311 | { 312 | Array.set(urls, counter, createObject("java", "java.io.File").init(iterator.next()).toURL()); 313 | counter = counter + 1; 314 | } 315 | 316 | urlClassLoader = createObject("java", "java.net.URLClassLoader").init(urls); 317 | 318 | //put it on the server scope 319 | server[key] = urlClassLoader; 320 | } 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | return createObject("java", "coldfusion.runtime.java.JavaProxy").init(arguments.class); 330 | 331 | 332 | 333 | 334 | 335 | 336 | return createObject("component", "JavaProxy")._init(arguments.class); 337 | 338 | 339 | 340 | 341 | 342 | try 343 | { 344 | createObject("java", "coldfusion.runtime.java.JavaProxy"); 345 | } 346 | catch(Object exc) 347 | { 348 | //do method replacement, as it will be much faster long term 349 | variables.createJavaProxy = variables.createJavaProxyCFC; 350 | } 351 | 352 | 353 | 354 | 355 | 356 | var qJars = 0; 357 | //the path to my jar library 358 | var path = getDirectoryFromPath(getMetaData(this).path) & "lib/"; 359 | var jarList = ""; 360 | var aJars = ArrayNew(1); 361 | var libName = 0; 362 | 363 | 364 | 365 | 366 | 367 | libName = ListGetAt(name, 1, "-"); 368 | //let's not use the lib's that have the same name, but a lower datestamp 369 | if(NOT ListFind(jarList, libName)) 370 | { 371 | ArrayAppend(aJars, path & "/" & name); 372 | jarList = ListAppend(jarList, libName); 373 | } 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | -------------------------------------------------------------------------------- /lib/javaloader/JavaProxy.cfc: -------------------------------------------------------------------------------- 1 | 22 | 23 | 24 | 25 | 29 | 30 | 31 | 32 | 33 | var classLoader = createObject("java", "java.lang.ClassLoader").getSystemClassLoader(); 34 | var objectClass = classLoader.loadClass("java.lang.Object"); 35 | 36 | _setArray(createObject("java", "java.lang.reflect.Array")); 37 | 38 | _setClassMethod(objectClass.getMethod("getClass", JavaCast("null", 0))); 39 | 40 | _setObjectClass(objectClass); 41 | 42 | _setClass(arguments.class); 43 | 44 | _setModifier(createObject("java", "java.lang.reflect.Modifier")); 45 | 46 | _setStaticFields(); 47 | 48 | _initMethodCollection(); 49 | 50 | return this; 51 | 52 | 53 | 54 | 55 | 56 | var constructor = 0; 57 | var instance = 0; 58 | 59 | //make sure we only ever have one instance 60 | if(_hasClassInstance()) 61 | { 62 | return _getClassInstance(); 63 | } 64 | 65 | constructor = _resolveMethodByParams("Constructor", _getClass().getConstructors(), arguments); 66 | 67 | instance = constructor.newInstance(_buildArgumentArray(arguments)); 68 | 69 | _setClassInstance(instance); 70 | 71 | return _getClassInstance(); 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | var method = _findMethod(arguments.missingMethodName, arguments.missingMethodArguments); 81 | 82 | if(_getModifier().isStatic(method.getModifiers())) 83 | { 84 | return method.invoke(JavaCast("null", 0), _buildArgumentArray(arguments.missingMethodArguments)); 85 | } 86 | else 87 | { 88 | if(NOT _hasClassInstance()) 89 | { 90 | //run the default constructor, just like in normal CF, if there is no instance 91 | init(); 92 | } 93 | 94 | return method.invoke(_getClassInstance(), _buildArgumentArray(arguments.missingMethodArguments)); 95 | } 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | var fields = _getClass().getFields(); 108 | var counter = 1; 109 | var len = ArrayLen(fields); 110 | var field = 0; 111 | 112 | for(; counter <= len; counter++) 113 | { 114 | field = fields[counter]; 115 | if(_getModifier().isStatic(field.getModifiers())) 116 | { 117 | this[field.getName()] = field.get(JavaCast("null", 0)); 118 | } 119 | } 120 | 121 | 122 | 123 | 124 | 125 | 126 | var len = StructCount(arguments); 127 | var objArray = _getArray().newInstance(_getObjectClass(), len); 128 | var counter = 1; 129 | var obj = 0; 130 | 131 | for(; counter <= len; counter++) 132 | { 133 | obj = arguments[counter]; 134 | _getArray().set(objArray, counter - 1, obj); 135 | } 136 | 137 | return objArray; 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | var decision = 0; 146 | 147 | if(StructKeyExists(_getMethodCollection(), arguments.methodName)) 148 | { 149 | decision = StructFind(_getMethodCollection(), arguments.methodName); 150 | 151 | //if there is only one option, try it, it's only going to throw a runtime exception if it doesn't work. 152 | if(ArrayLen(decision) == 1) 153 | { 154 | return decision[1]; 155 | } 156 | else 157 | { 158 | return _resolveMethodByParams(arguments.methodName, decision, arguments.methodArgs); 159 | } 160 | } 161 | 162 | throwException("JavaProxy.MethodNotFoundException", "Could not find the designated method", "Could not find the method '#arguments.methodName#' in the class #_getClass().getName()#"); 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | var decisionLen = ArrayLen(arguments.decision); 172 | var method = 0; 173 | var counter = 1; 174 | var argLen = ArrayLen(arguments.methodArgs); 175 | var parameters = 0; 176 | var paramLen = 0; 177 | var pCounter = 0; 178 | var param = 0; 179 | var class = 0; 180 | var found = true; 181 | 182 | for(; counter <= decisionLen; counter++) 183 | { 184 | method = arguments.decision[counter]; 185 | parameters = method.getParameterTypes(); 186 | paramLen = ArrayLen(parameters); 187 | 188 | found = true; 189 | 190 | if(argLen eq paramLen) 191 | { 192 | for(pCounter = 1; pCounter <= paramLen AND found; pCounter++) 193 | { 194 | param = parameters[pCounter]; 195 | class = _getClassMethod().invoke(arguments.methodArgs[pCounter], JavaCast("null", 0)); 196 | 197 | if(param.isAssignableFrom(class)) 198 | { 199 | found = true; 200 | } 201 | else if(param.isPrimitive()) //if it's a primitive, it can be mapped to object primtive classes 202 | { 203 | if(param.getName() eq "boolean" AND class.getName() eq "java.lang.Boolean") 204 | { 205 | found = true; 206 | } 207 | else if(param.getName() eq "int" AND class.getName() eq "java.lang.Integer") 208 | { 209 | found = true; 210 | } 211 | else if(param.getName() eq "long" AND class.getName() eq "java.lang.Long") 212 | { 213 | found = true; 214 | } 215 | else if(param.getName() eq "float" AND class.getName() eq "java.lang.Float") 216 | { 217 | found = true; 218 | } 219 | else if(param.getName() eq "double" AND class.getName() eq "java.lang.Double") 220 | { 221 | found = true; 222 | } 223 | else if(param.getName() eq "char" AND class.getName() eq "java.lang.Character") 224 | { 225 | found = true; 226 | } 227 | else if(param.getName() eq "byte" AND class.getName() eq "java.lang.Byte") 228 | { 229 | found = true; 230 | } 231 | else if(param.getName() eq "short" AND class.getName() eq "java.lang.Short") 232 | { 233 | found = true; 234 | } 235 | else 236 | { 237 | found = false; 238 | } 239 | } 240 | else 241 | { 242 | found = false; 243 | } 244 | } 245 | 246 | if(found) 247 | { 248 | return method; 249 | } 250 | } 251 | } 252 | 253 | throwException("JavaProxy.MethodNotFoundException", "Could not find the designated method", "Could not find the method '#arguments.methodName#' in the class #_getClass().getName()#"); 254 | 255 | 256 | 257 | 258 | 259 | var methods = _getClass().getMethods(); 260 | var len = ArrayLen(methods); 261 | var counter = 1; 262 | var method = 0; 263 | 264 | _setMethodCollection(StructNew()); 265 | 266 | for(; counter <= len; counter++) 267 | { 268 | method = methods[counter]; 269 | 270 | if(NOT StructKeyExists(_getMethodCollection(), method.getName())) 271 | { 272 | StructInsert(_getMethodCollection(), method.getName(), ArrayNew(1)); 273 | } 274 | 275 | ArrayAppend(StructFind(_getMethodCollection(), method.getName()), method); 276 | } 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | -------------------------------------------------------------------------------- /lib/javaloader/lib/classloader-20100119110136.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/virtix/cfmongodb/214c58592d402bb4a31fb9cbf125e2d53360482c/lib/javaloader/lib/classloader-20100119110136.jar -------------------------------------------------------------------------------- /lib/javaloader/lib/classloader-src.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/virtix/cfmongodb/214c58592d402bb4a31fb9cbf125e2d53360482c/lib/javaloader/lib/classloader-src.zip -------------------------------------------------------------------------------- /lib/javaloader/licence.txt: -------------------------------------------------------------------------------- 1 | Common Public License Version 1.0 2 | 3 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS COMMON PUBLIC 4 | LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM 5 | CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 6 | 7 | 1. DEFINITIONS 8 | 9 | "Contribution" means: 10 | 11 | a) in the case of the initial Contributor, the initial code and 12 | documentation distributed under this Agreement, and 13 | 14 | b) in the case of each subsequent Contributor: 15 | 16 | i) changes to the Program, and 17 | 18 | ii) additions to the Program; 19 | 20 | where such changes and/or additions to the Program originate from and are 21 | distributed by that particular Contributor. A Contribution 'originates' from a 22 | Contributor if it was added to the Program by such Contributor itself or anyone 23 | acting on such Contributor's behalf. Contributions do not include additions to 24 | the Program which: (i) are separate modules of software distributed in 25 | conjunction with the Program under their own license agreement, and (ii) are not 26 | derivative works of the Program. 27 | 28 | "Contributor" means any person or entity that distributes the Program. 29 | 30 | "Licensed Patents " mean patent claims licensable by a Contributor which are 31 | necessarily infringed by the use or sale of its Contribution alone or when 32 | combined with the Program. 33 | 34 | "Program" means the Contributions distributed in accordance with this Agreement. 35 | 36 | "Recipient" means anyone who receives the Program under this Agreement, 37 | including all Contributors. 38 | 39 | 2. GRANT OF RIGHTS 40 | 41 | a) Subject to the terms of this Agreement, each Contributor hereby grants 42 | Recipient a non-exclusive, worldwide, royalty-free copyright license to 43 | reproduce, prepare derivative works of, publicly display, publicly perform, 44 | distribute and sublicense the Contribution of such Contributor, if any, and such 45 | derivative works, in source code and object code form. 46 | 47 | b) Subject to the terms of this Agreement, each Contributor hereby grants 48 | Recipient a non-exclusive, worldwide, royalty-free patent license under Licensed 49 | Patents to make, use, sell, offer to sell, import and otherwise transfer the 50 | Contribution of such Contributor, if any, in source code and object code form. 51 | This patent license shall apply to the combination of the Contribution and the 52 | Program if, at the time the Contribution is added by the Contributor, such 53 | addition of the Contribution causes such combination to be covered by the 54 | Licensed Patents. The patent license shall not apply to any other combinations 55 | which include the Contribution. No hardware per se is licensed hereunder. 56 | 57 | c) Recipient understands that although each Contributor grants the licenses 58 | to its Contributions set forth herein, no assurances are provided by any 59 | Contributor that the Program does not infringe the patent or other intellectual 60 | property rights of any other entity. Each Contributor disclaims any liability to 61 | Recipient for claims brought by any other entity based on infringement of 62 | intellectual property rights or otherwise. As a condition to exercising the 63 | rights and licenses granted hereunder, each Recipient hereby assumes sole 64 | responsibility to secure any other intellectual property rights needed, if any. 65 | For example, if a third party patent license is required to allow Recipient to 66 | distribute the Program, it is Recipient's responsibility to acquire that license 67 | before distributing the Program. 68 | 69 | d) Each Contributor represents that to its knowledge it has sufficient 70 | copyright rights in its Contribution, if any, to grant the copyright license set 71 | forth in this Agreement. 72 | 73 | 3. REQUIREMENTS 74 | 75 | A Contributor may choose to distribute the Program in object code form under its 76 | own license agreement, provided that: 77 | 78 | a) it complies with the terms and conditions of this Agreement; and 79 | 80 | b) its license agreement: 81 | 82 | i) effectively disclaims on behalf of all Contributors all warranties and 83 | conditions, express and implied, including warranties or conditions of title and 84 | non-infringement, and implied warranties or conditions of merchantability and 85 | fitness for a particular purpose; 86 | 87 | ii) effectively excludes on behalf of all Contributors all liability for 88 | damages, including direct, indirect, special, incidental and consequential 89 | damages, such as lost profits; 90 | 91 | iii) states that any provisions which differ from this Agreement are offered 92 | by that Contributor alone and not by any other party; and 93 | 94 | iv) states that source code for the Program is available from such 95 | Contributor, and informs licensees how to obtain it in a reasonable manner on or 96 | through a medium customarily used for software exchange. 97 | 98 | When the Program is made available in source code form: 99 | 100 | a) it must be made available under this Agreement; and 101 | 102 | b) a copy of this Agreement must be included with each copy of the Program. 103 | 104 | Contributors may not remove or alter any copyright notices contained within the 105 | Program. 106 | 107 | Each Contributor must identify itself as the originator of its Contribution, if 108 | any, in a manner that reasonably allows subsequent Recipients to identify the 109 | originator of the Contribution. 110 | 111 | 4. COMMERCIAL DISTRIBUTION 112 | 113 | Commercial distributors of software may accept certain responsibilities with 114 | respect to end users, business partners and the like. While this license is 115 | intended to facilitate the commercial use of the Program, the Contributor who 116 | includes the Program in a commercial product offering should do so in a manner 117 | which does not create potential liability for other Contributors. Therefore, if 118 | a Contributor includes the Program in a commercial product offering, such 119 | Contributor ("Commercial Contributor") hereby agrees to defend and indemnify 120 | every other Contributor ("Indemnified Contributor") against any losses, damages 121 | and costs (collectively "Losses") arising from claims, lawsuits and other legal 122 | actions brought by a third party against the Indemnified Contributor to the 123 | extent caused by the acts or omissions of such Commercial Contributor in 124 | connection with its distribution of the Program in a commercial product 125 | offering. The obligations in this section do not apply to any claims or Losses 126 | relating to any actual or alleged intellectual property infringement. In order 127 | to qualify, an Indemnified Contributor must: a) promptly notify the Commercial 128 | Contributor in writing of such claim, and b) allow the Commercial Contributor to 129 | control, and cooperate with the Commercial Contributor in, the defense and any 130 | related settlement negotiations. The Indemnified Contributor may participate in 131 | any such claim at its own expense. 132 | 133 | For example, a Contributor might include the Program in a commercial product 134 | offering, Product X. That Contributor is then a Commercial Contributor. If that 135 | Commercial Contributor then makes performance claims, or offers warranties 136 | related to Product X, those performance claims and warranties are such 137 | Commercial Contributor's responsibility alone. Under this section, the 138 | Commercial Contributor would have to defend claims against the other 139 | Contributors related to those performance claims and warranties, and if a court 140 | requires any other Contributor to pay any damages as a result, the Commercial 141 | Contributor must pay those damages. 142 | 143 | 5. NO WARRANTY 144 | 145 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN 146 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR 147 | IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, 148 | NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each 149 | Recipient is solely responsible for determining the appropriateness of using and 150 | distributing the Program and assumes all risks associated with its exercise of 151 | rights under this Agreement, including but not limited to the risks and costs of 152 | program errors, compliance with applicable laws, damage to or loss of data, 153 | programs or equipment, and unavailability or interruption of operations. 154 | 155 | 6. DISCLAIMER OF LIABILITY 156 | 157 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY 158 | CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, 159 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST 160 | PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 161 | STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 162 | OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS 163 | GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 164 | 165 | 7. GENERAL 166 | 167 | If any provision of this Agreement is invalid or unenforceable under applicable 168 | law, it shall not affect the validity or enforceability of the remainder of the 169 | terms of this Agreement, and without further action by the parties hereto, such 170 | provision shall be reformed to the minimum extent necessary to make such 171 | provision valid and enforceable. 172 | 173 | If Recipient institutes patent litigation against a Contributor with respect to 174 | a patent applicable to software (including a cross-claim or counterclaim in a 175 | lawsuit), then any patent licenses granted by that Contributor to such Recipient 176 | under this Agreement shall terminate as of the date such litigation is filed. In 177 | addition, if Recipient institutes patent litigation against any entity 178 | (including a cross-claim or counterclaim in a lawsuit) alleging that the Program 179 | itself (excluding combinations of the Program with other software or hardware) 180 | infringes such Recipient's patent(s), then such Recipient's rights granted under 181 | Section 2(b) shall terminate as of the date such litigation is filed. 182 | 183 | All Recipient's rights under this Agreement shall terminate if it fails to 184 | comply with any of the material terms or conditions of this Agreement and does 185 | not cure such failure in a reasonable period of time after becoming aware of 186 | such noncompliance. If all Recipient's rights under this Agreement terminate, 187 | Recipient agrees to cease use and distribution of the Program as soon as 188 | reasonably practicable. However, Recipient's obligations under this Agreement 189 | and any licenses granted by Recipient relating to the Program shall continue and 190 | survive. 191 | 192 | Everyone is permitted to copy and distribute copies of this Agreement, but in 193 | order to avoid inconsistency the Agreement is copyrighted and may only be 194 | modified in the following manner. The Agreement Steward reserves the right to 195 | publish new versions (including revisions) of this Agreement from time to time. 196 | No one other than the Agreement Steward has the right to modify this Agreement. 197 | IBM is the initial Agreement Steward. IBM may assign the responsibility to serve 198 | as the Agreement Steward to a suitable separate entity. Each new version of the 199 | Agreement will be given a distinguishing version number. The Program (including 200 | Contributions) may always be distributed subject to the version of the Agreement 201 | under which it was received. In addition, after a new version of the Agreement 202 | is published, Contributor may elect to distribute the Program (including its 203 | Contributions) under the new version. Except as expressly stated in Sections 204 | 2(a) and 2(b) above, Recipient receives no rights or licenses to the 205 | intellectual property of any Contributor under this Agreement, whether 206 | expressly, by implication, estoppel or otherwise. All rights in the Program not 207 | expressly granted under this Agreement are reserved. 208 | 209 | This Agreement is governed by the laws of the State of New York and the 210 | intellectual property laws of the United States of America. No party to this 211 | Agreement will bring a legal action under this Agreement more than one year 212 | after the cause of action arose. Each party waives its rights to a jury trial in 213 | any resulting litigation. 214 | -------------------------------------------------------------------------------- /lib/javaloader/readme.txt: -------------------------------------------------------------------------------- 1 | JavaLoader v1.0 2 | Author: Mark Mandel 3 | Date: 10 September 2010 4 | 5 | Documentation can now be found at: 6 | http://www.compoundtheory.com/javaloader/docs/ -------------------------------------------------------------------------------- /lib/javaloader/tags/directory.cfm: -------------------------------------------------------------------------------- 1 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /lib/mongo-2.4.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/virtix/cfmongodb/214c58592d402bb4a31fb9cbf125e2d53360482c/lib/mongo-2.4.jar -------------------------------------------------------------------------------- /lib/mxunit-ant.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/virtix/cfmongodb/214c58592d402bb4a31fb9cbf125e2d53360482c/lib/mxunit-ant.jar -------------------------------------------------------------------------------- /test/.gitignore: -------------------------------------------------------------------------------- 1 | /testresults 2 | -------------------------------------------------------------------------------- /test/AuthenticationTest.cfc: -------------------------------------------------------------------------------- 1 | 16 | 17 | 35 | 36 | 37 | 38 | import cfmongodb.core.*; 39 | 40 | variables.testDatabase = "cfmongodb_auth_tests"; 41 | variables.testCollection = "authtests"; 42 | variables.javaloaderFactory = createObject('component','cfmongodb.core.JavaloaderFactory').init(); 43 | variables.mongoConfig = createObject('component','cfmongodb.core.MongoConfig').init(dbName=variables.testDatabase, mongoFactory=javaloaderFactory); 44 | variables.mongoConfig.setAuthDetails("username", "verysecurepassword!"); 45 | 46 | function init_should_error_when_authentication_fails() { 47 | expectException("AuthenticationFailedException"); 48 | 49 | var mongo = createObject('component','cfmongodb.core.Mongo'); 50 | //we entirely spoof the authentication internals 51 | injectMethod(mongo, this, "isAuthenticationRequiredOverride", "isAuthenticationRequired"); 52 | injectMethod(mongo, this, "authenticateOverride", "authenticate"); 53 | 54 | mongo.init(mongoConfig); 55 | } 56 | 57 | function init_should_succeed_when_authentication_passes() { 58 | var mongo = createObject('component','cfmongodb.core.Mongo'); 59 | //we entirely spoof the authentication internals 60 | injectMethod(mongo, this, "isAuthenticationRequiredOverride", "isAuthenticationRequired"); 61 | injectMethod(mongo, this, "authenticateSuccessOverride", "authenticate"); 62 | 63 | mongo.init(mongoConfig); 64 | } 65 | 66 | function tearDown(){ 67 | 68 | var mongo = createObject('component','cfmongodb.core.Mongo').init(mongoConfig); 69 | try{ 70 | mongo.dropDatabase(); 71 | }catch(any e){ 72 | debug("error dropping database"); 73 | debug(e); 74 | } 75 | 76 | //close connection 77 | mongo.close(); 78 | 79 | } 80 | 81 | private function isAuthenticationRequiredOverride(){ return true; } 82 | private function authenticateOverride(){ return {authenticated=false, error={}}; } 83 | private function authenticateSuccessOverride(){ return {authenticated=true, error={}}; } 84 | 85 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /test/BaseTestCase.cfc: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /test/HttpAntRunner.cfc: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /test/IncludeExamplesTest.cfc: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /test/MongoTest.cfc: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | import cfmongodb.core.*; 11 | 12 | 13 | javaloaderFactory = createObject('component','cfmongodb.core.JavaloaderFactory').init(); 14 | mongoConfig = createObject('component','cfmongodb.core.MongoConfig').init(dbName="cfmongodb_tests", mongoFactory=javaloaderFactory); 15 | //mongoConfig = createObject('component','cfmongodb.core.MongoConfig').init(dbName="cfmongodb_tests"); 16 | 17 | 18 | function setUp(){ 19 | mongo = createObject('component','cfmongodb.core.Mongo').init(mongoConfig); 20 | col = 'people'; 21 | atomicCol = 'atomictests'; 22 | deleteCol = 'deletetests'; 23 | types = { 24 | 'number' = 100, 25 | 'negativefloat' = -987.097654, 26 | 'positivefloat' = 9654.5555555, 27 | 'israd' = true, 28 | 'stringwithnum' = 'string ending with 1', 29 | 'numbers' = [1,2,3], 30 | 'booleans' = [true, false], 31 | 'floats' = [1.3,2.59870,-148.27654] 32 | }; 33 | doc = { 34 | 'name'='unittest', 35 | 'address' = { 36 | 'street'='123 big top lane', 37 | 'city'='anytowne', 38 | 'state'='??', 39 | 'country'='USA' 40 | }, 41 | 'favorite-foods'=['popcicles','hot-dogs','ice-cream','cotton candy'], 42 | 'types' = types 43 | }; 44 | structAppend( doc, types ); 45 | } 46 | 47 | function tearDown(){ 48 | var delete = {"name"="unittest"}; 49 | var atomicDelete = {}; 50 | mongo.remove( delete, col ); 51 | 52 | mongo.close(); 53 | 54 | //mongo.remove(atomicDelete, atomicCol); 55 | } 56 | 57 | 58 | function deleteTest(){ 59 | mongo.getMongoDbCollection(deleteCol).drop(); 60 | mongo.ensureIndex(["somenumber"], deleteCol); 61 | mongo.ensureIndex(["name"], deleteCol); 62 | var doc = { 63 | 'name'='delete me', 64 | 'somenumber' = 1, 65 | 'address' = { 66 | 'street'='123 bye bye ln', 67 | 'city'='where-ever', 68 | 'state'='??', 69 | 'country'='USA' 70 | } 71 | }; 72 | 73 | doc['_id'] = mongo.save( doc, deleteCol ); 74 | //debug(doc); 75 | 76 | results = mongo.query(deleteCol).$eq('somenumber',1).search(); 77 | //debug(results.getQuery().toString()); 78 | //debug(results.asArray()); 79 | 80 | var writeResult = mongo.remove( doc, deleteCol ); 81 | results = mongo.query(deleteCol).$eq('name','delete me').search(); 82 | //debug(results.getQuery().toString()); 83 | assertEquals( 0, results.size() ); 84 | } 85 | 86 | 87 | function updateTest(){ 88 | var originalCount = mongo.query(col).$eq('name', 'bill' ).count(); 89 | var doc = { 90 | 'name'='jabber-walkie', 91 | 'address' = { 92 | 'street'='456 boom boom', 93 | 'city'='anytowne', 94 | 'state'='??', 95 | 'country'='USA' 96 | }, 97 | 'favorite-foods'=['munchies'] 98 | }; 99 | 100 | 101 | mongo.save(doc,col); 102 | results = mongo.query(col).startsWith('name','jabber').search(); 103 | 104 | 105 | //debug(results.getQuery().toString()); 106 | 107 | replace_this = results.asArray()[1]; 108 | debug(replace_this); 109 | replace_this['name'] = 'bill'; 110 | mongo.update( replace_this, col ); 111 | results = mongo.query(col).$eq('name', 'bill' ).search(); 112 | debug(results.asArray()); 113 | var finalSize = results.size(); 114 | //debug(finalSize); 115 | var writeResult = mongo.remove( replace_this, col ); 116 | 117 | assertEquals(originalCount+1, finalSize, "results should have been 1 but was #results.size()#" ); 118 | } 119 | 120 | 121 | function testSearch(){ 122 | var initial = mongo.query(col).startsWith('name','unittest').search().asArray(); 123 | //debug(initial); 124 | 125 | var addNew = 5; 126 | var people = createPeople( addNew, true ); 127 | var afterSave = mongo.query(col).startsWith('name','unittest').search().asArray(); 128 | 129 | assertEquals( arrayLen(afterSave), arrayLen(initial) + addNew ); 130 | } 131 | 132 | function distinct_should_return_array_of_distinct_values(){ 133 | var collection = "distincts"; 134 | var all = [ 135 | {val=1}, 136 | {val=1}, 137 | {val=2}, 138 | {val=1}, 139 | {val=100} 140 | ]; 141 | mongo.remove({}, collection); 142 | var initial = mongo.distinct("VAL", collection); 143 | assertEquals(0,arrayLen(initial)); 144 | 145 | mongo.saveAll( all, collection ); 146 | var distincts = mongo.distinct("VAL", collection); 147 | assertEquals(1, distincts[1]); 148 | assertEquals(2, distincts[2]); 149 | assertEquals(100, distincts[3]); 150 | } 151 | 152 | 153 | function save_should_add_id_to_doc(){ 154 | //debug(doc); 155 | id = mongo.save( doc, col ); 156 | assert( NOT isSimpleValue(id) ); 157 | mongo.remove( doc, col ); 158 | } 159 | 160 | function saveAll_should_return_immediately_if_no_docs_present(){ 161 | assertEquals( [], mongo.saveAll([],col) ); 162 | } 163 | 164 | function saveAll_should_save_ArrayOfDBObjects(){ 165 | var i = 1; 166 | var people = []; 167 | var u = mongo.getMongoUtil(); 168 | var purpose = "SaveAllDBObjectsTest"; 169 | for( i = 1; i <= 2; i++ ){ 170 | arrayAppend( people, u.toMongo( {"name"="unittest", "purpose"=purpose} ) ); 171 | } 172 | mongo.saveAll( people, col ); 173 | var result = mongo.query( col ).$eq("purpose",purpose).count(); 174 | assertEquals(2,result,"We inserted 2 pre-created BasicDBObjects with purpose #purpose# but only found #result#"); 175 | } 176 | 177 | function saveAll_should_save_ArrayOfStructs(){ 178 | var i = 1; 179 | var people = []; 180 | var purpose = "SaveAllStructsTest"; 181 | for( i = 1; i <= 2; i++ ){ 182 | arrayAppend( people, {"name"="unittest", "purpose"=purpose} ); 183 | } 184 | mongo.saveAll( people, col ); 185 | var result = mongo.query( col ).$eq("purpose",purpose).count(); 186 | assertEquals(2,result,"We inserted 2 structs with purpose #purpose# but only found #result#"); 187 | } 188 | 189 | function findById_should_return_doc_for_id(){ 190 | var id = mongo.save( doc, col ); 191 | 192 | var fetched = mongo.findById(id.toString(), col); 193 | assertEquals(id, fetched._id.toString()); 194 | } 195 | 196 | function search_sort_should_be_applied(){ 197 | var people = createPeople(5, true); 198 | var asc = mongo.query(col).$eq("name","unittest").search(); 199 | var desc = mongo.query(col).$eq("name","unittest").search(sort={"name"=-1}); 200 | 201 | var ascResults = asc.asArray(); 202 | var descResults = desc.asArray(); 203 | //debug( desc.getQuery().toString() ); 204 | 205 | //debug(ascResults); 206 | //debug(descResults); 207 | 208 | assertEquals( ascResults[1].age, descResults[ desc.size() ].age ); 209 | } 210 | 211 | function search_limit_should_be_applied(){ 212 | var people = createPeople(5, true); 213 | var limit = 2; 214 | 215 | var full = mongo.query(col).$eq("name","unittest").search(); 216 | var limited = mongo.query(col).$eq("name","unittest").search(limit=limit); 217 | assertEquals(limit, limited.size()); 218 | assertTrue( full.size() GT limited.size() ); 219 | } 220 | 221 | function search_skip_should_be_applied(){ 222 | var people = createPeople(5, true); 223 | var skip = 1; 224 | var full = mongo.query(col).$eq("name","unittest").search(); 225 | var skipped = mongo.query(col).$eq("name","unittest").search(skip=skip); 226 | 227 | assertEquals(full.asArray()[2] , skipped.asArray()[1], "lemme splain, Lucy: since we're skipping 1, then the first element of skipped should be the second element of full" ); 228 | } 229 | 230 | function count_should_consider_query(){ 231 | createPeople(2, true, "not unit test"); 232 | 233 | mongo.ensureIndex(["nowaythiscolumnexists"], col); 234 | var allresults = mongo.query(col).search(); 235 | //debug(allresults.size()); 236 | var all = mongo.query(col).count(); 237 | assertTrue( all GT 0 ); 238 | 239 | var none = mongo.query(col).$eq("nowaythiscolumnexists", "I'm no tree... I am an Ent!").count(); 240 | //debug(none); 241 | assertEquals( 0, none ); 242 | 243 | var people = createPeople(2, true); 244 | 245 | var some = mongo.query(col).$eq("name", "unittest").count(); 246 | all = mongo.query(col).count(); 247 | assertTrue( some GTE 2 ); 248 | assertTrue( some LT all, "Some [#some#] should have been less than all [#all#]"); 249 | } 250 | 251 | private function createPeople( count=5, save="true", name="unittest" ){ 252 | var i = 1; 253 | var people = []; 254 | for(i = 1; i LTE count; i++){ 255 | var person = { 256 | "name"=name, 257 | "age"=randRange(10,100), 258 | "now"=getTickCount(), 259 | "counter"=i, 260 | inprocess=false 261 | }; 262 | arrayAppend(people, person); 263 | } 264 | if(save){ 265 | mongo.saveAll(people, col); 266 | } 267 | return people; 268 | } 269 | 270 | function findAndModify_should_atomically_update_and_return_new(){ 271 | var collection = "atomictests"; 272 | var count = 5; 273 | var people = createPeople(count=count, save="false"); 274 | mongo.ensureIndex(["INPROCESS"], atomicCol); 275 | mongo.saveAll(people, atomicCol); 276 | 277 | flush(); 278 | 279 | 280 | //get total inprocess count 281 | var inprocess = mongo.query(atomicCol).$eq("INPROCESS",false).search().size(); 282 | 283 | 284 | //guard 285 | assertEquals(count, arrayLen(people)); 286 | var query = {inprocess=false}; 287 | var update = {inprocess=true, started=now(),owner=cgi.SERVER_NAME}; 288 | var new = mongo.findAndModify(query=query, update=update, collectionName=atomicCol); 289 | flush(); 290 | //debug(new); 291 | 292 | assertTrue( structKeyExists(new, "age") ); 293 | assertTrue( structKeyExists(new, "name") ); 294 | assertTrue( structKeyExists(new, "now") ); 295 | assertTrue( structKeyExists(new, "started") ); 296 | assertEquals( true, new.inprocess ); 297 | assertEquals( cgi.SERVER_NAME, new.owner ); 298 | 299 | 300 | var newinprocess = mongo.query(atomicCol).$eq("INPROCESS",false).search(); 301 | //debug(newinprocess.getQuery().toString()); 302 | 303 | assertEquals(inprocess-1, newinprocess.size()); 304 | } 305 | 306 | function group_should_honor_optional_command_parameters(){ 307 | var coll = "groups"; 308 | mongo.remove({},coll); 309 | 310 | mongo.ensureIndex( collectionName=coll, fields=["ACTIVE"]); 311 | 312 | var groups = [ 313 | {STATUS="P", ACTIVE=1, ADDED=now()}, 314 | {STATUS="P", ACTIVE=1, ADDED=now()}, 315 | {STATUS="P", ACTIVE=0, ADDED=now()}, 316 | {STATUS="R", ACTIVE=1, ADDED=now()}, 317 | {STATUS="R", ACTIVE=1, ADDED=now()} 318 | ]; 319 | mongo.saveAll( groups, coll ); 320 | var groupResult = mongo.group( coll, "STATUS", {TOTAL=0}, "function(obj,agg){ agg.TOTAL++; }" ); 321 | //debug(groupResult); 322 | 323 | assertEquals( arrayLen(groups), groupResult[1].TOTAL + groupResult[2].TOTAL, "Without any query criteria, total number of results for status should match total number of documents in collection" ); 324 | 325 | //add a criteria query 326 | groupResult = mongo.group( coll, "STATUS", {TOTAL=0}, "function(obj,agg){ agg.TOTAL++; }", {ACTIVE=1} ); 327 | assertEquals( arrayLen(groups)-1, groupResult[1].TOTAL + groupResult[2].TOTAL, "Looking at only ACTIVE=1 documents, total number of results for status should match total number of 'ACTIVE' documents in collection" ); 328 | 329 | //add a finalize function 330 | groupResult = mongo.group( collectionName=coll, keys="STATUS", initial={TOTAL=0}, reduce="function(obj,agg){ agg.TOTAL++; }", finalize="function(out){ out.HI='mom'; }" ); 331 | assertTrue( structKeyExists(groupResult[1], "HI"), "output group should have contained the key added by finalize but did not" ); 332 | 333 | //use the keyf function to create a composite key 334 | groupResult = mongo.group( collectionName=coll, keys="", initial={TOTAL=0}, reduce="function(obj,agg){ agg.TOTAL++; }", keyf="function(doc){ return {'TASK_STATUS' : doc.STATUS }; }" ); 335 | debug(groupResult); 336 | 337 | //TODO: get a better example of keyf 338 | assertTrue( structKeyExists(groupResult[1], "TASK_STATUS"), "Key should have been TASK_STATUS since we override the key in keyf function" ); 339 | } 340 | 341 | 342 | function testGetIndexes(){ 343 | var result = mongo.dropIndexes(collectionName=col); 344 | //guard 345 | assertEquals( 1, arrayLen(result), "always an index on _id" ); 346 | 347 | mongo.ensureIndex( collectionName=col, fields=["name"]); 348 | mongo.ensureIndex( collectionName=col, fields=[{"name"=1},{"address.state"=-1}]); 349 | result = mongo.getIndexes( col ); 350 | //debug(result); 351 | 352 | assertTrue( arrayLen(result) GT 1, "Should be at least 2: 1 for the _id, and one for the index we just added"); 353 | } 354 | 355 | function testListCommandsViaMongoDriver(){ 356 | var result = mongo.getMongoDB().command("listCommands"); 357 | //debug(result); 358 | assertTrue( structKeyExists(result, "commands") ); 359 | //NOTE: this is not a true CF struct, but a regular java hashmap; consequently, it is case sensitive! 360 | assertTrue( structCount(result["commands"]) GT 1); 361 | } 362 | 363 | function isAuthenticationRequired_should_return_true_if_index_queries_fail(){ 364 | //guard 365 | var result = mongo.isAuthenticationRequired(); 366 | assertFalse(result, "queries against an un-authed mongod should not cause errors"); 367 | 368 | //now spoof the authentication failure 369 | injectMethod(mongo, this, "getIndexesFailOverride", "getIndexes"); 370 | result = mongo.isAuthenticationRequired(); 371 | assertTrue(result, "when a simple find query fails, we assume authentication is required"); 372 | } 373 | 374 | private function getIndexesFailOverride(){ 375 | throw("authentication failed"); 376 | } 377 | 378 | 379 | /** test java getters */ 380 | function testGetMongo(){ 381 | assertIsTypeOf( mongo, "cfmongodb.core.Mongo" ); 382 | } 383 | 384 | function getMongo_should_return_underlying_java_Mongo(){ 385 | var jMongo = mongo.getMongo(); 386 | assertEquals("com.mongodb.Mongo",jMongo.getClass().getCanonicalName()); 387 | } 388 | 389 | function getMongoDB_should_return_underlying_java_MongoDB(){ 390 | 391 | var jMongoDB = mongo.getMongoDB(mongoConfig); 392 | assertEquals("com.mongodb.DBApiLayer",jMongoDB.getClass().getCanonicalName()); 393 | } 394 | 395 | function getMongoDBCollection_should_return_underlying_java_DBCollection(){ 396 | var jColl = mongo.getMongoDBCollection(col, mongoConfig); 397 | assertEquals("com.mongodb.DBApiLayer.mycollection",jColl.getClass().getCanonicalName()); 398 | } 399 | 400 | 401 | /** dumping grounnd for proof of concept tests */ 402 | 403 | function poc_profiling(){ 404 | var u = mongo.getMongoUtil(); 405 | var command = u.toMongo({"profile"=2}); 406 | var result = mongo.getMongoDB().command( command ); 407 | //debug(result); 408 | 409 | var result = mongo.query("system.profile").search(limit=50,sort={"ts"=-1}).asArray(); 410 | //debug(result); 411 | 412 | command = u.toMongo({"profile"=0}); 413 | result = mongo.getMongoDB().command( command ); 414 | //debug(result); 415 | } 416 | 417 | private function flush(){ 418 | //forces mongo to flush 419 | mongo.getMongoDB().getLastError(); 420 | } 421 | 422 | function newDBObject_should_be_acceptably_fast(){ 423 | var i = 1; 424 | var count = 500; 425 | var u = mongo.getMongoUtil(); 426 | var st = {string="string",number=1,float=1.5,date=now(),boolean=true}; 427 | //get the first one out of its system 428 | var dbo = u.toMongo( st ); 429 | var startTS = getTickCount(); 430 | for(i=1; i LTE count; i++){ 431 | dbo = u.toMongo( st ); 432 | } 433 | var total = getTickCount() - startTS; 434 | assertTrue( total lt 200, "total should be acceptably fast but was #total#" ); 435 | } 436 | 437 | function newDBObject_should_create_correct_datatypes(){ 438 | var origNums = mongo.query( col ).$eq("number", types.number).count(); 439 | var origNestedNums = mongo.query( col ).$eq("types.number", types.number).count(); 440 | var origBool = mongo.query( col ).$eq("israd", true).count(); 441 | var origNestedBool = mongo.query( col ).$eq("types.israd", true).count(); 442 | var origFloats = mongo.query( col ).$eq("floats",1.3).count(); 443 | var origNestedFloats = mongo.query( col ).$eq("types.floats",1.3).count(); 444 | var origString = mongo.query( col ).$eq("address.street", "123 big top lane").count(); 445 | 446 | mongo.save( doc, col ); 447 | 448 | var newNums = mongo.query( col ).$eq("number", types.number).count(); 449 | var newNestedNums = mongo.query( col ).$eq("types.number", types.number).count(); 450 | var newBool = mongo.query( col ).$eq("israd", true).count(); 451 | var newNestedBool = mongo.query( col ).$eq("types.israd", true).count(); 452 | var newFloats = mongo.query( col ).$eq("floats",1.3).count(); 453 | var newNestedFloats = mongo.query( col ).$eq("types.floats",1.3).count(); 454 | var newString = mongo.query( col ).$eq("address.street", "123 big top lane").count(); 455 | 456 | assertEquals( origNums+1, newNums ); 457 | assertEquals( origNestedNums+1, newNestedNums ); 458 | assertEquals( origBool+1, newBool ); 459 | assertEquals( origNestedBool+1, newNestedBool ); 460 | assertEquals( origFloats+1, newFloats ); 461 | assertEquals( origNestedFloats+1, newNestedFloats ); 462 | assertEquals( origString+1, newString ); 463 | 464 | } 465 | 466 | /** 467 | * Confirm getLastError works and mongo has not changed its response. 468 | */ 469 | function getLastError_should_return_error_when_expected() 470 | { 471 | var jColl = mongo.getMongoDBCollection(col, mongoConfig); 472 | var mongoUtil = mongo.getMongoUtil(); 473 | 474 | // Create people to steal an id from 475 | createPeople(); 476 | 477 | // Get the result of the last activity from CreatePeople() 478 | local.lastActivity = mongo.getLastError(); 479 | 480 | // Verify the structure returned by Mongo has not changed 481 | var expected = listToArray('n,ok,err'); 482 | local.actualKeys = structKeyArray(local.lastActivity); 483 | arraySort(local.actualKeys,'text'); 484 | arraySort(expected,'text'); 485 | assertEquals( 486 | local.actualKeys 487 | ,expected 488 | ,'Mongo may have changed the getLastError() response.' 489 | ); 490 | 491 | local.peeps = mongo.query(collectionName=col).search(limit="1").asArray(); 492 | assertFalse( 493 | arrayIsEmpty(local.peeps) 494 | ,'Some people should have been returned.' 495 | ); 496 | 497 | 498 | // Let's duplicate the record. 499 | local.person = local.peeps[1]; 500 | jColl.insert([mongoUtil.toMongo(local.person)]); 501 | 502 | // Get the result of the last activity 503 | local.lastActivity = mongo.getLastError(); 504 | 505 | // Confirm we did try to duplicate an id. 506 | assert( 507 | structKeyExists(local.lastActivity,'code') 508 | ,'Mongo should be upset a record was duplicated. Check the test.' 509 | ); 510 | 511 | // We now expect the error code to exist. 512 | var expected = listToArray('n,ok,err,code'); 513 | local.actualKeys = structKeyArray(local.lastActivity); 514 | arraySort(local.actualKeys,'text'); 515 | arraySort(expected,'text'); 516 | assertEquals( 517 | local.actualKeys 518 | ,expected 519 | ,'Mongo may have changed the getLastError() response.' 520 | ); 521 | 522 | return; 523 | } 524 | 525 | 526 | 527 | 528 | -------------------------------------------------------------------------------- /test/RemoteFacade.cfc: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /test/run.cfm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | #results.getResultsOutput("html")# 22 | 23 | 24 |
25 | 26 |
27 |
-------------------------------------------------------------------------------- /test/scratch.cfm: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | -------------------------------------------------------------------------------- /test/temp.cfm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | javaPaths = directoryList( expandPath("/cfmongodb/lib"), false, "path", "*.jar" ); 8 | binPath = expandPath("/cfmongodb/java/bin/"); 9 | arrayappend(javaPaths, binPath); 10 | javaloader = createObject('component','cfmongodb.lib.javaloader.javaloader').init(javaPaths); 11 | javaloaderFactory = createObject('component','cfmongodb.core.JavaloaderFactory').init(javaloader); 12 | 13 | mongoConfig = createObject('component','cfmongodb.core.MongoConfig').init(db_name="cfmongodb_tests", mongoFactory=javaloaderFactory); 14 | mongo = createObject('component','cfmongodb.core.Mongo').init(mongoConfig); 15 | 16 | 17 | i = 1; 18 | count = 200; 19 | u = mongo.getMongoUtil(); 20 | st = { 21 | string="string",number=1,float=1.5,date=now(),boolean=true, 22 | array = ["one",1,5], struct = {one="one",two=2,bool=false}, 23 | arrayOfStruct = [ {one=1, two="two"}, {three=3, four="four"} ] 24 | }; 25 | startTS = getTickCount(); 26 | for(i=1; i LTE count; i++){ 27 | dbo = u.toMongo( st ); 28 | } 29 | total = getTickCount() - startTS; 30 | 31 | writeDump(dbo.toString()); 32 | 33 | mongo.save(st,"people"); 34 | 35 | 36 | result = mongo.query("people").between("age",5,45).search(); 37 | 38 | 39 | writeDump(result.getQuery().toString()); 40 | 41 | 42 | 43 | 44 | --------------------------------------------------------------------------------