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.1.0 in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-nosql-generic.git
commit f92bfcd831065782735b554a0ecd8c4bbbbb1e3d Author: Stefan Seifert <[email protected]> AuthorDate: Wed Feb 24 11:54:46 2016 +0000 [maven-release-plugin] copy for tag org.apache.sling.nosql.generic-1.1.0 git-svn-id: https://svn.apache.org/repos/asf/sling/tags/org.apache.sling.nosql.generic-1.1.0@1732089 13f79535-47bb-0310-9956-ffa450edef68 --- generic/README.md | 16 ++ generic/pom.xml | 154 ++++++++++++ .../generic/adapter/AbstractNoSqlAdapter.java | 43 ++++ .../sling/nosql/generic/adapter/MapConverter.java | 87 +++++++ .../adapter/MetricsNoSqlAdapterWrapper.java | 122 +++++++++ .../nosql/generic/adapter/MultiValueMode.java | 36 +++ .../sling/nosql/generic/adapter/NoSqlAdapter.java | 93 +++++++ .../sling/nosql/generic/adapter/NoSqlData.java | 71 ++++++ .../sling/nosql/generic/adapter/package-info.java | 23 ++ .../AbstractNoSqlResourceProviderFactory.java | 54 ++++ .../nosql/generic/resource/impl/NoSqlResource.java | 90 +++++++ .../resource/impl/NoSqlResourceProvider.java | 280 +++++++++++++++++++++ .../nosql/generic/resource/impl/NoSqlValueMap.java | 147 +++++++++++ .../nosql/generic/resource/impl/PathUtil.java | 52 ++++ .../impl/ValueMapConvertingNoSqlAdapter.java | 181 +++++++++++++ .../sling/nosql/generic/resource/package-info.java | 23 ++ .../nosql/generic/adapter/MapConverterTest.java | 79 ++++++ .../AbstractNoSqlResourceProviderRootTest.java | 133 ++++++++++ .../impl/AbstractNoSqlResourceProviderTest.java | 217 ++++++++++++++++ ...ractNoSqlResourceProviderTransactionalTest.java | 227 +++++++++++++++++ .../nosql/generic/resource/impl/PathUtilTest.java | 56 +++++ .../SimpleNoSqlResourceProviderQueryTest.java | 102 ++++++++ .../SimpleNoSqlResourceProviderRootTest.java | 39 +++ .../simple/SimpleNoSqlResourceProviderTest.java | 64 +++++ ...mpleNoSqlResourceProviderTransactionalTest.java | 73 ++++++ .../simple/provider/SimpleNoSqlAdapter.java | 128 ++++++++++ .../SimpleNoSqlResourceProviderFactory.java | 77 ++++++ ...provider.SimpleNoSqlResourceProviderFactory.xml | 32 +++ 28 files changed, 2699 insertions(+) diff --git a/generic/README.md b/generic/README.md new file mode 100644 index 0000000..3c2efba --- /dev/null +++ b/generic/README.md @@ -0,0 +1,16 @@ +Apache Sling NoSQL Generic Resource Provider +============================================ + +Generic implementation of a Sling ResourceProvider that helps writing ResourceProviders using NoSQL databases as persistence. + +The generic implementation helps mapping the resource data to document-oriented key-value NoSQL databases like MongoDB or Couchbase. + +Features: + +* Defines a simplified "NoSqlAdapter" concept that is implemented for each NoSQL database. It boils down to simple get/put/list operations. Query support is optional. +* Complete implementation of Resource, ResourceProvider, ResourceProviderFactory and ValueMap based on the NoSqlAdapter +* "Transaction management" of Sling CRUD (commit/revert methods) is implemented +* ValueMap supports String, Integer, Long, Double, Date, Calendar and InputStream/byte\[\] (binary data) and arrays of them. Date/Calendar and binary data is serialized to a string before storing, so the NoSQL databases have not to support them directly. +* Sends resource notifications via OSGi EventAdmin +* Provides a "tests" JAR that can be used for integration tests with NoSQL databases to test the own adapter implementation +* Can be mounted as root provider without any JCR at all diff --git a/generic/pom.xml b/generic/pom.xml new file mode 100644 index 0000000..5b596b7 --- /dev/null +++ b/generic/pom.xml @@ -0,0 +1,154 @@ +<?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. +--> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>org.apache.sling</groupId> + <artifactId>sling</artifactId> + <version>26</version> + <relativePath /> + </parent> + + <artifactId>org.apache.sling.nosql.generic</artifactId> + <version>1.1.0</version> + <packaging>bundle</packaging> + + <name>Apache Sling NoSQL Generic Resource Provider</name> + + <scm> + <connection>scm:svn:http://svn.apache.org/repos/asf/sling/tags/org.apache.sling.nosql.generic-1.1.0</connection> + <developerConnection>scm:svn:https://svn.apache.org/repos/asf/sling/tags/org.apache.sling.nosql.generic-1.1.0</developerConnection> + <url>http://svn.apache.org/viewvc/sling/tags/org.apache.sling.nosql.generic-1.1.0</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.apache.commons</groupId> + <artifactId>commons-lang3</artifactId> + <version>3.3.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.5.0</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.sling</groupId> + <artifactId>org.apache.sling.testing.logging-mock</artifactId> + <version>1.0.0</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/generic/src/main/java/org/apache/sling/nosql/generic/adapter/AbstractNoSqlAdapter.java b/generic/src/main/java/org/apache/sling/nosql/generic/adapter/AbstractNoSqlAdapter.java new file mode 100644 index 0000000..38eff2b --- /dev/null +++ b/generic/src/main/java/org/apache/sling/nosql/generic/adapter/AbstractNoSqlAdapter.java @@ -0,0 +1,43 @@ +/* + * 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; + +/** + * Default implementation of {@link NoSqlAdapter}. + */ +@ConsumerType +public abstract class AbstractNoSqlAdapter implements NoSqlAdapter { + + @Override + public boolean validPath(String path) { + // by default all paths are accepted + return true; + } + + @Override + public Iterator<NoSqlData> query(String query, String language) { + // not supported unless it is overwritten explicitly + return null; + } + +} diff --git a/generic/src/main/java/org/apache/sling/nosql/generic/adapter/MapConverter.java b/generic/src/main/java/org/apache/sling/nosql/generic/adapter/MapConverter.java new file mode 100644 index 0000000..a04c8f6 --- /dev/null +++ b/generic/src/main/java/org/apache/sling/nosql/generic/adapter/MapConverter.java @@ -0,0 +1,87 @@ +/* + * 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.lang.reflect.Array; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import org.apache.commons.lang3.ArrayUtils; + +/** + * Transforms NoSqlData maps to a valid form for couchbase JSON document. + * All arrays have to be transformed to lists. + */ +final class MapConverter { + + private MapConverter() { + // static methods only + } + + /** + * @param map Map with multi-valued arrays + * @return Map with multi-valued lists + */ + public static Map<String, Object> mapArrayToList(Map<String, Object> map) { + for (Map.Entry<String, Object> entry : map.entrySet()) { + if (entry.getValue().getClass().isArray()) { + Class componentType = entry.getValue().getClass().getComponentType(); + if (componentType == int.class) { + entry.setValue(Arrays.asList(ArrayUtils.toObject((int[]) entry.getValue()))); + } + else if (componentType == long.class) { + entry.setValue(Arrays.asList(ArrayUtils.toObject((long[]) entry.getValue()))); + } + else if (componentType == double.class) { + entry.setValue(Arrays.asList(ArrayUtils.toObject((double[]) entry.getValue()))); + } + else if (componentType == boolean.class) { + entry.setValue(Arrays.asList(ArrayUtils.toObject((boolean[]) entry.getValue()))); + } + else { + entry.setValue(Arrays.asList((Object[]) entry.getValue())); + } + } + } + return map; + } + + /** + * @param map Map with multi-valued lists + * @return Map with multi-valued arrays + */ + @SuppressWarnings("unchecked") + public static Map<String, Object> mapListToArray(Map<String, Object> map) { + for (Map.Entry<String, Object> entry : map.entrySet()) { + if (entry.getValue() instanceof List) { + List list = (List) entry.getValue(); + if (list.size() == 0) { + entry.setValue(null); + } + else { + Class type = list.get(0).getClass(); + entry.setValue(list.toArray((Object[]) Array.newInstance(type, list.size()))); + } + } + } + return map; + } + +} diff --git a/generic/src/main/java/org/apache/sling/nosql/generic/adapter/MetricsNoSqlAdapterWrapper.java b/generic/src/main/java/org/apache/sling/nosql/generic/adapter/MetricsNoSqlAdapterWrapper.java new file mode 100644 index 0000000..319406f --- /dev/null +++ b/generic/src/main/java/org/apache/sling/nosql/generic/adapter/MetricsNoSqlAdapterWrapper.java @@ -0,0 +1,122 @@ +/* + * 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 org.apache.sling.api.resource.LoginException; +import org.slf4j.Logger; + +/** + * Wrapper for {@link NoSqlAdapter} that enables logging and time counting for each call. + */ +public final class MetricsNoSqlAdapterWrapper implements NoSqlAdapter { + + private final NoSqlAdapter delegate; + private final Logger logger; + + public MetricsNoSqlAdapterWrapper(NoSqlAdapter delegate, Logger logger) { + this.delegate = delegate; + this.logger = logger; + } + + public boolean validPath(String path) { + return delegate.validPath(path); + } + + public NoSqlData get(String path) { + Metrics metrics = new Metrics(); + try { + return delegate.get(path); + } + finally { + metrics.finish("get({})", path); + } + } + + public Iterator<NoSqlData> getChildren(String parentPath) { + Metrics metrics = new Metrics(); + try { + return delegate.getChildren(parentPath); + } + finally { + metrics.finish("getChildren({})", parentPath); + } + } + + public boolean store(NoSqlData data) { + Metrics metrics = new Metrics(); + try { + return delegate.store(data); + } + finally { + metrics.finish("store({})", data.getPath()); + } + } + + public boolean deleteRecursive(String path) { + Metrics metrics = new Metrics(); + try { + return delegate.deleteRecursive(path); + } + finally { + metrics.finish("deleteRecursive({})", path); + } + } + + public Iterator<NoSqlData> query(String query, String language) { + Metrics metrics = new Metrics(); + try { + return delegate.query(query, language); + } + finally { + metrics.finish("query({})", query); + } + } + + @Override + public void checkConnection() throws LoginException { + delegate.checkConnection(); + } + + @Override + public void createIndexDefinitions() { + delegate.createIndexDefinitions(); + } + + private class Metrics { + + private long startTime; + + public Metrics() { + if (logger.isDebugEnabled()) { + startTime = System.currentTimeMillis(); + } + } + + public void finish(String message, Object... data) { + if (logger.isDebugEnabled()) { + long duration = System.currentTimeMillis() - startTime; + logger.debug(message + " - " + duration + "ms", data); + } + } + + } + +} diff --git a/generic/src/main/java/org/apache/sling/nosql/generic/adapter/MultiValueMode.java b/generic/src/main/java/org/apache/sling/nosql/generic/adapter/MultiValueMode.java new file mode 100644 index 0000000..c0d966d --- /dev/null +++ b/generic/src/main/java/org/apache/sling/nosql/generic/adapter/MultiValueMode.java @@ -0,0 +1,36 @@ +/* + * 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; + +/** + * Mode for multi-valued field data in {@link NoSqlData} properties. + */ +public enum MultiValueMode { + + /** + * Return multi-valued field values as array (default). + */ + ARRAYS, + + /** + * Return multi-valued field values as lists. + */ + LISTS + +} diff --git a/generic/src/main/java/org/apache/sling/nosql/generic/adapter/NoSqlAdapter.java b/generic/src/main/java/org/apache/sling/nosql/generic/adapter/NoSqlAdapter.java new file mode 100644 index 0000000..fb4d3e9 --- /dev/null +++ b/generic/src/main/java/org/apache/sling/nosql/generic/adapter/NoSqlAdapter.java @@ -0,0 +1,93 @@ +/* + * 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 org.apache.sling.api.resource.LoginException; + +import aQute.bnd.annotation.ConsumerType; + +/** + * Adapter for NoSQL databases to be hooked into the Generic NoSQL resource provider. + * All implementors should should extend {@link AbstractNoSqlAdapter} to be compatible for future extensions. + */ +@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); + + /** + * Checks whether the connection to the NoSQL database is possible + * @throws LoginException in case of any errors + */ + void checkConnection() throws LoginException; + + /** + * Creates index definitions for accessing the NoSQL database. + * This is called every time an adapter instances is created. If the indexes are + * already present it should do nothing. + * It is only called when the {@link #checkConnection()} call succeeds. + */ + void createIndexDefinitions(); + +} diff --git a/generic/src/main/java/org/apache/sling/nosql/generic/adapter/NoSqlData.java b/generic/src/main/java/org/apache/sling/nosql/generic/adapter/NoSqlData.java new file mode 100644 index 0000000..0b52038 --- /dev/null +++ b/generic/src/main/java/org/apache/sling/nosql/generic/adapter/NoSqlData.java @@ -0,0 +1,71 @@ +/* + * 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, properties, MultiValueMode.ARRAYS); + } + + public NoSqlData(String path, Map<String, Object> properties, MultiValueMode multiValueMode) { + this.path = path; + switch (multiValueMode) { + case ARRAYS: + this.properties = properties; + break; + case LISTS: + this.properties = MapConverter.mapListToArray(properties); + break; + default: + throw new IllegalArgumentException("Multi value mode not supported: " + multiValueMode); + } + } + + public String getPath() { + return path; + } + + public Map<String, Object> getProperties() { + return getProperties(MultiValueMode.ARRAYS); + } + + public Map<String, Object> getProperties(MultiValueMode multiValueMode) { + switch (multiValueMode) { + case ARRAYS: + return properties; + case LISTS: + return MapConverter.mapArrayToList(properties); + default: + throw new IllegalArgumentException("Multi value mode not supported: " + multiValueMode); + } + } + +} diff --git a/generic/src/main/java/org/apache/sling/nosql/generic/adapter/package-info.java b/generic/src/main/java/org/apache/sling/nosql/generic/adapter/package-info.java new file mode 100644 index 0000000..88474a2 --- /dev/null +++ b/generic/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. + */ [email protected]("2.0.0") +package org.apache.sling.nosql.generic.adapter; diff --git a/generic/src/main/java/org/apache/sling/nosql/generic/resource/AbstractNoSqlResourceProviderFactory.java b/generic/src/main/java/org/apache/sling/nosql/generic/resource/AbstractNoSqlResourceProviderFactory.java new file mode 100644 index 0000000..47be2fe --- /dev/null +++ b/generic/src/main/java/org/apache/sling/nosql/generic/resource/AbstractNoSqlResourceProviderFactory.java @@ -0,0 +1,54 @@ +/* + * 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 { + NoSqlAdapter adapter = getNoSqlAdapter(); + adapter.checkConnection(); + adapter.createIndexDefinitions(); + return new NoSqlResourceProvider(adapter, 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/generic/src/main/java/org/apache/sling/nosql/generic/resource/impl/NoSqlResource.java b/generic/src/main/java/org/apache/sling/nosql/generic/resource/impl/NoSqlResource.java new file mode 100644 index 0000000..5431cc1 --- /dev/null +++ b/generic/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/generic/src/main/java/org/apache/sling/nosql/generic/resource/impl/NoSqlResourceProvider.java b/generic/src/main/java/org/apache/sling/nosql/generic/resource/impl/NoSqlResourceProvider.java new file mode 100644 index 0000000..e5ad414 --- /dev/null +++ b/generic/src/main/java/org/apache/sling/nosql/generic/resource/impl/NoSqlResourceProvider.java @@ -0,0 +1,280 @@ +/* + * 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.LinkedHashMap; +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 static final String ROOT_PATH = "/"; + + private final NoSqlAdapter adapter; + private final EventAdmin eventAdmin; + private final Map<String, NoSqlData> changedResources = new LinkedHashMap<String, NoSqlData>(); + private final Set<String> deletedResources = new HashSet<String>(); + + public NoSqlResourceProvider(NoSqlAdapter adapter, EventAdmin eventAdmin) { + this.adapter = new ValueMapConvertingNoSqlAdapter(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); + } + else if (ROOT_PATH.equals(path)) { + // root path exists implicitly - bot not yet in nosql store - return a "virtual" resource until something is stored in it + NoSqlData rootData = new NoSqlData(ROOT_PATH, new HashMap<String, Object>()); + return new NoSqlResource(rootData, 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) { + if (path.equals(deletedPath) || path.equals(deletedPath + "/")) { + return true; + } + } + return false; + } + + + // ### WRITE ACCESS ### + + public Resource create(ResourceResolver resolver, String path, Map<String, Object> properties) + throws PersistenceException { + if (ROOT_PATH.equals(path) || !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 + Map<String, Object> writableMap = properties != null ? new HashMap<String, Object>(properties) : new HashMap<String, Object>(); + NoSqlData data = new NoSqlData(path, NoSqlValueMap.convertForWriteAll(writableMap)); + changedResources.put(path, data); + return new NoSqlResource(data, resolver, this); + } + + public void delete(ResourceResolver resolver, String path) throws PersistenceException { + if (ROOT_PATH.equals(path) || !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); + 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); + 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); + 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/generic/src/main/java/org/apache/sling/nosql/generic/resource/impl/NoSqlValueMap.java b/generic/src/main/java/org/apache/sling/nosql/generic/resource/impl/NoSqlValueMap.java new file mode 100644 index 0000000..6f7fcf4 --- /dev/null +++ b/generic/src/main/java/org/apache/sling/nosql/generic/resource/impl/NoSqlValueMap.java @@ -0,0 +1,147 @@ +/* + * 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.util.Calendar; +import java.util.Date; +import java.util.Map; + +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 == Date.class) { + Calendar value = get(name, Calendar.class); + if (value != null) { + return (T)value.getTime(); + } + } + 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 == 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 Date) { + Calendar calendar = Calendar.getInstance(); + calendar.setTime((Date)value); + value = calendar; + } + 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 != null && !isValidType(value.getClass())) { + throw new IllegalArgumentException("Data type not supported for NoSqlValueMap: " + value.getClass()); + } + return value; + } + + static boolean isValidType(Class clazz) { + if (clazz.isArray()) { + if (clazz.getComponentType() == byte.class) { + // byte only supported as array + return true; + } + return isValidType(clazz.getComponentType()); + } + else { + return clazz == String.class + || clazz == Integer.class + || clazz == Long.class + || clazz == Double.class + || clazz == Boolean.class + || Calendar.class.isAssignableFrom(clazz); + } + } + + 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; + } + +} diff --git a/generic/src/main/java/org/apache/sling/nosql/generic/resource/impl/PathUtil.java b/generic/src/main/java/org/apache/sling/nosql/generic/resource/impl/PathUtil.java new file mode 100644 index 0000000..8af73e6 --- /dev/null +++ b/generic/src/main/java/org/apache/sling/nosql/generic/resource/impl/PathUtil.java @@ -0,0 +1,52 @@ +/* + * 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; + +import org.apache.commons.lang3.StringUtils; + +/** + * 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(StringUtils.removeEnd(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/generic/src/main/java/org/apache/sling/nosql/generic/resource/impl/ValueMapConvertingNoSqlAdapter.java b/generic/src/main/java/org/apache/sling/nosql/generic/resource/impl/ValueMapConvertingNoSqlAdapter.java new file mode 100644 index 0000000..37e3c45 --- /dev/null +++ b/generic/src/main/java/org/apache/sling/nosql/generic/resource/impl/ValueMapConvertingNoSqlAdapter.java @@ -0,0 +1,181 @@ +/* + * 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.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Locale; +import java.util.Map; + +import javax.xml.bind.DatatypeConverter; + +import org.apache.sling.api.resource.LoginException; +import org.apache.sling.nosql.generic.adapter.NoSqlAdapter; +import org.apache.sling.nosql.generic.adapter.NoSqlData; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Special adapter wrapper that converts all Calendar and byte[] values in ValueMap to String values + * when passing to the underlying NoSql adapter and back to typed values when reading from it. + * This is required because too many implementations access ValueMap without type specifier so + * we cannot only rely on the type conversion in the typed get methods of a ValueMap. + */ +class ValueMapConvertingNoSqlAdapter implements NoSqlAdapter { + + private static final String PREFIX_CALENDAR = "{{calendar}}"; + private static final String PREFIX_BYTE_ARRAY = "{{bytes}}"; + + private final NoSqlAdapter delegate; + + private static final Logger log = LoggerFactory.getLogger(ValueMapConvertingNoSqlAdapter.class); + + public ValueMapConvertingNoSqlAdapter(NoSqlAdapter delegate) { + this.delegate = delegate; + } + + public boolean validPath(String path) { + return delegate.validPath(path); + } + + public NoSqlData get(String path) { + return deserializeUnsupportedTypes(delegate.get(path)); + } + + public Iterator<NoSqlData> getChildren(String parentPath) { + return deserializeUnsupportedTypes(delegate.getChildren(parentPath)); + } + + public boolean store(NoSqlData data) { + return delegate.store(serializeUnsupportedTypes(data)); + } + + public boolean deleteRecursive(String path) { + return delegate.deleteRecursive(path); + } + + public Iterator<NoSqlData> query(String query, String language) { + return deserializeUnsupportedTypes(delegate.query(query, language)); + } + + private Iterator<NoSqlData> deserializeUnsupportedTypes(final Iterator<NoSqlData> source) { + if (source == null) { + return null; + } + return new Iterator<NoSqlData>() { + @Override + public boolean hasNext() { + return source.hasNext(); + } + @Override + public NoSqlData next() { + return deserializeUnsupportedTypes(source.next()); + } + @Override + public void remove() { + source.remove(); + } + }; + } + + private NoSqlData serializeUnsupportedTypes(NoSqlData data) { + if (data == null) { + return null; + } + + Map<String,Object> serializedMap = new HashMap<String, Object>(); + + for (Map.Entry<String, Object> entry : data.getProperties().entrySet()) { + Object serializedValue = entry.getValue(); + + // Calendar.class + if (entry.getValue() instanceof Calendar) { + serializedValue = PREFIX_CALENDAR + getISO8601Format().format(((Calendar)entry.getValue()).getTime()); + } + + // byte[].class + else if (entry.getValue() instanceof byte[]) { + serializedValue = PREFIX_BYTE_ARRAY + DatatypeConverter.printBase64Binary((byte[])entry.getValue()); + } + + serializedMap.put(entry.getKey(), serializedValue); + } + + return new NoSqlData(data.getPath(), serializedMap); + } + + private NoSqlData deserializeUnsupportedTypes(NoSqlData data) { + if (data == null) { + return null; + } + + Map<String,Object> deserializedMap = new HashMap<String, Object>(); + + for (Map.Entry<String, Object> entry : data.getProperties().entrySet()) { + Object deserializedValue = entry.getValue(); + if (entry.getValue() instanceof String) { + String value = (String)entry.getValue(); + + // Calendar.class + if (value.indexOf(PREFIX_CALENDAR) == 0) { + String calendarValue = value.substring(PREFIX_CALENDAR.length()); + try { + Date date = getISO8601Format().parse((String)calendarValue); + Calendar calendar = Calendar.getInstance(); + calendar.setTime(date); + deserializedValue = calendar; + } + catch (ParseException ex) { + log.warn("Unable to parse serialized calendar value: " + entry.getValue(), ex); + } + } + + // byte[].class + else if (value.indexOf(PREFIX_BYTE_ARRAY) == 0) { + String byteArrayValue = value.substring(PREFIX_BYTE_ARRAY.length()); + deserializedValue = DatatypeConverter.parseBase64Binary(byteArrayValue); + } + + } + deserializedMap.put(entry.getKey(), deserializedValue); + } + + return new NoSqlData(data.getPath(), deserializedMap); + } + + @Override + public void checkConnection() throws LoginException { + delegate.checkConnection(); + } + + @Override + public void createIndexDefinitions() { + delegate.createIndexDefinitions(); + } + + private static DateFormat getISO8601Format() { + return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ", Locale.US); + } + +} diff --git a/generic/src/main/java/org/apache/sling/nosql/generic/resource/package-info.java b/generic/src/main/java/org/apache/sling/nosql/generic/resource/package-info.java new file mode 100644 index 0000000..3788b29 --- /dev/null +++ b/generic/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. + */ [email protected]("1.0.0") +package org.apache.sling.nosql.generic.resource; diff --git a/generic/src/test/java/org/apache/sling/nosql/generic/adapter/MapConverterTest.java b/generic/src/test/java/org/apache/sling/nosql/generic/adapter/MapConverterTest.java new file mode 100644 index 0000000..32aa7cd --- /dev/null +++ b/generic/src/test/java/org/apache/sling/nosql/generic/adapter/MapConverterTest.java @@ -0,0 +1,79 @@ +/* + * 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 static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; + +import java.util.Map; + +import org.apache.sling.nosql.generic.adapter.MapConverter; +import org.junit.Test; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; + +public class MapConverterTest { + + @Test + public void testMapArrayToList() throws Exception { + Map<String, Object> result = MapConverter.mapArrayToList(Maps.newHashMap(ImmutableMap.<String, Object>builder() + .put("prop1", "value1") + .put("prop2", 2) + .put("stringArray", new String[] { "value1", "value2" }) + .put("integerArray", new Integer[] { 1, 2, 3 }) + .put("integerArray2", new int[] { 1, 2, 3 }) + .put("longArray", new long[] { 1L, 2L }) + .put("doubleArray", new double[] { 1.1d, 1.2d }) + .put("booleanArray", new boolean[] { true, false }) + .build())); + + assertEquals("prop1", "value1", result.get("prop1")); + assertEquals("prop2", 2, result.get("prop2")); + assertEquals("stringArray", ImmutableList.of("value1", "value2"), result.get("stringArray")); + assertEquals("integerArray", ImmutableList.of(1, 2, 3), result.get("integerArray")); + assertEquals("integerArray2", ImmutableList.of(1, 2, 3), result.get("integerArray2")); + assertEquals("longArray", ImmutableList.of(1L, 2L), result.get("longArray")); + assertEquals("doubleArray", ImmutableList.of(1.1d, 1.2d), result.get("doubleArray")); + assertEquals("booleanArray", ImmutableList.of(true, false), result.get("booleanArray")); + } + + @Test + public void testMapListToArray() throws Exception { + Map<String, Object> result = MapConverter.mapListToArray(Maps.newHashMap(ImmutableMap.<String, Object>builder() + .put("prop1", "value1") + .put("prop2", 2) + .put("stringArray", ImmutableList.of("value1", "value2")) + .put("integerArray", ImmutableList.of(1, 2, 3)) + .put("longArray", ImmutableList.of(1L, 2L)) + .put("doubleArray", ImmutableList.of(1.1d, 1.2d)) + .put("booleanArray", ImmutableList.of(true, false)) + .build())); + + assertEquals("prop1", "value1", result.get("prop1")); + assertEquals("prop2", 2, result.get("prop2")); + assertArrayEquals("stringArray", new String[] { "value1", "value2" }, (String[]) result.get("stringArray")); + assertArrayEquals("integerArray", new Integer[] { 1, 2, 3 }, (Integer[]) result.get("integerArray")); + assertArrayEquals("longArray", new Long[] { 1L, 2L }, (Long[]) result.get("longArray")); + assertArrayEquals("doubleArray", new Double[] { 1.1d, 1.2d }, (Double[]) result.get("doubleArray")); + assertArrayEquals("booleanArray", new Boolean[] { true, false }, (Boolean[]) result.get("booleanArray")); + } + +} diff --git a/generic/src/test/java/org/apache/sling/nosql/generic/resource/impl/AbstractNoSqlResourceProviderRootTest.java b/generic/src/test/java/org/apache/sling/nosql/generic/resource/impl/AbstractNoSqlResourceProviderRootTest.java new file mode 100644 index 0000000..1fead01 --- /dev/null +++ b/generic/src/test/java/org/apache/sling/nosql/generic/resource/impl/AbstractNoSqlResourceProviderRootTest.java @@ -0,0 +1,133 @@ +/* + * 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.hamcrest.CoreMatchers.equalTo; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; +import java.util.List; + +import org.apache.commons.lang3.StringUtils; +import org.apache.jackrabbit.JcrConstants; +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.api.resource.ResourceUtil; +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; +import com.google.common.collect.Lists; + +/** + * Test monting NoSqlResourceProvider as root resource provider. + */ +public abstract class AbstractNoSqlResourceProviderRootTest { + + @Rule + public SlingContext context = new SlingContext(ResourceResolverType.NONE); + + protected abstract void registerResourceProviderFactoryAsRoot(); + + @Before + public void setUp() throws Exception { + registerResourceProviderFactoryAsRoot(); + } + + @After + public void tearDown() { + context.resourceResolver().revert(); + } + + @Test + public void testRoot() { + Resource root = context.resourceResolver().getResource("/"); + assertNotNull(root); + assertTrue(root instanceof NoSqlResource); + } + + @Test + public void testCreatePath() throws PersistenceException { + ResourceUtil.getOrCreateResource(context.resourceResolver(), "/test/test1", + ImmutableMap.<String, Object>of(JcrConstants.JCR_PRIMARYTYPE, JcrConstants.NT_UNSTRUCTURED), + JcrConstants.NT_UNSTRUCTURED, true); + + Resource test = context.resourceResolver().getResource("/test"); + assertNotNull(test); + + Resource test1 = context.resourceResolver().getResource("/test/test1"); + assertNotNull(test1); + + context.resourceResolver().delete(test); + } + + @Test + public void testListChildren_RootNode() throws IOException { + Resource testResource = ResourceUtil.getOrCreateResource(context.resourceResolver(), "/test", + ImmutableMap.<String, Object>of(JcrConstants.JCR_PRIMARYTYPE, JcrConstants.NT_UNSTRUCTURED), + JcrConstants.NT_UNSTRUCTURED, true); + + Resource root = context.resourceResolver().getResource("/"); + + List<Resource> children = Lists.newArrayList(root.listChildren()); + assertFalse(children.isEmpty()); + assertTrue(containsResource(children, testResource)); + + children = Lists.newArrayList(root.getChildren()); + assertFalse(children.isEmpty()); + assertTrue(containsResource(children, testResource)); + + context.resourceResolver().delete(testResource); + } + + private boolean containsResource(List<Resource> children, Resource resource) { + for (Resource child : children) { + if (StringUtils.equals(child.getPath(), resource.getPath())) { + return true; + } + } + return false; + } + + @Test(expected = PersistenceException.class) + public void testDeleteRootPath() throws PersistenceException { + Resource root = context.resourceResolver().getResource("/"); + context.resourceResolver().delete(root); + } + + @Test + public void testUpdateRootPath() throws PersistenceException { + Resource root = context.resourceResolver().getResource("/"); + ModifiableValueMap props = root.adaptTo(ModifiableValueMap.class); + props.put("prop1", "value1"); + context.resourceResolver().commit(); + + root = context.resourceResolver().getResource("/"); + assertThat(root.getValueMap().get("prop1", String.class), equalTo("value1")); + } + +} diff --git a/generic/src/test/java/org/apache/sling/nosql/generic/resource/impl/AbstractNoSqlResourceProviderTest.java b/generic/src/test/java/org/apache/sling/nosql/generic/resource/impl/AbstractNoSqlResourceProviderTest.java new file mode 100644 index 0000000..fa3a82e --- /dev/null +++ b/generic/src/test/java/org/apache/sling/nosql/generic/resource/impl/AbstractNoSqlResourceProviderTest.java @@ -0,0 +1,217 @@ +/* + * 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.isValidType(entry.getValue().getClass())); + } + } + + @Test + public void testPrimaryTypeResourceType() throws PersistenceException { + Resource resource = context.resourceResolver().getResource(testRoot().getPath()); + assertEquals(JcrConstants.NT_UNSTRUCTURED, resource.getResourceType()); + } + + @Test + public void testCreateWithNullMap() throws IOException { + Resource resource1 = context.resourceResolver().getResource(testRoot().getPath() + "/node1"); + context.resourceResolver().create(resource1, "nullMap", null); + } + +} diff --git a/generic/src/test/java/org/apache/sling/nosql/generic/resource/impl/AbstractNoSqlResourceProviderTransactionalTest.java b/generic/src/test/java/org/apache/sling/nosql/generic/resource/impl/AbstractNoSqlResourceProviderTransactionalTest.java new file mode 100644 index 0000000..3320806 --- /dev/null +++ b/generic/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/generic/src/test/java/org/apache/sling/nosql/generic/resource/impl/PathUtilTest.java b/generic/src/test/java/org/apache/sling/nosql/generic/resource/impl/PathUtilTest.java new file mode 100644 index 0000000..67efd33 --- /dev/null +++ b/generic/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/generic/src/test/java/org/apache/sling/nosql/generic/simple/SimpleNoSqlResourceProviderQueryTest.java b/generic/src/test/java/org/apache/sling/nosql/generic/simple/SimpleNoSqlResourceProviderQueryTest.java new file mode 100644 index 0000000..6c79168 --- /dev/null +++ b/generic/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/generic/src/test/java/org/apache/sling/nosql/generic/simple/SimpleNoSqlResourceProviderRootTest.java b/generic/src/test/java/org/apache/sling/nosql/generic/simple/SimpleNoSqlResourceProviderRootTest.java new file mode 100644 index 0000000..61cf09a --- /dev/null +++ b/generic/src/test/java/org/apache/sling/nosql/generic/simple/SimpleNoSqlResourceProviderRootTest.java @@ -0,0 +1,39 @@ +/* + * 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 org.apache.sling.api.resource.ResourceProvider; +import org.apache.sling.nosql.generic.resource.impl.AbstractNoSqlResourceProviderRootTest; +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 SimpleNoSqlResourceProviderRootTest extends AbstractNoSqlResourceProviderRootTest { + + @Override + protected void registerResourceProviderFactoryAsRoot() { + context.registerInjectActivateService(new SimpleNoSqlResourceProviderFactory(), ImmutableMap.<String, Object>builder() + .put(ResourceProvider.ROOTS, "/") + .build()); + } + +} diff --git a/generic/src/test/java/org/apache/sling/nosql/generic/simple/SimpleNoSqlResourceProviderTest.java b/generic/src/test/java/org/apache/sling/nosql/generic/simple/SimpleNoSqlResourceProviderTest.java new file mode 100644 index 0000000..aa37032 --- /dev/null +++ b/generic/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/generic/src/test/java/org/apache/sling/nosql/generic/simple/SimpleNoSqlResourceProviderTransactionalTest.java b/generic/src/test/java/org/apache/sling/nosql/generic/simple/SimpleNoSqlResourceProviderTransactionalTest.java new file mode 100644 index 0000000..6c8fe12 --- /dev/null +++ b/generic/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/generic/src/test/java/org/apache/sling/nosql/generic/simple/provider/SimpleNoSqlAdapter.java b/generic/src/test/java/org/apache/sling/nosql/generic/simple/provider/SimpleNoSqlAdapter.java new file mode 100644 index 0000000..89da8b1 --- /dev/null +++ b/generic/src/test/java/org/apache/sling/nosql/generic/simple/provider/SimpleNoSqlAdapter.java @@ -0,0 +1,128 @@ +/* + * 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.api.resource.LoginException; +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(); + } + + @Override + public void checkConnection() throws LoginException { + // nothing to do + } + + @Override + public void createIndexDefinitions() { + // nothing to do + } + +} diff --git a/generic/src/test/java/org/apache/sling/nosql/generic/simple/provider/SimpleNoSqlResourceProviderFactory.java b/generic/src/test/java/org/apache/sling/nosql/generic/simple/provider/SimpleNoSqlResourceProviderFactory.java new file mode 100644 index 0000000..1a9d09b --- /dev/null +++ b/generic/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/generic/src/test/resources/OSGI-INF/org.apache.sling.nosql.generic.simple.provider.SimpleNoSqlResourceProviderFactory.xml b/generic/src/test/resources/OSGI-INF/org.apache.sling.nosql.generic.simple.provider.SimpleNoSqlResourceProviderFactory.xml new file mode 100644 index 0000000..21f2864 --- /dev/null +++ b/generic/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 "[email protected]" <[email protected]>.
