This is an automated email from the ASF dual-hosted git repository. rombert pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-nosql-couchbase-resourceprovider.git
commit 27c0b6581865b51cc5748b5bb48f741b900d6c30 Author: Stefan Seifert <[email protected]> AuthorDate: Mon Sep 14 23:09:39 2015 +0000 SLING-4381 switch to Couchbase 4.0 with N1QL to get rid of couchbase views git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1703076 13f79535-47bb-0310-9956-ffa450edef68 --- README.md | 18 +---- pom.xml | 9 ++- src/main/couchbase-views/ancestorPath.js | 40 ----------- src/main/couchbase-views/ancestorPathTester.html | 84 ---------------------- src/main/couchbase-views/parentPath.js | 37 ---------- src/main/couchbase-views/parentPathTester.html | 82 --------------------- .../impl/CouchbaseNoSqlAdapter.java | 64 ++++++++++++----- 7 files changed, 58 insertions(+), 276 deletions(-) diff --git a/README.md b/README.md index 4fd582b..6b88a77 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,8 @@ Sling ResourceProvider implementation that uses [Couchbase](http://www.couchbase Based on the "Apache Sling NoSQL Generic Resource Provider" and "Apache Sling NoSQL Couchbase Client". +Couchbase Server 4.0 with N1QL support is required for this implementation. + Configuration on deployment --------------------------- @@ -13,21 +15,6 @@ Configuration on deployment * Additionally a factory configuration for "Apache Sling NoSQL Couchbase Resource Provider Factory" defines the root of the resource tree that should be stored in Couchbase -Couchbase Views for path-based access -------------------------------------- - -For list and delete operations two couchbase views have to be defined and published in the bucket that is used by the resource provider. - -Steps to create those views: -* Log into Couchbase Console -* Go to "Views" and select the correct bucket -* Add a new design document via "Create Development View" and name it "\_design/dev\_resourceIndex" (the prefix "\_design/dev\_" is added automatically) -* Use the name "ancestorPath" for the first view that is created together with the design document -* Paste the view code from [ancestorPath.js](src/main/couchbase-views/ancestorPath.js) into the editor and save it -* Create another view named "parentPath", paste the view code from [parentPath.js](src/main/couchbase-views/parentPath.js) and save it -* Publish the design document so the views are production views - - Run integration tests --------------------- @@ -36,4 +23,3 @@ To run the integration tests you have to set up a real couchbase server and run ``` mvn -Pcouchbase-integration-test -DcouchbaseHosts=localhost:8091 -DbucketName=test integration-test ``` - diff --git a/pom.xml b/pom.xml index 07c509d..49fc370 100644 --- a/pom.xml +++ b/pom.xml @@ -67,7 +67,14 @@ <version>2.2.2</version> <scope>provided</scope> </dependency> - + + <dependency> + <groupId>org.apache.commons</groupId> + <artifactId>commons-lang3</artifactId> + <version>3.3.2</version> + <scope>provided</scope> + </dependency> + <dependency> <groupId>org.apache.sling</groupId> <artifactId>org.apache.sling.testing.sling-mock</artifactId> diff --git a/src/main/couchbase-views/ancestorPath.js b/src/main/couchbase-views/ancestorPath.js deleted file mode 100644 index f8cd94b..0000000 --- a/src/main/couchbase-views/ancestorPath.js +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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. - */ -/* - * Emits for each document the all parent paths - allowing to fetch children and their decendants by path. - * Includes the path of the item itself. - */ -function(doc, meta) { - - // handle only sling resource documents with a valid path - if (!(meta.id.indexOf("sling-resource:")==0 && doc.path && doc.data)) { - return; - } - var pathParts = doc.path.split("/"); - if (pathParts.length < 3) { - return; - } - - while (pathParts.length >= 2) { - // remove last element to get parent path - var parentPath = pathParts.join("/"); - emit(parentPath, null); - pathParts.pop(); - } -} diff --git a/src/main/couchbase-views/ancestorPathTester.html b/src/main/couchbase-views/ancestorPathTester.html deleted file mode 100644 index 9933e6f..0000000 --- a/src/main/couchbase-views/ancestorPathTester.html +++ /dev/null @@ -1,84 +0,0 @@ -<!DOCTYPE html> -<!-- - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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. ---> -<html> - <head> - <title>Couchbase View Tester</title> - <style>body { font-family: Courier }</style> - </head> - <body> - -<script> - -var emit = function(key, value) { - document.write("[" + key + "]" + "<br/>"); -} - -var testFunction = function(doc, meta) { - - // handle only sling resource documents with a valid path - if (!(meta.id.indexOf("sling-resource:")==0 && doc.path && doc.data)) { - return; - } - var pathParts = doc.path.split("/"); - if (pathParts.length < 3) { - return; - } - - while (pathParts.length >= 2) { - // remove last element to get parent path - var parentPath = pathParts.join("/"); - emit(parentPath, null); - pathParts.pop(); - } -}; - -var testInput = [ - null, - "", - "abc", - "/", - "/content", - "/content/node1", - "/content/node1/node2", - "/content/node1/node2/node3", - "/content/node1/node2/node3/node4" -]; - -</script> - - <table border="1"> - <tr> - <th>Input</th> - <th>Output</th> - </tr> -<script> -for (var i=0; i < testInput.length; i++) { - document.write("<tr>") - document.write("<td>" + testInput[i] + "</td>") - document.write("<td>") - testFunction({path: testInput[i], data: {}}, {id: "sling-resource:doc" + i}); - document.write("</td>") - document.write("</tr>") -} -</script> - </table> - - </body> -</html> diff --git a/src/main/couchbase-views/parentPath.js b/src/main/couchbase-views/parentPath.js deleted file mode 100644 index 54c4c1f..0000000 --- a/src/main/couchbase-views/parentPath.js +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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. - */ -/* - * Emits for each document the direct parent path - allowing to fetch direct children by path. - */ -function(doc, meta) { - - // handle only sling resource documents with a valid path - if (!(meta.id.indexOf("sling-resource:")==0 && doc.path && doc.data)) { - return; - } - var pathParts = doc.path.split("/"); - if (pathParts.length < 3) { - return; - } - - // remove last element to get parent path - pathParts.pop(); - var parentPath = pathParts.join("/"); - emit(parentPath, null); -} diff --git a/src/main/couchbase-views/parentPathTester.html b/src/main/couchbase-views/parentPathTester.html deleted file mode 100644 index efcc2d1..0000000 --- a/src/main/couchbase-views/parentPathTester.html +++ /dev/null @@ -1,82 +0,0 @@ -<!DOCTYPE html> -<!-- - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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. ---> -<html> - <head> - <title>Couchbase View Tester</title> - <style>body { font-family: Courier }</style> - </head> - <body> - -<script> - -var emit = function(key, value) { - document.write("[" + key + "]" + "<br/>"); -} - -var testFunction = function(doc, meta) { - - // handle only sling resource documents with a valid path - if (!(meta.id.indexOf("sling-resource:")==0 && doc.path && doc.data)) { - return; - } - var pathParts = doc.path.split("/"); - if (pathParts.length < 3) { - return; - } - - // remove last element to get parent path - pathParts.pop(); - var parentPath = pathParts.join("/"); - emit(parentPath, null); -}; - -var testInput = [ - null, - "", - "abc", - "/", - "/content", - "/content/node1", - "/content/node1/node2", - "/content/node1/node2/node3", - "/content/node1/node2/node3/node4" -]; - -</script> - - <table border="1"> - <tr> - <th>Input</th> - <th>Output</th> - </tr> -<script> -for (var i=0; i < testInput.length; i++) { - document.write("<tr>") - document.write("<td>" + testInput[i] + "</td>") - document.write("<td>") - testFunction({path: testInput[i], data: {}}, {id: "sling-resource:doc" + i}); - document.write("</td>") - document.write("</tr>") -} -</script> - </table> - - </body> -</html> diff --git a/src/main/java/org/apache/sling/nosql/couchbase/resourceprovider/impl/CouchbaseNoSqlAdapter.java b/src/main/java/org/apache/sling/nosql/couchbase/resourceprovider/impl/CouchbaseNoSqlAdapter.java index 5ebcc6f..fd4a798 100644 --- a/src/main/java/org/apache/sling/nosql/couchbase/resourceprovider/impl/CouchbaseNoSqlAdapter.java +++ b/src/main/java/org/apache/sling/nosql/couchbase/resourceprovider/impl/CouchbaseNoSqlAdapter.java @@ -18,8 +18,12 @@ */ package org.apache.sling.nosql.couchbase.resourceprovider.impl; +import static com.couchbase.client.java.query.Select.select; + import java.util.Iterator; +import java.util.regex.Pattern; +import org.apache.commons.lang3.StringUtils; import org.apache.sling.nosql.couchbase.client.CouchbaseClient; import org.apache.sling.nosql.couchbase.client.CouchbaseKey; import org.apache.sling.nosql.generic.adapter.AbstractNoSqlAdapter; @@ -30,9 +34,11 @@ import com.couchbase.client.java.Bucket; import com.couchbase.client.java.document.JsonDocument; import com.couchbase.client.java.document.json.JsonObject; import com.couchbase.client.java.error.DocumentAlreadyExistsException; -import com.couchbase.client.java.view.Stale; -import com.couchbase.client.java.view.ViewQuery; -import com.couchbase.client.java.view.ViewRow; +import com.couchbase.client.java.query.N1qlParams; +import com.couchbase.client.java.query.N1qlQuery; +import com.couchbase.client.java.query.N1qlQueryResult; +import com.couchbase.client.java.query.N1qlQueryRow; +import com.couchbase.client.java.query.consistency.ScanConsistency; /** * {@link org.apache.sling.nosql.generic.adapter.NoSqlAdapter} implementation for Couchbase. @@ -49,16 +55,18 @@ public final class CouchbaseNoSqlAdapter extends AbstractNoSqlAdapter { */ public static final String PN_DATA = "data"; - private static final String VIEW_DESIGN_DOCUMENT = "resourceIndex"; - private static final String VIEW_PARENT_PATH = "parentPath"; - private static final String VIEW_ANCESTOR_PATH = "ancestorPath"; - private final CouchbaseClient couchbaseClient; private final String cacheKeyPrefix; + + private static final N1qlParams N1QL_PARAMS = N1qlParams.build().consistency(ScanConsistency.REQUEST_PLUS); public CouchbaseNoSqlAdapter(CouchbaseClient couchbaseClient, String cacheKeyPrefix) { this.couchbaseClient = couchbaseClient; this.cacheKeyPrefix = cacheKeyPrefix; + + // make sure primary index is present - ignore error if it is already present + Bucket bucket = couchbaseClient.getBucket(); + bucket.query(N1qlQuery.simple("CREATE PRIMARY INDEX ON " + couchbaseClient.getBucketName())); } @Override @@ -89,8 +97,14 @@ public final class CouchbaseNoSqlAdapter extends AbstractNoSqlAdapter { public Iterator<NoSqlData> getChildren(String parentPath) { Bucket bucket = couchbaseClient.getBucket(); // fetch all direct children of this path - final Iterator<ViewRow> results = bucket.query( - ViewQuery.from(VIEW_DESIGN_DOCUMENT, VIEW_PARENT_PATH).key(parentPath).stale(Stale.FALSE)).rows(); + Pattern directChildren = Pattern.compile("^" + parentPath + "/[^/]+$"); + N1qlQuery query = N1qlQuery.simple(select("*") + .from(couchbaseClient.getBucketName()) + .where("REGEXP_LIKE(`" + PN_PATH + "`, '" + directChildren.pattern() + "')"), + N1QL_PARAMS); + N1qlQueryResult queryResult = bucket.query(query); + handleQueryError(queryResult); + final Iterator<N1qlQueryRow> results = queryResult.iterator(); return new Iterator<NoSqlData>() { @Override public boolean hasNext() { @@ -99,8 +113,8 @@ public final class CouchbaseNoSqlAdapter extends AbstractNoSqlAdapter { @Override public NoSqlData next() { - JsonDocument doc = results.next().document(); - JsonObject envelope = doc.content(); + JsonObject item = results.next().value(); + JsonObject envelope = item.getObject(couchbaseClient.getBucketName()); String path = envelope.getString(PN_PATH); JsonObject data = envelope.getObject(PN_DATA); return new NoSqlData(path, data.toMap(), MultiValueMode.LISTS); @@ -136,16 +150,34 @@ public final class CouchbaseNoSqlAdapter extends AbstractNoSqlAdapter { @Override public boolean deleteRecursive(String path) { Bucket bucket = couchbaseClient.getBucket(); - // fetch referenced item and all descendants - Iterator<ViewRow> results = bucket.query( - ViewQuery.from(VIEW_DESIGN_DOCUMENT, VIEW_ANCESTOR_PATH).key(path).stale(Stale.FALSE)).rows(); + // fetch all descendants and self for deletion + Pattern descendantsAndSelf = Pattern.compile("^" + path + "(/.+)?$"); + N1qlQuery query = N1qlQuery.simple(select("*") + .from(couchbaseClient.getBucketName()) + .where("REGEXP_LIKE(`" + PN_PATH + "`, '" + descendantsAndSelf.pattern() + "')"), + N1QL_PARAMS); + N1qlQueryResult queryResult = bucket.query(query); + handleQueryError(queryResult); + final Iterator<N1qlQueryRow> results = queryResult.iterator(); boolean deletedAny = false; while (results.hasNext()) { - ViewRow result = results.next(); - bucket.remove(result.document()); + JsonObject item = results.next().value(); + JsonObject envelope = item.getObject(couchbaseClient.getBucketName()); + String itemPath = envelope.getString(PN_PATH); + String itemCacheKey = CouchbaseKey.build(itemPath, cacheKeyPrefix); + bucket.remove(itemCacheKey); deletedAny = true; } return deletedAny; } + + private void handleQueryError(N1qlQueryResult queryResult) { + if (!queryResult.parseSuccess()) { + throw new RuntimeException("Couchbase query parsing error: " + StringUtils.join(queryResult.errors(), "\n")); + } + if (!queryResult.finalSuccess()) { + throw new RuntimeException("Couchbase query error: " + StringUtils.join(queryResult.errors(), "\n")); + } + } } -- To stop receiving notification emails like this one, please contact "[email protected]" <[email protected]>.
