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