├── .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 |
35 |
36 |
37 |
38 | #navlinks#
39 |
40 |
41 |
42 |
43 |
44 | N |
45 | Name |
46 | Spouse |
47 | Kids |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 | #person.counter# |
56 | #person.name# |
57 | #person.spouse# |
58 |
59 |
60 |
61 | #kid.name# |
62 |
63 |
64 | None
65 |
66 | |
67 |
68 |
69 |
70 |
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 |
--------------------------------------------------------------------------------