pax_global_header00006660000000000000000000000064121503572160014514gustar00rootroot0000000000000052 comment=b20335ef6dd4b97b8e78cd3d9679ff4fcdd0800b LightCouch-lightcouch-0.0.6/000077500000000000000000000000001215035721600156775ustar00rootroot00000000000000LightCouch-lightcouch-0.0.6/.gitignore000066400000000000000000000000631215035721600176660ustar00rootroot00000000000000.project .settings .classpath target *.log *.log.* LightCouch-lightcouch-0.0.6/LICENSE000066400000000000000000000261521215035721600167120ustar00rootroot00000000000000 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 2011 Ahmed Yehia (ahmed.yehia.m@gmail.com) 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. LightCouch-lightcouch-0.0.6/README.md000066400000000000000000000006711215035721600171620ustar00rootroot00000000000000CouchDB Java API ================ * Homepage: LightCouch aims at providing a simple and easy-to-use APIs for accessing CouchDB databases. It offers a powerful and lightweight persistence interface with minimal code base and dependency. Getting Started guide is available on the website at: API documentation and User Guide: LightCouch-lightcouch-0.0.6/guide/000077500000000000000000000000001215035721600167745ustar00rootroot00000000000000LightCouch-lightcouch-0.0.6/guide/LICENSE000066400000000000000000000250401215035721600200020ustar00rootroot00000000000000 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 Copyright 2011 Ahmed Yehia (ahmed.yehia.m@gmail.com) 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. LightCouch-lightcouch-0.0.6/guide/README000066400000000000000000000000341215035721600176510ustar00rootroot00000000000000Usage: mvn generate-sources LightCouch-lightcouch-0.0.6/guide/pom.xml000066400000000000000000000023601215035721600203120ustar00rootroot00000000000000 4.0.0 org.lightcouch lightcouch-guide 0.0.1-SNAPSHOT com.agilejava.docbkx docbkx-maven-plugin 2.0.13 generate-html generate-pdf generate-sources org.docbook docbook-xml 4.4 runtime http://www.lightcouch.org/css/manual.css src/docbkx/resources/pdf.xsl src/docbkx/resources/html.xsl LightCouch-lightcouch-0.0.6/guide/src/000077500000000000000000000000001215035721600175635ustar00rootroot00000000000000LightCouch-lightcouch-0.0.6/guide/src/docbkx/000077500000000000000000000000001215035721600210355ustar00rootroot00000000000000LightCouch-lightcouch-0.0.6/guide/src/docbkx/lightcouch-guide.xml000066400000000000000000000703611215035721600250120ustar00rootroot00000000000000
LightCouch User Guide 0.0.6 Author: Ahmed Yehia Oct 24, 2012
Overview LightCouch provides a Java interface for communicating with CouchDB RESTful JSON APIs. APIs in LightCouch is accessed by establishing a 'client' towards a CouchDB database server. A client is the main object which performs every request to the database, under it's context, such as document persistence or querying Views. The APIs focus on Documents CRUD, Views, Attachments, Design Documents, Bulk operations, Changes notification, and database specific such as Compaction and Replication. API usage and use cases examples is covered by integration unit tests, that runs against a running database. Download. Internally LightCouch depends on Apache HttpClient for handling the HTTP communications & Gson for JSON / Java mappings.
Setup Maven org.lightcouch lightcouch 0.0.6 ]]> Alternatively download the dependencies. LightCouch 0.0.6 HttpClient 4.2.5 HttpCore 4.2.4 Commons Codec 1.6 Commons Logging 1.1.1 Gson 2.2.4
Configuration and Use LightCouch client, i.e. org.lightcouch.CouchDbClient can be configured and constructed in several ways as preferred. Configuration parameters can be supplied to a client via .properties files, or through convenient constructors. Typical usage is creating a file named couchdb.properties in the default classpath of your application, containing the following parameters: Next, create a client instance, using the default constructor: CouchDbClient provides additional constructors for instantiating a client. One which accepts a .properties file name e.g, mycouchdb.properties, another that accepts individual parameters, the final uses an object with properties CouchDbProperties. Typically a client instance is created once per an application, and reused. Multiple client instances within an application; can be created to handle multiple databases, each instance runs in isolation. After usage, it might be useful to shutdown a client's underlying connection manager, for proper release of resources:
Spring DI ]]> Note, the example synchronize Design documents with the databse, after a client is created.
Documents API Documents API is accessed via a client instance which acts like JPA EntityManager. The API provides CRUD kind of operations for documents. Model classes that represent documents in CouchDB, can choose to extend org.lightcouch.Document for conventience. Plain objects need to define id and revision fields, i.e. _id & _rev. Gson Mapping annotations eg. @SerializedName("_id") String id; could be used to indicate the JSON name of a field. Fields with null value are not serialized by defualt. An instance of Gson may be obtained by a client, for use in an application when desired.
Saving/Updating Documents Saving / Updating Documents is handled by save(Object) and update(Object). An object can be: 1. Plain Java Object. 2. java.util.Map. 3. com.google.gson.JsonObject. Saving a new object internally is handled by HTTP PUT, a case where a document must be assigned an id prior to sending to the database. If the new object does not define an id, the API will generate an UUID similar to database generated. UUIDs can also by obtained from the database, an example below on Databse API section. Documents can also be saved via batch(Object). The Result of save or update operations returns a org.lightcouch.Response object that represents the database response. map = new HashMap<>(); map.put("_id", "doc-id"); map.put("title", "value"); dbClient.save(map); JsonObject json = new JsonObject(); json.addProperty("_id", "doc-id"); json.add("array", new JsonArray()); dbClient.save(json); String jsonstr = "{\"title\":\"val\"}"; JsonObject jsonobj = dbClient.getGson().fromJson(jsonstr, JsonObject.class); dbClient.save(jsonobj); ]]>
Finding Documents Finding documents API is available through find()s, that expect the document id, or w/ rev, and optional query parameters through org.lightcouch.Params An additional finder findAny() gives the option to get any document from the database, given a URI, as string, encoding may be required. Documents returned as: 1. Plain Java Objects. 2. com.google.gson.JsonObject. 3. java.io.InputStream. java.util.Map, however does not qualify as a return type. Note: Input streams need to be closed properly; to release the connection.
Deleting Documents The API provides a remove() for deleting documents, expected parameters: 1. An object containing _id and _rev. 2. id and revision values.
Custom Serializers Configuring a custom serializer gives the option to register classes Gson may not be able to handle by default, eg. JodaTime DateTime class. () { public JsonElement serialize(DateTime src, Type typeOfSrc, JsonSerializationContext context) { return new JsonPrimitive(src.toString()); } }); gsonBuilder.registerTypeAdapter(DateTime.class, new JsonDeserializer() { public DateTime deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { return new DateTime(json.getAsJsonPrimitive().getAsString()); } }); CouchDbClient dbClient = new CouchDbClient(); dbClient.setGsonBuilder(gsonBuilder); // .. client is ready to use ]]>
Attachments API The API supports both inline and standalone Attachments.
Inline Attachments org.lightcouch.Attachment represents an inline attachment enclosed in a document. The base64 data of an attachment may be encoded utilizing the included dependency on Apache Codec Base64.encodeBase64String(bytes). Model classes that extend org.lightcouch.Document inherit the support for inline attachments. To retrieve the base64 data of an attachment, include a parameter to the find()
Standalone Attachments Standalone attachments could be saved to an existing, or to a new document. The attachment data is provided as java.io.InputStream.
Logging and Error Handling
Logging LightCouch logs information about API usage, such as a request URI, and the returned HTTP status code from the database server, such information is logged at the level INFO. Log4j configuration:
Error Handling In the event of API call failure, an exception of a specific type is thrown to report the failure. Defined Exceptions: 1. NoDocumentException when a requested document could not be found (or a View with no result). 2. DocumentConflictException when a conflict is detected during a save or update. 3. CouchDbException is the super class for the former two exceptions, conventiently it is thrown in cases of unexcpected exceptions, such as database errors 4xx or 5xx.
Views API Views API is accessible under the context view("design_name/my_view"). Views can be queried in a number of ways: 1. ]]> A list of specified type. 2. Scalar values, int, boolean etc. 3. View entries, key/value pairs as stored in the Database B-Tree. 4. java.io.InputStream. 5. Pagination. List list = dbClient.view("example/foo") .includeDocs(true) .startKey("start-key") .endKey("end-key") .limit(10) .query(Foo.class); // primitive types int count = dbClient.view("example/by_tag").key("couchdb").queryForInt(); // View entries (keys/values), with Documents View view = dbClient.view("example/by_date") .key(2011, 10, 15) // complex key as: values, or array .reduce(false) .includeDocs(true); ViewResult entries = view.queryView(int[].class, String.class, Foo.class); ]]>
Special Views Views API extends to support special Views such as _all_docs & _temp_view. Temporary Views functions may be be defined in local .js files; saved in the application classpath, or through MapReduce object. allDocs = dbClient.view("_all_docs").query(JsonObject.class); // _temp_view List list = dbClient.view("_temp_view") .tempView("temp_1") // .tempView(mapRedObj) .includeDocs(true) .reduce(false) .query(Foo.class); ]]> Temp views directory structure.
Pagination Paging over a View results is offered by an API that features previous & next navigation model. A View page is respresented by ]]> Usage example in web application. page = dbClient.view("example/foo").reduce(false).queryPage(resultsPerPage, param, Foo.class); request.setAttribute("page", page); // .. forward to a JSP for display } ]]> <%-- iterate over documents --%>

${foo.title} <%-- show paging status --%>

Showing: ${page.resultFrom} - ${page.resultTo} of total ${page.totalResults} <%-- handle navigation --%>

Previous Previous ${page.pageNumber} Next Next ]]>

Bulk Documents Bulk documents API performs two operations: Modify & Fetch for bulk documents. newDocs = new ArrayList(); newDocs.add(new Foo()); newDocs.add(new JsonObject()); boolean allOrNothing = true; List bulkResponse = dbClient.bulk(newDocs, allOrNothing); // Fetch List keys = Arrays.asList(new String[]{"doc-id-1", "doc-id-2"}); List docs = dbClient.view("_all_docs") .includeDocs(true) .keys(keys) .query(Foo.class); ]]>
Design Documents The API for Design Documents is accessible under the context design(). A Design Document is represented by org.lightcouch.DesignDocument. Design Documents may be maintained on local .js files; saved in an application classpath. An API is provides a kind of synchronization to copy from local files to the database. org.lightcouch.CouchDbDesign lists all the supported operations. An example design document can be found under the test package of the source code. Design documents directory structure:
Change Notifications Change notifications API is accessible under the context changes(). The API supports Changes feed of both types normal and continuous. A Row in the Changes feed is returned as com.google.gson.JsonObject rows = changes.getResults(); for (ChangesResult.Row row : rows) { String docId = row.getId() JsonObject doc = row.getDoc(); } // feed type continuous Changes changes = dbClient.changes() .includeDocs(true) .since(since) .heartBeat(30000) .filter("example/filter") .continuousChanges(); // live access to continuous feeds while (changes.hasNext()) { ChangesResult.Row feed = changes.next(); String docId = feed.getId(); JsonObject doc = feed.getDoc(); // changes.stop(); // stop the running feed } ]]>
Update Handlers Update handlers can be invoked by a client by calling invokeUpdateHandler().
Database API Database specific APIs is accessible under the context context().
Replication Replication is handled by two sets of API, one that replicate by POSTing to _replicate URI, and the newer replicator database (i.e _replicator) introduced with CouchDB 1.1.0 . queryParams = new HashMap<>(); queryParams.put("key1", "val"); // String queryParams = "{\"key1\":\"val\"}"; // normal replication ReplicationResult result = dbClient.replication() .source("source-db") .target("http://user:secret@127.0.0.1:5984/target-db") .createTarget(true) .sinceSeq(100) // as of CouchDB 1.2 // .cancel() // cancel a replication .filter("example/filter1") .queryParams(queryParams); .trigger(); List histories = result.getHistories(); // _replicator database Replicator replicator = dbClient.replicator() .source("source-db") .target("target-db") .continuous(true) .createTarget(true) .sinceSeq(100) // as of CouchDB 1.2 .replicatorDB("replicator_2") // optional, defaults to _replicator .replicatorDocId("doc-id") // optional, defaults to UUID .filter("example/filter1") .queryParams(queryParams) .userCtxName("user") // for delegated requests .userCtxRoles("admin", "manager"); Response response = replicator.save(); // triggers a replication ReplicatorDocument findDocument = dbClient.replicator() .replicatorDocId("doc-id") .find(); List docs = dbClient.replicator().findAll(); Response removeResponse = dbClient.replicator() .replicatorDocId("doc-id") .replicatorDocRev("doc-rev") .remove(); // cancels an ongoing replication ]]>
LightCouch-lightcouch-0.0.6/guide/src/docbkx/resources/000077500000000000000000000000001215035721600230475ustar00rootroot00000000000000LightCouch-lightcouch-0.0.6/guide/src/docbkx/resources/html.xsl000066400000000000000000000005321215035721600245430ustar00rootroot00000000000000 LightCouch-lightcouch-0.0.6/guide/src/docbkx/resources/pdf.xsl000066400000000000000000000010571215035721600243530ustar00rootroot00000000000000 blue LightCouch-lightcouch-0.0.6/pom.xml000066400000000000000000000044371215035721600172240ustar00rootroot00000000000000 4.0.0 org.lightcouch lightcouch 0.0.6 jar LightCouch CouchDB Java API 2011 http://www.lightcouch.org org.sonatype.oss oss-parent 7 The Apache Software License, Version 2.0 http://www.apache.org/licenses/LICENSE-2.0.txt repo scm:git:git://github.com/ahmedyha/LightCouch.git scm:git:git@github.com:ahmedyha/LightCouch.git https://ahmedyha@github.com/ahmedyha/LightCouch.git ahmedyha Ahmed Yehia ahmed.yehia.m@gmail.com UTF-8 4.2.5 2.2.4 4.8.2 org.apache.httpcomponents httpclient ${httpclient.version} com.google.code.gson gson ${gson.version} junit junit ${junit.version} test org.apache.maven.plugins maven-compiler-plugin 3.1 1.5 1.5 LightCouch-lightcouch-0.0.6/src/000077500000000000000000000000001215035721600164665ustar00rootroot00000000000000LightCouch-lightcouch-0.0.6/src/main/000077500000000000000000000000001215035721600174125ustar00rootroot00000000000000LightCouch-lightcouch-0.0.6/src/main/java/000077500000000000000000000000001215035721600203335ustar00rootroot00000000000000LightCouch-lightcouch-0.0.6/src/main/java/org/000077500000000000000000000000001215035721600211225ustar00rootroot00000000000000LightCouch-lightcouch-0.0.6/src/main/java/org/lightcouch/000077500000000000000000000000001215035721600232535ustar00rootroot00000000000000LightCouch-lightcouch-0.0.6/src/main/java/org/lightcouch/Attachment.java000066400000000000000000000034321215035721600262100ustar00rootroot00000000000000/* * Copyright (C) 2011 Ahmed Yehia (ahmed.yehia.m@gmail.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.lightcouch; import com.google.gson.annotations.SerializedName; /** *

Represents an in-line document attachment. *

On saving, the fields 'data' holds the base64 encoded data, * and 'contentType' holds the Content-Type of the attachment. * @author Ahmed Yehia */ public class Attachment { private String data; @SerializedName("content_type") private String contentType; private int revpos; private String digest; private long length; private boolean stub; public String getData() { return data; } public String getContentType() { return contentType; } public int getRevpos() { return revpos; } public String getDigest() { return digest; } public long getLength() { return length; } public boolean isStub() { return stub; } public void setData(String data) { this.data = data; } public void setContentType(String contentType) { this.contentType = contentType; } public void setRevpos(int revpos) { this.revpos = revpos; } public void setDigest(String digest) { this.digest = digest; } public void setLength(long length) { this.length = length; } public void setStub(boolean stub) { this.stub = stub; } } LightCouch-lightcouch-0.0.6/src/main/java/org/lightcouch/Changes.java000066400000000000000000000111271215035721600254700ustar00rootroot00000000000000/* * Copyright (C) 2011 Ahmed Yehia (ahmed.yehia.m@gmail.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.lightcouch; import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.net.URI; import org.apache.http.client.methods.HttpGet; import org.lightcouch.ChangesResult.Row; import com.google.gson.Gson; /** *

Contains the Change Notifications API, supports change feeds of types normal * and continuous. *

Usage Example:

*
 * // feed type normal 
 * ChangesResult result = dbClient.changes()
 *	.since("seq-value") // *
 *	.limit(10)
 *	.filter("example/filter")
 *	.getChanges();
 *
 * // feed type continuous
 * Changes changes = dbClient.changes()
 *	.includeDocs(true) 
 *	.heartBeat(30000)
 *	.continuousChanges(); 
 * 
 * while (changes.hasNext()) { 
 *	ChangesResult.Row feed = changes.next();
 *	// changes.stop(); // stop continuous feed
 * }
 * 
*

* Getting changes since latest update may be obtained by: *

 * CouchDbInfo dbInfo = dbClient.context().info();
 * String since = dbInfo.getUpdateSeq();
 * 
 
 * @see ChangesResult
 * @author Ahmed Yehia
 */
public class Changes {
	
	private BufferedReader reader;
	private HttpGet httpGet;
	private Row nextRow;
	private boolean stop;
	
	private CouchDbClient dbc;
	private Gson gson;
	private URIBuilder uriBuilder;
	
	Changes(CouchDbClient dbc) {
		this.dbc = dbc;
		this.gson = dbc.getGson();
		this.uriBuilder = URIBuilder.builder(dbc.getDBUri()).path("_changes");
	}

