Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

initial commit to get the repository going

  • Loading branch information...
commit 998630781c52cc613a98d4819b768f116af61b6d 0 parents
@thiloplanz authored
Showing with 1,903 additions and 0 deletions.
  1. +202 −0 LICENSE_APACHEv2.txt
  2. +51 −0 pom.xml
  3. +15 −0 src/license_header_apachev2.txt
  4. +268 −0 src/main/java/jmockmongo/BSONUtils.java
  5. +27 −0 src/main/java/jmockmongo/CommandHandler.java
  6. +44 −0 src/main/java/jmockmongo/DefaultInsertHandler.java
  7. +54 −0 src/main/java/jmockmongo/DefaultQueryHandler.java
  8. +29 −0 src/main/java/jmockmongo/InsertHandler.java
  9. +51 −0 src/main/java/jmockmongo/MockDB.java
  10. +61 −0 src/main/java/jmockmongo/MockDBCollection.java
  11. +132 −0 src/main/java/jmockmongo/MockMongo.java
  12. +28 −0 src/main/java/jmockmongo/QueryHandler.java
  13. +32 −0 src/main/java/jmockmongo/commands/Count.java
  14. +32 −0 src/main/java/jmockmongo/commands/GetLastError.java
  15. +32 −0 src/main/java/jmockmongo/commands/IsMaster.java
  16. +32 −0 src/main/java/jmockmongo/commands/ListDatabases.java
  17. +80 −0 src/main/java/jmockmongo/wire/InsertMessage.java
  18. +34 −0 src/main/java/jmockmongo/wire/LoggingHandler.java
  19. +159 −0 src/main/java/jmockmongo/wire/Message.java
  20. +107 −0 src/main/java/jmockmongo/wire/MessageDecoder.java
  21. +99 −0 src/main/java/jmockmongo/wire/QueryMessage.java
  22. +103 −0 src/main/java/jmockmongo/wire/ReplyHandler.java
  23. +85 −0 src/main/java/jmockmongo/wire/ReplyMessage.java
  24. +42 −0 src/test/java/jmockmongo/DefaultInsertHandlerTest.java
  25. +60 −0 src/test/java/jmockmongo/MockMongoSetup.java
  26. +44 −0 src/test/java/jmockmongo/MockMongoTest.java
