You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ro...@apache.org on 2017/11/07 09:56:04 UTC
[sling-org-apache-sling-nosql-generic] 01/23: SLING-4381 generic
nosql resource provider
This is an automated email from the ASF dual-hosted git repository.
rombert pushed a commit to annotated tag org.apache.sling.nosql.generic-1.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-nosql-generic.git
commit c0cfaf51d9208f356f37090ec95491699029f045
Author: Stefan Seifert <ss...@apache.org>
AuthorDate: Wed May 20 12:53:50 2015 +0000
SLING-4381 generic nosql resource provider
git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/contrib/nosql/generic@1680549 13f79535-47bb-0310-9956-ffa450edef68
---
pom.xml | 139 +++++++++++
.../sling/nosql/generic/adapter/NoSqlAdapter.java | 76 ++++++
.../sling/nosql/generic/adapter/NoSqlData.java | 47 ++++
.../sling/nosql/generic/adapter/package-info.java | 23 ++
.../AbstractNoSqlResourceProviderFactory.java | 51 ++++
.../nosql/generic/resource/impl/NoSqlResource.java | 90 +++++++
.../resource/impl/NoSqlResourceProvider.java | 272 +++++++++++++++++++++
.../nosql/generic/resource/impl/NoSqlValueMap.java | 178 ++++++++++++++
.../nosql/generic/resource/impl/PathUtil.java | 50 ++++
.../sling/nosql/generic/resource/package-info.java | 23 ++
.../impl/AbstractNoSqlResourceProviderTest.java | 211 ++++++++++++++++
...ractNoSqlResourceProviderTransactionalTest.java | 227 +++++++++++++++++
.../nosql/generic/resource/impl/PathUtilTest.java | 56 +++++
.../SimpleNoSqlResourceProviderQueryTest.java | 102 ++++++++
.../simple/SimpleNoSqlResourceProviderTest.java | 64 +++++
...mpleNoSqlResourceProviderTransactionalTest.java | 73 ++++++
.../simple/provider/SimpleNoSqlAdapter.java | 117 +++++++++
.../SimpleNoSqlResourceProviderFactory.java | 77 ++++++
...provider.SimpleNoSqlResourceProviderFactory.xml | 32 +++
19 files changed, 1908 insertions(+)
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..5781f27
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,139 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!--
+ 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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>sling</artifactId>
+ <version>22</version>
+ </parent>
+
+ <artifactId>org.apache.sling.nosql.generic</artifactId>
+ <version>0.5.0-SNAPSHOT</version>
+ <packaging>bundle</packaging>
+
+ <name>Apache Sling Generic NoSQL Resource Provider</name>
+
+ <scm>
+ <connection>scm:svn:http://svn.apache.org/repos/asf/sling/trunk/contrib/extensions/nosql-generic</connection>
+ <developerConnection>scm:svn:https://svn.apache.org/repos/asf/sling/trunk/contrib/extensions/nosql-generic</developerConnection>
+ <url>http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/nosql-generic</url>
+ </scm>
+
+ <properties>
+ <sling.java.version>7</sling.java.version>
+ </properties>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-scr-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <extensions>true</extensions>
+ </plugin>
+
+ <!-- Publish test artifact -->
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ <executions>
+ <execution>
+ <goals>
+ <goal>test-jar</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+
+ </plugins>
+ </build>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>org.apache.sling.api</artifactId>
+ <version>2.9.0</version>
+ <scope>compile</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>org.apache.sling.commons.osgi</artifactId>
+ <version>2.2.2</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.compendium</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.scr.annotations</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>servlet-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>commons-io</groupId>
+ <artifactId>commons-io</artifactId>
+ <version>2.4</version>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-all</artifactId>
+ <version>1.10.19</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>org.apache.sling.testing.sling-mock</artifactId>
+ <version>1.2.1-SNAPSHOT</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>javax.jcr</groupId>
+ <artifactId>jcr</artifactId>
+ <version>2.0</version>
+ <scope>test</scope>
+ </dependency>
+
+ </dependencies>
+
+</project>
diff --git a/src/main/java/org/apache/sling/nosql/generic/adapter/NoSqlAdapter.java b/src/main/java/org/apache/sling/nosql/generic/adapter/NoSqlAdapter.java
new file mode 100644
index 0000000..0359796
--- /dev/null
+++ b/src/main/java/org/apache/sling/nosql/generic/adapter/NoSqlAdapter.java
@@ -0,0 +1,76 @@
+/*
+ * 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.
+ */
+package org.apache.sling.nosql.generic.adapter;
+
+import java.util.Iterator;
+
+import aQute.bnd.annotation.ConsumerType;
+
+/**
+ * Adapter for NoSQL databases to be hooked into the Generic NoSQL resource provider.
+ */
+@ConsumerType
+public interface NoSqlAdapter {
+
+ /**
+ * True if the given path is valid and supported by the NoSQL database.
+ * @param path Path
+ * @return true if valid, false if invalid
+ */
+ boolean validPath(String path);
+
+ /**
+ * Get data for a single resource from NoSQL database.
+ * @param path Path
+ * @return Data or null if non exists
+ */
+ NoSqlData get(String path);
+
+ /**
+ * Get data for all children of a resource from NoSQL database.
+ * @param parentPath Parent path
+ * @return List if child data or empty iterator
+ */
+ Iterator<NoSqlData> getChildren(String parentPath);
+
+ /**
+ * Store data with the given path in NoSQL database.
+ * It is guaranteed that the map of the NoSqlData object does only contain primitive
+ * value types String, Integer, Long, Double, Boolean or arrays of them.
+ * @param data Data with path
+ * @return true if a new entry was created, false if an existing was overridden.
+ */
+ boolean store(NoSqlData data);
+
+ /**
+ * Remove data including all path-related children from NoSQL database.
+ * @param path Path to remove
+ * @return true if anything was removed
+ */
+ boolean deleteRecursive(String path);
+
+ /**
+ * Query for data.
+ * @param query Query
+ * @param language Query language
+ * @return Query result or null if query not supported
+ */
+ Iterator<NoSqlData> query(String query, String language);
+
+}
diff --git a/src/main/java/org/apache/sling/nosql/generic/adapter/NoSqlData.java b/src/main/java/org/apache/sling/nosql/generic/adapter/NoSqlData.java
new file mode 100644
index 0000000..2aef8b4
--- /dev/null
+++ b/src/main/java/org/apache/sling/nosql/generic/adapter/NoSqlData.java
@@ -0,0 +1,47 @@
+/*
+ * 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.
+ */
+package org.apache.sling.nosql.generic.adapter;
+
+import java.util.Map;
+
+import aQute.bnd.annotation.ProviderType;
+
+/**
+ * Wrapper for properties of a NoSQL document for a given path.
+ */
+@ProviderType
+public final class NoSqlData {
+
+ private final String path;
+ private final Map<String,Object> properties;
+
+ public NoSqlData(String path, Map<String, Object> properties) {
+ this.path = path;
+ this.properties = properties;
+ }
+
+ public String getPath() {
+ return path;
+ }
+
+ public Map<String, Object> getProperties() {
+ return properties;
+ }
+
+}
diff --git a/src/main/java/org/apache/sling/nosql/generic/adapter/package-info.java b/src/main/java/org/apache/sling/nosql/generic/adapter/package-info.java
new file mode 100644
index 0000000..3a4287a
--- /dev/null
+++ b/src/main/java/org/apache/sling/nosql/generic/adapter/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+/**
+ * Adapter for NoSQL resource provider implementation.
+ */
+@aQute.bnd.annotation.Version("0.5")
+package org.apache.sling.nosql.generic.adapter;
diff --git a/src/main/java/org/apache/sling/nosql/generic/resource/AbstractNoSqlResourceProviderFactory.java b/src/main/java/org/apache/sling/nosql/generic/resource/AbstractNoSqlResourceProviderFactory.java
new file mode 100644
index 0000000..2e56b2b
--- /dev/null
+++ b/src/main/java/org/apache/sling/nosql/generic/resource/AbstractNoSqlResourceProviderFactory.java
@@ -0,0 +1,51 @@
+/*
+ * 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.
+ */
+package org.apache.sling.nosql.generic.resource;
+
+import java.util.Map;
+
+import org.apache.sling.api.resource.LoginException;
+import org.apache.sling.api.resource.ResourceProvider;
+import org.apache.sling.api.resource.ResourceProviderFactory;
+import org.apache.sling.nosql.generic.adapter.NoSqlAdapter;
+import org.apache.sling.nosql.generic.resource.impl.NoSqlResourceProvider;
+import org.osgi.service.event.EventAdmin;
+
+import aQute.bnd.annotation.ConsumerType;
+
+/**
+ * Abstract implementation of resource provider factory.
+ * NoSQL resource providers implement this, add their own configuration support and and provide the matching NoSQL adapter implementation.
+ */
+@ConsumerType
+public abstract class AbstractNoSqlResourceProviderFactory implements ResourceProviderFactory {
+
+ public final ResourceProvider getResourceProvider(Map<String, Object> authenticationInfo) throws LoginException {
+ return new NoSqlResourceProvider(getNoSqlAdapter(), getEventAdmin());
+ }
+
+ public final ResourceProvider getAdministrativeResourceProvider(Map<String, Object> authenticationInfo) throws LoginException {
+ return getResourceProvider(authenticationInfo);
+ }
+
+ protected abstract NoSqlAdapter getNoSqlAdapter();
+
+ protected abstract EventAdmin getEventAdmin();
+
+}
diff --git a/src/main/java/org/apache/sling/nosql/generic/resource/impl/NoSqlResource.java b/src/main/java/org/apache/sling/nosql/generic/resource/impl/NoSqlResource.java
new file mode 100644
index 0000000..5431cc1
--- /dev/null
+++ b/src/main/java/org/apache/sling/nosql/generic/resource/impl/NoSqlResource.java
@@ -0,0 +1,90 @@
+/*
+ * 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.
+ */
+package org.apache.sling.nosql.generic.resource.impl;
+
+import java.util.Map;
+
+import org.apache.sling.api.SlingConstants;
+import org.apache.sling.api.resource.AbstractResource;
+import org.apache.sling.api.resource.ModifiableValueMap;
+import org.apache.sling.api.resource.ResourceMetadata;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ValueMap;
+import org.apache.sling.api.wrappers.DeepReadModifiableValueMapDecorator;
+import org.apache.sling.api.wrappers.DeepReadValueMapDecorator;
+import org.apache.sling.nosql.generic.adapter.NoSqlData;
+
+/**
+ * Generic implementation of a NoSQL database resource.
+ */
+class NoSqlResource extends AbstractResource {
+
+ private final NoSqlData data;
+ private final ResourceResolver resourceResolver;
+ private final NoSqlResourceProvider resourceProvider;
+ private final ResourceMetadata metadata;
+
+ public NoSqlResource(NoSqlData data, ResourceResolver resourceResolver, NoSqlResourceProvider resourceProvider) {
+ this.data = data;
+ this.resourceResolver = resourceResolver;
+ this.resourceProvider = resourceProvider;
+ this.metadata = new ResourceMetadata();
+ }
+
+ public ResourceResolver getResourceResolver() {
+ return resourceResolver;
+ }
+
+ public String getPath() {
+ return data.getPath();
+ }
+
+ public String getResourceType() {
+ return getValueMap().get(ResourceResolver.PROPERTY_RESOURCE_TYPE, "nt:unstructured");
+ }
+
+ public String getResourceSuperType() {
+ return getValueMap().get(SlingConstants.NAMESPACE_PREFIX + ":" + SlingConstants.PROPERTY_RESOURCE_SUPER_TYPE, String.class);
+ }
+
+ public ResourceMetadata getResourceMetadata() {
+ return metadata;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public <AdapterType> AdapterType adaptTo(Class<AdapterType> type) {
+ if (type == ValueMap.class || type == Map.class) {
+ return (AdapterType)new DeepReadValueMapDecorator(this, new NoSqlValueMap(data.getProperties(), this, resourceProvider));
+ }
+ if (type == ModifiableValueMap.class) {
+ return (AdapterType)new DeepReadModifiableValueMapDecorator(this, new NoSqlValueMap(data.getProperties(), this, resourceProvider));
+ }
+ return super.adaptTo(type);
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName()
+ + ", type=" + getResourceType()
+ + ", superType=" + getResourceSuperType()
+ + ", path=" + getPath();
+ }
+
+}
diff --git a/src/main/java/org/apache/sling/nosql/generic/resource/impl/NoSqlResourceProvider.java b/src/main/java/org/apache/sling/nosql/generic/resource/impl/NoSqlResourceProvider.java
new file mode 100644
index 0000000..384a8e3
--- /dev/null
+++ b/src/main/java/org/apache/sling/nosql/generic/resource/impl/NoSqlResourceProvider.java
@@ -0,0 +1,272 @@
+/*
+ * 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.
+ */
+package org.apache.sling.nosql.generic.resource.impl;
+
+import java.util.Dictionary;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.TreeMap;
+import java.util.regex.Pattern;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.sling.api.SlingConstants;
+import org.apache.sling.api.resource.ModifyingResourceProvider;
+import org.apache.sling.api.resource.PersistenceException;
+import org.apache.sling.api.resource.QueriableResourceProvider;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceProvider;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ValueMap;
+import org.apache.sling.nosql.generic.adapter.NoSqlAdapter;
+import org.apache.sling.nosql.generic.adapter.NoSqlData;
+import org.osgi.service.event.Event;
+import org.osgi.service.event.EventAdmin;
+
+/**
+ * Generic implementation of a NoSQL resource provider.
+ * The mapping to the NoSQL database implementation details is done via the provided {@link NoSqlAdapter}.
+ */
+public class NoSqlResourceProvider implements ResourceProvider, ModifyingResourceProvider, QueriableResourceProvider {
+
+ private final NoSqlAdapter adapter;
+ private final EventAdmin eventAdmin;
+ private final Map<String, NoSqlData> changedResources = new HashMap<String, NoSqlData>();
+ private final Set<String> deletedResources = new HashSet<String>();
+
+ public NoSqlResourceProvider(NoSqlAdapter adapter, EventAdmin eventAdmin) {
+ this.adapter = adapter;
+ this.eventAdmin = eventAdmin;
+ }
+
+
+ // ### READONLY ACCESS ###
+
+ public Resource getResource(ResourceResolver resourceResolver, String path) {
+ if (!adapter.validPath(path)) {
+ return null;
+ }
+ if (!this.deletedResources.isEmpty()) {
+ for (String deletedPath : deletedResources) {
+ Pattern deletedPathPattern = PathUtil.getSameOrDescendantPathPattern(deletedPath);
+ if (deletedPathPattern.matcher(path).matches()) {
+ return null;
+ }
+ }
+ }
+ if (this.changedResources.containsKey(path)) {
+ return new NoSqlResource(this.changedResources.get(path), resourceResolver, this);
+ }
+ NoSqlData data = adapter.get(path);
+ if (data != null) {
+ return new NoSqlResource(data, resourceResolver, this);
+ }
+ return null;
+ }
+
+ public Resource getResource(ResourceResolver resourceResolver, HttpServletRequest request, String path) {
+ return getResource(resourceResolver, path);
+ }
+
+ public Iterator<Resource> listChildren(Resource parent) {
+
+ // use map to consolidate data from adapter minus deleted plus changed resources
+ // always sorty result alphabetically to have a consistent ordering - the nosql data source does not support ordering
+ SortedMap<String, Resource> children = new TreeMap<String, Resource>();
+
+ Iterator<NoSqlData> fromAdapter = adapter.getChildren(parent.getPath());
+ while (fromAdapter.hasNext()) {
+ NoSqlData item = fromAdapter.next();
+ if (isDeleted(item.getPath()) || changedResources.containsKey(item.getPath())) {
+ continue;
+ }
+ children.put(item.getPath(), new NoSqlResource(item, parent.getResourceResolver(), this));
+ }
+
+ Pattern childPathPattern = PathUtil.getChildPathPattern(parent.getPath());
+ for (NoSqlData item : changedResources.values()) {
+ if (childPathPattern.matcher(item.getPath()).matches()) {
+ children.put(item.getPath(), new NoSqlResource(item, parent.getResourceResolver(), this));
+ }
+ }
+
+ return children.values().iterator();
+ }
+
+ private boolean isDeleted(String path) {
+ for (String deletedPath : deletedResources) {
+ return path.equals(deletedPath) || path.equals(deletedPath + "/");
+ }
+ return false;
+ }
+
+
+ // ### WRITE ACCESS ###
+
+ public Resource create(ResourceResolver resolver, String path, Map<String, Object> properties)
+ throws PersistenceException {
+ if (!adapter.validPath(path)) {
+ throw new PersistenceException("Illegal path - unable to create resource at " + path, null, path, null);
+ }
+
+ // check if already exists
+ boolean deleted = this.deletedResources.remove(path);
+ boolean exists = changedResources.containsKey(path) || this.adapter.get(path) != null;
+ if (!deleted && exists) {
+ throw new PersistenceException("Resource already exists at " + path, null, path, null);
+ }
+
+ // create new resource in changeset
+ NoSqlData data = new NoSqlData(path, NoSqlValueMap.convertForWriteAll(new HashMap<String, Object>(properties)));
+ changedResources.put(path, data);
+ return new NoSqlResource(data, resolver, this);
+ }
+
+ public void delete(ResourceResolver resolver, String path) throws PersistenceException {
+ if (!adapter.validPath(path)) {
+ throw new PersistenceException("Unable to delete resource at {}" + path, null, path, null);
+ }
+
+ Pattern pathsToDeletePattern = PathUtil.getSameOrDescendantPathPattern(path);
+
+ // remove all existing path and probably descendant paths from list of deleted paths
+ Iterator<String> deletedResourcesIterator = deletedResources.iterator();
+ while (deletedResourcesIterator.hasNext()) {
+ String deletedPath = deletedResourcesIterator.next();
+ if (pathsToDeletePattern.matcher(deletedPath).matches()) {
+ deletedResourcesIterator.remove();
+ }
+ }
+
+ // remove all changed descendant items from changeset
+ Iterator<Map.Entry<String, NoSqlData>> changeResourcesIterator = changedResources.entrySet().iterator();
+ while (changeResourcesIterator.hasNext()) {
+ Map.Entry<String, NoSqlData> entry = changeResourcesIterator.next();
+ if (pathsToDeletePattern.matcher(entry.getKey()).matches()) {
+ changeResourcesIterator.remove();
+ }
+ }
+
+ // add path to delete
+ deletedResources.add(path);
+ }
+
+ public void revert(ResourceResolver resolver) {
+ changedResources.clear();
+ deletedResources.clear();
+ }
+
+ public void commit(ResourceResolver resolver) throws PersistenceException {
+ try {
+ for (String path : deletedResources) {
+ adapter.deleteRecursive(path);
+ notifyRemoved(path);
+ }
+ for (NoSqlData item : changedResources.values()) {
+ boolean created = adapter.store(item);
+ if (created) {
+ notifyAdded(item.getPath());
+ }
+ else {
+ notifyUpdated(item.getPath());
+ }
+ }
+ }
+ finally {
+ this.revert(resolver);
+ }
+ }
+
+ public boolean hasChanges(ResourceResolver resolver) {
+ return !(changedResources.isEmpty() && deletedResources.isEmpty());
+ }
+
+ void markAsChanged(Resource resource) {
+ changedResources.put(resource.getPath(), new NoSqlData(resource.getPath(), resource.getValueMap()));
+ }
+
+ private void notifyAdded(String path) {
+ final Dictionary<String, Object> props = new Hashtable<String, Object>();
+ props.put(SlingConstants.PROPERTY_PATH, path);
+ props.put("event.distribute", "");
+ final Event event = new Event(SlingConstants.TOPIC_RESOURCE_ADDED, props);
+ this.eventAdmin.postEvent(event);
+ }
+
+ private void notifyUpdated(String path) {
+ final Dictionary<String, Object> props = new Hashtable<String, Object>();
+ props.put(SlingConstants.PROPERTY_PATH, path);
+ props.put("event.distribute", "");
+ final Event event = new Event(SlingConstants.TOPIC_RESOURCE_CHANGED, props);
+ this.eventAdmin.postEvent(event);
+ }
+
+ private void notifyRemoved(String path) {
+ final Dictionary<String, Object> props = new Hashtable<String, Object>();
+ props.put(SlingConstants.PROPERTY_PATH, path);
+ props.put("event.distribute", "");
+ final Event event = new Event(SlingConstants.TOPIC_RESOURCE_REMOVED, props);
+ this.eventAdmin.postEvent(event);
+ }
+
+
+ // ### QUERY ACCESS ###
+
+ public Iterator<Resource> findResources(final ResourceResolver resolver, final String query, final String language) {
+ final Iterator<NoSqlData> result = adapter.query(query, language);
+ if (result == null) {
+ return null;
+ }
+ return new Iterator<Resource>() {
+ public boolean hasNext() {
+ return result.hasNext();
+ }
+ public Resource next() {
+ return new NoSqlResource(result.next(), resolver, NoSqlResourceProvider.this);
+ }
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ };
+ }
+
+ public Iterator<ValueMap> queryResources(final ResourceResolver resolver, final String query, final String language) {
+ final Iterator<Resource> result = findResources(resolver, query, language);
+ if (result == null) {
+ return null;
+ }
+ return new Iterator<ValueMap>() {
+ public boolean hasNext() {
+ return result.hasNext();
+ }
+ public ValueMap next() {
+ return result.next().getValueMap();
+ }
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ };
+ }
+
+}
diff --git a/src/main/java/org/apache/sling/nosql/generic/resource/impl/NoSqlValueMap.java b/src/main/java/org/apache/sling/nosql/generic/resource/impl/NoSqlValueMap.java
new file mode 100644
index 0000000..42e07f9
--- /dev/null
+++ b/src/main/java/org/apache/sling/nosql/generic/resource/impl/NoSqlValueMap.java
@@ -0,0 +1,178 @@
+/*
+ * 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.
+ */
+package org.apache.sling.nosql.generic.resource.impl;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.Locale;
+import java.util.Map;
+
+import javax.xml.bind.DatatypeConverter;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.sling.api.resource.ModifiableValueMap;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.wrappers.ValueMapDecorator;
+
+/**
+ * Enhances ValueMap that adds special support for deep path access.
+ * Additionally date and binary types are converted to string and back when reading.
+ * Besides this only primitive types String, Integer, Long, Double, Boolean and arrays of them are supported.
+ */
+class NoSqlValueMap extends ValueMapDecorator implements ModifiableValueMap {
+
+ private final Resource resource;
+ private final NoSqlResourceProvider resourceProvider;
+
+ public NoSqlValueMap(Map<String,Object> map, Resource resource, NoSqlResourceProvider resourceProvider) {
+ super(convertForWriteAll(map));
+ this.resource = resource;
+ this.resourceProvider = resourceProvider;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public <T> T get(String name, Class<T> type) {
+
+ if (type == Calendar.class) {
+ Date date = get(name, Date.class);
+ if (date != null) {
+ Calendar calendar = Calendar.getInstance();
+ calendar.setTime(date);
+ return (T)calendar;
+ }
+ else {
+ return null;
+ }
+ }
+ else if (type == Date.class) {
+ Object value = get(name);
+ if (value instanceof String) {
+ try {
+ return (T)getISO8601Format().parse((String)value);
+ } catch (ParseException e) {
+ return null;
+ }
+ }
+ }
+ else if (type == InputStream.class) {
+ // Support conversion from byte array to InputStream
+ byte[] data = get(name, byte[].class);
+ if (data != null) {
+ return (T)new ByteArrayInputStream(data);
+ }
+ else {
+ return null;
+ }
+ }
+ else if (type == byte[].class) {
+ // Support conversion from base64 string to byte array
+ Object value = get(name);
+ if (value instanceof String) {
+ return (T)DatatypeConverter.parseBase64Binary((String)value);
+ }
+ }
+ else if ( type == null ) {
+ return (T) super.get(name);
+ }
+ return super.get(name, type);
+ }
+
+ @Override
+ public Object put(String key, Object value) {
+ Object result = super.put(key, convertForWrite(value));
+ resourceProvider.markAsChanged(resource);
+ return result;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public void putAll(Map<? extends String, ?> map) {
+ super.putAll((Map<? extends String, ?>)convertForWriteAll((Map<String, Object>)map));
+ resourceProvider.markAsChanged(resource);
+ }
+
+ @Override
+ public Object remove(Object key) {
+ Object result = super.remove(key);
+ resourceProvider.markAsChanged(resource);
+ return result;
+ }
+
+ @Override
+ public void clear() {
+ super.clear();
+ resourceProvider.markAsChanged(resource);
+ }
+
+ private static Object convertForWrite(Object value) {
+ if (value instanceof Calendar) {
+ value = getISO8601Format().format(((Calendar)value).getTime());
+ }
+ if (value instanceof Date) {
+ value = getISO8601Format().format((Date)value);
+ }
+ else if (value instanceof InputStream) {
+ // Store InputStream values as byte array
+ try {
+ value = convertForWrite(IOUtils.toByteArray((InputStream)value));
+ } catch (IOException ex) {
+ throw new RuntimeException("Unable to convert input stream to byte array.");
+ }
+ }
+ else if (value instanceof byte[]) {
+ value = DatatypeConverter.printBase64Binary((byte[])value);
+ }
+ else if (value != null && !isValidPrimitveType(value.getClass())) {
+ throw new IllegalArgumentException("Data type not supported for NoSqlValueMap: " + value.getClass());
+ }
+ return value;
+ }
+
+ static boolean isValidPrimitveType(Class clazz) {
+ if (clazz.isArray()) {
+ return isValidPrimitveType(clazz.getComponentType());
+ }
+ else {
+ return clazz == String.class
+ || clazz == Integer.class
+ || clazz == Long.class
+ || clazz == Double.class
+ || clazz == Boolean.class;
+ }
+ }
+
+ public static Map<String, Object> convertForWriteAll(Map<String, Object> map) {
+ for (Map.Entry<String, Object> entry : map.entrySet()) {
+ map.put(entry.getKey(), convertForWrite(entry.getValue()));
+ }
+ return map;
+ }
+
+ private static DateFormat getISO8601Format() {
+ return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ", Locale.US);
+ }
+
+}
diff --git a/src/main/java/org/apache/sling/nosql/generic/resource/impl/PathUtil.java b/src/main/java/org/apache/sling/nosql/generic/resource/impl/PathUtil.java
new file mode 100644
index 0000000..a4a59b0
--- /dev/null
+++ b/src/main/java/org/apache/sling/nosql/generic/resource/impl/PathUtil.java
@@ -0,0 +1,50 @@
+/*
+ * 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.
+ */
+package org.apache.sling.nosql.generic.resource.impl;
+
+import java.util.regex.Pattern;
+
+/**
+ * Helper functions for handling paths.
+ */
+public final class PathUtil {
+
+ private PathUtil() {
+ // static methods only
+ }
+
+ /**
+ * Generated a regex pattern that accepts all paths that are direct children of the given parent path.
+ * @param parentPath Parent path
+ * @return Regex pattern
+ */
+ public static Pattern getChildPathPattern(String parentPath) {
+ return Pattern.compile("^" + Pattern.quote(parentPath) + "/[^/]+$");
+ }
+
+ /**
+ * Generated a regex pattern that accepts all paths that are same or descendants of the given parent path.
+ * @param path Path
+ * @return Regex pattern
+ */
+ public static Pattern getSameOrDescendantPathPattern(String path) {
+ return Pattern.compile("^" + Pattern.quote(path) + "(/.*)?$");
+ }
+
+}
diff --git a/src/main/java/org/apache/sling/nosql/generic/resource/package-info.java b/src/main/java/org/apache/sling/nosql/generic/resource/package-info.java
new file mode 100644
index 0000000..a804837
--- /dev/null
+++ b/src/main/java/org/apache/sling/nosql/generic/resource/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+/**
+ * Generic NoSQL resource provider implementation.
+ */
+@aQute.bnd.annotation.Version("0.5")
+package org.apache.sling.nosql.generic.resource;
diff --git a/src/test/java/org/apache/sling/nosql/generic/resource/impl/AbstractNoSqlResourceProviderTest.java b/src/test/java/org/apache/sling/nosql/generic/resource/impl/AbstractNoSqlResourceProviderTest.java
new file mode 100644
index 0000000..eda8f8c
--- /dev/null
+++ b/src/test/java/org/apache/sling/nosql/generic/resource/impl/AbstractNoSqlResourceProviderTest.java
@@ -0,0 +1,211 @@
+/*
+ * 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.
+ */
+package org.apache.sling.nosql.generic.resource.impl;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.sling.api.resource.PersistenceException;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceUtil;
+import org.apache.sling.api.resource.ValueMap;
+import org.apache.sling.testing.mock.sling.ResourceResolverType;
+import org.apache.sling.testing.mock.sling.junit.SlingContext;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+/**
+ * Test basic ResourceResolver and ValueMap with different data types.
+ */
+public abstract class AbstractNoSqlResourceProviderTest {
+
+ private static final String STRING_VALUE = "value1";
+ private static final String[] STRING_ARRAY_VALUE = new String[] { "value1", "value2" };
+ private static final int INTEGER_VALUE = 25;
+ private static final double DOUBLE_VALUE = 3.555d;
+ private static final boolean BOOLEAN_VALUE = true;
+ private static final Date DATE_VALUE = new Date(10000);
+ private static final Calendar CALENDAR_VALUE = Calendar.getInstance();
+ private static final byte[] BINARY_VALUE = new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 };
+
+ @Rule
+ public SlingContext context = new SlingContext(ResourceResolverType.JCR_MOCK);
+
+ protected abstract void registerResourceProviderFactory();
+
+ protected abstract Resource testRoot();
+
+ @Before
+ public void setUp() throws Exception {
+ registerResourceProviderFactory();
+
+ // prepare some test data using Sling CRUD API
+ Resource rootNode = testRoot();
+
+ Map<String, Object> props = new HashMap<String, Object>();
+ props.put(JcrConstants.JCR_PRIMARYTYPE, JcrConstants.NT_UNSTRUCTURED);
+ props.put("stringProp", STRING_VALUE);
+ props.put("stringArrayProp", STRING_ARRAY_VALUE);
+ props.put("integerProp", INTEGER_VALUE);
+ props.put("doubleProp", DOUBLE_VALUE);
+ props.put("booleanProp", BOOLEAN_VALUE);
+ props.put("dateProp", DATE_VALUE);
+ props.put("calendarProp", CALENDAR_VALUE);
+ props.put("binaryProp", new ByteArrayInputStream(BINARY_VALUE));
+ Resource node1 = context.resourceResolver().create(rootNode, "node1", props);
+
+ context.resourceResolver().create(node1, "node11", ImmutableMap.<String, Object>builder()
+ .put("stringProp11", STRING_VALUE)
+ .build());
+ context.resourceResolver().create(node1, "node12", ValueMap.EMPTY);
+
+ context.resourceResolver().commit();
+ }
+
+ @After
+ public void tearDown() {
+ context.resourceResolver().revert();
+ try {
+ context.resourceResolver().delete(testRoot());
+ context.resourceResolver().commit();
+ }
+ catch (PersistenceException ex) {
+ // ignore
+ }
+ }
+
+ @Test
+ public void testSimpleProperties() throws IOException {
+ Resource resource1 = context.resourceResolver().getResource(testRoot().getPath() + "/node1");
+ assertTrue(resource1 instanceof NoSqlResource);
+
+ assertNotNull(resource1);
+ assertEquals("node1", resource1.getName());
+
+ ValueMap props = ResourceUtil.getValueMap(resource1);
+ assertEquals(STRING_VALUE, props.get("stringProp", String.class));
+ assertArrayEquals(STRING_ARRAY_VALUE, props.get("stringArrayProp", String[].class));
+ assertEquals((Integer) INTEGER_VALUE, props.get("integerProp", Integer.class));
+ assertEquals(DOUBLE_VALUE, props.get("doubleProp", Double.class), 0.0001);
+ assertEquals(BOOLEAN_VALUE, props.get("booleanProp", Boolean.class));
+ }
+
+ @Test
+ public void testSimpleProperties_DeepPathAccess() throws IOException {
+ Resource resource1 = context.resourceResolver().getResource(testRoot().getPath());
+ assertNotNull(resource1);
+ assertEquals(testRoot().getName(), resource1.getName());
+
+ ValueMap props = ResourceUtil.getValueMap(resource1);
+ assertEquals(STRING_VALUE, props.get("node1/stringProp", String.class));
+ assertArrayEquals(STRING_ARRAY_VALUE, props.get("node1/stringArrayProp", String[].class));
+ assertEquals((Integer) INTEGER_VALUE, props.get("node1/integerProp", Integer.class));
+ assertEquals(DOUBLE_VALUE, props.get("node1/doubleProp", Double.class), 0.0001);
+ assertEquals(BOOLEAN_VALUE, props.get("node1/booleanProp", Boolean.class));
+ assertEquals(STRING_VALUE, props.get("node1/node11/stringProp11", String.class));
+ }
+
+ @Test
+ public void testDateProperty() throws IOException {
+ Resource resource1 = context.resourceResolver().getResource(testRoot().getPath() + "/node1");
+ ValueMap props = ResourceUtil.getValueMap(resource1);
+ assertEquals(DATE_VALUE, props.get("dateProp", Date.class));
+ }
+
+ @Test
+ public void testDatePropertyToCalendar() throws IOException {
+ Resource resource1 = context.resourceResolver().getResource(testRoot().getPath() + "/node1");
+ ValueMap props = ResourceUtil.getValueMap(resource1);
+ Calendar calendarValue = props.get("dateProp", Calendar.class);
+ assertNotNull(calendarValue);
+ assertEquals(DATE_VALUE, calendarValue.getTime());
+ }
+
+ @Test
+ public void testCalendarProperty() throws IOException {
+ Resource resource1 = context.resourceResolver().getResource(testRoot().getPath() + "/node1");
+ ValueMap props = ResourceUtil.getValueMap(resource1);
+ assertEquals(CALENDAR_VALUE.getTime(), props.get("calendarProp", Calendar.class).getTime());
+ }
+
+ @Test
+ public void testCalendarPropertyToDate() throws IOException {
+ Resource resource1 = context.resourceResolver().getResource(testRoot().getPath() + "/node1");
+ ValueMap props = ResourceUtil.getValueMap(resource1);
+ Date dateValue = props.get("calendarProp", Date.class);
+ assertNotNull(dateValue);
+ assertEquals(CALENDAR_VALUE.getTime(), dateValue);
+ }
+
+ @Test
+ public void testListChildren() throws IOException {
+ Resource resource1 = context.resourceResolver().getResource(testRoot().getPath() + "/node1");
+
+ List<Resource> children = ImmutableList.copyOf(resource1.listChildren());
+ assertEquals(2, children.size());
+ assertEquals("node11", children.get(0).getName());
+ assertEquals("node12", children.get(1).getName());
+ }
+
+ @Test
+ public void testBinaryData() throws IOException {
+ Resource resource1 = context.resourceResolver().getResource(testRoot().getPath() + "/node1");
+
+ InputStream is = ResourceUtil.getValueMap(resource1).get("binaryProp", InputStream.class);
+ byte[] dataFromResource = IOUtils.toByteArray(is);
+ is.close();
+ assertArrayEquals(BINARY_VALUE, dataFromResource);
+ }
+
+ @Test
+ public void testValueMapTypes() throws IOException {
+ Resource resource1 = context.resourceResolver().getResource(testRoot().getPath() + "/node1");
+
+ // ensure that value map has only supported primitive types (all other supported types converted to string)
+ ValueMap valueMap = resource1.getValueMap();
+ for (Map.Entry<String, Object> entry : valueMap.entrySet()) {
+ assertTrue(NoSqlValueMap.isValidPrimitveType(entry.getValue().getClass()));
+ }
+ }
+
+ @Test
+ public void testPrimaryTypeResourceType() throws PersistenceException {
+ Resource resource = context.resourceResolver().getResource(testRoot().getPath());
+ assertEquals(JcrConstants.NT_UNSTRUCTURED, resource.getResourceType());
+ }
+
+}
diff --git a/src/test/java/org/apache/sling/nosql/generic/resource/impl/AbstractNoSqlResourceProviderTransactionalTest.java b/src/test/java/org/apache/sling/nosql/generic/resource/impl/AbstractNoSqlResourceProviderTransactionalTest.java
new file mode 100644
index 0000000..3320806
--- /dev/null
+++ b/src/test/java/org/apache/sling/nosql/generic/resource/impl/AbstractNoSqlResourceProviderTransactionalTest.java
@@ -0,0 +1,227 @@
+/*
+ * 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.
+ */
+package org.apache.sling.nosql.generic.resource.impl;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Iterator;
+
+import org.apache.sling.api.resource.ModifiableValueMap;
+import org.apache.sling.api.resource.PersistenceException;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.testing.mock.sling.ResourceResolverType;
+import org.apache.sling.testing.mock.sling.junit.SlingContext;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import com.google.common.collect.ImmutableMap;
+
+/**
+ * Test basic ResourceResolver and ValueMap with different data types.
+ */
+public abstract class AbstractNoSqlResourceProviderTransactionalTest {
+
+ @Rule
+ public SlingContext context = new SlingContext(ResourceResolverType.JCR_MOCK);
+
+ protected abstract void registerResourceProviderFactory();
+
+ protected abstract Resource testRoot();
+
+ @Before
+ public void setUp() throws Exception {
+ registerResourceProviderFactory();
+ }
+
+ @After
+ public void tearDown() {
+ context.resourceResolver().revert();
+ try {
+ context.resourceResolver().delete(testRoot());
+ context.resourceResolver().commit();
+ }
+ catch (PersistenceException ex) {
+ // ignore
+ }
+ }
+
+ @Test
+ public void testRootNode() {
+ assertTrue(testRoot() instanceof NoSqlResource);
+ }
+
+ @Test
+ public void testAddDeleteNodesPartialCommit() throws PersistenceException {
+ context.resourceResolver().create(testRoot(), "node0", ImmutableMap.<String, Object>of());
+ context.resourceResolver().create(testRoot(), "node1", ImmutableMap.<String, Object>of());
+ context.resourceResolver().commit();
+
+ assertFalse(context.resourceResolver().hasChanges());
+
+ context.resourceResolver().create(testRoot(), "node2", ImmutableMap.<String, Object>of());
+ context.resourceResolver().create(testRoot(), "node3", ImmutableMap.<String, Object>of());
+
+ assertTrue(context.resourceResolver().hasChanges());
+
+ assertNotNull(testRoot().getChild("node0"));
+ assertNotNull(testRoot().getChild("node1"));
+ assertNotNull(testRoot().getChild("node2"));
+ assertNotNull(testRoot().getChild("node3"));
+
+ context.resourceResolver().delete(testRoot().getChild("node0"));
+ context.resourceResolver().delete(testRoot().getChild("node2"));
+
+ assertNull(testRoot().getChild("node0"));
+ assertNotNull(testRoot().getChild("node1"));
+ assertNull(testRoot().getChild("node2"));
+ assertNotNull(testRoot().getChild("node3"));
+
+ Iterator<Resource> children = testRoot().listChildren();
+ assertEquals("node1", children.next().getName());
+ assertEquals("node3", children.next().getName());
+ assertFalse(children.hasNext());
+
+ assertTrue(context.resourceResolver().hasChanges());
+
+ context.resourceResolver().revert();
+
+ assertFalse(context.resourceResolver().hasChanges());
+
+ assertNotNull(testRoot().getChild("node1"));
+ assertNull(testRoot().getChild("node2"));
+ assertNull(testRoot().getChild("node3"));
+
+ children = testRoot().listChildren();
+ assertEquals("node0", children.next().getName());
+ assertEquals("node1", children.next().getName());
+ assertFalse(children.hasNext());
+ }
+
+ @Test
+ public void testRecursiveDeleteWithoutCommit() throws PersistenceException {
+ Resource node1 = context.resourceResolver().create(testRoot(), "node1", ImmutableMap.<String, Object>of());
+ Resource node11 = context.resourceResolver().create(node1, "node11", ImmutableMap.<String, Object>of());
+ context.resourceResolver().create(node11, "node111", ImmutableMap.<String, Object>of());
+
+ assertNotNull(testRoot().getChild("node1"));
+ assertNotNull(testRoot().getChild("node1/node11"));
+ assertNotNull(testRoot().getChild("node1/node11/node111"));
+
+ context.resourceResolver().delete(node1);
+
+ assertNull(testRoot().getChild("node1"));
+ assertNull(testRoot().getChild("node1/node11"));
+ assertNull(testRoot().getChild("node1/node11/node111"));
+ }
+
+ @Test
+ public void testRecursiveDeleteWithCommit() throws PersistenceException {
+ Resource node1 = context.resourceResolver().create(testRoot(), "node1", ImmutableMap.<String, Object>of());
+ Resource node11 = context.resourceResolver().create(node1, "node11", ImmutableMap.<String, Object>of());
+ context.resourceResolver().create(node11, "node111", ImmutableMap.<String, Object>of());
+
+ assertTrue(context.resourceResolver().hasChanges());
+
+ context.resourceResolver().commit();
+
+ assertFalse(context.resourceResolver().hasChanges());
+
+ assertNotNull(testRoot().getChild("node1"));
+ assertNotNull(testRoot().getChild("node1/node11"));
+ assertNotNull(testRoot().getChild("node1/node11/node111"));
+
+ context.resourceResolver().delete(node1);
+
+ assertNull(testRoot().getChild("node1"));
+ assertNull(testRoot().getChild("node1/node11"));
+ assertNull(testRoot().getChild("node1/node11/node111"));
+
+ assertTrue(context.resourceResolver().hasChanges());
+
+ context.resourceResolver().commit();
+
+ assertFalse(context.resourceResolver().hasChanges());
+
+ assertNull(testRoot().getChild("node1"));
+ assertNull(testRoot().getChild("node1/node11"));
+ assertNull(testRoot().getChild("node1/node11/node111"));
+ }
+
+ @Test(expected = PersistenceException.class)
+ public void testCreateAlreadyExistWithoutCommit() throws PersistenceException {
+ context.resourceResolver().create(testRoot(), "node1", ImmutableMap.<String, Object>of());
+ context.resourceResolver().create(testRoot(), "node1", ImmutableMap.<String, Object>of());
+ }
+
+ @Test(expected = PersistenceException.class)
+ public void testCreateAlreadyExistWithCommit() throws PersistenceException {
+ context.resourceResolver().create(testRoot(), "node1", ImmutableMap.<String, Object>of());
+ context.resourceResolver().commit();
+ context.resourceResolver().create(testRoot(), "node1", ImmutableMap.<String, Object>of());
+ }
+
+ @Test
+ public void testCreateAlreadyExistDeletedWithoutCommit() throws PersistenceException {
+ context.resourceResolver().create(testRoot(), "node1", ImmutableMap.<String, Object>of());
+ context.resourceResolver().delete(testRoot().getChild("node1"));
+ context.resourceResolver().create(testRoot(), "node1", ImmutableMap.<String, Object>of());
+ }
+
+ @Test
+ public void testCreateAlreadyExistDeletedWithCommit() throws PersistenceException {
+ context.resourceResolver().create(testRoot(), "node1", ImmutableMap.<String, Object>of());
+ context.resourceResolver().commit();
+ context.resourceResolver().delete(testRoot().getChild("node1"));
+ context.resourceResolver().commit();
+ context.resourceResolver().create(testRoot(), "node1", ImmutableMap.<String, Object>of());
+ }
+
+ @Test
+ public void testUpdateWithoutCommit() throws PersistenceException {
+ Resource node1 = context.resourceResolver().create(testRoot(), "node1", ImmutableMap.<String, Object>of("prop1", "value1"));
+ assertEquals("value1", node1.getValueMap().get("prop1", String.class));
+
+ ModifiableValueMap props = node1.adaptTo(ModifiableValueMap.class);
+ props.put("prop1", "value2");
+
+ node1 = testRoot().getChild("node1");
+ assertEquals("value2", node1.getValueMap().get("prop1", String.class));
+ }
+
+ @Test
+ public void testUpdateWithCommit() throws PersistenceException {
+ Resource node1 = context.resourceResolver().create(testRoot(), "node1", ImmutableMap.<String, Object>of("prop1", "value1"));
+ assertEquals("value1", node1.getValueMap().get("prop1", String.class));
+ context.resourceResolver().commit();
+
+ ModifiableValueMap props = node1.adaptTo(ModifiableValueMap.class);
+ props.put("prop1", "value2");
+ context.resourceResolver().commit();
+
+ node1 = testRoot().getChild("node1");
+ assertEquals("value2", node1.getValueMap().get("prop1", String.class));
+ }
+
+}
diff --git a/src/test/java/org/apache/sling/nosql/generic/resource/impl/PathUtilTest.java b/src/test/java/org/apache/sling/nosql/generic/resource/impl/PathUtilTest.java
new file mode 100644
index 0000000..67efd33
--- /dev/null
+++ b/src/test/java/org/apache/sling/nosql/generic/resource/impl/PathUtilTest.java
@@ -0,0 +1,56 @@
+/*
+ * 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.
+ */
+package org.apache.sling.nosql.generic.resource.impl;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.regex.Pattern;
+
+import org.junit.Test;
+
+public class PathUtilTest {
+
+ @Test
+ public void testGetChildPathPattern() {
+ Pattern pattern = PathUtil.getChildPathPattern("/my/path");
+
+ assertFalse(pattern.matcher("/my/path").matches());
+ assertTrue(pattern.matcher("/my/path/child1").matches());
+ assertTrue(pattern.matcher("/my/path/child2").matches());
+ assertFalse(pattern.matcher("/my/path/child1/subchild1").matches());
+ assertFalse(pattern.matcher("/my/path/child1/subchild1/subchild2").matches());
+ assertFalse(pattern.matcher("/my/sibling").matches());
+ assertFalse(pattern.matcher("/other").matches());
+ }
+
+ @Test
+ public void testGetDescendantPathPattern() {
+ Pattern pattern = PathUtil.getSameOrDescendantPathPattern("/my/path");
+
+ assertTrue(pattern.matcher("/my/path").matches());
+ assertTrue(pattern.matcher("/my/path/child1").matches());
+ assertTrue(pattern.matcher("/my/path/child2").matches());
+ assertTrue(pattern.matcher("/my/path/child1/subchild1").matches());
+ assertTrue(pattern.matcher("/my/path/child1/subchild1/subchild2").matches());
+ assertFalse(pattern.matcher("/my/sibling").matches());
+ assertFalse(pattern.matcher("/other").matches());
+ }
+
+}
diff --git a/src/test/java/org/apache/sling/nosql/generic/simple/SimpleNoSqlResourceProviderQueryTest.java b/src/test/java/org/apache/sling/nosql/generic/simple/SimpleNoSqlResourceProviderQueryTest.java
new file mode 100644
index 0000000..6c79168
--- /dev/null
+++ b/src/test/java/org/apache/sling/nosql/generic/simple/SimpleNoSqlResourceProviderQueryTest.java
@@ -0,0 +1,102 @@
+/*
+ * 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.
+ */
+package org.apache.sling.nosql.generic.simple;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceProvider;
+import org.apache.sling.nosql.generic.simple.provider.SimpleNoSqlResourceProviderFactory;
+import org.apache.sling.testing.mock.sling.ResourceResolverType;
+import org.apache.sling.testing.mock.sling.junit.SlingContext;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import com.google.common.collect.ImmutableMap;
+
+/**
+ * Test basic ResourceResolver and ValueMap with different data types.
+ */
+public class SimpleNoSqlResourceProviderQueryTest {
+
+ @Rule
+ public SlingContext context = new SlingContext(ResourceResolverType.JCR_MOCK);
+
+ private Resource testRoot;
+
+ @Before
+ public void setUp() throws Exception {
+ context.registerInjectActivateService(new SimpleNoSqlResourceProviderFactory(), ImmutableMap.<String, Object>builder()
+ .put(ResourceProvider.ROOTS, "/nosql-simple")
+ .build());
+
+ // prepare some test data using Sling CRUD API
+ Map<String, Object> props = new HashMap<String, Object>();
+ props.put(JcrConstants.JCR_PRIMARYTYPE, JcrConstants.NT_UNSTRUCTURED);
+ final Resource root = context.resourceResolver().getResource("/");
+ Resource noSqlRoot = context.resourceResolver().create(root, "nosql-simple", props);
+ this.testRoot = context.resourceResolver().create(noSqlRoot, "test", props);
+
+ context.resourceResolver().create(testRoot, "node1", ImmutableMap.<String, Object>of("prop1", "value1"));
+ context.resourceResolver().create(testRoot, "node2", ImmutableMap.<String, Object>of("prop1", "value2"));
+
+ context.resourceResolver().commit();
+ }
+
+ @Test
+ public void testFindResources_ValidQuery() {
+ Iterator<Resource> result = context.resourceResolver().findResources("all", "simple");
+ assertEquals("/nosql-simple", result.next().getPath());
+ assertEquals("/nosql-simple/test", result.next().getPath());
+ assertEquals("/nosql-simple/test/node1", result.next().getPath());
+ assertEquals("/nosql-simple/test/node2", result.next().getPath());
+ assertFalse(result.hasNext());
+ }
+
+ @Test
+ public void testFindResources_InvalidQuery() {
+ Iterator<Resource> result = context.resourceResolver().findResources("all", "invalid");
+ assertFalse(result.hasNext());
+ }
+
+ @Test
+ public void testQueryResources_ValidQuery() {
+ Iterator<Map<String, Object>> result = context.resourceResolver().queryResources("all", "simple");
+ assertNull(result.next().get("prop1"));
+ assertNull(result.next().get("prop1"));
+ assertEquals("value1", result.next().get("prop1"));
+ assertEquals("value2", result.next().get("prop1"));
+ assertFalse(result.hasNext());
+ }
+
+ @Test
+ public void testQueryResources_InvalidQuery() {
+ Iterator<Map<String, Object>> result = context.resourceResolver().queryResources("all", "invalid");
+ assertFalse(result.hasNext());
+ }
+
+}
diff --git a/src/test/java/org/apache/sling/nosql/generic/simple/SimpleNoSqlResourceProviderTest.java b/src/test/java/org/apache/sling/nosql/generic/simple/SimpleNoSqlResourceProviderTest.java
new file mode 100644
index 0000000..aa37032
--- /dev/null
+++ b/src/test/java/org/apache/sling/nosql/generic/simple/SimpleNoSqlResourceProviderTest.java
@@ -0,0 +1,64 @@
+/*
+ * 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.
+ */
+package org.apache.sling.nosql.generic.simple;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.sling.api.resource.PersistenceException;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceProvider;
+import org.apache.sling.nosql.generic.resource.impl.AbstractNoSqlResourceProviderTest;
+import org.apache.sling.nosql.generic.simple.provider.SimpleNoSqlResourceProviderFactory;
+
+import com.google.common.collect.ImmutableMap;
+
+/**
+ * Test basic ResourceResolver and ValueMap with different data types.
+ */
+public class SimpleNoSqlResourceProviderTest extends AbstractNoSqlResourceProviderTest {
+
+ private Resource testRoot;
+
+ @Override
+ protected void registerResourceProviderFactory() {
+ context.registerInjectActivateService(new SimpleNoSqlResourceProviderFactory(), ImmutableMap.<String, Object>builder()
+ .put(ResourceProvider.ROOTS, "/nosql-simple")
+ .build());
+ }
+
+ @Override
+ protected Resource testRoot() {
+ if (this.testRoot == null) {
+ try {
+ Map<String, Object> props = new HashMap<String, Object>();
+ props.put(JcrConstants.JCR_PRIMARYTYPE, JcrConstants.NT_UNSTRUCTURED);
+ final Resource root = context.resourceResolver().getResource("/");
+ Resource noSqlRoot = context.resourceResolver().create(root, "nosql-simple", props);
+ this.testRoot = context.resourceResolver().create(noSqlRoot, "test", props);
+ }
+ catch (PersistenceException ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+ return this.testRoot;
+ }
+
+}
diff --git a/src/test/java/org/apache/sling/nosql/generic/simple/SimpleNoSqlResourceProviderTransactionalTest.java b/src/test/java/org/apache/sling/nosql/generic/simple/SimpleNoSqlResourceProviderTransactionalTest.java
new file mode 100644
index 0000000..6c8fe12
--- /dev/null
+++ b/src/test/java/org/apache/sling/nosql/generic/simple/SimpleNoSqlResourceProviderTransactionalTest.java
@@ -0,0 +1,73 @@
+/*
+ * 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.
+ */
+package org.apache.sling.nosql.generic.simple;
+
+import static org.junit.Assert.assertNull;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.sling.api.resource.PersistenceException;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceProvider;
+import org.apache.sling.nosql.generic.resource.impl.AbstractNoSqlResourceProviderTransactionalTest;
+import org.apache.sling.nosql.generic.simple.provider.SimpleNoSqlResourceProviderFactory;
+import org.junit.Test;
+
+import com.google.common.collect.ImmutableMap;
+
+/**
+ * Test basic ResourceResolver and ValueMap with different data types.
+ */
+public class SimpleNoSqlResourceProviderTransactionalTest extends AbstractNoSqlResourceProviderTransactionalTest {
+
+ private Resource testRoot;
+
+ @Override
+ protected void registerResourceProviderFactory() {
+ context.registerInjectActivateService(new SimpleNoSqlResourceProviderFactory(), ImmutableMap.<String, Object>builder()
+ .put(ResourceProvider.ROOTS, "/nosql-simple")
+ .build());
+ }
+
+ @Override
+ protected Resource testRoot() {
+ if (this.testRoot == null) {
+ try {
+ Map<String, Object> props = new HashMap<String, Object>();
+ props.put(JcrConstants.JCR_PRIMARYTYPE, JcrConstants.NT_UNSTRUCTURED);
+ final Resource root = context.resourceResolver().getResource("/");
+ Resource noSqlRoot = context.resourceResolver().create(root, "nosql-simple", props);
+ this.testRoot = context.resourceResolver().create(noSqlRoot, "test", props);
+ context.resourceResolver().commit();
+ }
+ catch (PersistenceException ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+ return this.testRoot;
+ }
+
+ @Test
+ public void testGetInvalidPath() {
+ assertNull(context.resourceResolver().getResource(testRoot().getPath() + "/invalid/1"));
+ }
+
+}
diff --git a/src/test/java/org/apache/sling/nosql/generic/simple/provider/SimpleNoSqlAdapter.java b/src/test/java/org/apache/sling/nosql/generic/simple/provider/SimpleNoSqlAdapter.java
new file mode 100644
index 0000000..f1743ff
--- /dev/null
+++ b/src/test/java/org/apache/sling/nosql/generic/simple/provider/SimpleNoSqlAdapter.java
@@ -0,0 +1,117 @@
+/*
+ * 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.
+ */
+package org.apache.sling.nosql.generic.simple.provider;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.SortedMap;
+import java.util.TreeMap;
+import java.util.regex.Pattern;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.sling.nosql.generic.adapter.NoSqlAdapter;
+import org.apache.sling.nosql.generic.adapter.NoSqlData;
+import org.apache.sling.nosql.generic.resource.impl.PathUtil;
+
+import com.google.common.base.Function;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Iterators;
+
+/**
+ * Stores resource data in a hash map for testing.
+ */
+public class SimpleNoSqlAdapter implements NoSqlAdapter {
+
+ private final SortedMap<String, Map<String,Object>> store = new TreeMap<String, Map<String,Object>>();
+
+ public boolean validPath(String path) {
+ return !(StringUtils.contains(path, "/invalid/") || StringUtils.endsWith(path, "/invalid"));
+ }
+
+ public NoSqlData get(String path) {
+ Map<String,Object> properties = store.get(path);
+ if (properties != null) {
+ return new NoSqlData(path, properties);
+ }
+ else {
+ return null;
+ }
+ }
+
+ public Iterator<NoSqlData> getChildren(String parentPath) {
+ Iterator<String> keys = store.keySet().iterator();
+
+ final Pattern childKeyPattern = PathUtil.getChildPathPattern(parentPath);
+ Iterator<String> childKeys = Iterators.filter(keys, new Predicate<String>() {
+ public boolean apply(String path) {
+ return childKeyPattern.matcher(path).matches();
+ }
+ });
+
+ return Iterators.transform(childKeys, new Function<String, NoSqlData>() {
+ public NoSqlData apply(String path) {
+ return get(path);
+ }
+ });
+ }
+
+ public boolean store(NoSqlData data) {
+ boolean exists = store.containsKey(data.getPath());
+ store.put(data.getPath(), new HashMap<String, Object>(data.getProperties()));
+ return !exists;
+ }
+
+ public boolean deleteRecursive(String path) {
+ boolean deletedAnything = false;
+ final Pattern pathToDeletePattern = PathUtil.getSameOrDescendantPathPattern(path);
+ Iterator<Entry<String, Map<String,Object>>> entries = store.entrySet().iterator();
+ while (entries.hasNext()) {
+ Entry<String, Map<String,Object>> entry = entries.next();
+ if (pathToDeletePattern.matcher(entry.getKey()).matches()) {
+ entries.remove();
+ deletedAnything = true;
+ }
+ }
+ return deletedAnything;
+ }
+
+ public Iterator<NoSqlData> query(String query, String language) {
+ // implement simple dummy query
+ if (StringUtils.equals(language, "simple") && StringUtils.equals(query, "all")) {
+ final Iterator<Entry<String, Map<String,Object>>> entries = store.entrySet().iterator();
+ return new Iterator<NoSqlData>() {
+ public boolean hasNext() {
+ return entries.hasNext();
+ }
+ public NoSqlData next() {
+ Entry<String, Map<String,Object>> entry = entries.next();
+ return new NoSqlData(entry.getKey(), entry.getValue());
+ }
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ };
+ }
+ return Collections.<NoSqlData>emptyList().iterator();
+ }
+
+}
diff --git a/src/test/java/org/apache/sling/nosql/generic/simple/provider/SimpleNoSqlResourceProviderFactory.java b/src/test/java/org/apache/sling/nosql/generic/simple/provider/SimpleNoSqlResourceProviderFactory.java
new file mode 100644
index 0000000..1a9d09b
--- /dev/null
+++ b/src/test/java/org/apache/sling/nosql/generic/simple/provider/SimpleNoSqlResourceProviderFactory.java
@@ -0,0 +1,77 @@
+/*
+ * 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.
+ */
+package org.apache.sling.nosql.generic.simple.provider;
+
+import java.util.Map;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.ConfigurationPolicy;
+import org.apache.felix.scr.annotations.Properties;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.sling.api.resource.QueriableResourceProvider;
+import org.apache.sling.api.resource.ResourceProvider;
+import org.apache.sling.api.resource.ResourceProviderFactory;
+import org.apache.sling.nosql.generic.adapter.NoSqlAdapter;
+import org.apache.sling.nosql.generic.resource.AbstractNoSqlResourceProviderFactory;
+import org.osgi.service.event.EventAdmin;
+
+/**
+ * Simple NoSQL resource provider factory based on {@link SimpleNoSqlAdapter} which just stores
+ * the resource data in a hash map.
+ */
+@Component(configurationFactory = true, policy = ConfigurationPolicy.REQUIRE, metatype = true)
+@Service(value = ResourceProviderFactory.class)
+@Properties({
+ @Property(name = ResourceProvider.ROOTS, value = ""),
+ @Property(name = QueriableResourceProvider.LANGUAGES, value = { "simple" })
+})
+public class SimpleNoSqlResourceProviderFactory extends AbstractNoSqlResourceProviderFactory {
+
+ @Reference
+ private EventAdmin eventAdmin;
+
+ private NoSqlAdapter noSqlAdapter;
+
+ @Activate
+ protected void activate(final Map<String, Object> props) {
+ noSqlAdapter = new SimpleNoSqlAdapter();
+ }
+
+ @Override
+ protected NoSqlAdapter getNoSqlAdapter() {
+ return noSqlAdapter;
+ }
+
+ @Override
+ protected EventAdmin getEventAdmin() {
+ return eventAdmin;
+ }
+
+ protected void bindEventAdmin(EventAdmin eventAdmin) {
+ this.eventAdmin = eventAdmin;
+ }
+
+ protected void unbindEventAdmin(EventAdmin eventAdmin) {
+ this.eventAdmin = null;
+ }
+
+}
diff --git a/src/test/resources/OSGI-INF/org.apache.sling.nosql.generic.simple.provider.SimpleNoSqlResourceProviderFactory.xml b/src/test/resources/OSGI-INF/org.apache.sling.nosql.generic.simple.provider.SimpleNoSqlResourceProviderFactory.xml
new file mode 100644
index 0000000..21f2864
--- /dev/null
+++ b/src/test/resources/OSGI-INF/org.apache.sling.nosql.generic.simple.provider.SimpleNoSqlResourceProviderFactory.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+-->
+<components xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0">
+ <scr:component name="org.apache.sling.nosql.generic.simple.SimpleNoSqlResourceProviderFactory" configuration-policy="require" activate="activate">
+ <implementation class="org.apache.sling.nosql.generic.simple.provider.SimpleNoSqlResourceProviderFactory"/>
+ <service servicefactory="false">
+ <provide interface="org.apache.sling.api.resource.ResourceProviderFactory"/>
+ </service>
+ <property name="provider.roots" value=""/>
+ <property name="provider.query.languages" value="simple"/>
+ <property name="service.vendor" value="The Apache Software Foundation"/>
+ <property name="service.pid" value="org.apache.sling.nosql.generic.simple.provider.SimpleNoSqlResourceProviderFactory"/>
+ <reference name="eventAdmin" interface="org.osgi.service.event.EventAdmin" cardinality="1..1" policy="static" bind="bindEventAdmin" unbind="unbindEventAdmin"/>
+ </scr:component>
+</components>
--
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.