	/**
	 * 

Requests Change notifications of feed type continuous. *

Feed notifications are accessed in an iterator style. */ public Changes continuousChanges() { URI uri = uriBuilder.query("feed", "continuous").build(); httpGet = new HttpGet(uri); InputStream in = dbc.get(httpGet); InputStreamReader is = new InputStreamReader(in); setReader(new BufferedReader(is)); return this; } /** * Checks whether a feed is available in the continuous stream, blocking * until a feed is received. */ public boolean hasNext() { return readNextRow(); } /** * @return The next feed in the stream. */ public Row next() { return getNextRow(); } /** * Stops a running continuous feed. */ public void stop() { stop = true; } /** * Requests Change notifications of feed type normal. */ public ChangesResult getChanges() { URI uri = uriBuilder.query("feed", "normal").build(); return dbc.get(uri, ChangesResult.class); } // -------------------------------- Query Params public Changes since(String since) { uriBuilder.query("since", since); return this; } public Changes limit(int limit) { uriBuilder.query("limit", limit); return this; } public Changes heartBeat(long heartBeat) { uriBuilder.query("heartbeat", heartBeat); return this; } public Changes timeout(long timeout) { uriBuilder.query("timeout", timeout); return this; } public Changes filter(String filter) { uriBuilder.query("filter", filter); return this; } public Changes includeDocs(boolean includeDocs) { uriBuilder.query("include_docs", includeDocs); return this; } public Changes style(String style) { uriBuilder.query("style", style); return this; } //---------------------------------------- Private /** * Reads and sets the next feed in the stream. */ private boolean readNextRow() { boolean hasNext = false; try { if(!stop) { String row = getReader().readLine(); if(row != null && !row.startsWith("{\"last_seq\":")) { setNextRow(gson.fromJson(row, Row.class)); hasNext = true; } } } catch (Exception e) { terminate(); throw new CouchDbException("Error reading continuous stream.", e); } if(!hasNext) terminate(); return hasNext; } private BufferedReader getReader() { return reader; } private void setReader(BufferedReader reader) { this.reader = reader; } private Row getNextRow() { return nextRow; } private void setNextRow(Row nextRow) { this.nextRow = nextRow; } private void terminate() { httpGet.abort(); CouchDbUtil.close(getReader()); } } LightCouch-lightcouch-0.0.6/src/main/java/org/lightcouch/ChangesResult.java000066400000000000000000000044521215035721600266720ustar00rootroot00000000000000/* * Copyright (C) 2011 Ahmed Yehia (ahmed.yehia.m@gmail.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.lightcouch; import java.util.List; import com.google.gson.JsonObject; import com.google.gson.annotations.SerializedName; /** *

Represents a Change notifications feed result. * @see Changes * @author Ahmed Yehia * */ public class ChangesResult { private List results; @SerializedName("last_seq") private String lastSeq; public List getResults() { return results; } public void setResults(List results) { this.results = results; } public String getLastSeq() { return lastSeq; } public void setLastSeq(String lastSeq) { this.lastSeq = lastSeq; } /** * Represent a row in Changes result. */ public static class Row { private String seq; private String id; private List changes; private boolean deleted; private JsonObject doc; public String getSeq() { return seq; } public void setSeq(String seq) { this.seq = seq; } public String getId() { return id; } public void setId(String id) { this.id = id; } public List getChanges() { return changes; } public void setChanges(List changes) { this.changes = changes; } public boolean isDeleted() { return deleted; } public void setDeleted(boolean deleted) { this.deleted = deleted; } public JsonObject getDoc() { return doc; } public void setDoc(JsonObject doc) { this.doc = doc; } /** * Represent a Change rev. */ public static class Rev { private String rev; public String getRev() { return rev; } public void setRev(String rev) { this.rev = rev; } } // end class Rev } // end class Row } // end class ChangesResult LightCouch-lightcouch-0.0.6/src/main/java/org/lightcouch/CouchDbClient.java000066400000000000000000000363021215035721600265700ustar00rootroot00000000000000/* * Copyright (C) 2011 Ahmed Yehia (ahmed.yehia.m@gmail.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.lightcouch; import static org.lightcouch.CouchDbUtil.assertNotEmpty; import static org.lightcouch.CouchDbUtil.close; import static org.lightcouch.CouchDbUtil.generateUUID; import static org.lightcouch.CouchDbUtil.getElement; import static org.lightcouch.CouchDbUtil.streamToString; import static org.lightcouch.URIBuilder.builder; import java.io.InputStream; import java.net.URI; import java.util.List; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpPut; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonObject; /** *

Presents a client to a CouchDB database instance. *

This is the main class to use to gain access to the various APIs defined by this client. * *

Usage Example:

*

Instantiating an instance of this class requires configuration options to be supplied. * Properties files may be used for this purpose. See overloaded constructors for available options. *

A typical example for creating an instance is by preparing a properties file named * couchdb.properties and placing it in your application classpath: * *

 * couchdb.name=my-db
 * couchdb.createdb.if-not-exist=true
 * couchdb.protocol=http
 * couchdb.host=127.0.0.1
 * couchdb.port=5984
 * couchdb.username=
 * couchdb.password=
 * 
* *

Then construct a new instance using the default constructor: *

 * CouchDbClient dbClient = new CouchDbClient(); // looks for classpath:couchdb.properties
 * // access the API here
 * 
*

Multiple client instances could be created to handle multiple database instances simultaneously in a thread-safe manner, * typically one client for each database. * *

A client instance provides access to various APIs, accessible under several locations or contexts. *

Document APIs are available directly under this instance: *

 *  Foo foo = dbClient.find(Foo.class, "some-id");
 * 
* *

Design documents API under the context design() {@link CouchDbDesign} contains usage example. * *

View APIs under the context view() {@link View} contains usage examples. * *

Change Notifications API under the context changes() see {@link Changes} for usage example. * *

Replication APIs under two contexts: replication() and replicator(), * the latter supports the replicator database introduced with CouchDB v 1.1.0 * {@link Replication} and {@link Replicator} provide usage examples. * *

Database APIs under the context context() * *

After completing usage of this client, it might be useful to shutdown it's * underlying connection manager to ensure proper release of resources: * dbClient.shutdown() * * @author Ahmed Yehia * */ public final class CouchDbClient extends CouchDbClientBase { // -------------------------------------------------------------------------- Constructors /** * Constructs a new instance of this class, expects a configuration file named * couchdb.properties to be available in your application classpath. */ public CouchDbClient() { super(); } /** * Constructs a new instance of this class. * @param configFileName The configuration file name. */ public CouchDbClient(String configFileName) { super(new CouchDbConfig(configFileName)); } /** * Constructs a new instance of this class. * @param dbName The database name. * @param createDbIfNotExist To create a new database if it does not already exist. * @param protocol The protocol to use (i.e http or https) * @param host The database host address * @param port The database listening port * @param username The Username credential * @param password The Password credential */ public CouchDbClient(String dbName, boolean createDbIfNotExist, String protocol, String host, int port, String username, String password) { super(new CouchDbConfig(new CouchDbProperties(dbName, createDbIfNotExist, protocol, host, port, username, password))); } /** * Constructs a new instance of this class. * @param properties An object containing configuration properties. * @see {@link CouchDbProperties} */ public CouchDbClient(CouchDbProperties properties) { super(new CouchDbConfig(properties)); } private CouchDbContext context; private CouchDbDesign design; { context = new CouchDbContext(this); design = new CouchDbDesign(this); } /** * {@inheritDoc} */ @Override public void setGsonBuilder(GsonBuilder gsonBuilder) { super.setGsonBuilder(gsonBuilder); } /** * Provides access to the database APIs. */ public CouchDbContext context() { return context; } /** * Provides access to the database design documents API. */ public CouchDbDesign design() { return design; } /** * Provides access to the View APIs. */ public View view(String viewId) { return new View(this, viewId); } /** * Provides access to the replication APIs. */ public Replication replication() { return new Replication(this); } /** * Provides access to the replicator database APIs. */ public Replicator replicator() { return new Replicator(this); } /** * Provides access to the Change Notifications API. */ public Changes changes() { return new Changes(this); } /** * Synchronize all design documents on desk with the database. *

Shorthand for {@link CouchDbDesign#synchronizeAllWithDb()} *

This method might be used to sync design documents upon a client creation, eg. a Spring bean init-method. */ public void syncDesignDocsWithDb() { design().synchronizeAllWithDb(); } /** * Finds an Object of the specified type. * @param Object type. * @param classType The class of type T. * @param id The document id. * @return An object of type T. * @throws NoDocumentException If the document is not found in the database. */ public T find(Class classType, String id) { assertNotEmpty(classType, "Class"); assertNotEmpty(id, "id"); return get(builder(getDBUri()).path(id).build(), classType); } /** * Finds an Object of the specified type. * @param Object type. * @param classType The class of type T. * @param id The document id. * @param params Extra parameters to append. * @return An object of type T. * @throws NoDocumentException If the document is not found in the database. */ public T find(Class classType, String id, Params params) { assertNotEmpty(classType, "Class"); assertNotEmpty(id, "id"); return get(builder(getDBUri()).path(id).query(params).build(), classType); } /** * Finds an Object of the specified type. * @param Object type. * @param classType The class of type T. * @param id The document id to get. * @param rev The document revision. * @return An object of type T. * @throws NoDocumentException If the document is not found in the database. */ public T find(Class classType, String id, String rev) { assertNotEmpty(classType, "Class"); assertNotEmpty(id, "id"); assertNotEmpty(id, "rev"); URI uri = builder(getDBUri()).path(id).query("rev", rev).build(); return get(uri, classType); } /** * A General purpose find, that gives more control over the query. *

Unlike other finders, this method expects a fully formated and encoded URI to be supplied. * @param classType The class of type T. * @param uri The URI. * @return An object of type T. */ public T findAny(Class classType, String uri) { assertNotEmpty(classType, "Class"); assertNotEmpty(uri, "uri"); return get(URI.create(uri), classType); } /** *

Finds a document and returns the result as an {@link InputStream}.

* The stream should be properly closed after usage, as to avoid connection leaks. * @param id The document id. * @return The result of the request as an {@link InputStream} * @throws NoDocumentException If the document is not found in the database. * @see #find(String, String) */ public InputStream find(String id) { assertNotEmpty(id, "id"); return get(builder(getDBUri()).path(id).build()); } /** *

Finds a document given an id and revision, returns the result as {@link InputStream}.

* The stream should be properly closed after usage, as to avoid connection leaks. * @param id The document id. * @param rev The document revision. * @return The result of the request as an {@link InputStream} * @throws NoDocumentException If the document is not found in the database. */ public InputStream find(String id, String rev) { assertNotEmpty(id, "id"); assertNotEmpty(rev, "rev"); return get(builder(getDBUri()).path(id).query("rev", rev).build()); } /** * Checks if the database contains a document given an id. * @param id The document id. * @return true If the document is found, false otherwise. */ public boolean contains(String id) { assertNotEmpty(id, "id"); HttpResponse response = null; try { response = head(builder(getDBUri()).path(id).build()); } catch (NoDocumentException e) { return false; } finally { close(response); } return true; } /** * Saves an object in the database. * @param object The object to save * @throws DocumentConflictException If a conflict is detected during the save. * @return {@link Response} */ public Response save(Object object) { return put(getDBUri(), object, true); } /** * Saves the given object in a batch request. * @param object The object to save. */ public void batch(Object object) { assertNotEmpty(object, "object"); HttpResponse response = null; try { URI uri = builder(getDBUri()).query("batch", "ok").build(); response = post(uri, getGson().toJson(object)); } finally { close(response); } } /** * Performs a Bulk Documents request. * @param objects The {@link List} of objects. * @param allOrNothing Indicated whether the request has all-or-nothing semantics. * @return {@code List} Containing the resulted entries. */ public List bulk(List objects, boolean allOrNothing) { assertNotEmpty(objects, "objects"); HttpResponse response = null; try { String allOrNothingVal = allOrNothing ? "\"all_or_nothing\": true, " : ""; URI uri = builder(getDBUri()).path("_bulk_docs").build(); String json = String.format("{%s%s%s}", allOrNothingVal, "\"docs\": ", getGson().toJson(objects)); response = post(uri, json); return getResponseList(response); } finally { close(response); } } /** *

Saves an attachment under a new document with a generated UUID as the document id. *

To retrieve an attachment, see {@link #find(String)}. * @param instream The {@link InputStream} holding the binary data. * @param name The attachment name. * @param contentType The attachment "Content-Type". * @return {@link Response} */ public Response saveAttachment(InputStream instream, String name, String contentType) { assertNotEmpty(instream, "InputStream"); assertNotEmpty(name, "name"); assertNotEmpty(contentType, "ContentType"); URI uri = builder(getDBUri()).path(generateUUID()).path("/").path(name).build(); return put(uri, instream, contentType); } /** *

Saves an attachment under an existing document given both a document id * and revision, or under a new document given only the document id. *

To retrieve an attachment, see {@link #find(String)}. * @param instream The {@link InputStream} holding the binary data. * @param name The attachment name. * @param contentType The attachment "Content-Type". * @param docId The document id to save the attachment under, or {@code null} to save under a new document. * @param docRev The document revision to save the attachment under, or {@code null} when saving to a new document. * @throws DocumentConflictException * @return {@link Response} */ public Response saveAttachment(InputStream instream, String name, String contentType, String docId, String docRev) { assertNotEmpty(instream, "InputStream"); assertNotEmpty(name, "name"); assertNotEmpty(contentType, "ContentType"); assertNotEmpty(docId, "DocId"); URI uri = builder(getDBUri()).path(docId).path("/").path(name).query("rev", docRev).build(); return put(uri, instream, contentType); } /** * Updates an object in the database, the object must have the correct id and revision values. * @param object The object to update * @throws DocumentConflictException If a conflict is detected during the update. * @return {@link Response} */ public Response update(Object object) { return put(getDBUri(), object, false); } /** * Removes an object from the database, the object must have the correct id and revision values. * @param object The object to remove * @throws NoDocumentException If the document could not be found in the database. * @return {@link Response} */ public Response remove(Object object) { assertNotEmpty(object, "object"); JsonObject jsonObject = getGson().toJsonTree(object).getAsJsonObject(); String id = getElement(jsonObject, "_id"); String rev = getElement(jsonObject, "_rev"); return remove(id, rev); } /** * Removes a document from the database, given both an id and revision values. * @param id The document id * @param rev The document revision * @throws NoDocumentException If the document could not be found in the database. * @return {@link Response} */ public Response remove(String id, String rev) { assertNotEmpty(id, "id"); assertNotEmpty(rev, "rev"); return delete(builder(getDBUri()).path(id).query("rev", rev).build()); } /** * Invokes an Update Handler. * @param updateHandlerUri The Update Handler URI, in the format: designDocId/updateFunction * @param docId The document id to update. * @param query The query string parameters, e.g, field=field1&value=value1 * @return The output of the request. */ public String invokeUpdateHandler(String updateHandlerUri, String docId, String query) { assertNotEmpty(updateHandlerUri, "updateHandlerUri"); assertNotEmpty(docId, "docId"); String[] v = updateHandlerUri.split("/"); String path = String.format("_design/%s/_update/%s/%s", v[0], v[1], docId); URI uri = builder(getDBUri()).path(path).query(query).build(); HttpResponse response = executeRequest(new HttpPut(uri)); return streamToString(getStream(response)); } /** * {@inheritDoc} */ @Override public URI getDBUri() { return super.getDBUri(); } /** * {@inheritDoc} */ @Override public URI getBaseUri() { return super.getBaseUri(); } /** * {@inheritDoc} */ @Override public Gson getGson() { return super.getGson(); } /** * {@inheritDoc} */ @Override public void shutdown() { super.shutdown(); } } LightCouch-lightcouch-0.0.6/src/main/java/org/lightcouch/CouchDbClientBase.java000066400000000000000000000355331215035721600273700ustar00rootroot00000000000000/* * Copyright (C) 2011 Ahmed Yehia (ahmed.yehia.m@gmail.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.lightcouch; import static java.lang.String.format; import static org.lightcouch.CouchDbUtil.*; import static org.lightcouch.URIBuilder.builder; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.io.UnsupportedEncodingException; import java.lang.reflect.Type; import java.net.URI; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.List; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocket; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.http.HttpHost; import org.apache.http.HttpRequest; import org.apache.http.HttpRequestInterceptor; import org.apache.http.HttpResponse; import org.apache.http.HttpResponseInterceptor; import org.apache.http.HttpStatus; import org.apache.http.auth.AuthScope; import org.apache.http.auth.UsernamePasswordCredentials; import org.apache.http.client.AuthCache; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpDelete; import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpHead; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPut; import org.apache.http.client.methods.HttpRequestBase; import org.apache.http.client.protocol.ClientContext; import org.apache.http.conn.params.ConnRoutePNames; import org.apache.http.conn.scheme.PlainSocketFactory; import org.apache.http.conn.scheme.Scheme; import org.apache.http.conn.scheme.SchemeRegistry; import org.apache.http.conn.scheme.SchemeSocketFactory; import org.apache.http.conn.ssl.SSLSocketFactory; import org.apache.http.entity.InputStreamEntity; import org.apache.http.entity.StringEntity; import org.apache.http.impl.auth.BasicScheme; import org.apache.http.impl.client.BasicAuthCache; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.impl.conn.PoolingClientConnectionManager; import org.apache.http.params.CoreConnectionPNames; import org.apache.http.params.CoreProtocolPNames; import org.apache.http.protocol.BasicHttpContext; import org.apache.http.protocol.HttpContext; import org.apache.http.util.EntityUtils; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonDeserializationContext; import com.google.gson.JsonDeserializer; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParseException; import com.google.gson.JsonSerializationContext; import com.google.gson.JsonSerializer; import com.google.gson.reflect.TypeToken; /** * Base client class to be extended by a concrete subclass, responsible for establishing * a connection with the database and the definition of the basic HTTP request handling and validation. * @see CouchDbClient * @author Ahmed Yehia */ abstract class CouchDbClientBase { static final Log log = LogFactory.getLog(CouchDbClientBase.class); private HttpClient httpClient; private URI baseURI; private URI dbURI; private Gson gson; private CouchDbConfig config; private HttpHost host; private BasicHttpContext context; protected CouchDbClientBase() { this(new CouchDbConfig()); } protected CouchDbClientBase(CouchDbConfig config) { CouchDbProperties props = config.getProperties(); this.httpClient = createHttpClient(props); this.gson = initGson(new GsonBuilder()); this.config = config; baseURI = builder().scheme(props.getProtocol()).host(props.getHost()).port(props.getPort()).path("/").build(); dbURI = builder(baseURI).path(props.getDbName()).path("/").build(); } // ---------------------------------------------- Getters /** * @return The database URI. */ protected URI getDBUri() { return dbURI; } /** * @return The base URI. */ protected URI getBaseUri() { return baseURI; } /** * @return The Gson instance. */ protected Gson getGson() { return gson; } protected CouchDbConfig getConfig() { return config; } // HTTP Requests /** * Performs a HTTP GET request. * @return {@link InputStream} */ InputStream get(HttpGet httpGet) { HttpResponse response = executeRequest(httpGet); return getStream(response); } /** * Performs a HTTP GET request. * @return {@link InputStream} */ InputStream get(URI uri) { HttpGet get = new HttpGet(uri); get.addHeader("Accept", "application/json"); return get(get); } /** * Performs a HTTP GET request. * @return An object of type T */ T get(URI uri, Class classType) { InputStream instream = null; try { instream = get(uri); return deserialize(instream, classType); } finally { close(instream); } } /** * Performs a HTTP HEAD request. * @return {@link HttpResponse} */ HttpResponse head(URI uri) { return executeRequest(new HttpHead(uri)); } /** * Performs a HTTP PUT request, saves or updates a document. * @return {@link Response} */ Response put(URI uri, Object object, boolean newEntity) { assertNotEmpty(object, "object"); HttpResponse response = null; try { JsonObject json = getGson().toJsonTree(object).getAsJsonObject(); String id = getElement(json, "_id"); String rev = getElement(json, "_rev"); if(newEntity) { // save assertNull(rev, "revision"); id = (id == null) ? generateUUID() : id; } else { // update assertNotEmpty(id, "id"); assertNotEmpty(rev, "revision"); } HttpPut put = new HttpPut(builder(uri).path(id).build()); setEntity(put, json.toString()); response = executeRequest(put); return getResponse(response); } finally { close(response); } } /** * Performs a HTTP PUT request, saves an attachment. * @return {@link Response} */ Response put(URI uri, InputStream instream, String contentType) { HttpResponse response = null; try { HttpPut httpPut = new HttpPut(uri); InputStreamEntity entity = new InputStreamEntity(instream, -1); entity.setContentType(contentType); httpPut.setEntity(entity); response = executeRequest(httpPut); return getResponse(response); } finally { close(response); } } /** * Performs a HTTP POST request. * @return {@link HttpResponse} */ HttpResponse post(URI uri, String json) { HttpPost post = new HttpPost(uri); setEntity(post, json); return executeRequest(post); } /** * Performs a HTTP DELETE request. * @return {@link Response} */ Response delete(URI uri) { HttpResponse response = null; try { HttpDelete delete = new HttpDelete(uri); response = executeRequest(delete); return getResponse(response); } finally { close(response); } } /** * Executes a HTTP request. * @param request The HTTP request to execute. * @return {@link HttpResponse} */ protected HttpResponse executeRequest(HttpRequestBase request) { HttpResponse response = null; try { response = httpClient.execute(host, request, context); } catch (IOException e) { request.abort(); log.error("Error executing request. " + e.getMessage()); throw new CouchDbException(e); } return response; } // Helpers /** * @return {@link DefaultHttpClient} instance. */ private HttpClient createHttpClient(CouchDbProperties props) { DefaultHttpClient httpclient = null; try { SchemeSocketFactory ssf = null; if(props.getProtocol().equals("https")) { TrustManager trustManager = new X509TrustManager() { public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { } public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { } public X509Certificate[] getAcceptedIssuers() { return null; } }; SSLContext sslcontext = SSLContext.getInstance("TLS"); sslcontext.init(null, new TrustManager[] { trustManager }, null); ssf = new SSLSocketFactory(sslcontext, SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); SSLSocket socket = (SSLSocket) ssf.createSocket(null); socket.setEnabledCipherSuites(new String[] { "SSL_RSA_WITH_RC4_128_MD5" }); } else { ssf = PlainSocketFactory.getSocketFactory(); } SchemeRegistry schemeRegistry = new SchemeRegistry(); schemeRegistry.register(new Scheme(props.getProtocol(), props.getPort(), ssf)); PoolingClientConnectionManager ccm = new PoolingClientConnectionManager(schemeRegistry); httpclient = new DefaultHttpClient(ccm); host = new HttpHost(props.getHost(), props.getPort(), props.getProtocol()); context = new BasicHttpContext(); // Http params httpclient.getParams().setParameter(CoreProtocolPNames.HTTP_CONTENT_CHARSET, "UTF-8"); httpclient.getParams().setParameter(CoreConnectionPNames.SO_TIMEOUT, props.getSocketTimeout()); httpclient.getParams().setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, props.getConnectionTimeout()); int maxConnections = props.getMaxConnections(); if(maxConnections != 0) { ccm.setMaxTotal(maxConnections); ccm.setDefaultMaxPerRoute(maxConnections); } if(props.getProxyHost() != null) { HttpHost proxy = new HttpHost(props.getProxyHost(), props.getProxyPort()); httpclient.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY, proxy); } // basic authentication if(props.getUsername() != null && props.getPassword() != null) { httpclient.getCredentialsProvider().setCredentials( new AuthScope(props.getHost(), props.getPort()), new UsernamePasswordCredentials(props.getUsername(), props.getPassword())); props.clearPassword(); AuthCache authCache = new BasicAuthCache(); BasicScheme basicAuth = new BasicScheme(); authCache.put(host, basicAuth); context.setAttribute(ClientContext.AUTH_CACHE, authCache); } // request interceptor httpclient.addRequestInterceptor(new HttpRequestInterceptor() { public void process( final HttpRequest request, final HttpContext context) throws IOException { if(log.isInfoEnabled()) log.info(">> " + request.getRequestLine()); } }); // response interceptor httpclient.addResponseInterceptor(new HttpResponseInterceptor() { public void process( final HttpResponse response, final HttpContext context) throws IOException { validate(response); if(log.isInfoEnabled()) log.info("<< Status: " + response.getStatusLine().getStatusCode()); } }); } catch (Exception e) { log.error("Error Creating HTTP client. " + e.getMessage()); throw new IllegalStateException(e); } return httpclient; } /** * Validates a HTTP response; on error cases logs status and throws relevant exceptions. * @param response The HTTP response. */ private void validate(HttpResponse response) throws IOException { int code = response.getStatusLine().getStatusCode(); if(code == 200 || code == 201 || code == 202) { // success (ok | created | accepted) return; } String msg = format("<< Status: %s (%s) ", code, response.getStatusLine().getReasonPhrase()); switch (code) { case HttpStatus.SC_NOT_FOUND: { log.info(msg); throw new NoDocumentException(msg); } case HttpStatus.SC_CONFLICT: { log.warn(msg); throw new DocumentConflictException(msg); } default: { // other errors: 400 | 401 | 500 etc. log.error(msg += EntityUtils.toString(response.getEntity())); throw new CouchDbException(msg); } } } /** * @param response The {@link HttpResponse} * @return {@link Response} */ Response getResponse(HttpResponse response) throws CouchDbException { return deserialize(getStream(response), Response.class); } /** * @param response The {@link HttpResponse} * @return {@link Response} */ List getResponseList(HttpResponse response) throws CouchDbException { InputStream instream = getStream(response); Reader reader = new InputStreamReader(instream); return getGson().fromJson(reader, new TypeToken>(){}.getType()); } /** * Sets a JSON String as a request entity. * @param httpRequest The request to set entity. * @param json The JSON String to set. */ protected void setEntity(HttpEntityEnclosingRequestBase httpRequest, String json) { try { StringEntity entity = new StringEntity(json, "UTF-8"); entity.setContentType("application/json"); httpRequest.setEntity(entity); } catch (UnsupportedEncodingException e) { log.error("Error setting request data. " + e.getMessage()); throw new IllegalArgumentException(e); } } /** * @return {@link InputStream} from a {@link HttpResponse} */ InputStream getStream(HttpResponse response) { try { return response.getEntity().getContent(); } catch (Exception e) { log.error("Error reading response. " + e.getMessage()); throw new CouchDbException(e); } } T deserialize(InputStream instream, Class classType) { Reader reader = new InputStreamReader(instream); return getGson().fromJson(reader, classType); } /** *

The supplied {@link GsonBuilder} is used to create a new {@link Gson} instance. * Useful for registering custom serializers/deserializers, for example JodaTime DateTime class. */ protected void setGsonBuilder(GsonBuilder gsonBuilder) { this.gson = initGson(gsonBuilder); } /** * Builds {@link Gson} and registers any required serializer/deserializer. * @return {@link Gson} instance */ private Gson initGson(GsonBuilder gsonBuilder) { gsonBuilder.registerTypeAdapter(JsonObject.class, new JsonDeserializer() { public JsonObject deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { return json.getAsJsonObject(); } }); gsonBuilder.registerTypeAdapter(JsonObject.class, new JsonSerializer() { public JsonElement serialize(JsonObject src, Type typeOfSrc, JsonSerializationContext context) { return src.getAsJsonObject(); } }); return gsonBuilder.create(); } /** * Shuts down the connection manager used by this client instance. */ protected void shutdown() { this.httpClient.getConnectionManager().shutdown(); } } LightCouch-lightcouch-0.0.6/src/main/java/org/lightcouch/CouchDbConfig.java000066400000000000000000000074461215035721600265660ustar00rootroot00000000000000/* * Copyright (C) 2011 Ahmed Yehia (ahmed.yehia.m@gmail.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.lightcouch; import static org.lightcouch.CouchDbUtil.*; import java.io.InputStream; import java.util.Properties; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * Provides configuration to client instance. * @author Ahmed Yehia */ class CouchDbConfig { private static final Log log = LogFactory.getLog(CouchDbConfig.class); private static final String DEFAULT_FILE = "couchdb.properties"; private Properties properties = new Properties(); private String configFile; private CouchDbProperties dbProperties; public CouchDbConfig() { this(DEFAULT_FILE); } public CouchDbConfig(String configFile) { this.configFile = configFile; try { InputStream instream = CouchDbConfig.class.getClassLoader().getResourceAsStream(configFile); properties.load(instream); } catch (Exception e) { String msg = "Could not read configuration file from the classpath: " + configFile; log.error(msg); throw new IllegalStateException(msg, e); } readProperties(); } public CouchDbConfig(CouchDbProperties dbProperties) { assertNotEmpty(dbProperties, "Properties"); assertNotEmpty(dbProperties.getDbName(), "Database"); assertNotEmpty(dbProperties.getProtocol(), "Protocol"); assertNotEmpty(dbProperties.getHost(), "Host"); assertNotEmpty(dbProperties.getPort(), "Port"); this.dbProperties = dbProperties; } private void readProperties() { try { // required dbProperties = new CouchDbProperties(); dbProperties.setDbName(getProperty("couchdb.name", true)); boolean create = Boolean.parseBoolean(getProperty("couchdb.createdb.if-not-exist", true)); dbProperties.setCreateDbIfNotExist(create); dbProperties.setProtocol(getProperty("couchdb.protocol", true)); dbProperties.setHost(getProperty("couchdb.host", true)); int port = Integer.parseInt(getProperty("couchdb.port", true)); dbProperties.setPort(port); dbProperties.setUsername(getProperty("couchdb.username", true)); dbProperties.setPassword(getProperty("couchdb.password", true)); // optional dbProperties.setSocketTimeout(getPropertyAsInt("couchdb.http.socket.timeout", false)); dbProperties.setConnectionTimeout(getPropertyAsInt("couchdb.http.connection.timeout", false)); dbProperties.setMaxConnections(getPropertyAsInt("couchdb.max.connections", false)); dbProperties.setProxyHost(getProperty("couchdb.proxy.host", false)); dbProperties.setProxyPort(getPropertyAsInt("couchdb.proxy.port", false)); } catch (Exception e) { throw new IllegalStateException(e); } properties = null; } public CouchDbProperties getProperties() { return dbProperties; } private String getProperty(String key, boolean isRequired) { String property = properties.getProperty(key); if(property == null && isRequired) { String msg = String.format("A required property is missing. Key: %s, File: %s", key, configFile); log.error(msg); throw new IllegalStateException(msg); } else { return (property != null && property.length() != 0) ? property.trim() : null; } } private int getPropertyAsInt(String key, boolean isRequired) { String prop = getProperty(key, isRequired); return (prop != null) ? Integer.parseInt(prop) : 0; } } LightCouch-lightcouch-0.0.6/src/main/java/org/lightcouch/CouchDbContext.java000066400000000000000000000107151215035721600267760ustar00rootroot00000000000000/* * Copyright (C) 2011 Ahmed Yehia (ahmed.yehia.m@gmail.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.lightcouch; import static org.lightcouch.CouchDbUtil.*; import static org.lightcouch.URIBuilder.*; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.lang.reflect.Type; import java.net.URI; import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpPut; import com.google.gson.JsonObject; import com.google.gson.JsonParser; import com.google.gson.reflect.TypeToken; /** * Provides access to database APIs. * @author Ahmed Yehia */ public class CouchDbContext { private static final Log log = LogFactory.getLog(CouchDbContext.class); private CouchDbClient dbc; CouchDbContext(CouchDbClient dbc) { this.dbc = dbc; CouchDbProperties props = dbc.getConfig().getProperties(); if (props.isCreateDbIfNotExist()) { createDB(props.getDbName()); } else { serverVersion(); // pre warm up client } } /** * Deletes a database. * @param dbName The database name to delete * @param confirm For double checking, the text "delete database" must be supplied. */ public void deleteDB(String dbName, String confirm) { assertNotEmpty(dbName, "Database name"); if(!"delete database".equals(confirm)) throw new IllegalArgumentException("Cannot delete database without confirmation!"); dbc.delete(builder(dbc.getBaseUri()).path(dbName).build()); } /** * Creates a new Database, if it does not already exist. * @param dbName The Database name */ public void createDB(String dbName) { assertNotEmpty(dbName, "Database name"); HttpResponse headresp = null; HttpResponse putresp = null; URI uri = builder(dbc.getBaseUri()).path(dbName).build(); try { headresp = dbc.head(uri); } catch (NoDocumentException e) { // db doesn't exist HttpPut put = new HttpPut(uri); putresp = dbc.executeRequest(put); log.info(String.format("Database: '%s' is created.", dbName)); } finally { close(headresp); close(putresp); } } /** * @return All server databases. */ public List getAllDbs() { InputStream instream = null; try { Type typeOfList = new TypeToken>() {}.getType(); instream = dbc.get(builder(dbc.getBaseUri()).path("_all_dbs").build()); Reader reader = new InputStreamReader(instream); return dbc.getGson().fromJson(reader, typeOfList); } finally { close(instream); } } /** * Gets the info of the associated database instance with this client. * @return {@link CouchDbInfo} */ public CouchDbInfo info() { return dbc.get(builder(dbc.getDBUri()).build(), CouchDbInfo.class); } /** * @return CouchDB server version. */ public String serverVersion() { InputStream instream = null; try { instream = dbc.get(builder(dbc.getBaseUri()).build()); Reader reader = new InputStreamReader(instream); return getElement(new JsonParser().parse(reader).getAsJsonObject(), "version"); } finally { close(instream); } } /** * Triggers a database compaction request. */ public void compact() { HttpResponse response = null; try { response = dbc.post(builder(dbc.getDBUri()).path("_compact").build(), ""); } finally { close(response); } } /** * Requests the database commits any recent changes to disk. */ public void ensureFullCommit() { HttpResponse response = null; try { response = dbc.post(builder(dbc.getDBUri()).path("_ensure_full_commit").build(), ""); } finally { close(response); } } /** * Request a database sends a list of UUIDs. * @param count The count of UUIDs. */ public List uuids(long count) { String uri = String.format("%s_uuids?count=%d", dbc.getBaseUri(), count); JsonObject json = dbc.findAny(JsonObject.class, uri); return dbc.getGson().fromJson(json.get("uuids").toString(), new TypeToken>(){}.getType()); } } LightCouch-lightcouch-0.0.6/src/main/java/org/lightcouch/CouchDbDesign.java000066400000000000000000000157201215035721600265640ustar00rootroot00000000000000/* * Copyright (C) 2011 Ahmed Yehia (ahmed.yehia.m@gmail.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.lightcouch; import static org.lightcouch.CouchDbUtil.*; import static java.lang.String.format; import static org.lightcouch.URIBuilder.builder; import java.net.URI; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.lightcouch.DesignDocument.MapReduce; /** * Provides methods to create and save CouchDB design documents. *

Usage Example:

*
 * DesignDocument exampleDoc = dbClient.design().getFromDesk("example");
 * Response response = dbClient.design().synchronizeWithDb(exampleDoc);
 * DesignDocument documentFromDb = dbClient.design().getFromDb("_design/example");
 * 
* @see DesignDocument * @author Ahmed Yehia */ public class CouchDbDesign { private static final String DESIGN_DOCS_DIR = "design-docs"; private static final String JAVASCRIPT = "javascript"; private static final String DESIGN_PREFIX = "_design/"; private static final String VALIDATE_DOC = "validate_doc_update"; private static final String VIEWS = "views"; private static final String FILTERS = "filters"; private static final String SHOWS = "shows"; private static final String LISTS = "lists"; private static final String UPDATES = "updates"; private static final String MAP_JS = "map.js"; private static final String REDUCE_JS = "reduce.js"; private CouchDbClient dbc; CouchDbDesign(CouchDbClient dbc) { this.dbc = dbc; } /** * Synchronizes a design document to the Database. *

This method will first try to find a document in the database with the same id * as the given document, if it is not found then the given document will be saved to the database. *

If the document was found in the database, it will be compared with the given document using * {@code equals()}. If both documents are not equal, then the given document will be saved to the * database and updates the existing document. * @param document The design document to synchronize * @return {@link Response} as a result of a document save or update, or returns {@code null} if no * action was taken and the document in the database is up-to-date with the given document. */ public Response synchronizeWithDb(DesignDocument document) { assertNotEmpty(document, "Document"); DesignDocument documentFromDb = null; try { documentFromDb = getFromDb(document.getId()); } catch (NoDocumentException e) { return dbc.save(document); } if(!document.equals(documentFromDb)) { document.setRevision(documentFromDb.getRevision()); return dbc.update(document); } return null; } /** * Synchronize all design documents on desk to the database. * @see #synchronizeWithDb(DesignDocument) * @see CouchDbClient#syncDesignDocsWithDb() */ public void synchronizeAllWithDb() { List documents = getAllFromDesk(); for (DesignDocument dd : documents) { synchronizeWithDb(dd); } } /** * Gets a design document from the database. * @param id The document id * @return {@link DesignDocument} */ public DesignDocument getFromDb(String id) { assertNotEmpty(id, "id"); URI uri = builder(dbc.getDBUri()).path(id).build(); return dbc.get(uri, DesignDocument.class); } /** * Gets a design document from the database. * @param id The document id * @param rev The document revision * @return {@link DesignDocument} */ public DesignDocument getFromDb(String id, String rev) { assertNotEmpty(id, "id"); assertNotEmpty(id, "rev"); URI uri = builder(dbc.getDBUri()).path(id).query("rev", rev).build(); return dbc.get(uri, DesignDocument.class); } /** * Gets all design documents from desk. * @see #getFromDesk(String) */ public List getAllFromDesk() { List designDocsList = new ArrayList(); for (String docName : listResources(format("%s/", DESIGN_DOCS_DIR))) { designDocsList.add(getFromDesk(docName)); } return designDocsList; } /** * Gets a design document from desk. * @param id The document id to get. * @return {@link DesignDocument} */ public DesignDocument getFromDesk(String id) { assertNotEmpty(id, "id"); DesignDocument dd = new DesignDocument(); String rootPath = format("%s/%s/", DESIGN_DOCS_DIR, id); List elements = listResources(rootPath); if(elements == null) { throw new IllegalArgumentException("Design docs directory cannot be empty."); } if(elements.contains(VALIDATE_DOC)) { // validate_doc_update String validateDocPath = format("%s%s/", rootPath, VALIDATE_DOC); List dirList = listResources(validateDocPath); for (String file : dirList) { String contents = readFile(format("/%s%s", validateDocPath, file)); dd.setValidateDocUpdate(contents); break; // only one validate_doc_update file } } // /validate_doc_update Map views = null; if(elements.contains(VIEWS)) { // views String viewsPath = format("%s%s/", rootPath, VIEWS); views = new HashMap(); for (String viewDirName : listResources(viewsPath)) { // views sub-dirs MapReduce mr = new MapReduce(); String viewPath = format("%s%s/", viewsPath, viewDirName); List dirList = listResources(viewPath); for (String fileName : dirList) { // view files String def = readFile(format("/%s%s", viewPath, fileName)); if(MAP_JS.equals(fileName)) mr.setMap(def); else if(REDUCE_JS.equals(fileName)) mr.setReduce(def); } // /foreach view files views.put(viewDirName, mr); } // /foreach views sub-dirs } // /views dd.setId(DESIGN_PREFIX + id); dd.setLanguage(JAVASCRIPT); dd.setViews(views); dd.setFilters(populateMap(rootPath, elements, FILTERS)); dd.setShows(populateMap(rootPath, elements, SHOWS)); dd.setLists(populateMap(rootPath, elements, LISTS)); dd.setUpdates(populateMap(rootPath, elements, UPDATES)); return dd; } private Map populateMap(String rootPath, List elements, String element) { Map functionsMap = null; if(elements.contains(element)) { functionsMap = new HashMap(); String path = format("%s%s/", rootPath, element); for (String fileName : listResources(path)) { String contents = readFile(format("/%s%s", path, fileName)); functionsMap.put(removeExtension(fileName), contents); } } return functionsMap; } } LightCouch-lightcouch-0.0.6/src/main/java/org/lightcouch/CouchDbException.java000066400000000000000000000021301215035721600273000ustar00rootroot00000000000000/* * Copyright (C) 2011 Ahmed Yehia (ahmed.yehia.m@gmail.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.lightcouch; /** * Base runtime exception class. * @see {@link NoDocumentException}, {@link DocumentConflictException} * @author Ahmed Yehia */ public class CouchDbException extends RuntimeException { private static final long serialVersionUID = 1L; public CouchDbException(String message) { super(message); } public CouchDbException(Throwable cause) { super(cause); } public CouchDbException(String message, Throwable cause) { super(message, cause); } } LightCouch-lightcouch-0.0.6/src/main/java/org/lightcouch/CouchDbInfo.java000066400000000000000000000043221215035721600262420ustar00rootroot00000000000000/* * Copyright (C) 2011 Ahmed Yehia (ahmed.yehia.m@gmail.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.lightcouch; import com.google.gson.annotations.SerializedName; /** * Holds information about a CouchDB database instance. * @author Ahmed Yehia */ public class CouchDbInfo { @SerializedName("db_name") private String dbName; @SerializedName("doc_count") private long docCount; @SerializedName("doc_del_count") private String docDelCount; @SerializedName("update_seq") private String updateSeq; @SerializedName("purge_seq") private long purgeSeq; @SerializedName("compact_running") private boolean compactRunning; @SerializedName("disk_size") private long diskSize; @SerializedName("instance_start_time") private long instanceStartTime; @SerializedName("disk_format_version") private int diskFormatVersion; public String getDbName() { return dbName; } public long getDocCount() { return docCount; } public String getDocDelCount() { return docDelCount; } public String getUpdateSeq() { return updateSeq; } public long getPurgeSeq() { return purgeSeq; } public boolean isCompactRunning() { return compactRunning; } public long getDiskSize() { return diskSize; } public long getInstanceStartTime() { return instanceStartTime; } public int getDiskFormatVersion() { return diskFormatVersion; } @Override public String toString() { return String .format("CouchDbInfo [dbName=%s, docCount=%s, docDelCount=%s, updateSeq=%s, purgeSeq=%s, compactRunning=%s, diskSize=%s, instanceStartTime=%s, diskFormatVersion=%s]", dbName, docCount, docDelCount, updateSeq, purgeSeq, compactRunning, diskSize, instanceStartTime, diskFormatVersion); } } LightCouch-lightcouch-0.0.6/src/main/java/org/lightcouch/CouchDbProperties.java000066400000000000000000000070671215035721600275140ustar00rootroot00000000000000/* * Copyright (C) 2011 Ahmed Yehia (ahmed.yehia.m@gmail.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.lightcouch; /** * Represents configuration properties for connecting to CouchDB. * * @author Ahmed Yehia * @author Daan van Berkel */ public class CouchDbProperties { // required private String dbName; private boolean createDbIfNotExist; private String protocol; private String host; private int port; private String username; private String password; // optional private int socketTimeout; private int connectionTimeout; private int maxConnections; private String proxyHost; private int proxyPort; public CouchDbProperties() { // default constructor } public CouchDbProperties(String dbName, boolean createDbIfNotExist, String protocol, String host, int port, String username, String password) { this.dbName = dbName; this.createDbIfNotExist = createDbIfNotExist; this.protocol = protocol; this.host = host; this.port = port; this.username = username; this.password = password; } public String getDbName() { return dbName; } public boolean isCreateDbIfNotExist() { return createDbIfNotExist; } public String getProtocol() { return protocol; } public String getHost() { return host; } public int getPort() { return port; } public String getUsername() { return username; } public String getPassword() { return password; } public int getSocketTimeout() { return socketTimeout; } public int getConnectionTimeout() { return connectionTimeout; } public int getMaxConnections() { return maxConnections; } public String getProxyHost() { return proxyHost; } public int getProxyPort() { return proxyPort; } public CouchDbProperties setDbName(String dbName) { this.dbName = dbName; return this; } public CouchDbProperties setCreateDbIfNotExist(boolean createDbIfNotExist) { this.createDbIfNotExist = createDbIfNotExist; return this; } public CouchDbProperties setProtocol(String protocol) { this.protocol = protocol; return this; } public CouchDbProperties setHost(String host) { this.host = host; return this; } public CouchDbProperties setPort(int port) { this.port = port; return this; } public CouchDbProperties setUsername(String username) { this.username = username; return this; } public CouchDbProperties setPassword(String password) { this.password = password; return this; } public CouchDbProperties setSocketTimeout(int socketTimeout) { this.socketTimeout = socketTimeout; return this; } public CouchDbProperties setConnectionTimeout(int connectionTimeout) { this.connectionTimeout = connectionTimeout; return this; } public CouchDbProperties setMaxConnections(int maxConnections) { this.maxConnections = maxConnections; return this; } public CouchDbProperties setProxyHost(String proxyHost) { this.proxyHost = proxyHost; return this; } public CouchDbProperties setProxyPort(int proxyPort) { this.proxyPort = proxyPort; return this; } public void clearPassword() { setPassword(""); setPassword(null); } } LightCouch-lightcouch-0.0.6/src/main/java/org/lightcouch/CouchDbUtil.java000066400000000000000000000125061215035721600262670ustar00rootroot00000000000000/* * Copyright (C) 2011 Ahmed Yehia (ahmed.yehia.m@gmail.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.lightcouch; import static java.lang.String.format; import java.io.Closeable; import java.io.File; import java.io.InputStream; import java.net.URL; import java.net.URLDecoder; import java.util.ArrayList; import java.util.Arrays; import java.util.Enumeration; import java.util.HashSet; import java.util.List; import java.util.Scanner; import java.util.Set; import java.util.UUID; import java.util.jar.JarEntry; import java.util.jar.JarFile; import org.apache.http.HttpResponse; import com.google.gson.Gson; import com.google.gson.JsonElement; import com.google.gson.JsonObject; /** * Provides various utility methods, for internal use. * @author Ahmed Yehia */ final class CouchDbUtil { private CouchDbUtil() { // Utility class } public static void assertNotEmpty(Object object, String prefix) throws IllegalArgumentException { if(object == null) { throw new IllegalArgumentException(format("%s may not be null.", prefix)); } else if(object instanceof String && ((String)object).length() == 0) { throw new IllegalArgumentException(format("%s may not be empty.", prefix)); } } public static void assertNull(Object object, String prefix) throws IllegalArgumentException { if(object != null) { throw new IllegalArgumentException(format("%s should be null.", prefix)); } } public static String generateUUID() { return UUID.randomUUID().toString().replace("-", ""); } // JSON public static T JsonToObject(Gson gson, JsonElement elem, String key, Class classType) { return gson.fromJson(elem.getAsJsonObject().get(key), classType); } /** * @return A JSON element as a String, or null if not found. */ public static String getElement(JsonObject j, String e) { return (j.get(e) == null) ? null : j.get(e).getAsString(); } public static long getElementAsLong(JsonObject j, String e) { return (j.get(e) == null) ? 0L : j.get(e).getAsLong(); } public static int getElementAsInt(JsonObject j, String e) { return (j.get(e) == null) ? 0 : j.get(e).getAsInt(); } // Files private static final String LINE_SEP = System.getProperty("line.separator"); /** * List directory contents for a resource folder. Not recursive. * This is basically a brute-force implementation. * Works for regular files and also JARs. * * @author Greg Briggs * @param clazz Any java class that lives in the same place as the resources you want. * @param path Should end with "/", but not start with one. * @return Just the name of each member item, not the full paths. */ public static List listResources(String path) { try { Class clazz = CouchDbUtil.class; URL dirURL = clazz.getClassLoader().getResource(path); if (dirURL != null && dirURL.getProtocol().equals("file")) { return Arrays.asList(new File(dirURL.toURI()).list()); } if (dirURL != null && dirURL.getProtocol().equals("jar")) { String jarPath = dirURL.getPath().substring(5, dirURL.getPath().indexOf("!")); JarFile jar = new JarFile(URLDecoder.decode(jarPath, "UTF-8")); Enumeration entries = jar.entries(); Set result = new HashSet(); while(entries.hasMoreElements()) { String name = entries.nextElement().getName(); if (name.startsWith(path)) { String entry = name.substring(path.length()); int checkSubdir = entry.indexOf("/"); if (checkSubdir >= 0) { entry = entry.substring(0, checkSubdir); } if(entry.length() > 0) { result.add(entry); } } } return new ArrayList(result); } return null; } catch (Exception e) { throw new CouchDbException(e); } } public static String readFile(String path) { InputStream instream = CouchDbUtil.class.getResourceAsStream(path); StringBuilder content = new StringBuilder(); Scanner scanner = null; try { scanner = new Scanner(instream); while(scanner.hasNextLine()) { content.append(scanner.nextLine() + LINE_SEP); } } finally { scanner.close(); } return content.toString(); } public static String removeExtension(String fileName) { return fileName.substring(0, fileName.lastIndexOf('.')); } public static String streamToString(InputStream in) { Scanner s = new Scanner(in).useDelimiter("\\A"); String str = s.hasNext() ? s.next() : null; close(in); return str; } /** * Closes the response input stream. * * @param response The {@link HttpResponse} */ public static void close(HttpResponse response) { try { close(response.getEntity().getContent()); } catch (Exception e) {} } /** * Closes a resource. * * @param c The {@link Closeable} resource. */ public static void close(Closeable c) { try { c.close(); } catch (Exception e) {} } } LightCouch-lightcouch-0.0.6/src/main/java/org/lightcouch/DesignDocument.java000066400000000000000000000122341215035721600270300ustar00rootroot00000000000000/* * Copyright (C) 2011 Ahmed Yehia (ahmed.yehia.m@gmail.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.lightcouch; import java.util.Map; import com.google.gson.annotations.SerializedName; /** * Represents a design document. * @see CouchDbDesign * @author Ahmed Yehia */ public class DesignDocument extends Document { private String language; private Map views; @SerializedName("validate_doc_update") private String validateDocUpdate; private Map filters; private Map shows; private Map lists; private Map updates; public String getLanguage() { return language; } public Map getViews() { return views; } public String getValidateDocUpdate() { return validateDocUpdate; } public Map getFilters() { return filters; } public Map getShows() { return shows; } public Map getLists() { return lists; } public Map getUpdates() { return updates; } public void setLanguage(String language) { this.language = language; } public void setViews(Map views) { this.views = views; } public void setValidateDocUpdate(String validateDocUpdate) { this.validateDocUpdate = validateDocUpdate; } public void setFilters(Map filters) { this.filters = filters; } public void setShows(Map shows) { this.shows = shows; } public void setLists(Map lists) { this.lists = lists; } public void setUpdates(Map updates) { this.updates = updates; } @Override public int hashCode() { final int prime = 31; int result = super.hashCode(); result = prime * result + ((filters == null) ? 0 : filters.hashCode()); result = prime * result + ((language == null) ? 0 : language.hashCode()); result = prime * result + ((lists == null) ? 0 : lists.hashCode()); result = prime * result + ((shows == null) ? 0 : shows.hashCode()); result = prime * result + ((updates == null) ? 0 : updates.hashCode()); result = prime * result + ((validateDocUpdate == null) ? 0 : validateDocUpdate.hashCode()); result = prime * result + ((views == null) ? 0 : views.hashCode()); return result; } /** * Indicates whether some other design document is equals to this one. */ @Override public boolean equals(Object obj) { if (this == obj) return true; if (!super.equals(obj)) return false; if (getClass() != obj.getClass()) return false; DesignDocument other = (DesignDocument) obj; if (filters == null) { if (other.filters != null) return false; } else if (!filters.equals(other.filters)) return false; if (language == null) { if (other.language != null) return false; } else if (!language.equals(other.language)) return false; if (lists == null) { if (other.lists != null) return false; } else if (!lists.equals(other.lists)) return false; if (shows == null) { if (other.shows != null) return false; } else if (!shows.equals(other.shows)) return false; if (updates == null) { if (other.updates != null) return false; } else if (!updates.equals(other.updates)) return false; if (validateDocUpdate == null) { if (other.validateDocUpdate != null) return false; } else if (!validateDocUpdate.equals(other.validateDocUpdate)) return false; if (views == null) { if (other.views != null) return false; } else if (!views.equals(other.views)) return false; return true; } /** * Holds Map Reduce functions in a view. * @author Ahmed Yehia */ public static class MapReduce { private String map; private String reduce; public String getMap() { return map; } public String getReduce() { return reduce; } public void setMap(String map) { this.map = map; } public void setReduce(String reduce) { this.reduce = reduce; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((map == null) ? 0 : map.hashCode()); result = prime * result + ((reduce == null) ? 0 : reduce.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; MapReduce other = (MapReduce) obj; if (map == null) { if (other.map != null) return false; } else if (!map.equals(other.map)) return false; if (reduce == null) { if (other.reduce != null) return false; } else if (!reduce.equals(other.reduce)) return false; return true; } } // /class MapReduce } LightCouch-lightcouch-0.0.6/src/main/java/org/lightcouch/Document.java000066400000000000000000000042251215035721600256770ustar00rootroot00000000000000/* * Copyright (C) 2011 Ahmed Yehia (ahmed.yehia.m@gmail.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.lightcouch; import java.util.HashMap; import java.util.Map; import com.google.gson.annotations.SerializedName; /** * Convenient base class for CouchDB documents, defines the basic * id and revision properties and attachments. * @author Ahmed Yehia * */ public class Document { @SerializedName("_id") private String id; @SerializedName("_rev") private String revision; @SerializedName("_attachments") private Map attachments; public String getId() { return id; } public String getRevision() { return revision; } public Map getAttachments() { return attachments; } public void setId(String id) { this.id = id; } public void setRevision(String revision) { this.revision = revision; } public void setAttachments(Map attachments) { this.attachments = attachments; } public void addAttachment(String name, Attachment attachment) { if(attachments == null) attachments = new HashMap(); attachments.put(name, attachment); } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((id == null) ? 0 : id.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Document other = (Document) obj; if (id == null) { if (other.id != null) return false; } else if (!id.equals(other.id)) return false; return true; } } LightCouch-lightcouch-0.0.6/src/main/java/org/lightcouch/DocumentConflictException.java000066400000000000000000000021231215035721600312330ustar00rootroot00000000000000/* * Copyright (C) 2011 Ahmed Yehia (ahmed.yehia.m@gmail.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.lightcouch; /** * Thrown when a conflict is detected during a save or update. * @author Ahmed Yehia */ public class DocumentConflictException extends CouchDbException { private static final long serialVersionUID = 1L; public DocumentConflictException(String message) { super(message); } public DocumentConflictException(Throwable cause) { super(cause); } public DocumentConflictException(String message, Throwable cause) { super(message, cause); } } LightCouch-lightcouch-0.0.6/src/main/java/org/lightcouch/NoDocumentException.java000066400000000000000000000020571215035721600300540ustar00rootroot00000000000000/* * Copyright (C) 2011 Ahmed Yehia (ahmed.yehia.m@gmail.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.lightcouch; /** * Thrown when a requested document is not found. * @author Ahmed Yehia */ public class NoDocumentException extends CouchDbException { private static final long serialVersionUID = 1L; public NoDocumentException(String message) { super(message); } public NoDocumentException(Throwable cause) { super(cause); } public NoDocumentException(String message, Throwable cause) { super(message, cause); } } LightCouch-lightcouch-0.0.6/src/main/java/org/lightcouch/Page.java000066400000000000000000000045261215035721600250010ustar00rootroot00000000000000/* * Copyright (C) 2011 Ahmed Yehia (ahmed.yehia.m@gmail.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.lightcouch; import java.util.List; /** * Holds data of a page as result of a view pagination query. * @see View#queryPage(int, String, Class) * @param Object type T * * @author Ahmed Yehia */ public class Page { private boolean isHasPrevious; private boolean isHasNext; private List resultList; private long totalResults; private int resultFrom; private int resultTo; private int pageNumber; private String nextParam; private String previousParam; public boolean isHasPrevious() { return isHasPrevious; } public boolean isHasNext() { return isHasNext; } public List getResultList() { return resultList; } public long getTotalResults() { return totalResults; } public int getResultFrom() { return resultFrom; } public int getResultTo() { return resultTo; } public int getPageNumber() { return pageNumber; } public String getNextParam() { return nextParam; } public String getPreviousParam() { return previousParam; } public void setHasPrevious(boolean isHasPrevious) { this.isHasPrevious = isHasPrevious; } public void setHasNext(boolean isHasNext) { this.isHasNext = isHasNext; } public void setResultList(List resultList) { this.resultList = resultList; } public void setTotalResults(long totalResults) { this.totalResults = totalResults; } public void setResultFrom(int resultFrom) { this.resultFrom = resultFrom; } public void setResultTo(int resultTo) { this.resultTo = resultTo; } public void setPageNumber(int pageNumber) { this.pageNumber = pageNumber; } public void setNextParam(String nextParam) { this.nextParam = nextParam; } public void setPreviousParam(String previousParam) { this.previousParam = previousParam; } } LightCouch-lightcouch-0.0.6/src/main/java/org/lightcouch/Params.java000066400000000000000000000033201215035721600253370ustar00rootroot00000000000000/* * Copyright (C) 2011 Ahmed Yehia (ahmed.yehia.m@gmail.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.lightcouch; import static java.lang.String.format; import java.util.ArrayList; import java.util.List; /** * Query parameters to append to find requests. *

Example: *

 * dbClient.find(Foo.class, "doc-id", new Params().revsInfo().attachments());
 * 
 * @see CouchDbClient#find(Class, String, Params)
 * @author Ahmed Yehia
 * 
 */
public class Params {

	private List params = new ArrayList();

	public Params revsInfo() {
		params.add("revs_info=true");
		return this;
	}

	public Params attachments() {
		params.add("attachments=true");
		return this;
	}

	public Params revisions() {
		params.add("revs=true");
		return this;
	}

	public Params rev(String rev) {
		params.add(format("rev=%s", rev));
		return this;
	}

	public Params conflicts() {
		params.add("conflicts=true");
		return this;
	}

	public Params localSeq() {
		params.add("local_seq=true");
		return this;
	}

	public Params addParam(String name, Object value) {
		params.add(format("%s=%s", name, value));
		return this;
	}

	public List getParams() {
		return params.isEmpty() ? null : params;
	}
}
LightCouch-lightcouch-0.0.6/src/main/java/org/lightcouch/Replication.java000066400000000000000000000125721215035721600263760ustar00rootroot00000000000000/*
 * Copyright (C) 2011 Ahmed Yehia (ahmed.yehia.m@gmail.com)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.lightcouch;

import static org.lightcouch.CouchDbUtil.assertNotEmpty;
import static org.lightcouch.CouchDbUtil.close;
import static org.lightcouch.URIBuilder.builder;

import java.net.URI;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.HttpResponse;

import com.google.gson.JsonObject;

/**
 * 

This class allows construction and sending of replication requests. *

Replication is triggered by sending a POST request to _replicate. * *

Usage Example:

*
 * ReplicationResult result = dbClient.replication()
 * 	.source("source-db")
 * 	.target("target-db")
 * 	.createTarget(true)
 * 	.trigger();
 * 
* * @see Replicator * @author Ahmed Yehia * */ public class Replication { static final Log log = LogFactory.getLog(Replication.class); private String source; private String target; private Boolean cancel; private Boolean continuous; private String filter; private JsonObject queryParams; private String[] docIds; private String proxy; private Boolean createTarget; private Integer sinceSeq; // OAuth private JsonObject targetOauth; private String consumerSecret; private String consumerKey; private String tokenSecret; private String token; private CouchDbClient dbc; public Replication(CouchDbClient dbc) { this.dbc = dbc; } /** * Triggers a replication request. */ public ReplicationResult trigger() { assertNotEmpty(source, "Source"); assertNotEmpty(target, "Target"); HttpResponse response = null; try { JsonObject json = createJson(); if(log.isDebugEnabled()) { log.debug(json); } URI uri = builder(dbc.getBaseUri()).path("_replicate").build(); response = dbc.post(uri, json.toString()); return dbc.deserialize(dbc.getStream(response), ReplicationResult.class); } finally { close(response); } } // fields public Replication source(String source) { this.source = source; return this; } public Replication target(String target) { this.target = target; return this; } public Replication continuous(Boolean continuous) { this.continuous = continuous; return this; } public Replication filter(String filter) { this.filter = filter; return this; } public Replication queryParams(String queryParams) { this.queryParams = dbc.getGson().fromJson(queryParams, JsonObject.class); return this; } public Replication queryParams(Map queryParams) { this.queryParams = dbc.getGson().toJsonTree(queryParams).getAsJsonObject(); return this; } public Replication docIds(String... docIds) { this.docIds = docIds; return this; } public Replication proxy(String proxy) { this.proxy = proxy; return this; } public Replication cancel(Boolean cancel) { this.cancel = cancel; return this; } public Replication createTarget(Boolean createTarget) { this.createTarget = createTarget; return this; } /** * Starts a replication since an update sequence. */ public Replication sinceSeq(Integer sinceSeq) { this.sinceSeq = sinceSeq; return this; } public Replication targetOauth(String consumerSecret, String consumerKey, String tokenSecret, String token) { targetOauth = new JsonObject(); this.consumerSecret = consumerKey; this.consumerKey = consumerKey; this.tokenSecret = tokenSecret; this.token = token; return this; } // helper private JsonObject createJson() { JsonObject json = new JsonObject(); addProperty(json, "source", source); addProperty(json, "cancel", cancel); addProperty(json, "continuous", continuous); addProperty(json, "filter", filter); if(queryParams != null) json.add("query_params", queryParams); if(docIds != null) json.add("doc_ids", dbc.getGson().toJsonTree(docIds, String[].class)); addProperty(json, "proxy", proxy); addProperty(json, "since_seq", sinceSeq); addProperty(json, "create_target", createTarget); if(targetOauth != null) { JsonObject auth = new JsonObject(); JsonObject oauth = new JsonObject(); addProperty(oauth, "consumer_secret", consumerSecret); addProperty(oauth, "consumer_key", consumerKey); addProperty(oauth, "token_secret", tokenSecret); addProperty(oauth, "token", token); addProperty(targetOauth, "url", target); auth.add("oauth", oauth); targetOauth.add("auth", auth); json.add("target", targetOauth); } else { addProperty(json, "target", target); } return json; } private void addProperty(JsonObject json, String name, Object value) { if(value != null) { if(value instanceof Boolean) json.addProperty(name, (Boolean)value); else if (value instanceof String) json.addProperty(name, (String)value); else if (value instanceof Integer) json.addProperty(name, (Integer)value); } } } LightCouch-lightcouch-0.0.6/src/main/java/org/lightcouch/ReplicationResult.java000066400000000000000000000057001215035721600275700ustar00rootroot00000000000000/* * Copyright (C) 2011 Ahmed Yehia (ahmed.yehia.m@gmail.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.lightcouch; import com.google.gson.annotations.SerializedName; import java.util.List; /** * Holds the result of a replication request, along with previous sessions history. * @see Replication * @author Ahmed Yehia */ public class ReplicationResult { @SerializedName("ok") private boolean ok; @SerializedName("session_id") private String sessionId; @SerializedName("source_last_seq") private String sourceLastSeq; @SerializedName("_local_id") private String localId; @SerializedName("history") private List histories; public boolean isOk() { return ok; } public String getSessionId() { return sessionId; } public String getSourceLastSeq() { return sourceLastSeq; } public String getLocalId() { return localId; } public List getHistories() { return histories; } /** * Represents a replication session history. * @author Ahmed Yehia */ public static class ReplicationHistory { @SerializedName("session_id") private String sessionId; @SerializedName("start_time") private String startTime; @SerializedName("end_time") private String endTime; @SerializedName("start_last_seq") private String startLastSeq; @SerializedName("end_last_seq") private String endLastSeq; @SerializedName("recorded_seq") private String recordedSeq; @SerializedName("missing_checked") private long missingChecked; @SerializedName("missing_found") private long missingFound; @SerializedName("docs_read") private long docsRead; @SerializedName("docs_written") private long docsWritten; @SerializedName("doc_write_failures") private long docWriteFailures; public String getSessionId() { return sessionId; } public String getStartTime() { return startTime; } public String getEndTime() { return endTime; } public String getStartLastSeq() { return startLastSeq; } public String getEndLastSeq() { return endLastSeq; } public String getRecordedSeq() { return recordedSeq; } public long getMissingChecked() { return missingChecked; } public long getMissingFound() { return missingFound; } public long getDocsRead() { return docsRead; } public long getDocsWritten() { return docsWritten; } public long getDocWriteFailures() { return docWriteFailures; } } // /class ReplicationHistory } LightCouch-lightcouch-0.0.6/src/main/java/org/lightcouch/Replicator.java000066400000000000000000000163251215035721600262310ustar00rootroot00000000000000/* * Copyright (C) 2011 Ahmed Yehia (ahmed.yehia.m@gmail.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.lightcouch; import static org.lightcouch.CouchDbUtil.*; import static org.lightcouch.URIBuilder.builder; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.net.URI; import java.util.ArrayList; import java.util.List; import java.util.Map; import org.lightcouch.ReplicatorDocument.UserCtx; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; /** *

This class allows construction and sending of replication requests targeting a replicator database. *

The Replicator database, by default is called _replicator was introduced in CouchDB version 1.1.0 *

The API supports triggering replication requests by adding a document to the replicator database, * and cancelling a replication by removing the document that triggered the replication. * *

Usage Example:

*
 * Response response = dbClient.replicator()
 * 	.source("source-db")
 * 	.target("target-db")
 * 	.continuous(true)
 * 	.createTarget(true)
 * 	.replicatorDB("replicator-db-name") // optionally specify database name, defaults to _replicator
 * 	.replicatorDocId("doc-id")          // optionally specify document id, defaults to a new UUID being assigned
 * 	.save(); 
 * 
 * ReplicatorDocument document = dbClient.replicator()
 * 	.replicatorDocId("doc-id")
 * 	.replicatorDocRev("doc-rev") // optional
 * 	.find();
 * 
 * {@code 
 * List docs = dbClient.replicator().findAll();
 * }
 * 
 * Response response = dbClient.replicator()
 * 	.replicatorDocId("doc-id")
 * 	.replicatorDocRev("doc-rev")
 * 	.remove();
 * 
* * @see Replication * @author Ahmed Yehia * */ public class Replicator { private String replicatorDB; private String userCtxName; private String[] userCtxRoles; private CouchDbClient dbc; private ReplicatorDocument replicatorDoc; private URI dbURI; public Replicator(CouchDbClient dbc) { this.dbc = dbc; replicatorDoc = new ReplicatorDocument(); replicatorDB = "_replicator"; // default replicator db userCtxRoles = new String[0]; // default roles dbURI = builder(dbc.getBaseUri()).path(replicatorDB).path("/").build(); } /** * Adds a new document to the replicator database. * @return {@link Response} */ public Response save() { assertNotEmpty(replicatorDoc.getSource(), "Source"); assertNotEmpty(replicatorDoc.getTarget(), "Target"); if(userCtxName != null) { UserCtx ctx = replicatorDoc.new UserCtx(); ctx.setName(userCtxName); ctx.setRoles(userCtxRoles); replicatorDoc.setUserCtx(ctx); } return dbc.put(dbURI, replicatorDoc, true); } /** * Finds a document in the replicator database. * @return {@link ReplicatorDocument} */ public ReplicatorDocument find() { assertNotEmpty(replicatorDoc.getId(), "Doc id"); URI uri = builder(dbURI).path(replicatorDoc.getId()).query("rev", replicatorDoc.getRevision()).build(); return dbc.get(uri, ReplicatorDocument.class); } /** * Finds all documents in the replicator database. */ public List findAll() { InputStream instream = null; try { URI uri = builder(dbURI).path("_all_docs").query("include_docs", "true").build(); Reader reader = new InputStreamReader(instream = dbc.get(uri)); JsonArray jsonArray = new JsonParser().parse(reader) .getAsJsonObject().getAsJsonArray("rows"); List list = new ArrayList(); for (JsonElement jsonElem : jsonArray) { JsonElement elem = jsonElem.getAsJsonObject().get("doc"); if(!getElement(elem.getAsJsonObject(), "_id").startsWith("_design")) { // skip design docs ReplicatorDocument rd = dbc.getGson().fromJson(elem, ReplicatorDocument.class); list.add(rd); } } return list; } finally { close(instream); } } /** * Removes a document from the replicator database. * @return {@link Response} */ public Response remove() { assertNotEmpty(replicatorDoc.getId(), "Doc id"); assertNotEmpty(replicatorDoc.getRevision(), "Doc rev"); URI uri = builder(dbURI).path(replicatorDoc.getId()).query("rev", replicatorDoc.getRevision()).build(); return dbc.delete(uri); } // fields public Replicator source(String source) { replicatorDoc.setSource(source); return this; } public Replicator target(String target) { replicatorDoc.setTarget(target); return this; } public Replicator continuous(boolean continuous) { replicatorDoc.setContinuous(continuous); return this; } public Replicator filter(String filter) { replicatorDoc.setFilter(filter); return this; } public Replicator queryParams(String queryParams) { replicatorDoc.setQueryParams(dbc.getGson().fromJson(queryParams, JsonObject.class)); return this; } public Replicator queryParams(Map queryParams) { replicatorDoc.setQueryParams(dbc.getGson().toJsonTree(queryParams).getAsJsonObject()); return this; } public Replicator docIds(String... docIds) { replicatorDoc.setDocIds(docIds); return this; } public Replicator proxy(String proxy) { replicatorDoc.setProxy(proxy); return this; } public Replicator createTarget(Boolean createTarget) { replicatorDoc.setCreateTarget(createTarget); return this; } public Replicator replicatorDB(String replicatorDB) { this.replicatorDB = replicatorDB; dbURI = builder(dbc.getBaseUri()).path(replicatorDB).path("/").build(); return this; } public Replicator replicatorDocId(String replicatorDocId) { replicatorDoc.setId(replicatorDocId); return this; } public Replicator replicatorDocRev(String replicatorDocRev) { replicatorDoc.setRevision(replicatorDocRev); return this; } public Replicator workerProcesses(int workerProcesses) { replicatorDoc.setWorkerProcesses(workerProcesses); return this; } public Replicator workerBatchSize(int workerBatchSize) { replicatorDoc.setWorkerBatchSize(workerBatchSize); return this; } public Replicator httpConnections(int httpConnections) { replicatorDoc.setHttpConnections(httpConnections); return this; } public Replicator connectionTimeout(long connectionTimeout) { replicatorDoc.setConnectionTimeout(connectionTimeout); return this; } public Replicator retriesPerRequest(int retriesPerRequest) { replicatorDoc.setRetriesPerRequest(retriesPerRequest); return this; } public Replicator userCtxName(String userCtxName) { this.userCtxName = userCtxName; return this; } public Replicator userCtxRoles(String... userCtxRoles) { this.userCtxRoles = userCtxRoles; return this; } public Replicator sinceSeq(Integer sinceSeq) { replicatorDoc.setSinceSeq(sinceSeq); return this; } } LightCouch-lightcouch-0.0.6/src/main/java/org/lightcouch/ReplicatorDocument.java000066400000000000000000000117641215035721600277320ustar00rootroot00000000000000/* * Copyright (C) 2011 Ahmed Yehia (ahmed.yehia.m@gmail.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.lightcouch; import com.google.gson.JsonObject; import com.google.gson.annotations.SerializedName; /** * Represents a replication document in a replicator database. * @see Replicator * @author Ahmed Yehia * */ public class ReplicatorDocument extends Document { @SerializedName("source") private String source; @SerializedName("target") private String target; @SerializedName("continuous") private Boolean continuous; @SerializedName("filter") private String filter; @SerializedName("query_params") private JsonObject queryParams; @SerializedName("doc_ids") private String[] docIds; @SerializedName("proxy") private String proxy; @SerializedName("create_target") private Boolean createTarget; @SerializedName("_replication_id") private String replicationId; @SerializedName("_replication_state") private String replicationState; @SerializedName("_replication_state_time") private String replicationStateTime; @SerializedName("worker_processes") private Integer workerProcesses; @SerializedName("worker_batch_size") private Integer workerBatchSize; @SerializedName("http_connections") private Integer httpConnections; @SerializedName("connection_timeout ") private Long connectionTimeout; @SerializedName("retries_per_request") private Integer retriesPerRequest; @SerializedName("user_ctx") private UserCtx userCtx; @SerializedName("since_seq") private Integer sinceSeq; public String getSource() { return source; } public String getTarget() { return target; } public Boolean getContinuous() { return continuous; } public String getFilter() { return filter; } public JsonObject getQueryParams() { return queryParams; } public String[] getDocIds() { return docIds; } public String getProxy() { return proxy; } public Boolean getCreateTarget() { return createTarget; } public String getReplicationId() { return replicationId; } public String getReplicationState() { return replicationState; } public String getReplicationStateTime() { return replicationStateTime; } public UserCtx getUserCtx() { return userCtx; } public Integer getWorkerProcesses() { return workerProcesses; } public Integer getWorkerBatchSize() { return workerBatchSize; } public Integer getHttpConnections() { return httpConnections; } public Long getConnectionTimeout() { return connectionTimeout; } public Integer getRetriesPerRequest() { return retriesPerRequest; } public void setSource(String source) { this.source = source; } public void setTarget(String target) { this.target = target; } public void setContinuous(Boolean continuous) { this.continuous = continuous; } public void setFilter(String filter) { this.filter = filter; } public void setQueryParams(JsonObject queryParams) { this.queryParams = queryParams; } public void setDocIds(String[] docIds) { this.docIds = docIds; } public void setProxy(String proxy) { this.proxy = proxy; } public void setCreateTarget(Boolean createTarget) { this.createTarget = createTarget; } public void setReplicationId(String replicationId) { this.replicationId = replicationId; } public void setReplicationState(String replicationState) { this.replicationState = replicationState; } public void setReplicationStateTime(String replicationStateTime) { this.replicationStateTime = replicationStateTime; } public void setUserCtx(UserCtx userCtx) { this.userCtx = userCtx; } public void setWorkerProcesses(Integer workerProcesses) { this.workerProcesses = workerProcesses; } public void setWorkerBatchSize(Integer workerBatchSize) { this.workerBatchSize = workerBatchSize; } public void setHttpConnections(Integer httpConnections) { this.httpConnections = httpConnections; } public void setConnectionTimeout(Long connectionTimeout) { this.connectionTimeout = connectionTimeout; } public void setRetriesPerRequest(Integer retriesPerRequest) { this.retriesPerRequest = retriesPerRequest; } public Integer getSinceSeq() { return sinceSeq; } public void setSinceSeq(Integer sinceSeq) { this.sinceSeq = sinceSeq; } public class UserCtx { private String name; private String[] roles; public String getName() { return name; } public String[] getRoles() { return roles; } public void setName(String name) { this.name = name; } public void setRoles(String[] roles) { this.roles = roles; } } // /class UserCtx } LightCouch-lightcouch-0.0.6/src/main/java/org/lightcouch/Response.java000066400000000000000000000022211215035721600257110ustar00rootroot00000000000000/* * Copyright (C) 2011 Ahmed Yehia (ahmed.yehia.m@gmail.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.lightcouch; /** * Represents CouchDB response as a result of a request. * @author Ahmed Yehia * */ public class Response { private String id; private String rev; // related to bulk response only private String error; private String reason; public String getId() { return id; } public String getRev() { return rev; } public String getError() { return error; } public String getReason() { return reason; } @Override public String toString() { return "Response [id=" + id + ", rev=" + rev + "]"; } } LightCouch-lightcouch-0.0.6/src/main/java/org/lightcouch/URIBuilder.java000066400000000000000000000046741215035721600260770ustar00rootroot00000000000000/* * Copyright (C) 2011 Ahmed Yehia (ahmed.yehia.m@gmail.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.lightcouch; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.List; /** * Helper class for construction of HTTP request URIs. * * @author Ahmed Yehia * */ class URIBuilder { private String scheme; private String host; private int port; private String path = ""; /* The final query */ private final StringBuilder query = new StringBuilder(); private final List queries = new ArrayList(); public static URIBuilder builder() { return new URIBuilder(); } public static URIBuilder builder(URI uri) { URIBuilder builder = URIBuilder.builder().scheme(uri.getScheme()) .host(uri.getHost()).port(uri.getPort()).path(uri.getPath()); return builder; } public URI build() { try { for (int i = 0; i < queries.size(); i++) { query.append(queries.get(i)); if (i != queries.size() - 1) query.append("&"); } String q = (query.length() == 0) ? null : query.toString(); return new URI(scheme, null, host, port, path, q, null); } catch (URISyntaxException e) { throw new IllegalArgumentException(e); } } public URIBuilder scheme(String scheme) { this.scheme = scheme; return this; } public URIBuilder host(String host) { this.host = host; return this; } public URIBuilder port(int port) { this.port = port; return this; } public URIBuilder path(String path) { this.path += path; return this; } public URIBuilder query(String name, Object value) { if (name != null && value != null) this.queries.add(String.format("%s=%s", name, value)); return this; } public URIBuilder query(String query) { if (query != null) this.query.append(query); return this; } public URIBuilder query(Params params) { if (params.getParams() != null) this.queries.addAll(params.getParams()); return this; } } LightCouch-lightcouch-0.0.6/src/main/java/org/lightcouch/View.java000066400000000000000000000443301215035721600250340ustar00rootroot00000000000000/* * Copyright (C) 2011 Ahmed Yehia (ahmed.yehia.m@gmail.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.lightcouch; import static java.lang.String.format; import static org.lightcouch.CouchDbUtil.*; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.net.URI; import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.apache.commons.codec.binary.Base64; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.lightcouch.DesignDocument.MapReduce; import com.google.gson.Gson; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; /** *

This class allows construction and sending of View query requests. * The API supports view queries for various data type results, and for pagination. * *

Usage Example:

*
 * {@code
 *  List list = dbClient.view("example/foo")
 *	.includeDocs(true).startKey("start-key").endKey("end-key").limit(10).query(Foo.class);
 *  
 * int count = dbClient.view("example/by_tag").key("couchdb").queryForInt(); // query for scalar values
 *  
 * // query for view entries
 * View view = dbClient.view("example/by_date")
 *	.key(2011, 10, 15) // complex key example
 *	.reduce(false)
 *	.includeDocs(true);
 * ViewResult result = 
 *	view.queryView(int[].class, String.class, Foo.class);
 * 
 * // pagination
 * Page page = dbClient.view("example/foo").queryPage(15, param, Foo.class);
 * // page.get*Param() contains the param to query subsequent pages, {@code null} param queries the first page
 * }
 * 
* * @author Ahmed Yehia */ public class View { private static final Log log = LogFactory.getLog(View.class); // paging param fields private static final String START_KEY = "s_k"; private static final String START_KEY_DOC_ID = "s_k_d_i"; private static final String CURRENT_START_KEY = "c_k"; private static final String CURRENT_START_KEY_DOC_ID = "c_k_d_i"; private static final String CURRENT_KEYS = "c"; private static final String ACTION = "a"; private static final String NEXT = "n"; private static final String PREVIOUS = "p"; // temp views private static final String TEMP_VIEWS_DIR = "temp-views"; private static final String MAP_JS = "map.js"; private static final String REDUCE_JS = "reduce.js"; // view fields private String key; private String startKey; private String startKeyDocId; private String endKey; private String endKeyDocId; private Integer limit; private String stale; private Boolean descending; private Integer skip; private Boolean group; private Integer groupLevel; private Boolean reduce; private Boolean includeDocs; private Boolean inclusiveEnd; private Boolean updateSeq; private CouchDbClient dbc; private Gson gson; private URIBuilder uriBuilder; private String allDocsKeys; // bulk docs private MapReduce tempView; // temp view View(CouchDbClient dbc, String viewId) { assertNotEmpty(viewId, "View id"); this.dbc = dbc; this.gson = dbc.getGson(); String view = viewId; if(viewId.contains("/")) { String[] v = viewId.split("/"); view = String.format("_design/%s/_view/%s", v[0], v[1]); } this.uriBuilder = URIBuilder.builder(dbc.getDBUri()).path(view); } // Query options /** * Queries a view as an {@link InputStream} *

The stream should be properly closed after usage, as to avoid connection leaks. * @return The result as an {@link InputStream}. */ public InputStream queryForStream() { URI uri = uriBuilder.build(); if(allDocsKeys != null) { // bulk docs return dbc.getStream(dbc.post(uri, allDocsKeys)); } if(tempView != null) { // temp view return dbc.getStream(dbc.post(uri, gson.toJson(tempView))); } return dbc.get(uri); } /** * Queries a view. * @param Object type T * @param classOfT The class of type T * @return The result of the view query as a {@code List } */ public List query(Class classOfT) { InputStream instream = null; try { Reader reader = new InputStreamReader(instream = queryForStream()); JsonArray jsonArray = new JsonParser().parse(reader) .getAsJsonObject().getAsJsonArray("rows"); List list = new ArrayList(); for (JsonElement jsonElem : jsonArray) { JsonElement elem = jsonElem.getAsJsonObject(); if(Boolean.TRUE.equals(this.includeDocs)) { elem = jsonElem.getAsJsonObject().get("doc"); } T t = this.gson.fromJson(elem, classOfT); list.add(t); } return list; } finally { close(instream); } } /** * Queries a view. * @param Object type K (key) * @param Object type V (value) * @param classOfK The class of type K. * @param classOfV The class of type V. * @param classOfT The class of type T. * @return The View result entries. */ public ViewResult queryView(Class classOfK, Class classOfV, Class classOfT) { InputStream instream = null; try { Reader reader = new InputStreamReader(instream = queryForStream()); JsonObject json = new JsonParser().parse(reader).getAsJsonObject(); ViewResult vr = new ViewResult(); vr.setTotalRows(getElementAsLong(json, "total_rows")); vr.setOffset(getElementAsInt(json, "offset")); vr.setUpdateSeq(getElementAsLong(json, "update_seq")); JsonArray jsonArray = json.getAsJsonArray("rows"); if(jsonArray.size() == 0) { // validate available rows throw new NoDocumentException("No result was returned by this view query."); } for (JsonElement e : jsonArray) { ViewResult.Rows row = vr.new Rows(); row.setId(JsonToObject(gson, e, "id", String.class)); row.setKey(JsonToObject(gson, e, "key", classOfK)); row.setValue(JsonToObject(gson, e, "value", classOfV)); if(Boolean.TRUE.equals(this.includeDocs)) { row.setDoc(JsonToObject(gson, e, "doc", classOfT)); } vr.getRows().add(row); } return vr; } finally { close(instream); } } /** * @return The result of the view as String. */ public String queryForString() { return queryValue(String.class); } /** * @return The result of the view as int. */ public int queryForInt() { return queryValue(int.class); } /** * @return The result of the view as long. */ public long queryForLong() { return queryValue(long.class); } /** * @return The result of the view as boolean. */ public boolean queryForBoolean() { return queryValue(boolean.class); } /** * Queries for scalar values. Internal use. */ private V queryValue(Class classOfV) { InputStream instream = null; try { Reader reader = new InputStreamReader(instream = queryForStream()); JsonArray array = new JsonParser().parse(reader). getAsJsonObject().get("rows").getAsJsonArray(); if(array.size() != 1) { // expect exactly 1 row throw new NoDocumentException("Expecting exactly a single result of this view query, but was: " + array.size()); } return JsonToObject(gson, array.get(0), "value", classOfV); } finally { close(instream); } } /** * Queries a view for pagination, returns a next or a previous page, this method * figures out which page to return based on the given param that is generated by an * earlier call to this method, quering the first page is done by passing a {@code null} param. * @param Object type T * @param rowsPerPage The number of rows per page. * @param param The request parameter to use to query a page, or {@code null} to return the first page. * @param classOfT The class of type T. * @return {@link Page} */ public Page queryPage(int rowsPerPage, String param, Class classOfT) { if(param == null) { // assume first page return queryNextPage(rowsPerPage, null, null, null, null, classOfT); } String currentStartKey; String currentStartKeyDocId; String startKey; String startKeyDocId; String action; try { // extract fields from the returned HEXed JSON object JsonObject json = new JsonParser().parse(new String(Base64.decodeBase64(param.getBytes()))).getAsJsonObject(); if(log.isDebugEnabled()) { log.debug("Paging Param Decoded = " + json); } JsonObject jsonCurrent = json.getAsJsonObject(CURRENT_KEYS); currentStartKey = jsonCurrent.get(CURRENT_START_KEY).getAsString(); currentStartKeyDocId = jsonCurrent.get(CURRENT_START_KEY_DOC_ID).getAsString(); startKey = json.get(START_KEY).getAsString(); startKeyDocId = json.get(START_KEY_DOC_ID).getAsString(); action = json.get(ACTION).getAsString(); } catch (Exception e) { throw new CouchDbException("could not parse the given param!", e); } if(PREVIOUS.equals(action)) { // previous return queryPreviousPage(rowsPerPage, currentStartKey, currentStartKeyDocId, startKey, startKeyDocId, classOfT); } else { // next return queryNextPage(rowsPerPage, currentStartKey, currentStartKeyDocId, startKey, startKeyDocId, classOfT); } } /** * @return The next page. */ private Page queryNextPage(int rowsPerPage, String currentStartKey, String currentStartKeyDocId, String startKey, String startKeyDocId, Class classOfT) { // set view query params limit(rowsPerPage + 1); includeDocs(true); if(startKey != null) { startKey(startKey); startKeyDocId(startKeyDocId); } // init page, query view Page page = new Page(); List pageList = new ArrayList(); ViewResult vr = queryView(String.class, String.class, classOfT); List.Rows> rows = vr.getRows(); int resultRows = rows.size(); int offset = vr.getOffset(); long totalRows = vr.getTotalRows(); // holds page params JsonObject currentKeys = new JsonObject(); JsonObject jsonNext = new JsonObject(); JsonObject jsonPrev = new JsonObject(); currentKeys.addProperty(CURRENT_START_KEY, rows.get(0).getKey()); currentKeys.addProperty(CURRENT_START_KEY_DOC_ID, rows.get(0).getId()); for (int i = 0; i < resultRows; i++) { // set keys for the next page if (i == resultRows - 1) { // last element (i.e rowsPerPage + 1) if(resultRows > rowsPerPage) { // if not last page page.setHasNext(true); jsonNext.addProperty(START_KEY, rows.get(i).getKey()); jsonNext.addProperty(START_KEY_DOC_ID, rows.get(i).getId()); jsonNext.add(CURRENT_KEYS, currentKeys); jsonNext.addProperty(ACTION, NEXT); page.setNextParam(Base64.encodeBase64URLSafeString(jsonNext.toString().getBytes())); continue; // exclude } } pageList.add(rows.get(i).getDoc()); } // set keys for the previous page if(offset != 0) { // if not first page page.setHasPrevious(true); jsonPrev.addProperty(START_KEY, currentStartKey); jsonPrev.addProperty(START_KEY_DOC_ID, currentStartKeyDocId); jsonPrev.add(CURRENT_KEYS, currentKeys); jsonPrev.addProperty(ACTION, PREVIOUS); page.setPreviousParam(Base64.encodeBase64URLSafeString(jsonPrev.toString().getBytes())); } // calculate paging display info page.setResultList(pageList); page.setTotalResults(totalRows); page.setResultFrom(offset + 1); int resultTo = rowsPerPage > resultRows ? resultRows : rowsPerPage; // fix when rowsPerPage exceeds returned rows page.setResultTo(offset + resultTo); page.setPageNumber((int) Math.ceil(page.getResultFrom() / Double.valueOf(rowsPerPage))); return page; } /** * @return The previous page. */ private Page queryPreviousPage(int rowsPerPage, String currentStartKey, String currentStartKeyDocId, String startKey, String startKeyDocId, Class classOfT) { // set view query params limit(rowsPerPage + 1); includeDocs(true); descending(true); // read backward startKey(currentStartKey); startKeyDocId(currentStartKeyDocId); // init page, query view Page page = new Page(); List pageList = new ArrayList(); ViewResult vr = queryView(String.class, String.class, classOfT); List.Rows> rows = vr.getRows(); int resultRows = rows.size(); int offset = vr.getOffset(); long totalRows = vr.getTotalRows(); Collections.reverse(rows); // fix order // holds page params JsonObject currentKeys = new JsonObject(); JsonObject jsonNext = new JsonObject(); JsonObject jsonPrev = new JsonObject(); currentKeys.addProperty(CURRENT_START_KEY, rows.get(0).getKey()); currentKeys.addProperty(CURRENT_START_KEY_DOC_ID, rows.get(0).getId()); for (int i = 0; i < resultRows; i++) { // set keys for the next page if (i == resultRows - 1) { // last element (i.e rowsPerPage + 1) if(resultRows >= rowsPerPage) { // if not last page page.setHasNext(true); jsonNext.addProperty(START_KEY, rows.get(i).getKey()); jsonNext.addProperty(START_KEY_DOC_ID, rows.get(i).getId()); jsonNext.add(CURRENT_KEYS, currentKeys); jsonNext.addProperty(ACTION, NEXT); page.setNextParam(Base64.encodeBase64URLSafeString(jsonNext.toString().getBytes())); continue; } } pageList.add(rows.get(i).getDoc()); } // set keys for the previous page if(offset != (totalRows - rowsPerPage - 1)) { // if not first page page.setHasPrevious(true); jsonPrev.addProperty(START_KEY, currentStartKey); jsonPrev.addProperty(START_KEY_DOC_ID, currentStartKeyDocId); jsonPrev.add(CURRENT_KEYS, currentKeys); jsonPrev.addProperty(ACTION, PREVIOUS); page.setPreviousParam(Base64.encodeBase64URLSafeString(jsonPrev.toString().getBytes())); } // calculate paging display info page.setResultList(pageList); page.setTotalResults(totalRows); page.setResultFrom((int) totalRows - (offset + rowsPerPage)); int resultTo = (int) totalRows - offset - 1; page.setResultTo(resultTo); page.setPageNumber(resultTo / rowsPerPage); return page; } // fields /** * @param key The key value, accepts a single value or multiple values for complex keys. */ public View key(Object... key) { this.key = getKeyAsJson(key); uriBuilder.query("key", this.key); return this; } /** * @param startKey The start key value, accepts a single value or multiple values for complex keys. */ public View startKey(Object... startKey) { this.startKey = getKeyAsJson(startKey); uriBuilder.query("startkey", this.startKey); return this; } public View startKeyDocId(String startKeyDocId) { this.startKeyDocId = startKeyDocId; uriBuilder.query("startkey_docid", this.startKeyDocId); return this; } /** * @param endKey The end key value, accepts a single value or multiple values for complex keys. */ public View endKey(Object... endKey) { this.endKey = getKeyAsJson(endKey); uriBuilder.query("endkey", this.endKey); return this; } public View endKeyDocId(String endKeyDocId) { this.endKeyDocId = endKeyDocId; uriBuilder.query("endkey_docid", this.endKeyDocId); return this; } public View limit(Integer limit) { this.limit = limit; uriBuilder.query("limit", this.limit); return this; } /** * @param stale Accept values: ok | update_after (update_after as of CouchDB 1.1.0) */ public View stale(String stale) { this.stale = stale; uriBuilder.query("stale", this.stale); return this; } /** * Reverses the reading direction, not the sort order. */ public View descending(Boolean descending) { this.descending = Boolean.valueOf(gson.toJson(descending)); uriBuilder.query("descending", this.descending); return this; } /** * @param skip Skips n number of documents. */ public View skip(Integer skip) { this.skip = skip; uriBuilder.query("skip", this.skip); return this; } /** * @param group Specifies whether the reduce function reduces the result to a set of keys, * or to a single result. Defaults to false (single result). */ public View group(Boolean group) { this.group = group; uriBuilder.query("group", this.group); return this; } public View groupLevel(Integer groupLevel) { this.groupLevel = groupLevel; uriBuilder.query("group_level", this.groupLevel); return this; } /** * @param reduce Indicates whether to use the reduce function of the view, * defaults to true if the reduce function is defined. */ public View reduce(Boolean reduce) { this.reduce = reduce; uriBuilder.query("reduce", this.reduce); return this; } public View includeDocs(Boolean includeDocs) { this.includeDocs = includeDocs; uriBuilder.query("include_docs", this.includeDocs); return this; } /** * @param inclusiveEnd Indicates whether the endkey is included in the result, * defaults to true. */ public View inclusiveEnd(Boolean inclusiveEnd) { this.inclusiveEnd = inclusiveEnd; uriBuilder.query("inclusive_end", this.inclusiveEnd); return this; } public View updateSeq(Boolean updateSeq) { this.updateSeq = updateSeq; uriBuilder.query("update_seq", this.updateSeq); return this; } public View keys(List keys) { this.allDocsKeys = String.format("{%s:%s}", gson.toJson("keys"), gson.toJson(keys)); return this; } // temp views public View tempView(String id) { assertNotEmpty(id, "id"); String viewPath = format("%s/%s/", TEMP_VIEWS_DIR, id); List dirList = listResources(viewPath); assertNotEmpty(dirList, "Temp view directory"); tempView = new MapReduce(); for (String mapRed : dirList) { String def = readFile(format("/%s%s", viewPath, mapRed)); if(MAP_JS.equals(mapRed)) tempView.setMap(def); else if(REDUCE_JS.equals(mapRed)) tempView.setReduce(def); } return this; } public View tempView(MapReduce mapReduce) { assertNotEmpty(mapReduce, "mapReduce"); tempView = mapReduce; return this; } private String getKeyAsJson(Object... key) { return (key.length == 1) ? gson.toJson(key[0]) : gson.toJson(key); // single or complex key } } LightCouch-lightcouch-0.0.6/src/main/java/org/lightcouch/ViewResult.java000066400000000000000000000044411215035721600262320ustar00rootroot00000000000000/* * Copyright (C) 2011 Ahmed Yehia (ahmed.yehia.m@gmail.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.lightcouch; import java.util.ArrayList; import java.util.List; import com.google.gson.annotations.SerializedName; /** * Holds a view result entries. * @see View * @author Ahmed Yehia */ public class ViewResult { @SerializedName("total_rows") private long totalRows; @SerializedName("update_seq") private long updateSeq; private int offset; private List rows = new ArrayList(); public long getTotalRows() { return totalRows; } public long getUpdateSeq() { return updateSeq; } public int getOffset() { return offset; } public List getRows() { return rows; } public void setTotalRows(long totalRows) { this.totalRows = totalRows; } public void setUpdateSeq(long updateSeq) { this.updateSeq = updateSeq; } public void setOffset(int offset) { this.offset = offset; } public void setRows(List rows) { this.rows = rows; } @Override public String toString() { return "ViewResult [totalRows=" + totalRows + ", updateSeq=" + updateSeq + ", offset=" + offset + ", rows=" + rows + "]"; } /** * Inner class holding the view rows. */ public class Rows { private String id; private K key; private V value; private T doc; public String getId() { return id; } public K getKey() { return key; } public V getValue() { return value; } public T getDoc() { return doc; } public void setId(String id) { this.id = id; } public void setKey(K key) { this.key = key; } public void setValue(V value) { this.value = value; } public void setDoc(T doc) { this.doc = doc; } @Override public String toString() { return "Rows [id=" + id + "]"; } } } LightCouch-lightcouch-0.0.6/src/test/000077500000000000000000000000001215035721600174455ustar00rootroot00000000000000LightCouch-lightcouch-0.0.6/src/test/java/000077500000000000000000000000001215035721600203665ustar00rootroot00000000000000LightCouch-lightcouch-0.0.6/src/test/java/org/000077500000000000000000000000001215035721600211555ustar00rootroot00000000000000LightCouch-lightcouch-0.0.6/src/test/java/org/lightcouch/000077500000000000000000000000001215035721600233065ustar00rootroot00000000000000LightCouch-lightcouch-0.0.6/src/test/java/org/lightcouch/tests/000077500000000000000000000000001215035721600244505ustar00rootroot00000000000000LightCouch-lightcouch-0.0.6/src/test/java/org/lightcouch/tests/Bar.java000066400000000000000000000004541215035721600260220ustar00rootroot00000000000000package org.lightcouch.tests; import org.lightcouch.Document; public class Bar extends Document { private String bar; public String getBar() { return bar; } public void setBar(String bar) { this.bar = bar; } @Override public String toString() { return "Bar [bar=" + bar + "]"; } } LightCouch-lightcouch-0.0.6/src/test/java/org/lightcouch/tests/CouchDbClientLoadTest.java000066400000000000000000000050331215035721600314220ustar00rootroot00000000000000/* * Copyright (C) 2011 Ahmed Yehia (ahmed.yehia.m@gmail.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.lightcouch.tests; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import org.lightcouch.CouchDbClient; /** * {@link CouchDbClient} load test. * *

Run test: *

$ mvn test -Dtest=org.lightcouch.tests.CouchDbClientLoadTest * * @author Ahmed Yehia * */ public class CouchDbClientLoadTest { private static CouchDbClient dbClient; private static final int NUM_THREADS = 1; private static final int NUM_DOCS = 1; @BeforeClass public static void setUpClass() { dbClient = new CouchDbClient(); } @AfterClass public static void tearDownClass() { dbClient.shutdown(); } @Test public void clientsLoadTest() { // init ExecutorService executor = Executors.newFixedThreadPool(NUM_THREADS); // start System.out.println("--------------- Load Test Running ..."); StopWatch.start(); for (int i = 0; i < NUM_THREADS; i++) { executor.execute(new MyRunnable()); } executor.shutdown(); do { /* waiting */ } while (!executor.isTerminated()); // result long elapsed = StopWatch.stop(); long seconds = elapsed / 1000; int totalDocs = NUM_THREADS * NUM_DOCS; StringBuilder sb = new StringBuilder(); sb.append("--------------- Load Test Ended:"); sb.append("\n* Thread count: " + NUM_THREADS); sb.append(", Docs per thread: " + NUM_DOCS); sb.append("\n* Saved total new documents: " + totalDocs); sb.append(String.format("\n* Elapsed time: %s seconds, %s ms.", seconds, elapsed - (seconds * 1000))); sb.append("\n* Average persisting time: " + (elapsed / totalDocs) + " ms per Document."); System.out.println(sb); } private class MyRunnable implements Runnable { public void run() { for (int i = 0; i < NUM_DOCS; i++) { dbClient.save(new Foo()); } } } } LightCouch-lightcouch-0.0.6/src/test/java/org/lightcouch/tests/CouchDbClientTest.java000066400000000000000000000454061215035721600306320ustar00rootroot00000000000000/* * Copyright (C) 2011 Ahmed Yehia (ahmed.yehia.m@gmail.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.lightcouch.tests; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.not; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; import org.apache.commons.codec.binary.Base64; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import org.lightcouch.Attachment; import org.lightcouch.Changes; import org.lightcouch.ChangesResult; import org.lightcouch.ChangesResult.Row; import org.lightcouch.CouchDbClient; import org.lightcouch.CouchDbInfo; import org.lightcouch.DesignDocument; import org.lightcouch.DocumentConflictException; import org.lightcouch.NoDocumentException; import org.lightcouch.Page; import org.lightcouch.Params; import org.lightcouch.ReplicationResult; import org.lightcouch.ReplicationResult.ReplicationHistory; import org.lightcouch.ReplicatorDocument; import org.lightcouch.Response; import org.lightcouch.ViewResult; import com.google.gson.JsonArray; import com.google.gson.JsonObject; /** * API Integration testing with live databases. * * @author Ahmed Yehia */ public class CouchDbClientTest { private static CouchDbClient dbClient; private static CouchDbClient dbClient2; @BeforeClass public static void setUpClass() { dbClient = new CouchDbClient(); dbClient2 = new CouchDbClient("couchdb-2.properties"); // dbClient = new CouchDbClient("db-name", true, "http", "127.0.0.1", 5984, "username", "secret"); /*CouchDbProperties properties = new CouchDbProperties() .setDbName("db-name") .setCreateDbIfNotExist(true) .setProtocol("https") .setHost("example.com") .setPort(443) .setUsername("username") .setPassword("secret") .setMaxConnections(100);*/ //dbClient = new CouchDbClient(properties); } @AfterClass public static void tearDownClass() { dbClient.shutdown(); dbClient2.shutdown(); } // Find @Test public void findById() { Response response = dbClient.save(new Foo()); Foo foo = dbClient.find(Foo.class, response.getId()); assertNotNull(foo); } @Test public void findByIdAndRev() { Response response = dbClient.save(new Foo()); Foo foo = dbClient.find(Foo.class, response.getId(), response.getRev()); assertNotNull(foo); } @Test public void findPOJO() { Response response = dbClient.save(new Foo()); Foo foo = dbClient.find(Foo.class, response.getId()); assertNotNull(foo); } @Test public void findJsonObject() { Response response = dbClient.save(new Foo()); JsonObject jsonObject = dbClient.find(JsonObject.class, response.getId()); assertNotNull(jsonObject); } @Test public void findAny() { JsonObject jsonObject = dbClient.findAny(JsonObject.class, dbClient.getDBUri().toString()); assertNotNull(jsonObject); } @Test public void findInputstream() throws IOException { Response response = dbClient.save(new Foo()); InputStream inputStream = dbClient.find(response.getId()); assertTrue(inputStream.read() != -1); inputStream.close(); } @Test public void findWithParams() { Response response = dbClient.save(new Foo()); Foo foo = dbClient.find(Foo.class, response.getId(), new Params().revsInfo().attachments()); assertNotNull(foo); } @Test(expected = IllegalArgumentException.class) public void findWithInvalidId_throwsIllegalArgumentException() { dbClient.find(Foo.class, ""); } @Test(expected = NoDocumentException.class) public void findWithUnknownId_throwsNoDocumentException() { dbClient.find(Foo.class, generateUUID()); } @Test public void contains() { boolean found = dbClient.contains(generateUUID()); assertFalse(found); Response response = dbClient.save(new Foo()); found = dbClient.contains(response.getId()); assertTrue(found); } // Save @Test public void savePOJO() { dbClient.save(new Foo()); } @Test public void saveMap() { Map map = new HashMap(); map.put("_id", generateUUID()); map.put("field1", "value1"); dbClient.save(map); } @Test public void saveJsonObject() { JsonObject json = new JsonObject(); json.addProperty("_id", generateUUID()); json.add("an-array", new JsonArray()); dbClient.save(json); } @Test(expected = IllegalArgumentException.class) public void saveInvalidObject_throwsIllegalArgumentException() { dbClient.save(null); } @Test(expected = IllegalArgumentException.class) public void saveWithRevision_throwsIllegalArgumentException() { Bar bar = new Bar(); bar.setRevision("rev-val"); dbClient.save(bar); } @Test(expected = DocumentConflictException.class) public void saveWitDuplicateId_throwsDocumentConflictException() { Foo foo = new Foo(generateUUID()); dbClient.save(foo); dbClient.save(foo); } @Test public void batch() { dbClient.batch(new Foo()); } // Update @Test public void update() { Response response = dbClient.save(new Foo()); Foo foo = dbClient.find(Foo.class, response.getId()); dbClient.update(foo); } @Test(expected = IllegalArgumentException.class) public void updateWithoutIdAndRev_throwsIllegalArgumentException() { dbClient.update(new Foo()); } @Test(expected = DocumentConflictException.class) public void updateWithOutdatedRev_throwsDocumentConflictException() { Response response = dbClient.save(new Foo()); Foo foo = dbClient.find(Foo.class, response.getId()); dbClient.update(foo); dbClient.update(foo); } // Bulk @Test public void bulkModifyDoc() { List newDocs = new ArrayList(); newDocs.add(new Foo()); newDocs.add(new JsonObject()); List responses = dbClient.bulk(newDocs, true); assertThat(responses.size(), is(2)); } @Test public void bulkGetDocs() { Response resp1 = dbClient.save(new Foo()); Response resp2 = dbClient.save(new Foo()); List keys = Arrays.asList(new String[] { resp1.getId(), resp2.getId() }); List docs = dbClient.view("_all_docs").includeDocs(true).keys(keys) .query(Foo.class); assertThat(docs.size(), is(2)); } // Attachment @Test public void attachmentInline() { Attachment attachment = new Attachment(); attachment.setContentType("text/plain"); attachment.setData("VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ="); Attachment attachment2 = new Attachment(); attachment2.setContentType("text/plain"); String data = Base64.encodeBase64String("some text contents".getBytes()); attachment2.setData(data); Bar bar = new Bar(); bar.addAttachment("bar.txt", attachment); bar.addAttachment("bar2.txt", attachment2); dbClient.save(bar); } @Test public void attachmentInline_getWithDocument() { Attachment attachment = new Attachment(); attachment.setContentType("text/plain"); attachment.setData("VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ="); Bar bar = new Bar(); bar.addAttachment("bar.txt", attachment); Response response = dbClient.save(bar); bar = dbClient.find(Bar.class, response.getId(), new Params().attachments()); String base64Data = bar.getAttachments().get("bar.txt").getData(); assertNotNull(base64Data); } @Test public void attachmentStandalone() throws IOException { byte[] bytesToDB = "some text contents".getBytes(); ByteArrayInputStream bytesIn = new ByteArrayInputStream(bytesToDB); Response response = dbClient.saveAttachment(bytesIn, "foo.txt", "text/plain"); String attachmentId = response.getId() + "/foo.txt"; InputStream in = dbClient.find(attachmentId); ByteArrayOutputStream bytesOut = new ByteArrayOutputStream(); int n; while ((n = in.read()) != -1) { bytesOut.write(n); } bytesOut.flush(); in.close(); byte[] bytesFromDB = bytesOut.toByteArray(); assertArrayEquals(bytesToDB, bytesFromDB); } // Delete @Test public void deleteObject() { Response response = dbClient.save(new Foo()); Foo foo = dbClient.find(Foo.class, response.getId()); dbClient.remove(foo); } @Test public void deleteByIdAndRev() { Response response = dbClient.save(new Foo()); dbClient.remove(response.getId(), response.getRev()); } @Test(expected = IllegalArgumentException.class) public void deleteObjectWithInvalidValues_throwsIllegalArgumentException() { Bar bar = new Bar(); bar.setId("doc-id"); bar.setRevision(null); dbClient.remove(bar); } // Views private void initDataForViews() { try { Foo foo = null; foo = new Foo("foo-id-1", "foo-key-1"); foo.setTags(Arrays.asList(new String[] { "couchdb", "views" })); foo.setComplexDate(new int[] { 2011, 10, 15 }); dbClient.save(foo); foo = new Foo("foo-id-2", "foo-key-2"); foo.setTags(Arrays.asList(new String[] { "java", "couchdb" })); foo.setComplexDate(new int[] { 2011, 10, 15 }); dbClient.save(foo); foo = new Foo("foo-id-3", "foo-key-3"); foo.setComplexDate(new int[] { 2013, 12, 17 }); dbClient.save(foo); DesignDocument exampleDoc = dbClient.design().getFromDesk("example"); dbClient.design().synchronizeWithDb(exampleDoc); } catch (DocumentConflictException e) { } } @Test() public void views() { initDataForViews(); List foos = dbClient.view("example/foo").includeDocs(true).query(Foo.class); assertThat(foos.size(), not(0)); } @Test() public void views_byKey() { initDataForViews(); List foos = dbClient.view("example/foo").includeDocs(true) .key("foo-key-1").query(Foo.class); assertThat(foos.size(), is(1)); } @Test() public void views_byStartAndEndKey() { initDataForViews(); List foos = dbClient.view("example/foo").startKey("foo-key-1").endKey("foo-key-2").includeDocs(true).query(Foo.class); assertThat(foos.size(), is(2)); } @Test() public void views_byComplexKey() { initDataForViews(); int[] complexKey = new int[] { 2011, 10, 15 }; List foos = dbClient.view("example/by_date").key(complexKey).includeDocs(true).reduce(false).query(Foo.class); assertThat(foos.size(), is(2)); } @Test() public void views_ViewResult() { initDataForViews(); ViewResult viewResult = dbClient.view("example/by_date") .reduce(false).queryView(int[].class, String.class, Foo.class); assertThat(viewResult.getRows().size(), is(3)); } @Test() public void views_scalarValues() { initDataForViews(); int allTags = dbClient.view("example/by_tag").queryForInt(); assertThat(allTags, is(4)); long couchDbTags = dbClient.view("example/by_tag").key("couchdb").queryForLong(); assertThat(couchDbTags, is(2L)); String javaTags = dbClient.view("example/by_tag").key("java").queryForString(); assertThat(javaTags, is("1")); } @Test(expected = NoDocumentException.class) public void viewsWithNoResult_throwsNoDocumentException() { initDataForViews(); dbClient.view("example/by_tag").key("javax").queryForInt(); } @Test public void views_ByGroupLevel() { ViewResult viewResult = dbClient.view("example/by_date") .groupLevel(2).queryView(int[].class, Integer.class, Foo.class); assertThat(viewResult.getRows().size(), is(2)); } @Test() public void tempViews() { dbClient.save(new Foo(generateUUID(), "title")); List list = dbClient.view("_temp_view").tempView("temp_1").includeDocs(true) .reduce(false).query(Foo.class); assertThat(list.size(), not(0)); } @Test public void allDocs() { dbClient.save(new Foo()); List allDocs = dbClient.view("_all_docs").query(JsonObject.class); assertThat(allDocs.size(), not(0)); } @Test public void testPagination() { dbClient.design().synchronizeAllWithDb(); for (int i = 0; i < 7; i++) { dbClient.save(new Foo(generateUUID(), "title")); } final int rowsPerPage = 3; // first page, page #1, rows: 1 - 3 Page page = dbClient.view("example/foo").queryPage(rowsPerPage, null, Foo.class); assertFalse(page.isHasPrevious()); assertTrue(page.isHasNext()); assertThat(page.getResultFrom(), is(1)); assertThat(page.getResultTo(), is(3)); assertThat(page.getPageNumber(), is(1)); assertThat(page.getResultList().size(), is(3)); String param = page.getNextParam(); // next page, page #2, rows: 4 - 6 page = dbClient.view("example/foo").queryPage(rowsPerPage, param, Foo.class); assertTrue(page.isHasPrevious()); assertTrue(page.isHasNext()); assertThat(page.getResultFrom(), is(4)); assertThat(page.getResultTo(), is(6)); assertThat(page.getPageNumber(), is(2)); assertThat(page.getResultList().size(), is(3)); param = page.getPreviousParam(); // previous page, page #1, rows: 1 - 3 page = dbClient.view("example/foo").queryPage(rowsPerPage, param, Foo.class); assertFalse(page.isHasPrevious()); assertTrue(page.isHasNext()); assertThat(page.getResultFrom(), is(1)); assertThat(page.getResultTo(), is(3)); assertThat(page.getPageNumber(), is(1)); assertThat(page.getResultList().size(), is(3)); } // Update handler @Test public void updateHandler() { dbClient.syncDesignDocsWithDb(); Foo foo = new Foo(); foo.setTitle("title"); Response response = dbClient.save(foo); String newTitle = "new title value"; String query = String.format("field=title&value=%s", newTitle); String output = dbClient.invokeUpdateHandler("example/example_update", response.getId(), query); foo = dbClient.find(Foo.class, response.getId()); assertNotNull(output); assertEquals(foo.getTitle(), newTitle); } // Replication @Test public void replication() { ReplicationResult result = dbClient.replication() .createTarget(true) .source(dbClient.getDBUri().toString()) .target(dbClient2.getDBUri().toString()) .trigger(); List histories = result.getHistories(); assertThat(histories.size(), not(0)); } @Test public void replication_filteredWithQueryParams() { Map queryParams = new HashMap(); queryParams.put("somekey1", "value 1"); dbClient.replication() .createTarget(true) .source(dbClient.getDBUri().toString()) .target(dbClient2.getDBUri().toString()) .filter("example/example_filter") .queryParams(queryParams) .trigger(); } @Test public void replication_conflict() { DesignDocument conflictsDoc = dbClient.design().getFromDesk("conflicts"); dbClient2.design().synchronizeWithDb(conflictsDoc); String docId = generateUUID(); Foo foodb1 = new Foo(docId, "title"); Foo foodb2 = null; foodb1 = new Foo(docId, "titleX"); dbClient.save(foodb1); dbClient.replication().source(dbClient.getDBUri().toString()) .target(dbClient2.getDBUri().toString()).trigger(); foodb2 = dbClient2.find(Foo.class, docId); foodb2.setTitle("titleY"); dbClient2.update(foodb2); foodb1 = dbClient.find(Foo.class, docId); foodb1.setTitle("titleZ"); dbClient.update(foodb1); dbClient.replication().source(dbClient.getDBUri().toString()) .target(dbClient2.getDBUri().toString()).trigger(); ViewResult conflicts = dbClient2.view("conflicts/conflict") .includeDocs(true).queryView(String[].class, String.class, Foo.class); assertThat(conflicts.getRows().size(), is(not(0))); } @Test public void replicatorDB() throws InterruptedException { String version = dbClient.context().serverVersion(); if (version.startsWith("0") || version.startsWith("1.0")) { return; } Response response = dbClient.replicator() .source(dbClient.getDBUri().toString()) .target(dbClient2.getDBUri().toString()).continuous(true) .createTarget(true) .save(); List replicatorDocs = dbClient.replicator() .findAll(); assertThat(replicatorDocs.size(), is(not(0))); ReplicatorDocument replicatorDoc = dbClient.replicator() .replicatorDocId(response.getId()) .find(); dbClient.replicator() .replicatorDocId(replicatorDoc.getId()) .replicatorDocRev(replicatorDoc.getRevision()) .remove(); } // Database @Test public void dbInfo() { CouchDbInfo dbInfo = dbClient.context().info(); assertNotNull(dbInfo); } @Test public void serverVersion() { String version = dbClient.context().serverVersion(); assertNotNull(version); } @Test public void compactDb() { dbClient.context().compact(); } @Test public void allDBs() { List allDbs = dbClient.context().getAllDbs(); assertThat(allDbs.size(), is(not(0))); } @Test public void ensureFullCommit() { dbClient.context().ensureFullCommit(); } // Design documents @Test public void designDocs() { dbClient.design().synchronizeAllWithDb(); DesignDocument designDoc = dbClient.design().getFromDesk("example"); DesignDocument designDoc1 = dbClient.design().getFromDesk("example"); assertTrue(designDoc.equals(designDoc1)); DesignDocument designDocFromDb = dbClient.design().getFromDb("_design/example"); assertTrue(designDoc.equals(designDocFromDb)); List designDocs = dbClient.design().getAllFromDesk(); assertThat(designDocs.size(), not(0)); } // Changes @SuppressWarnings("unused") @Test public void changes_normalFeed() { dbClient.save(new Foo()); ChangesResult changes = dbClient.changes() .includeDocs(true) .limit(1) .getChanges(); List rows = changes.getResults(); for (Row row : rows) { List revs = row.getChanges(); String docId = row.getId(); JsonObject doc = row.getDoc(); assertNotNull(doc); } assertThat(rows.size(), is(1)); } @Test public void changes_continuousFeed() { dbClient.save(new Foo()); CouchDbInfo dbInfo = dbClient.context().info(); String since = dbInfo.getUpdateSeq(); Changes changes = dbClient.changes() .includeDocs(true) .since(since) .heartBeat(30000) .continuousChanges(); Response response = dbClient.save(new Foo()); while (changes.hasNext()) { ChangesResult.Row feed = changes.next(); String docId = feed.getId(); assertEquals(response.getId(), docId); changes.stop(); } } // util private static String generateUUID() { return UUID.randomUUID().toString().replace("-", ""); } } LightCouch-lightcouch-0.0.6/src/test/java/org/lightcouch/tests/Foo.java000066400000000000000000000050771215035721600260470ustar00rootroot00000000000000package org.lightcouch.tests; import java.util.Arrays; import java.util.Date; import java.util.List; import java.util.Map; import java.util.Set; import org.lightcouch.Attachment; public class Foo { private String _id; private String _rev; private String title; private int position; private List tags; private int[] complexDate; private Set bars; private Date date; private Map _attachments; private List _revs_info; public Foo() { super(); } public Foo(String _id) { this._id = _id; } public Foo(String _id, String title) { this._id = _id; this.title = title; } public String get_id() { return _id; } public String get_rev() { return _rev; } public String getTitle() { return title; } public int getPosition() { return position; } public List getTags() { return tags; } public int[] getComplexDate() { return complexDate; } public Set getBars() { return bars; } public Date getDate() { return date; } public Map get_attachments() { return _attachments; } public List get_revs_info() { return _revs_info; } public void set_id(String _id) { this._id = _id; } public void set_rev(String _rev) { this._rev = _rev; } public void setTitle(String title) { this.title = title; } public void setPosition(int position) { this.position = position; } public void setTags(List tags) { this.tags = tags; } public void setComplexDate(int[] complexDate) { this.complexDate = complexDate; } public void setBars(Set bars) { this.bars = bars; } public void setDate(Date date) { this.date = date; } public void set_attachments(Map _attachments) { this._attachments = _attachments; } public void set_revs_info(List _revs_info) { this._revs_info = _revs_info; } @Override public String toString() { return "Foo [_id=" + _id + ", _rev=" + _rev + ", title=" + title + ", position=" + position + ", tags=" + tags + ", complexDate=" + Arrays.toString(complexDate) + ", bars=" + bars + ", _revs_info=" + _revs_info + "]"; } public static class RevInfo { private String rev; private String status; public String getRev() { return rev; } public void setRev(String rev) { this.rev = rev; } public String getStatus() { return status; } public void setStatus(String status) { this.status = status; } @Override public String toString() { return "RevInfo [rev=" + rev + ", status=" + status + "]"; } } // end RevInfo } // end Foo LightCouch-lightcouch-0.0.6/src/test/java/org/lightcouch/tests/StopWatch.java000066400000000000000000000005741215035721600272350ustar00rootroot00000000000000package org.lightcouch.tests; // not thread safe public final class StopWatch { private StopWatch() {} private static long start; private static long stop; public static void start() { start = System.currentTimeMillis(); stop = 0; } public static long stop() { stop = System.currentTimeMillis(); long value = stop - start; start = 0; return value; } } LightCouch-lightcouch-0.0.6/src/test/resources/000077500000000000000000000000001215035721600214575ustar00rootroot00000000000000LightCouch-lightcouch-0.0.6/src/test/resources/commons-logging.properties000066400000000000000000000001101215035721600266640ustar00rootroot00000000000000org.apache.commons.logging.Log=org.apache.commons.logging.impl.SimpleLogLightCouch-lightcouch-0.0.6/src/test/resources/couchdb-2.properties000066400000000000000000000011361215035721600253440ustar00rootroot00000000000000### Required couchdb.name=lightcouch-db-test-2 couchdb.createdb.if-not-exist=true # The protocol: http | https couchdb.protocol=http couchdb.host=127.0.0.1 # The port e.g: 5984 | 6984 couchdb.port=5984 # Blank username/password for no login couchdb.username= couchdb.password= ### Optional # Timeout to wait for a response, in ms. Defaults to 0 (no timeout). couchdb.http.socket.timeout= # Timeout to establish a connection, in ms. Defaults to 0 (no timeout). couchdb.http.connection.timeout= # Max connections. couchdb.max.connections= # Connect through Proxy. couchdb.proxy.host= couchdb.proxy.port=LightCouch-lightcouch-0.0.6/src/test/resources/couchdb.properties000066400000000000000000000011411215035721600252010ustar00rootroot00000000000000### Required couchdb.name=lightcouch-db-test couchdb.createdb.if-not-exist=true # The protocol: http | https couchdb.protocol=http couchdb.host=127.0.0.1 # The port e.g: 5984 | 6984 couchdb.port=5984 # Blank username/password for no login couchdb.username= couchdb.password= ### Optional # Timeout to wait for a response, in ms. Defaults to 0 (no timeout). couchdb.http.socket.timeout= # Timeout to establish a connection, in ms. Defaults to 0 (no timeout). couchdb.http.connection.timeout= # Max connections. couchdb.max.connections=100 # Connect through Proxy. couchdb.proxy.host= couchdb.proxy.port= LightCouch-lightcouch-0.0.6/src/test/resources/design-docs/000077500000000000000000000000001215035721600236565ustar00rootroot00000000000000LightCouch-lightcouch-0.0.6/src/test/resources/design-docs/conflicts/000077500000000000000000000000001215035721600256425ustar00rootroot00000000000000LightCouch-lightcouch-0.0.6/src/test/resources/design-docs/conflicts/views/000077500000000000000000000000001215035721600267775ustar00rootroot00000000000000LightCouch-lightcouch-0.0.6/src/test/resources/design-docs/conflicts/views/conflict/000077500000000000000000000000001215035721600306005ustar00rootroot00000000000000LightCouch-lightcouch-0.0.6/src/test/resources/design-docs/conflicts/views/conflict/map.js000066400000000000000000000000761215035721600317160ustar00rootroot00000000000000function(doc){ if(doc._conflicts) emit(doc._conflicts, null);}LightCouch-lightcouch-0.0.6/src/test/resources/design-docs/example/000077500000000000000000000000001215035721600253115ustar00rootroot00000000000000LightCouch-lightcouch-0.0.6/src/test/resources/design-docs/example/filters/000077500000000000000000000000001215035721600267615ustar00rootroot00000000000000LightCouch-lightcouch-0.0.6/src/test/resources/design-docs/example/filters/example_filter.js000066400000000000000000000001611215035721600323150ustar00rootroot00000000000000function(doc, req) { if (doc.title == req.query.somekey1) { return true; } else { return false; } }LightCouch-lightcouch-0.0.6/src/test/resources/design-docs/example/lists/000077500000000000000000000000001215035721600264475ustar00rootroot00000000000000LightCouch-lightcouch-0.0.6/src/test/resources/design-docs/example/lists/example_list.js000066400000000000000000000000261215035721600314710ustar00rootroot00000000000000function(head, req) {}LightCouch-lightcouch-0.0.6/src/test/resources/design-docs/example/shows/000077500000000000000000000000001215035721600264545ustar00rootroot00000000000000LightCouch-lightcouch-0.0.6/src/test/resources/design-docs/example/shows/example_show_1.js000066400000000000000000000000631215035721600317240ustar00rootroot00000000000000function(doc, req) { return '

Example

'; }LightCouch-lightcouch-0.0.6/src/test/resources/design-docs/example/shows/example_show_2.js000066400000000000000000000000631215035721600317250ustar00rootroot00000000000000function(doc, req) { return '

Example

'; }LightCouch-lightcouch-0.0.6/src/test/resources/design-docs/example/updates/000077500000000000000000000000001215035721600267565ustar00rootroot00000000000000LightCouch-lightcouch-0.0.6/src/test/resources/design-docs/example/updates/example_update.js000066400000000000000000000002511215035721600323070ustar00rootroot00000000000000function(doc, req) { var field = req.query.field; var value = req.query.value; var message = 'set '+field+' to '+value; doc[field] = value; return [doc, message]; }LightCouch-lightcouch-0.0.6/src/test/resources/design-docs/example/validate_doc_update/000077500000000000000000000000001215035721600312715ustar00rootroot00000000000000example_validate.js000066400000000000000000000000441215035721600350520ustar00rootroot00000000000000LightCouch-lightcouch-0.0.6/src/test/resources/design-docs/example/validate_doc_updatefunction(newDoc, oldDoc, userCtx) {}LightCouch-lightcouch-0.0.6/src/test/resources/design-docs/example/views/000077500000000000000000000000001215035721600264465ustar00rootroot00000000000000LightCouch-lightcouch-0.0.6/src/test/resources/design-docs/example/views/by_date/000077500000000000000000000000001215035721600300555ustar00rootroot00000000000000LightCouch-lightcouch-0.0.6/src/test/resources/design-docs/example/views/by_date/map.js000066400000000000000000000001261215035721600311670ustar00rootroot00000000000000function(doc){ if(doc.title && doc.complexDate){ emit(doc.complexDate, 1); } }LightCouch-lightcouch-0.0.6/src/test/resources/design-docs/example/views/by_date/reduce.js000066400000000000000000000000571215035721600316640ustar00rootroot00000000000000function(keys, values){ return sum(values); }LightCouch-lightcouch-0.0.6/src/test/resources/design-docs/example/views/by_tag/000077500000000000000000000000001215035721600277135ustar00rootroot00000000000000LightCouch-lightcouch-0.0.6/src/test/resources/design-docs/example/views/by_tag/map.js000066400000000000000000000001631215035721600310260ustar00rootroot00000000000000function(doc){ if(doc.title && doc.tags){ for(var idx in doc.tags){ emit(doc.tags[idx], 1); } } }LightCouch-lightcouch-0.0.6/src/test/resources/design-docs/example/views/by_tag/reduce.js000066400000000000000000000000571215035721600315220ustar00rootroot00000000000000function(keys, values){ return sum(values); }LightCouch-lightcouch-0.0.6/src/test/resources/design-docs/example/views/foo/000077500000000000000000000000001215035721600272315ustar00rootroot00000000000000LightCouch-lightcouch-0.0.6/src/test/resources/design-docs/example/views/foo/map.js000066400000000000000000000001101215035721600303340ustar00rootroot00000000000000function(doc){ if(doc.title){ emit(doc.title, doc.position); } }LightCouch-lightcouch-0.0.6/src/test/resources/simplelog.properties000066400000000000000000000005571215035721600255770ustar00rootroot00000000000000## SimpleLog settings. org.apache.commons.logging.simplelog.defaultlog=DEBUG org.apache.commons.logging.simplelog.showlogname=true org.apache.commons.logging.simplelog.showdatetime=true org.apache.commons.logging.simplelog.dateTimeFormat=HH:mm:ss:SSS zzz yyyy/MM/dd ## Configure 3rd party logging levels org.apache.commons.logging.simplelog.log.org.apache.http=ERRORLightCouch-lightcouch-0.0.6/src/test/resources/temp-views/000077500000000000000000000000001215035721600235575ustar00rootroot00000000000000LightCouch-lightcouch-0.0.6/src/test/resources/temp-views/temp_1/000077500000000000000000000000001215035721600247445ustar00rootroot00000000000000LightCouch-lightcouch-0.0.6/src/test/resources/temp-views/temp_1/map.js000066400000000000000000000000751215035721600260610ustar00rootroot00000000000000function(doc){ if(doc.title){ emit(doc.title, 1); } }LightCouch-lightcouch-0.0.6/src/test/resources/temp-views/temp_1/reduce.js000066400000000000000000000000571215035721600265530ustar00rootroot00000000000000function(keys, values){ return sum(values); }LightCouch-lightcouch-0.0.6/src/test/resources/temp-views/temp_2/000077500000000000000000000000001215035721600247455ustar00rootroot00000000000000LightCouch-lightcouch-0.0.6/src/test/resources/temp-views/temp_2/map.js000066400000000000000000000000751215035721600260620ustar00rootroot00000000000000function(doc){ if(doc.title){ emit(doc.title, 2); } }