202 LICENSE_APACHEv2.txt
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
51 pom.xml
@@ -0,0 +1,51 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>jmockmongo</groupId>
+ <artifactId>jmockmongo</artifactId>
+ <version>0.0.1-SNAPSHOT</version>
+ <name>Mocking MongoDB</name>
+ <description>Disturbingly incomplete in-memory MongoDB implementation for use in automated testing</description>
+ <licenses>
+ <license>
+ <name>Apache License Version 2.0</name>
+ </license>
+ </licenses>
+ <inceptionYear>2012</inceptionYear>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>com.mycila.maven-license-plugin</groupId>
+ <artifactId>maven-license-plugin</artifactId>
+ <configuration>
+ <basedir>${basedir}/src</basedir>
+ <header>${basedir}/src/license_header_apachev2.txt</header>
+ <strictCheck>true</strictCheck>
+ </configuration>
+ </plugin>
+
+ </plugins>
+ </build>
+ <dependencies>
+ <dependency>
+ <groupId>org.mongodb</groupId>
+ <artifactId>mongo-java-driver</artifactId>
+ <version>2.7.3</version>
+ <type>jar</type>
+ <scope>compile</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>3.8.2</version>
+ <type>jar</type>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>io.netty</groupId>
+ <artifactId>netty</artifactId>
+ <version>3.3.1.Final</version>
+ <scope>compile</scope>
+ </dependency>
+ </dependencies>
+</project>
15 src/license_header_apachev2.txt
@@ -0,0 +1,15 @@
+Copyright (c) 2012, Thilo Planz. All rights reserved.
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the Apache License, Version 2.0
+as published by the Apache Software Foundation (the "License").
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied. See the License for the
+specific language governing permissions and limitations
+under the License.
+
+You should have received a copy of the License along with this program.
+If not, see <http://www.apache.org/licenses/LICENSE-2.0>.
268 src/main/java/jmockmongo/BSONUtils.java
@@ -0,0 +1,268 @@
+/**
+ * Copyright (c) 2012, Thilo Planz. All rights reserved.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Apache License, Version 2.0
+ * as published by the Apache Software Foundation (the "License").
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ * You should have received a copy of the License along with this program.
+ * If not, see <http://www.apache.org/licenses/LICENSE-2.0>.
+ */
+
+package jmockmongo;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import org.bson.BSON;
+import org.bson.BSONObject;
+
+/**
+ * Utility class to work with BSON data.
+ * <ul>
+ * <li>performs loss-less type conversions
+ * <li>supports nested fields ("a.b.c"), including auto-vivification
+ * <li>fields that are null, empty array, or empty object are treated as missing
+ * (setting to null removes the field)
+ * </ul>
+ *
+ */
+
+class BSONUtils {
+
+ private static <T> T notNull(T x) {
+ if (x == null)
+ return x;
+ if (x instanceof Map<?, ?>) {
+ if (((Map<?, ?>) x).isEmpty())
+ return null;
+ return x;
+ }
+
+ if (x instanceof Object[]) {
+ if (((Object[]) x).length == 0)
+ return null;
+ return x;
+ }
+
+ if (x instanceof byte[]) {
+ if (((byte[]) x).length == 0)
+ return null;
+ return x;
+ }
+
+ if (x instanceof Collection<?>) {
+ if (((Collection<?>) x).isEmpty())
+ return null;
+ return x;
+ }
+
+ return x;
+
+ }
+
+ static Object get(BSONObject b, String fieldName) {
+ if (!fieldName.contains("."))
+ return notNull(b.get(fieldName));
+ String[] path = fieldName.split("\\.", 2);
+ Object nested = b.get(path[0]);
+ if (nested == null)
+ return null;
+ if (nested instanceof BSONObject)
+ return get((BSONObject) nested, path[1]);
+ throw new IllegalArgumentException("cannot get field `" + fieldName
+ + "` of " + b);
+ }
+
+ private static Object getRequired(BSONObject b, String fieldName) {
+ Object x = get(b, fieldName);
+ if (x == null)
+ throw new IllegalArgumentException("required field `" + fieldName
+ + "` is missing in " + b);
+ return x;
+ }
+
+ static Long toLong(Object x) {
+ if (x == null)
+ return null;
+ if (x instanceof Long)
+ return (Long) x;
+ if (x instanceof Integer)
+ return Long.valueOf(((Integer) x).intValue());
+ if (x instanceof String)
+ return Long.valueOf((String) x);
+ throw new IllegalArgumentException("cannot convert `" + x
+ + "` into a Long");
+ }
+
+ static Integer toInteger(Object x) {
+ if (x == null)
+ return null;
+ if (x instanceof Integer)
+ return (Integer) x;
+ if (x instanceof Long)
+ return Integer.valueOf(x.toString());
+ if (x instanceof String)
+ return Integer.valueOf((String) x);
+ throw new IllegalArgumentException("cannot convert `" + x
+ + "` into a Long");
+ }
+
+ static String toString(Object x) {
+ if (x == null)
+ return null;
+ if (x instanceof String)
+ return (String) x;
+ if (x instanceof Number)
+ return x.toString();
+
+ throw new IllegalArgumentException("cannot convert `" + x
+ + "` into a String");
+ }
+
+ static Long getLong(BSONObject b, String fieldName) {
+ return toLong(get(b, fieldName));
+ }
+
+ static Integer getInteger(BSONObject b, String fieldName) {
+ return toInteger(get(b, fieldName));
+ }
+
+ static int getRequiredInt(BSONObject b, String fieldName) {
+ return toInteger(getRequired(b, fieldName)).intValue();
+ }
+
+ static long getRequiredLong(BSONObject b, String fieldName) {
+ return toLong(getRequired(b, fieldName)).longValue();
+ }
+
+ static String getString(BSONObject b, String fieldName) {
+ return toString(get(b, fieldName));
+ }
+
+ static BSONObject getObject(BSONObject b, String fieldName) {
+ Object x = get(b, fieldName);
+ if (x == null)
+ return null;
+ if (x instanceof BSONObject)
+ return (BSONObject) x;
+ throw new IllegalArgumentException("cannot convert `" + x
+ + "` into a BSONObject");
+ }
+
+ static Object removeField(BSONObject b, String fieldName) {
+ if (fieldName.contains("."))
+ throw new UnsupportedOperationException("not yet implemented");
+ return b.removeField(fieldName);
+ }
+
+ private static void put(BSONObject b, String fieldName, Object x) {
+ x = notNull(x);
+ if (x == null) {
+ removeField(b, fieldName);
+ } else {
+ if (fieldName.contains("."))
+ throw new UnsupportedOperationException("not yet implemented");
+ b.put(fieldName, x);
+ }
+ }
+
+ static Integer putInteger(BSONObject b, String fieldName, Object x) {
+ Integer i = toInteger(x);
+ put(b, fieldName, i);
+ return i;
+ }
+
+ static Long putLong(BSONObject b, String fieldName, Object x) {
+ Long l = toLong(x);
+ put(b, fieldName, l);
+ return l;
+ }
+
+ static String putString(BSONObject b, String fieldName, Object x) {
+ String s = toString(x);
+ put(b, fieldName, s);
+ return s;
+ }
+
+ private static final Long MAX_INT = Long.valueOf(Integer.MAX_VALUE);
+ private static final Long MIN_INT = Long.valueOf(Integer.MIN_VALUE);
+
+ /**
+ * stores a number as Integer, if it fits, or as a Long, if not. This saves
+ * space in the database, but you lose the ability to sort or do range
+ * queries.
+ */
+ static Number putIntegerOrLong(BSONObject b, String fieldName, Object x) {
+ if (x instanceof Integer)
+ return putInteger(b, fieldName, x);
+
+ Long l = toLong(x);
+ if (l == null) {
+ removeField(b, fieldName);
+ return null;
+ }
+ if (l.compareTo(MAX_INT) < 0 && l.compareTo(MIN_INT) > 0) {
+ Integer i = l.intValue();
+ b.put(fieldName, i);
+ return i;
+ }
+ b.put(fieldName, l);
+ return l;
+ }
+
+ /**
+ * BSON objects are considered equal when their binary encoding matches
+ */
+ static boolean equals(BSONObject a, BSONObject b) {
+ return a.keySet().equals(b.keySet())
+ && Arrays.equals(BSON.encode(a), BSON.encode(b));
+ }
+
+ /**
+ * @return true, if the field contains (in case of an array) or is equal to
+ * (in case of a single value) the given BSONObject
+ */
+ static boolean contains(BSONObject b, String fieldName, BSONObject toLookFor) {
+ Object list = get(b, fieldName);
+ if (list == null)
+ return false;
+ if (list instanceof List<?>) {
+ for (Object o : (List<?>) list) {
+ if (o instanceof BSONObject) {
+ BSONObject x = (BSONObject) o;
+ if (equals(x, toLookFor))
+ return true;
+ }
+ }
+ return false;
+ }
+ if (list instanceof Object[]) {
+ for (Object o : (Object[]) list) {
+ if (o instanceof BSONObject) {
+ BSONObject x = (BSONObject) o;
+ if (equals(x, toLookFor))
+ return true;
+ }
+ }
+ return false;
+ }
+ if (list instanceof BSONObject) {
+ BSONObject x = (BSONObject) list;
+ if (equals(x, toLookFor))
+ return true;
+ }
+ return false;
+
+ }
+
+}
27 src/main/java/jmockmongo/CommandHandler.java
@@ -0,0 +1,27 @@
+/**
+ * Copyright (c) 2012, Thilo Planz. All rights reserved.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Apache License, Version 2.0
+ * as published by the Apache Software Foundation (the "License").
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ * You should have received a copy of the License along with this program.
+ * If not, see <http://www.apache.org/licenses/LICENSE-2.0>.
+ */
+
+package jmockmongo;
+
+import org.bson.BSONObject;
+
+public interface CommandHandler {
+
+ public BSONObject handleCommand(String database, BSONObject command);
+
+}
44 src/main/java/jmockmongo/DefaultInsertHandler.java
@@ -0,0 +1,44 @@
+/**
+ * Copyright (c) 2012, Thilo Planz. All rights reserved.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Apache License, Version 2.0
+ * as published by the Apache Software Foundation (the "License").
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ * You should have received a copy of the License along with this program.
+ * If not, see <http://www.apache.org/licenses/LICENSE-2.0>.
+ */
+
+package jmockmongo;
+
+import java.util.Iterator;
+
+import org.bson.BSONObject;
+
+class DefaultInsertHandler implements InsertHandler {
+
+ private final MockMongo mongo;
+
+ DefaultInsertHandler(MockMongo mongo) {
+ this.mongo = mongo;
+ }
+
+ public void handleInsert(String database, String collection,
+ boolean continueOnError, Iterator<BSONObject> docs) {
+
+ MockDBCollection c = mongo.getOrCreateDB(database)
+ .getOrCreateCollection(collection);
+ while (docs.hasNext()) {
+ c.insert(docs.next());
+ }
+
+ }
+
+}
54 src/main/java/jmockmongo/DefaultQueryHandler.java
@@ -0,0 +1,54 @@
+/**
+ * Copyright (c) 2012, Thilo Planz. All rights reserved.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Apache License, Version 2.0
+ * as published by the Apache Software Foundation (the "License").
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ * You should have received a copy of the License along with this program.
+ * If not, see <http://www.apache.org/licenses/LICENSE-2.0>.
+ */
+
+package jmockmongo;
+
+import org.bson.BSONObject;
+
+class DefaultQueryHandler implements QueryHandler {
+
+ private final MockMongo mongo;;
+
+ public DefaultQueryHandler(MockMongo mongo) {
+ this.mongo = mongo;
+ }
+
+ public BSONObject[] handleQuery(String database, String collection,
+ BSONObject command) {
+
+ if ("system.indexes".equals(collection))
+ return new BSONObject[0];
+
+ MockDB db = mongo.getDB(database);
+ if (db != null) {
+ Object id = command.get("_id");
+ if (id == null || command.keySet().size() > 1)
+ throw new UnsupportedOperationException(
+ "only _id queries are implemented, not " + command);
+ MockDBCollection c = db.getCollection(collection);
+ if (c != null) {
+ BSONObject result = c.findOne(id);
+ if (result != null)
+ return new BSONObject[] { result };
+ }
+ }
+
+ return new BSONObject[0];
+ }
+
+}
29 src/main/java/jmockmongo/InsertHandler.java
@@ -0,0 +1,29 @@
+/**
+ * Copyright (c) 2012, Thilo Planz. All rights reserved.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Apache License, Version 2.0
+ * as published by the Apache Software Foundation (the "License").
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ * You should have received a copy of the License along with this program.
+ * If not, see <http://www.apache.org/licenses/LICENSE-2.0>.
+ */
+
+package jmockmongo;
+
+import java.util.Iterator;
+
+import org.bson.BSONObject;
+
+public interface InsertHandler {
+
+ public void handleInsert(String database, String collection,
+ boolean continueOnError, Iterator<BSONObject> data);
+}
51 src/main/java/jmockmongo/MockDB.java
@@ -0,0 +1,51 @@
+/**
+ * Copyright (c) 2012, Thilo Planz. All rights reserved.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Apache License, Version 2.0
+ * as published by the Apache Software Foundation (the "License").
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ * You should have received a copy of the License along with this program.
+ * If not, see <http://www.apache.org/licenses/LICENSE-2.0>.
+ */
+
+package jmockmongo;
+
+import java.util.concurrent.ConcurrentHashMap;
+
+public class MockDB {
+
+ private final String name;
+
+ private final ConcurrentHashMap<String, MockDBCollection> collections = new ConcurrentHashMap<String, MockDBCollection>();
+
+ MockDB(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public MockDBCollection getCollection(String collectionName) {
+ return collections.get(collectionName);
+
+ }
+
+ public MockDBCollection getOrCreateCollection(String collectionName) {
+ MockDBCollection collection = collections.get(collectionName);
+ if (collection != null)
+ return collection;
+ collections.putIfAbsent(collectionName, new MockDBCollection(
+ collectionName));
+ return collections.get(collectionName);
+ }
+
+}
61 src/main/java/jmockmongo/MockDBCollection.java
@@ -0,0 +1,61 @@
+/**
+ * Copyright (c) 2012, Thilo Planz. All rights reserved.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Apache License, Version 2.0
+ * as published by the Apache Software Foundation (the "License").
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ * You should have received a copy of the License along with this program.
+ * If not, see <http://www.apache.org/licenses/LICENSE-2.0>.
+ */
+
+package jmockmongo;
+
+import java.math.BigInteger;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.bson.BSONObject;
+import org.bson.types.ObjectId;
+
+public class MockDBCollection {
+
+ private final String name;
+
+ private final Map<Object, BSONObject> data = new ConcurrentHashMap<Object, BSONObject>();
+
+ MockDBCollection(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public BSONObject findOne(Object id) {
+ // byte[] don't hash...
+ if (id instanceof byte[]) {
+ id = new BigInteger((byte[]) id);
+ }
+ return data.get(id);
+ }
+
+ public void insert(BSONObject b) {
+ if (!b.containsField("_id")) {
+ b.put("_id", new ObjectId());
+ }
+ Object id = b.get("_id");
+ // byte[] don't hash...
+ if (id instanceof byte[]) {
+ id = new BigInteger((byte[]) id);
+ }
+ data.put(id, b);
+ }
+}
132 src/main/java/jmockmongo/MockMongo.java
@@ -0,0 +1,132 @@
+/**
+ * Copyright (c) 2012, Thilo Planz. All rights reserved.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Apache License, Version 2.0
+ * as published by the Apache Software Foundation (the "License").
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ * You should have received a copy of the License along with this program.
+ * If not, see <http://www.apache.org/licenses/LICENSE-2.0>.
+ */
+
+package jmockmongo;
+
+import java.net.InetSocketAddress;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executors;
+
+import jmockmongo.commands.Count;
+import jmockmongo.commands.GetLastError;
+import jmockmongo.commands.IsMaster;
+import jmockmongo.commands.ListDatabases;
+import jmockmongo.wire.LoggingHandler;
+import jmockmongo.wire.MessageDecoder;
+import jmockmongo.wire.ReplyHandler;
+
+import org.jboss.netty.bootstrap.ServerBootstrap;
+import org.jboss.netty.channel.ChannelFactory;
+import org.jboss.netty.channel.ChannelHandlerContext;
+import org.jboss.netty.channel.ChannelPipeline;
+import org.jboss.netty.channel.ChannelPipelineFactory;
+import org.jboss.netty.channel.ChannelStateEvent;
+import org.jboss.netty.channel.Channels;
+import org.jboss.netty.channel.SimpleChannelHandler;
+import org.jboss.netty.channel.group.ChannelGroup;
+import org.jboss.netty.channel.group.DefaultChannelGroup;
+import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
+
+import com.mongodb.DBAddress;
+
+public class MockMongo {
+
+ private ChannelGroup channels;
+
+ private ServerBootstrap bootstrap;
+
+ private ConcurrentHashMap<String, MockDB> data;
+
+ MockDB getDB(String database) {
+ return data.get(database);
+ }
+
+ MockDB getOrCreateDB(String database) {
+ MockDB db = data.get(database);
+ if (db != null)
+ return db;
+ data.putIfAbsent(database, new MockDB(database));
+ return data.get(database);
+ }
+
+ MockDBCollection getCollection(String fullCollectionName) {
+ String[] fc = fullCollectionName.split("\\.", 2);
+ MockDB db = getDB(fc[0]);
+ if (db == null)
+ return null;
+ return db.getCollection(fc[1]);
+ }
+
+ MockDBCollection getCollection(String database, String collectionName) {
+ MockDB db = getDB(database);
+ if (db == null)
+ return null;
+ return db.getCollection(collectionName);
+ }
+
+ public void start() {
+ data = new ConcurrentHashMap<String, MockDB>();
+ final ChannelFactory factory = new NioServerSocketChannelFactory(
+ Executors.newCachedThreadPool(), Executors
+ .newCachedThreadPool());
+
+ channels = new DefaultChannelGroup("jmockmongo");
+ bootstrap = new ServerBootstrap(factory);
+
+ final ReplyHandler handler = new ReplyHandler();
+ handler.setCommandHandler("isMaster", new IsMaster());
+ handler.setCommandHandler("listDatabases", new ListDatabases());
+ handler.setCommandHandler("count", new Count());
+ handler.setCommandHandler("getlasterror", new GetLastError());
+
+ handler.setQueryHandler(new DefaultQueryHandler(this));
+
+ handler.setInsertHandler(new DefaultInsertHandler(this));
+
+ bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
+ public ChannelPipeline getPipeline() {
+ return Channels.pipeline(new SimpleChannelHandler() {
+
+ @Override
+ public void channelOpen(ChannelHandlerContext ctx,
+ ChannelStateEvent e) throws Exception {
+ channels.add(e.getChannel());
+ super.channelOpen(ctx, e);
+ }
+
+ }, new MessageDecoder(), new LoggingHandler(), handler);
+ }
+ });
+
+ channels.add(bootstrap.bind(new InetSocketAddress(DBAddress
+ .defaultPort())));
+ }
+
+ public void stop() {
+ if (channels != null) {
+ channels.close().awaitUninterruptibly();
+ }
+ channels = null;
+ if (bootstrap != null) {
+ bootstrap.releaseExternalResources();
+ }
+ bootstrap = null;
+
+ }
+
+}
28 src/main/java/jmockmongo/QueryHandler.java
@@ -0,0 +1,28 @@
+/**
+ * Copyright (c) 2012, Thilo Planz. All rights reserved.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Apache License, Version 2.0
+ * as published by the Apache Software Foundation (the "License").
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ * You should have received a copy of the License along with this program.
+ * If not, see <http://www.apache.org/licenses/LICENSE-2.0>.
+ */
+
+package jmockmongo;
+
+import org.bson.BSONObject;
+
+public interface QueryHandler {
+
+ public BSONObject[] handleQuery(String database, String collection,
+ BSONObject command);
+
+}
32 src/main/java/jmockmongo/commands/Count.java
@@ -0,0 +1,32 @@
+/**
+ * Copyright (c) 2012, Thilo Planz. All rights reserved.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Apache License, Version 2.0
+ * as published by the Apache Software Foundation (the "License").
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ * You should have received a copy of the License along with this program.
+ * If not, see <http://www.apache.org/licenses/LICENSE-2.0>.
+ */
+
+package jmockmongo.commands;
+
+import jmockmongo.CommandHandler;
+
+import org.bson.BSONObject;
+import org.bson.BasicBSONObject;
+
+public class Count implements CommandHandler {
+
+ public BSONObject handleCommand(String database, BSONObject command) {
+ return new BasicBSONObject("ok", 1).append("n", 0);
+ }
+
+}
32 src/main/java/jmockmongo/commands/GetLastError.java
@@ -0,0 +1,32 @@
+/**
+ * Copyright (c) 2012, Thilo Planz. All rights reserved.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Apache License, Version 2.0
+ * as published by the Apache Software Foundation (the "License").
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ * You should have received a copy of the License along with this program.
+ * If not, see <http://www.apache.org/licenses/LICENSE-2.0>.
+ */
+
+package jmockmongo.commands;
+
+import jmockmongo.CommandHandler;
+
+import org.bson.BSONObject;
+import org.bson.BasicBSONObject;
+
+public class GetLastError implements CommandHandler {
+
+ public BSONObject handleCommand(String database, BSONObject command) {
+ return new BasicBSONObject("ok", 1);
+ }
+
+}
32 src/main/java/jmockmongo/commands/IsMaster.java
@@ -0,0 +1,32 @@
+/**
+ * Copyright (c) 2012, Thilo Planz. All rights reserved.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Apache License, Version 2.0
+ * as published by the Apache Software Foundation (the "License").
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ * You should have received a copy of the License along with this program.
+ * If not, see <http://www.apache.org/licenses/LICENSE-2.0>.
+ */
+
+package jmockmongo.commands;
+
+import jmockmongo.CommandHandler;
+
+import org.bson.BSONObject;
+import org.bson.BasicBSONObject;
+
+public class IsMaster implements CommandHandler {
+
+ public BSONObject handleCommand(String database, BSONObject command) {
+ return new BasicBSONObject("ismaster", 1);
+ }
+
+}
32 src/main/java/jmockmongo/commands/ListDatabases.java
@@ -0,0 +1,32 @@
+/**
+ * Copyright (c) 2012, Thilo Planz. All rights reserved.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Apache License, Version 2.0
+ * as published by the Apache Software Foundation (the "License").
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ * You should have received a copy of the License along with this program.
+ * If not, see <http://www.apache.org/licenses/LICENSE-2.0>.
+ */
+
+package jmockmongo.commands;
+
+import jmockmongo.CommandHandler;
+
+import org.bson.BSONObject;
+import org.bson.BasicBSONObject;
+
+public class ListDatabases implements CommandHandler {
+
+ public BSONObject handleCommand(String database, BSONObject command) {
+ return new BasicBSONObject("ok", 1).append("databases", new Object[0]);
+ }
+
+}
80 src/main/java/jmockmongo/wire/InsertMessage.java
@@ -0,0 +1,80 @@
+/**
+ * Copyright (c) 2012, Thilo Planz. All rights reserved.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Apache License, Version 2.0
+ * as published by the Apache Software Foundation (the "License").
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ * You should have received a copy of the License along with this program.
+ * If not, see <http://www.apache.org/licenses/LICENSE-2.0>.
+ */
+
+package jmockmongo.wire;
+
+import static jmockmongo.wire.MessageDecoder.readCString;
+import static jmockmongo.wire.MessageDecoder.readDocument;
+import static jmockmongo.wire.MessageDecoder.readInt32;
+
+import java.util.Iterator;
+
+import org.bson.BSONObject;
+import org.jboss.netty.buffer.ChannelBuffer;
+
+class InsertMessage extends Message {
+
+ static final int OP_CODE_INSERT = 2002;
+
+ // struct {
+ // MsgHeader header; // standard message header
+ // int32 flags; // bit vector - see below
+ // cstring fullCollectionName; // "dbname.collectionname"
+ // document* documents; // one or more documents to insert into the
+ // collection
+ // }
+
+ private final int flags;
+
+ private final String fullCollectionName;
+
+ String getFullCollectionName() {
+ return fullCollectionName;
+ }
+
+ InsertMessage(ChannelBuffer data) {
+ super(data, OP_CODE_INSERT);
+ byte[] fourBytes = new byte[4];
+ flags = readInt32(data, fourBytes);
+ fullCollectionName = readCString(data);
+ }
+
+ Iterator<BSONObject> documents() {
+ final ChannelBuffer copy = data.slice();
+ final byte[] fourBytes = new byte[4];
+ return new Iterator<BSONObject>() {
+
+ public boolean hasNext() {
+ return copy.readable();
+ }
+
+ public BSONObject next() {
+ return readDocument(copy, fourBytes);
+ }
+
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ };
+ }
+
+ @Override
+ public String toString() {
+ return super.toString() + " collection: " + fullCollectionName;
+ }
+}
34 src/main/java/jmockmongo/wire/LoggingHandler.java
@@ -0,0 +1,34 @@
+/**
+ * Copyright (c) 2012, Thilo Planz. All rights reserved.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Apache License, Version 2.0
+ * as published by the Apache Software Foundation (the "License").
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ * You should have received a copy of the License along with this program.
+ * If not, see <http://www.apache.org/licenses/LICENSE-2.0>.
+ */
+
+package jmockmongo.wire;
+
+import org.jboss.netty.channel.ChannelHandlerContext;
+import org.jboss.netty.channel.MessageEvent;
+import org.jboss.netty.channel.SimpleChannelUpstreamHandler;
+
+public class LoggingHandler extends SimpleChannelUpstreamHandler {
+
+ @Override
+ public void messageReceived(ChannelHandlerContext ctx, MessageEvent e)
+ throws Exception {
+ System.out.println(e.getMessage());
+ super.messageReceived(ctx, e);
+ }
+
+}
159 src/main/java/jmockmongo/wire/Message.java
@@ -0,0 +1,159 @@
+/**
+ * Copyright (c) 2012, Thilo Planz. All rights reserved.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Apache License, Version 2.0
+ * as published by the Apache Software Foundation (the "License").
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ * You should have received a copy of the License along with this program.
+ * If not, see <http://www.apache.org/licenses/LICENSE-2.0>.
+ */
+
+package jmockmongo.wire;
+
+import static jmockmongo.wire.MessageDecoder.readInt32;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.jboss.netty.buffer.ChannelBuffers;
+
+abstract class Message {
+
+ // struct MsgHeader {
+ // int32 messageLength; // total message size, including this
+ // int32 requestID; // identifier for this message
+ // int32 responseTo; // requestID from the original request
+ // // (used in reponses from db)
+ // int32 opCode; // request type - see table below
+ // }
+
+ private final int messageLength;
+
+ private final int requestId;
+
+ private final int responseTo;
+
+ protected final int opCode;
+
+ protected final ChannelBuffer data;
+
+ Message(ChannelBuffer data, int expectedOpCode) {
+ byte[] fourBytes = new byte[4];
+
+ int check = data.readableBytes();
+ this.messageLength = readInt32(data, fourBytes);
+ if (check != messageLength) {
+ throw new IllegalArgumentException(String.format(
+ "message buffer has unexpected size (%d instead of %d)",
+ check, messageLength));
+ }
+ this.requestId = readInt32(data, fourBytes);
+ this.responseTo = readInt32(data, fourBytes);
+ this.opCode = readInt32(data, fourBytes);
+ this.data = data;
+ if (opCode != expectedOpCode) {
+ throw new IllegalArgumentException(String.format(
+ "message buffer has unexpected opcode (%d instead of %d)",
+ opCode, expectedOpCode));
+ }
+ }
+
+ static void writeInt(ChannelBuffer buffer, int x) {
+ buffer.writeByte(x >> 0);
+ buffer.writeByte(x >> 8);
+ buffer.writeByte(x >> 16);
+ buffer.writeByte(x >> 24);
+ }
+
+ static void writeLong(ChannelBuffer buffer, long x) {
+ buffer.writeByte((byte) (0xFFL & (x >> 0)));
+ buffer.writeByte((byte) (0xFFL & (x >> 8)));
+ buffer.writeByte((byte) (0xFFL & (x >> 16)));
+ buffer.writeByte((byte) (0xFFL & (x >> 24)));
+ buffer.writeByte((byte) (0xFFL & (x >> 32)));
+ buffer.writeByte((byte) (0xFFL & (x >> 40)));
+ buffer.writeByte((byte) (0xFFL & (x >> 48)));
+ buffer.writeByte((byte) (0xFFL & (x >> 56)));
+ }
+
+ static void outputMessageHeader(ChannelBuffer buffer, int messageLength,
+ int requestId, int responseTo, int opCode) {
+ writeInt(buffer, messageLength);
+ writeInt(buffer, requestId);
+ writeInt(buffer, responseTo);
+ writeInt(buffer, opCode);
+ }
+
+ static Message readMessage(ChannelBuffer buffer, int messageLength) {
+ byte[] fourBytes = new byte[4];
+ // messageLength
+ readInt32(buffer, fourBytes);
+ // requestId
+ readInt32(buffer, fourBytes);
+ // responseTo
+ readInt32(buffer, fourBytes);
+ int opCode = readInt32(buffer, fourBytes);
+ buffer.resetReaderIndex();
+ switch (opCode) {
+ case ReplyMessage.OP_CODE_REPLY:
+ return new ReplyMessage(buffer.readBytes(messageLength));
+ case QueryMessage.OP_CODE_QUERY:
+ return new QueryMessage(buffer.readBytes(messageLength));
+ case InsertMessage.OP_CODE_INSERT:
+ return new InsertMessage(buffer.readBytes(messageLength));
+ default:
+ throw new IllegalArgumentException(String.format(
+ "message buffer has unsupported opcode (%d)", opCode));
+
+ }
+ }
+
+ String getOpCodeName() {
+ switch (opCode) {
+ case 1:
+ return "OP_REPLY";
+ case 1000:
+ return "OP_MSG";
+ case 2001:
+ return "OP_UPDATE";
+ case 2002:
+ return "OP_INSERT";
+ case 2003:
+ return "RESERVED";
+ case 2004:
+ return "OP_QUERY";
+ case 2005:
+ return "OP_GET_MORE";
+ case 2006:
+ return "OP_DELETE";
+ case 2007:
+ return "OP_KILL_CURSORS";
+ }
+ return String.format("?(%d)", opCode);
+ }
+
+ ChannelBuffer getBytes() {
+ return ChannelBuffers.unmodifiableBuffer(data.slice(0, messageLength));
+ }
+
+ int getRequestId() {
+ return requestId;
+ }
+
+ @Override
+ public String toString() {
+ if (responseTo != 0) {
+ return "[" + getOpCodeName() + "] Re:#" + responseTo + " ("
+ + messageLength + " bytes)";
+ }
+ return "[" + getOpCodeName() + "] #" + requestId + " (" + messageLength
+ + " bytes)";
+ }
+
+}
107 src/main/java/jmockmongo/wire/MessageDecoder.java
@@ -0,0 +1,107 @@
+/**
+ * Copyright (c) 2012, Thilo Planz. All rights reserved.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Apache License, Version 2.0
+ * as published by the Apache Software Foundation (the "License").
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ * You should have received a copy of the License along with this program.
+ * If not, see <http://www.apache.org/licenses/LICENSE-2.0>.
+ */
+
+package jmockmongo.wire;
+
+import org.bson.BSON;
+import org.bson.BSONException;
+import org.bson.BSONObject;
+import org.bson.io.Bits;
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.jboss.netty.channel.Channel;
+import org.jboss.netty.channel.ChannelHandlerContext;
+import org.jboss.netty.handler.codec.frame.FrameDecoder;
+
+public class MessageDecoder extends FrameDecoder {
+
+ static final int readInt32(ChannelBuffer buffer, byte[] fourBytes) {
+ buffer.readBytes(fourBytes);
+ return Bits.readInt(fourBytes);
+ }
+
+ static final long readInt64(ChannelBuffer buffer, byte[] eightBytes) {
+ buffer.readBytes(eightBytes);
+ return Bits.readLong(eightBytes);
+ }
+
+ static final BSONObject readDocument(ChannelBuffer buffer, byte[] fourBytes) {
+ int length = readInt32(buffer, fourBytes);
+ byte[] bson = new byte[length];
+ buffer.readBytes(bson, 4, length - 4);
+ System.arraycopy(fourBytes, 0, bson, 0, 4);
+ return BSON.decode(bson);
+
+ }
+
+ static final String readCString(ChannelBuffer buffer) {
+ // find the terminating zero-byte
+ int length = buffer.bytesBefore((byte) 0);
+ byte[] bytes = new byte[length];
+ buffer.readBytes(bytes);
+ // also skip the terminator
+ buffer.readByte();
+ try {
+ return new String(bytes, "UTF-8");
+ } catch (java.io.UnsupportedEncodingException uee) {
+ throw new BSONException("impossible", uee);
+ }
+ }
+
+ static final String readUTF8String(ChannelBuffer buffer, byte[] fourBytes) {
+ int size = readInt32(buffer, fourBytes);
+ // this is just protection in case it's corrupted, to avoid huge strings
+ if (size <= 0 || size > (32 * 1024 * 1024))
+ throw new BSONException("bad string size: " + size);
+
+ byte[] b = new byte[size];
+ buffer.readBytes(b);
+
+ try {
+ return new String(b, 0, size - 1, "UTF-8");
+ } catch (java.io.UnsupportedEncodingException uee) {
+ throw new BSONException("impossible", uee);
+ }
+ }
+
+ @Override
+ protected Object decode(ChannelHandlerContext ctx, Channel channel,
+ ChannelBuffer buffer) throws Exception {
+
+ // struct MsgHeader {
+ // int32 messageLength; // total message size, including this
+ // int32 requestID; // identifier for this message
+ // int32 responseTo; // requestID from the original request
+ // // (used in reponses from db)
+ // int32 opCode; // request type - see table below
+ // }
+
+ if (buffer.readableBytes() < 4)
+ return null;
+ byte[] fourBytes = new byte[4];
+ buffer.markReaderIndex();
+ int messageLength = readInt32(buffer, fourBytes);
+ buffer.resetReaderIndex();
+
+ if (buffer.readableBytes() < messageLength) {
+ return null;
+ }
+
+ return Message.readMessage(buffer, messageLength);
+
+ }
+}
99 src/main/java/jmockmongo/wire/QueryMessage.java
@@ -0,0 +1,99 @@
+/**
+ * Copyright (c) 2012, Thilo Planz. All rights reserved.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Apache License, Version 2.0
+ * as published by the Apache Software Foundation (the "License").
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ * You should have received a copy of the License along with this program.
+ * If not, see <http://www.apache.org/licenses/LICENSE-2.0>.
+ */
+
+package jmockmongo.wire;
+
+import static jmockmongo.wire.MessageDecoder.readCString;
+import static jmockmongo.wire.MessageDecoder.readDocument;
+import static jmockmongo.wire.MessageDecoder.readInt32;
+
+import org.bson.BSONObject;
+import org.jboss.netty.buffer.ChannelBuffer;
+
+class QueryMessage extends Message {
+
+ static final int OP_CODE_QUERY = 2004;
+
+ // struct OP_QUERY {
+ // MsgHeader header; // standard message header
+ // int32 flags; // bit vector of query options. See below for details.
+ // cstring fullCollectionName; // "dbname.collectionname"
+ // int32 numberToSkip; // number of documents to skip
+ // int32 numberToReturn; // number of documents to return
+ // // in the first OP_REPLY batch
+ // document query; // query object. See below for details.
+ // [ document returnFieldSelector; ] // Optional. Selector indicating the
+ // fields
+ // // to return. See below for details.
+ // }
+
+ private final int flags;
+
+ private final String fullCollectionName;
+
+ private final int numberToSkip;
+
+ private final int numberToReturn;
+
+ private BSONObject query;
+
+ private BSONObject returnFieldSelector;
+
+ QueryMessage(ChannelBuffer data) {
+ super(data, OP_CODE_QUERY);
+ byte[] fourBytes = new byte[4];
+ flags = readInt32(data, fourBytes);
+ fullCollectionName = readCString(data);
+ numberToSkip = readInt32(data, fourBytes);
+ numberToReturn = readInt32(data, fourBytes);
+ }
+
+ BSONObject getQuery() {
+ if (query == null) {
+ byte[] fourBytes = new byte[4];
+ query = readDocument(data, fourBytes);
+ if (data.readable())
+ returnFieldSelector = readDocument(data, fourBytes);
+ }
+ return query;
+ }
+
+ BSONObject getReturnFieldSelector() {
+ if (query == null)
+ getQuery();
+ return returnFieldSelector;
+ }
+
+ String getFullCollectionName() {
+ return fullCollectionName;
+ }
+
+ @Override
+ public String toString() {
+ return super.toString()
+ + " collection: "
+ + fullCollectionName
+ + " query: "
+ + getQuery()
+ + (numberToSkip > 0 ? " skip: " + numberToSkip : "")
+ + (numberToReturn > 0 ? " return: " + numberToReturn : "")
+ + (returnFieldSelector != null ? " returnFieldSelector: "
+ + returnFieldSelector : "");
+ }
+
+}
103 src/main/java/jmockmongo/wire/ReplyHandler.java
@@ -0,0 +1,103 @@
+/**
+ * Copyright (c) 2012, Thilo Planz. All rights reserved.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Apache License, Version 2.0
+ * as published by the Apache Software Foundation (the "License").
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ * You should have received a copy of the License along with this program.
+ * If not, see <http://www.apache.org/licenses/LICENSE-2.0>.
+ */
+
+package jmockmongo.wire;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import jmockmongo.CommandHandler;
+import jmockmongo.InsertHandler;
+import jmockmongo.QueryHandler;
+
+import org.bson.BSONObject;
+import org.jboss.netty.channel.ChannelHandlerContext;
+import org.jboss.netty.channel.MessageEvent;
+import org.jboss.netty.channel.SimpleChannelUpstreamHandler;
+
+public class ReplyHandler extends SimpleChannelUpstreamHandler {
+
+ private final Map<String, CommandHandler> commands = new HashMap<String, CommandHandler>();
+
+ private QueryHandler queryHandler;
+
+ private InsertHandler insertHandler;
+
+ public void setCommandHandler(String command, CommandHandler handler) {
+ commands.put(command, handler);
+ }
+
+ public void setQueryHandler(QueryHandler handler) {
+ queryHandler = handler;
+ }
+
+ public void setInsertHandler(InsertHandler handler) {
+ insertHandler = handler;
+ }
+
+ @Override
+ public void messageReceived(ChannelHandlerContext ctx, MessageEvent e)
+ throws Exception {
+
+ Message request = (Message) e.getMessage();
+ if (request instanceof QueryMessage) {
+ QueryMessage query = (QueryMessage) request;
+ String fc = query.getFullCollectionName();
+ String[] db = fc.split("\\.", 2);
+
+ if ("$cmd".equals(db[1])) {
+ BSONObject command = query.getQuery();
+ String c = command.keySet().iterator().next();
+ CommandHandler handler = commands.get(c);
+ if (handler != null) {
+ BSONObject result = handler.handleCommand(db[0], command);
+ e.getChannel().write(
+ ReplyMessage.reply((Message) e.getMessage(), 0, 0,
+ 0, result).getBytes());
+ return;
+ }
+
+ throw new UnsupportedOperationException("command " + c + " "
+ + request.toString());
+
+ }
+ if (queryHandler != null) {
+ BSONObject[] result = queryHandler.handleQuery(db[0], db[1],
+ query.getQuery());
+ e.getChannel().write(
+ ReplyMessage.reply((Message) e.getMessage(), 0, 0, 0,
+ result).getBytes());
+ return;
+ }
+
+ }
+ if (request instanceof InsertMessage) {
+ if (insertHandler != null) {
+ InsertMessage insert = (InsertMessage) request;
+ String fc = insert.getFullCollectionName();
+ String[] db = fc.split("\\.", 2);
+ insertHandler.handleInsert(db[0], db[1], false, insert
+ .documents());
+ return;
+ }
+ }
+
+ throw new UnsupportedOperationException(request.toString());
+
+ }
+}
85 src/main/java/jmockmongo/wire/ReplyMessage.java
@@ -0,0 +1,85 @@
+/**
+ * Copyright (c) 2012, Thilo Planz. All rights reserved.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Apache License, Version 2.0
+ * as published by the Apache Software Foundation (the "License").
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ * You should have received a copy of the License along with this program.
+ * If not, see <http://www.apache.org/licenses/LICENSE-2.0>.
+ */
+
+package jmockmongo.wire;
+
+import static jmockmongo.wire.MessageDecoder.readInt32;
+
+import org.bson.BSON;
+import org.bson.BSONObject;
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.jboss.netty.buffer.ChannelBuffers;
+
+class ReplyMessage extends Message {
+
+ static final int OP_CODE_REPLY = 1;
+
+ // struct {
+ // MsgHeader header; // standard message header
+ // int32 responseFlags; // bit vector - see details below
+ // int64 cursorID; // cursor id if client needs to do get more's
+ // int32 startingFrom; // where in the cursor this reply is starting
+ // int32 numberReturned; // number of documents in the reply
+ // document* documents; // documents
+ // }
+ //
+
+ private final int responseFlags;
+
+ private final long cursorId;
+
+ private final int startingFrom;
+
+ private final int numberReturned;
+
+ ReplyMessage(ChannelBuffer data) {
+ super(data, OP_CODE_REPLY);
+ byte[] fourBytes = new byte[4];
+ byte[] eightBytes = new byte[8];
+ responseFlags = readInt32(data, fourBytes);
+ cursorId = MessageDecoder.readInt64(data, eightBytes);
+ startingFrom = readInt32(data, fourBytes);
+ numberReturned = readInt32(data, fourBytes);
+ }
+
+ @Override
+ public String toString() {
+ if (cursorId != 0) {
+ return super.toString() + " [cursor #" + cursorId + "@"
+ + startingFrom + "(" + numberReturned + ")]";
+ }
+ return super.toString() + " with " + numberReturned + " docs";
+ }
+
+ static ReplyMessage reply(Message request, int responseFlags,
+ long cursorId, int startingFrom, BSONObject... docs) {
+
+ ChannelBuffer buffer = ChannelBuffers.dynamicBuffer();
+ writeInt(buffer, responseFlags);
+ writeLong(buffer, cursorId);
+ writeInt(buffer, startingFrom);
+ writeInt(buffer, docs.length);
+ for (BSONObject doc : docs)
+ buffer.writeBytes(BSON.encode(doc));
+ ChannelBuffer header = ChannelBuffers.buffer(16);
+ outputMessageHeader(header, buffer.readableBytes() + 16, 1, request
+ .getRequestId(), OP_CODE_REPLY);
+
+ return new ReplyMessage(ChannelBuffers.wrappedBuffer(header, buffer));
+ }
+}
42 src/test/java/jmockmongo/DefaultInsertHandlerTest.java
@@ -0,0 +1,42 @@
+/**
+ * Copyright (c) 2012, Thilo Planz. All rights reserved.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Apache License, Version 2.0
+ * as published by the Apache Software Foundation (the "License").
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ * You should have received a copy of the License along with this program.
+ * If not, see <http://www.apache.org/licenses/LICENSE-2.0>.
+ */
+
+package jmockmongo;
+
+import java.net.UnknownHostException;
+
+import com.mongodb.BasicDBObject;
+import com.mongodb.Mongo;
+import com.mongodb.MongoException;
+import com.mongodb.WriteConcern;
+
+public class DefaultInsertHandlerTest extends MockMongoSetup {
+
+ public void testSimpleInsert() throws UnknownHostException, MongoException,
+ InterruptedException {
+
+ Mongo m = new Mongo();
+ m.getDB("x").getCollection("x").insert(WriteConcern.SAFE,
+ new BasicDBObject("_id", "x").append("field", "test"));
+
+ assertMockMongoFieldEquals("test", "x.x", "x", "field");
+ assertEquals("test", m.getDB("x").getCollection("x").findOne("x").get(
+ "field"));
+
+ }
+}
60 src/test/java/jmockmongo/MockMongoSetup.java
@@ -0,0 +1,60 @@
+/**
+ * Copyright (c) 2012, Thilo Planz. All rights reserved.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Apache License, Version 2.0
+ * as published by the Apache Software Foundation (the "License").
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ * You should have received a copy of the License along with this program.
+ * If not, see <http://www.apache.org/licenses/LICENSE-2.0>.
+ */
+
+package jmockmongo;
+
+import junit.framework.TestCase;
+
+import org.bson.BSONObject;
+
+public abstract class MockMongoSetup extends TestCase {
+
+ private MockMongo mongo;
+
+ @Override
+ protected void setUp() throws Exception {
+ mongo = new MockMongo();
+ mongo.start();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ mongo.stop();
+ }
+
+ protected BSONObject assertMockMongoContainsDocument(
+ String fullCollectionName, Object _id) {
+ MockDBCollection c = mongo.getCollection(fullCollectionName);
+ if (c == null) {
+ fail("no collection " + fullCollectionName);
+ }
+ BSONObject b = c.findOne(_id);
+ if (b == null) {
+ fail("no document " + _id + " in " + fullCollectionName);
+ }
+ return b;
+ }
+
+ protected void assertMockMongoFieldEquals(Object expected,
+ String fullCollectionName, Object _id, String field) {
+ BSONObject doc = assertMockMongoContainsDocument(fullCollectionName,
+ _id);
+ assertEquals(expected, BSONUtils.get(doc, field));
+ }
+
+}
44 src/test/java/jmockmongo/MockMongoTest.java
@@ -0,0 +1,44 @@
+/**
+ * Copyright (c) 2012, Thilo Planz. All rights reserved.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Apache License, Version 2.0
+ * as published by the Apache Software Foundation (the "License").
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ * You should have received a copy of the License along with this program.
+ * If not, see <http://www.apache.org/licenses/LICENSE-2.0>.
+ */
+
+package jmockmongo;
+
+import java.net.UnknownHostException;
+
+import junit.framework.TestCase;
+
+import com.mongodb.Mongo;
+import com.mongodb.MongoException;
+
+public class MockMongoTest extends TestCase {
+
+ public void testStartStop() throws UnknownHostException, MongoException,
+ InterruptedException {
+
+ MockMongo mongo = new MockMongo();
+ mongo.start();
+ new Mongo().getDatabaseNames();
+ mongo.stop();
+ try {
+ new Mongo().getDatabaseNames();
+ fail("should have stopped");
+ } catch (MongoException e) {
+ }
+ }
+
+}
Please sign in to comment.
Something went wrong with that request. Please try again.