You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ro...@apache.org on 2017/10/18 23:24:49 UTC

[sling-org-apache-sling-mongodb] 01/36: New MongoDB resource provider

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-mongodb.git

commit 45b6b2a64ee774e7c8d66eb42d08df58382f6d93
Author: Carsten Ziegeler <cz...@apache.org>
AuthorDate: Wed Aug 1 17:53:47 2012 +0000

    New MongoDB resource provider
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1368139 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml                                            |  90 ++++++
 .../sling/mongodb/impl/ChangeableValueMap.java     |  90 ++++++
 .../mongodb/impl/MongoDBCollectionResource.java    |  90 ++++++
 .../apache/sling/mongodb/impl/MongoDBContext.java  |  93 ++++++
 .../apache/sling/mongodb/impl/MongoDBResource.java | 146 +++++++++
 .../mongodb/impl/MongoDBResourceProvider.java      | 355 +++++++++++++++++++++
 .../impl/MongoDBResourceProviderFactory.java       | 109 +++++++
 .../sling/mongodb/impl/ReadableValueMap.java       | 227 +++++++++++++
 .../OSGI-INF/metatype/metatype.properties          |  42 +++
 9 files changed, 1242 insertions(+)

diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..d43dcc1
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,90 @@
+<?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>13</version>
+    </parent>
+
+    <groupId>org.apache.sling</groupId>
+    <artifactId>org.apache.sling.mongodb</artifactId>
+    <version>0.0.1-SNAPSHOT</version>
+    <packaging>bundle</packaging>
+
+    <name>org.apache.sling.mongodb</name>
+    <url>http://maven.apache.org</url>
+
+    <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>
+                <configuration>
+                    <instructions>
+                        <Private-Package>
+                            org.apache.sling.mongodb.impl
+                        </Private-Package>
+                    </instructions>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+    
+    <dependencies>
+        <dependency>
+    	    <groupId>org.apache.sling</groupId>
+    	    <artifactId>org.apache.sling.api</artifactId>
+    	    <version>2.2.5-SNAPSHOT</version>
+    	    <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.commons.osgi</artifactId>
+            <version>2.1.0</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+        	<groupId>org.mongodb</groupId>
+        	<artifactId>mongo-java-driver</artifactId>
+        	<version>2.8.0</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+        	<groupId>org.apache.felix</groupId>
+        	<artifactId>org.apache.felix.scr.annotations</artifactId>
+        </dependency>
+        <dependency>
+        	<groupId>org.slf4j</groupId>
+        	<artifactId>slf4j-api</artifactId>
+        </dependency>
+        <dependency>
+        	<groupId>javax.servlet</groupId>
+        	<artifactId>servlet-api</artifactId>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/src/main/java/org/apache/sling/mongodb/impl/ChangeableValueMap.java b/src/main/java/org/apache/sling/mongodb/impl/ChangeableValueMap.java
new file mode 100644
index 0000000..493a737
--- /dev/null
+++ b/src/main/java/org/apache/sling/mongodb/impl/ChangeableValueMap.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.mongodb.impl;
+
+import java.util.Map;
+
+import org.apache.sling.api.resource.ModifiableValueMap;
+
+public class ChangeableValueMap
+    extends ReadableValueMap
+    implements ModifiableValueMap {
+
+    private final MongoDBResource resource;
+
+    public ChangeableValueMap(final MongoDBResource resource) {
+        super(resource.getProperties());
+        this.resource = resource;
+    }
+
+    /**
+     * @see java.util.Map#clear()
+     */
+    public void clear() {
+        throw new UnsupportedOperationException("clear");
+    }
+
+    /**
+     * @see java.util.Map#put(java.lang.Object, java.lang.Object)
+     */
+    public Object put(final String name, final Object value) {
+        final Object oldValue = this.valueMap.get(name);
+        final String key;
+        if ( name.startsWith("_") ) {
+            key = "_" + name;
+        } else {
+            key = name;
+        }
+        this.resource.getProperties().put(key, value);
+
+        // update map and resource
+        this.createValueMap(this.resource.getProperties());
+        this.resource.changed();
+
+        return oldValue;
+    }
+
+    /**
+     * @see java.util.Map#putAll(java.util.Map)
+     */
+    public void putAll(final Map<? extends String, ? extends Object> m) {
+        for(final Map.Entry<? extends String, ? extends Object> e : m.entrySet() ) {
+            this.put(e.getKey(), e.getValue());
+        }
+    }
+
+    /**
+     * @see java.util.Map#remove(java.lang.Object)
+     */
+    public Object remove(final Object name) {
+        final Object result = this.valueMap.get(name);
+        if ( result != null ) {
+            final String key;
+            if ( name.toString().startsWith("_") ) {
+                key = "_" + name;
+            } else {
+                key = name.toString();
+            }
+            this.resource.getProperties().removeField(key);
+
+            // update map and resource
+            this.createValueMap(this.resource.getProperties());
+            this.resource.changed();
+        }
+        return result;
+    }
+}
diff --git a/src/main/java/org/apache/sling/mongodb/impl/MongoDBCollectionResource.java b/src/main/java/org/apache/sling/mongodb/impl/MongoDBCollectionResource.java
new file mode 100644
index 0000000..6cccb4a
--- /dev/null
+++ b/src/main/java/org/apache/sling/mongodb/impl/MongoDBCollectionResource.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.mongodb.impl;
+
+import java.util.Collections;
+import java.util.Map;
+
+import org.apache.sling.api.resource.AbstractResource;
+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.ValueMapDecorator;
+
+public class MongoDBCollectionResource extends AbstractResource {
+
+    private final String path;
+
+    private final ResourceResolver resourceResolver;
+
+    private final ResourceMetadata metadata = new ResourceMetadata();
+
+    public MongoDBCollectionResource(final ResourceResolver resolver, final String path) {
+        this.resourceResolver = resolver;
+        this.path = path;
+        this.metadata.setResolutionPath(this.path);
+    }
+
+    /**
+     * @see org.apache.sling.api.resource.Resource#getPath()
+     */
+    public String getPath() {
+        return this.path;
+    }
+
+    /**
+     * @see org.apache.sling.api.resource.Resource#getResourceType()
+     */
+    public String getResourceType() {
+        return "mongodb:collection";
+    }
+
+    /**
+     * @see org.apache.sling.api.resource.Resource#getResourceSuperType()
+     */
+    public String getResourceSuperType() {
+        return null;
+    }
+
+    /**
+     * @see org.apache.sling.api.resource.Resource#getResourceMetadata()
+     */
+    public ResourceMetadata getResourceMetadata() {
+        return this.metadata;
+    }
+
+    /**
+     * @see org.apache.sling.api.resource.Resource#getResourceResolver()
+     */
+    public ResourceResolver getResourceResolver() {
+        return this.resourceResolver;
+    }
+
+    /**
+     * @see org.apache.sling.api.adapter.SlingAdaptable#adaptTo(java.lang.Class)
+     */
+    @SuppressWarnings("unchecked")
+    @Override
+    public <AdapterType> AdapterType adaptTo(final Class<AdapterType> type) {
+        if ( type == ValueMap.class ) {
+            return (AdapterType) new ValueMapDecorator(Collections.EMPTY_MAP);
+        } else if ( type == Map.class ) {
+            return (AdapterType) Collections.EMPTY_MAP;
+        }
+        return super.adaptTo(type);
+    }
+}
diff --git a/src/main/java/org/apache/sling/mongodb/impl/MongoDBContext.java b/src/main/java/org/apache/sling/mongodb/impl/MongoDBContext.java
new file mode 100644
index 0000000..c801e8b
--- /dev/null
+++ b/src/main/java/org/apache/sling/mongodb/impl/MongoDBContext.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.mongodb.impl;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import com.mongodb.DB;
+
+public class MongoDBContext {
+
+    /** The roots. */
+    private final String[] roots;
+
+    /** The roots ended by a slash. */
+    private final String[] rootsWithSlash;
+
+    /** Don't show these collections. */
+    private final Set<String> filterCollectionNames = new HashSet<String>();
+
+    /** The database to be used. */
+    private final DB database;
+
+    public MongoDBContext(final DB database,
+                    final String[] configuredRoots,
+                    final String[] configuredFilterCollectionNames) {
+        this.database = database;
+        if ( configuredRoots != null ) {
+            final List<String> rootsList = new ArrayList<String>();
+            final List<String> rootsWithSlashList = new ArrayList<String>();
+            for(final String r : configuredRoots) {
+                if ( r != null ) {
+                    final String value = r.trim();
+                    if ( value.length() > 0 ) {
+                        if ( value.endsWith("/") ) {
+                            rootsWithSlashList.add(value);
+                            rootsList.add(value.substring(0, value.length() - 1));
+                        } else {
+                            rootsWithSlashList.add(value + "/");
+                            rootsList.add(value);
+                        }
+                    }
+                }
+            }
+            this.roots = rootsList.toArray(new String[rootsList.size()]);
+            this.rootsWithSlash = rootsWithSlashList.toArray(new String[rootsWithSlashList.size()]);
+        } else {
+            this.roots = new String[0];
+            this.rootsWithSlash = new String[0];
+        }
+        if ( configuredFilterCollectionNames != null ) {
+            for(final String name : configuredFilterCollectionNames) {
+                this.filterCollectionNames.add(name);
+            }
+        }
+    }
+
+    public String[] getRoots() {
+        return roots;
+    }
+
+    public String[] getRootsWithSlash() {
+        return this.rootsWithSlash;
+    }
+
+    public boolean isFilterCollectionName(final String name) {
+        return this.filterCollectionNames.contains(name);
+    }
+
+    public Set<String> getFilterCollectionNames() {
+        return this.filterCollectionNames;
+    }
+
+    public DB getDatabase() {
+        return this.database;
+    }
+}
diff --git a/src/main/java/org/apache/sling/mongodb/impl/MongoDBResource.java b/src/main/java/org/apache/sling/mongodb/impl/MongoDBResource.java
new file mode 100644
index 0000000..6ad685c
--- /dev/null
+++ b/src/main/java/org/apache/sling/mongodb/impl/MongoDBResource.java
@@ -0,0 +1,146 @@
+/*
+ * 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.mongodb.impl;
+
+import java.util.Map;
+
+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 com.mongodb.DBObject;
+
+public class MongoDBResource extends AbstractResource {
+
+    /** The complete resource path. */
+    private final String resourcePath;
+
+    /** The collection */
+    private final String collection;
+
+    /** The resource resolver. */
+    private final ResourceResolver resourceResolver;
+
+    /** Metadata. */
+    private final ResourceMetadata metadata = new ResourceMetadata();
+
+    /** The db object. */
+    private DBObject dbObject;
+
+    /** The MongoDB resource provider. */
+    private final MongoDBResourceProvider provider;
+
+    public MongoDBResource(final ResourceResolver resolver,
+                    final String resourcePath,
+                    final String collection,
+                    final DBObject dbObject,
+                    final MongoDBResourceProvider provider) {
+        this.resourceResolver = resolver;
+        this.resourcePath = resourcePath;
+        this.collection = collection;
+        this.dbObject = dbObject;
+        this.provider = provider;
+    }
+
+    /**
+     * @see org.apache.sling.api.resource.Resource#getPath()
+     */
+    public String getPath() {
+        return this.resourcePath;
+    }
+
+    /**
+     * @see org.apache.sling.api.resource.Resource#getResourceType()
+     */
+    public String getResourceType() {
+        // get resource type from data
+        final Object rt = this.dbObject.get("sling:resourceType");
+        if ( rt != null ) {
+            return rt.toString();
+        }
+        return "nt:unstructured";
+    }
+
+    /**
+     * @see org.apache.sling.api.resource.Resource#getResourceSuperType()
+     */
+    public String getResourceSuperType() {
+        // get resource type from data
+        final Object rt = this.dbObject.get("sling:resourceSuperType");
+        if ( rt != null ) {
+            return rt.toString();
+        }
+        return null;
+    }
+
+    /**
+     * @see org.apache.sling.api.resource.Resource#getResourceMetadata()
+     */
+    public ResourceMetadata getResourceMetadata() {
+        return this.metadata;
+    }
+
+    /**
+     * @see org.apache.sling.api.resource.Resource#getResourceResolver()
+     */
+    public ResourceResolver getResourceResolver() {
+        return this.resourceResolver;
+    }
+
+    /**
+     * @see org.apache.sling.api.adapter.SlingAdaptable#adaptTo(java.lang.Class)
+     */
+    @SuppressWarnings("unchecked")
+    @Override
+    public <AdapterType> AdapterType adaptTo(final Class<AdapterType> type) {
+        if ( type == ValueMap.class || type == Map.class ) {
+            this.dbObject = this.provider.getUpdatedDBObject(this.resourcePath, this.dbObject);
+            return (AdapterType) new ReadableValueMap(this.dbObject);
+        } else if ( type == ModifiableValueMap.class ) {
+            this.dbObject = this.provider.getUpdatedDBObject(this.resourcePath, this.dbObject);
+            return (AdapterType) new ChangeableValueMap(this);
+        }
+
+        return super.adaptTo(type);
+    }
+
+    /**
+     * Return the collection.
+     */
+    public String getCollection() {
+        return this.collection;
+    }
+
+    /**
+     * Get the current properties.
+     */
+    public DBObject getProperties() {
+        return this.dbObject;
+    }
+
+    @Override
+    public String toString() {
+        return "MongoDBResource [resourcePath=" + resourcePath + ", dbPath=" + this.dbObject.get(MongoDBResourceProvider.PROP_PATH) + ", collection=" + collection
+                        + ", resourceResolver=" + resourceResolver + "]";
+    }
+
+    public void changed() {
+        this.provider.changed(this);
+    }
+}
diff --git a/src/main/java/org/apache/sling/mongodb/impl/MongoDBResourceProvider.java b/src/main/java/org/apache/sling/mongodb/impl/MongoDBResourceProvider.java
new file mode 100644
index 0000000..86a31c5
--- /dev/null
+++ b/src/main/java/org/apache/sling/mongodb/impl/MongoDBResourceProvider.java
@@ -0,0 +1,355 @@
+/*
+ * 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.mongodb.impl;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.sling.api.resource.ModifyingResourceProvider;
+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.api.resource.ResourceResolver;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.mongodb.BasicDBObject;
+import com.mongodb.BasicDBObjectBuilder;
+import com.mongodb.DBCollection;
+import com.mongodb.DBCursor;
+import com.mongodb.DBObject;
+import com.mongodb.QueryBuilder;
+
+/**
+ * The MongoDB resource provider creates resources based on MongoDB entries.
+ * The resources contain all properties stored in the MongoDB except those starting with a "_".
+ */
+public class MongoDBResourceProvider implements ResourceProvider, ModifyingResourceProvider {
+
+    /** The special path property containing the (relative) path of the resource in the tree. */
+    public static final String PROP_PATH = "_path";
+
+    /** The id property. */
+    public static final String PROP_ID = "_id";
+
+    /** Logger. */
+    private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+    /** The global context .*/
+    private final MongoDBContext context;
+
+    private final Map<String, MongoDBResource> changedResources = new HashMap<String, MongoDBResource>();
+
+    private final Set<String> deletedResources = new HashSet<String>();
+
+    public MongoDBResourceProvider(final MongoDBContext context) {
+        this.context = context;
+    }
+
+    /**
+     * @see org.apache.sling.api.resource.ModifyingResourceProvider#create(org.apache.sling.api.resource.ResourceResolver, java.lang.String, java.util.Map)
+     */
+    public Resource create(final ResourceResolver resolver, final String path, final Map<String, Object> properties)
+    throws PersistenceException {
+        final String[] info = this.extractResourceInfo(path);
+        if ( info != null && info.length == 2) {
+            final boolean deleted = this.deletedResources.remove(path);
+            final MongoDBResource oldResource = (MongoDBResource)this.getResource(resolver, path, info);
+            if ( !deleted && oldResource != null ) {
+                throw new PersistenceException("Resource already exists at " + path, null, path, null);
+            }
+            final DBObject dbObj = new BasicDBObject();
+            dbObj.put(PROP_PATH, info[1]);
+            if ( properties != null ) {
+                for(Map.Entry<String, Object> entry : properties.entrySet()) {
+                    final String key;
+                    if ( entry.getKey().startsWith("_") ) {
+                        key = "_" + entry.getKey();
+                    } else {
+                        key = entry.getKey();
+                    }
+                    dbObj.put(key, entry.getValue());
+                }
+            }
+            if ( deleted && oldResource != null ) {
+                dbObj.put(PROP_ID, oldResource.getProperties().get(PROP_ID));
+            }
+            final MongoDBResource rsrc = new MongoDBResource(resolver, path, info[0], dbObj, this);
+            this.changedResources.put(path, rsrc);
+
+            return rsrc;
+        }
+        throw new PersistenceException("Illegal path - unable to create resource at " + path, null, path, null);
+    }
+
+    /**
+     * @see org.apache.sling.api.resource.ModifyingResourceProvider#delete(org.apache.sling.api.resource.ResourceResolver, java.lang.String)
+     */
+    public void delete(final ResourceResolver resolver, final String path)
+    throws PersistenceException {
+        final Resource rsrc = this.getResource(resolver, path);
+        if ( rsrc != null ) {
+            // TODO - delete all child resources!
+            this.deletedResources.add(path);
+            this.changedResources.remove(path);
+        }
+    }
+
+    /**
+     * @see org.apache.sling.api.resource.ModifyingResourceProvider#revert()
+     */
+    public void revert() {
+        this.changedResources.clear();
+        this.deletedResources.clear();
+    }
+
+    /**
+     * @see org.apache.sling.api.resource.ModifyingResourceProvider#commit()
+     */
+    public void commit() throws PersistenceException {
+        try {
+            for(final String deleted : this.deletedResources) {
+                final String[] info = this.extractResourceInfo(deleted);
+
+                // check if the database still exists
+                if ( this.hasDatabase(info[0]) ) {
+                    final DBCollection col = this.context.getDatabase().getCollection(info[0]);
+                    if ( col != null ) {
+                        col.findAndRemove(QueryBuilder.start(PROP_PATH).is(info[1]).get());
+                    }
+                }
+            }
+            for(final MongoDBResource changed : this.changedResources.values()) {
+
+                final DBCollection col = this.context.getDatabase().getCollection(changed.getCollection());
+                if ( col != null ) {
+                    // create or update?
+                    if ( changed.getProperties().get(PROP_ID) != null ) {
+                        col.update(QueryBuilder.start(PROP_PATH).is(changed.getProperties().get(PROP_PATH)).get(),
+                                        changed.getProperties());
+                    } else {
+                        // create
+                        col.save(changed.getProperties());
+                    }
+                } else {
+                    throw new PersistenceException("Unable to create collection " + changed.getCollection(), null, changed.getPath(), null);
+                }
+            }
+        } finally {
+            this.revert();
+        }
+    }
+
+    /**
+     * @see org.apache.sling.api.resource.ModifyingResourceProvider#hasChanges()
+     */
+    public boolean hasChanges() {
+        return this.changedResources.size() > 0 || this.deletedResources.size() > 0;
+    }
+
+    /**
+     * @see org.apache.sling.api.resource.ResourceProvider#getResource(org.apache.sling.api.resource.ResourceResolver, java.lang.String)
+     */
+    public Resource getResource(final ResourceResolver resourceResolver, final String path) {
+        if ( this.deletedResources.contains(path) ) {
+            return null;
+        }
+        if ( this.changedResources.containsKey(path) ) {
+            return this.changedResources.get(path);
+        }
+        final String[] info = this.extractResourceInfo(path);
+        if ( info != null ) {
+            return this.getResource(resourceResolver, path, info);
+        }
+        return null;
+    }
+
+    public void changed(final MongoDBResource resource) {
+        this.deletedResources.remove(resource.getPath());
+        this.changedResources.put(resource.getPath(), resource);
+    }
+
+    /**
+     * @see org.apache.sling.api.resource.ResourceProvider#listChildren(org.apache.sling.api.resource.Resource)
+     */
+    public Iterator<Resource> listChildren(final Resource parent) {
+        final String[] info = this.extractResourceInfo(parent.getPath());
+        if ( info != null ) {
+            if ( info.length == 0 ) {
+                // all collections
+                final Set<String> names = new HashSet<String>(context.getDatabase().getCollectionNames());
+                names.removeAll(this.context.getFilterCollectionNames());
+                final Iterator<String> i = names.iterator();
+                return new Iterator<Resource>() {
+
+                    public boolean hasNext() {
+                        return i.hasNext();
+                    }
+
+                    public Resource next() {
+                        final String name = i.next();
+                        return new MongoDBCollectionResource(parent.getResourceResolver(), parent.getPath() + '/' + name);
+                    }
+
+                    public void remove() {
+                        throw new UnsupportedOperationException("remove");
+                    }
+
+                };
+            }
+            if ( this.hasDatabase(info[0]) ) {
+                final DBCollection col = this.context.getDatabase().getCollection(info[0]);
+                if ( col != null ) {
+                    final String pattern;
+                    if ( info.length == 1 ) {
+                        pattern = "^([^/])*$";
+                    } else {
+                        pattern = "^" + Pattern.quote(info[1]) + "/([^/])*$";
+                    }
+
+                    final DBObject query = QueryBuilder.start(PROP_PATH).regex(Pattern.compile(pattern)).get();
+                    final DBCursor cur = col.find(query).
+                                    sort(BasicDBObjectBuilder.start(PROP_PATH, 1).get());
+                    return new Iterator<Resource>() {
+
+                        public boolean hasNext() {
+                            return cur.hasNext();
+                        }
+
+                        public Resource next() {
+                            final DBObject obj = cur.next();
+                            final String objPath = obj.get(PROP_PATH).toString();
+                            final int lastSlash = objPath.lastIndexOf('/');
+                            final String name;
+                            if (lastSlash == -1) {
+                                name = objPath;
+                            } else {
+                                name = objPath.substring(lastSlash + 1);
+                            }
+                            return new MongoDBResource(parent.getResourceResolver(),
+                                            parent.getPath() + '/' + name,
+                                            info[0],
+                                            obj,
+                                            MongoDBResourceProvider.this);
+                        }
+
+                        public void remove() {
+                            throw new UnsupportedOperationException("remove");
+                        }
+
+                    };
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * @see org.apache.sling.api.resource.ResourceProvider#getResource(org.apache.sling.api.resource.ResourceResolver, javax.servlet.http.HttpServletRequest, java.lang.String)
+     */
+    @SuppressWarnings("javadoc")
+    public Resource getResource(final ResourceResolver resourceResolver,
+                    final HttpServletRequest request,
+                    final String path) {
+        return this.getResource(resourceResolver, path);
+    }
+
+    /**
+     * Extract info about collection and path
+     */
+    private String[] extractResourceInfo(final String path) {
+        for(final String root : this.context.getRootsWithSlash()) {
+            if ( path.startsWith(root) ) {
+                if ( path.length() == root.length() ) {
+                    // special resource - show all collections
+                    return new String[0];
+                }
+                final String info = path.substring(root.length());
+                final int slashPos = info.indexOf('/');
+                if ( slashPos != -1 ) {
+                    return new String[] {info.substring(0, slashPos), info.substring(slashPos + 1)};
+                }
+                // special resource - collection
+                return new String[] {info};
+            }
+        }
+        for(final String root : this.context.getRoots()) {
+            if ( path.equals(root) ) {
+                // special resource - show all collections
+                return new String[0];
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Check if a database with a given name exists
+     */
+    private boolean hasDatabase(final String name) {
+        final Set<String> names = this.context.getDatabase().getCollectionNames();
+        return names.contains(name) && !this.context.isFilterCollectionName(name);
+    }
+
+    /**
+     * Create a resource
+     */
+    private Resource getResource(final ResourceResolver resourceResolver, final String path, final String[] info) {
+        if ( info.length == 0 ) {
+            // special resource : all collections
+            return new MongoDBCollectionResource(resourceResolver, path);
+        } else if ( info.length == 1 ) {
+            // special resource : collection
+            if ( this.hasDatabase(info[0]) ) {
+                return new MongoDBCollectionResource(resourceResolver, path);
+            }
+            return null;
+        }
+        logger.info("Searching {} in {}", info[1], info[0]);
+        if ( this.hasDatabase(info[0]) ) {
+            final DBCollection col = this.context.getDatabase().getCollection(info[0]);
+            if ( col != null ) {
+                final DBObject obj = col.findOne(QueryBuilder.start(PROP_PATH).is(info[1]).get());
+                logger.info("Result={}", obj);
+                if ( obj != null ) {
+                    return new MongoDBResource(resourceResolver,
+                                    path,
+                                    info[0],
+                                    obj,
+                                    this);
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Check if there is a newer db object for that path.
+     */
+    public DBObject getUpdatedDBObject(final String path, final DBObject dbObj) {
+        final MongoDBResource stored = this.changedResources.get(path);
+        if ( stored != null ) {
+            return stored.getProperties();
+        }
+        return dbObj;
+    }
+}
diff --git a/src/main/java/org/apache/sling/mongodb/impl/MongoDBResourceProviderFactory.java b/src/main/java/org/apache/sling/mongodb/impl/MongoDBResourceProviderFactory.java
new file mode 100644
index 0000000..a632215
--- /dev/null
+++ b/src/main/java/org/apache/sling/mongodb/impl/MongoDBResourceProviderFactory.java
@@ -0,0 +1,109 @@
+/*
+ * 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.mongodb.impl;
+
+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.PropertyUnbounded;
+import org.apache.felix.scr.annotations.Service;
+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.commons.osgi.PropertiesUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.mongodb.DB;
+import com.mongodb.Mongo;
+
+/**
+ * The MongoDB resource provider factory allows to provided resources stored
+ * in MongoDB.
+ */
+@Component(label="%factory.name",
+           description="%factory.description",
+           configurationFactory=true,
+           policy=ConfigurationPolicy.REQUIRE,
+           metatype=true)
+@Service(value=ResourceProviderFactory.class)
+@Properties({
+    @Property(name=ResourceProvider.ROOTS, value="/mongo")
+})
+public class MongoDBResourceProviderFactory implements ResourceProviderFactory {
+
+    private static final String DEFAULT_HOST = "localhost";
+
+    private static final int DEFAULT_PORT = 27017;
+
+    private static final String DEFAULT_DB = "sling";
+
+    @Property(value=DEFAULT_HOST)
+    private static final String PROP_HOST = "host";
+
+    @Property(intValue=DEFAULT_PORT)
+    private static final String PROP_PORT = "port";
+
+    @Property(value=DEFAULT_DB)
+    private static final String PROP_DB = "db";
+
+    @Property(unbounded=PropertyUnbounded.ARRAY, value="system.indexes")
+    private static final String PROP_FILTER_COLLECTIONS = "filter.collections";
+
+    /** Logger. */
+    private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+    /** The global context passed to each resource provider. */
+    private MongoDBContext context;
+
+    @Activate
+    protected void activate(final Map<String, Object> props) throws Exception {
+        final String host = PropertiesUtil.toString(props.get(PROP_HOST), DEFAULT_HOST);
+        final int port = PropertiesUtil.toInteger(props.get(PROP_PORT), DEFAULT_PORT);
+        final String db = PropertiesUtil.toString(props.get(PROP_DB), DEFAULT_DB);
+        logger.info("Starting MongoDB resource provider with host={}, port={}, db={}",
+                        new Object[] {host, port, db});
+
+        final Mongo m = new Mongo( host , port );
+        final DB database = m.getDB( db );
+        logger.info("Connected to database {}", database);
+
+        this.context = new MongoDBContext(database,
+                        PropertiesUtil.toStringArray(props.get(ResourceProvider.ROOTS)),
+                        PropertiesUtil.toStringArray(props.get(PROP_FILTER_COLLECTIONS)));
+    }
+
+    /**
+     * @see org.apache.sling.api.resource.ResourceProviderFactory#getResourceProvider(java.util.Map)
+     */
+    public ResourceProvider getResourceProvider(final Map<String, Object> authenticationInfo) throws LoginException {
+        // for now we allow anonymous access
+        return new MongoDBResourceProvider(this.context);
+    }
+
+    /**
+     * @see org.apache.sling.api.resource.ResourceProviderFactory#getAdministrativeResourceProvider(java.util.Map)
+     */
+    public ResourceProvider getAdministrativeResourceProvider(final Map<String, Object> authenticationInfo) throws LoginException {
+        // for now we allow anonymous access
+        return new MongoDBResourceProvider(this.context);
+    }
+}
diff --git a/src/main/java/org/apache/sling/mongodb/impl/ReadableValueMap.java b/src/main/java/org/apache/sling/mongodb/impl/ReadableValueMap.java
new file mode 100644
index 0000000..8da52d8
--- /dev/null
+++ b/src/main/java/org/apache/sling/mongodb/impl/ReadableValueMap.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.mongodb.impl;
+
+import java.lang.reflect.Array;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.sling.api.resource.ValueMap;
+
+import com.mongodb.DBObject;
+
+public class ReadableValueMap implements ValueMap {
+
+    protected Map<String, Object> valueMap;
+
+    public ReadableValueMap(final DBObject dbObject) {
+        this.createValueMap(dbObject);
+    }
+
+    protected void createValueMap(final DBObject dbObject) {
+        @SuppressWarnings("unchecked")
+        final Map<String, Object> map = new HashMap<String, Object>(dbObject.toMap());
+        final Set<String> names = new HashSet<String>(map.keySet());
+        for(final String name : names) {
+            if ( name.startsWith("__") ) {
+                final Object value = map.remove(name);
+                map.put(name.substring(1), value);
+            } else if ( name.startsWith("_") ) {
+                // remove internal props, like _id, _path
+                map.remove(name);
+            }
+        }
+        this.valueMap = Collections.unmodifiableMap(map);
+    }
+
+    /**
+     * @see java.util.Map#containsKey(java.lang.Object)
+     */
+    public boolean containsKey(final Object key) {
+        return this.valueMap.containsKey(key);
+    }
+
+    /**
+     * @see java.util.Map#containsValue(java.lang.Object)
+     */
+    public boolean containsValue(final Object value) {
+        return this.valueMap.containsValue(value);
+    }
+
+    /**
+     * @see java.util.Map#entrySet()
+     */
+    public Set<java.util.Map.Entry<String, Object>> entrySet() {
+        return this.valueMap.entrySet();
+    }
+
+    /**
+     * @see java.util.Map#get(java.lang.Object)
+     */
+    public Object get(final Object key) {
+        return this.valueMap.get(key);
+    }
+
+    /**
+     * @see java.util.Map#isEmpty()
+     */
+    public boolean isEmpty() {
+        return this.valueMap.isEmpty();
+    }
+
+    /**
+     * @see java.util.Map#keySet()
+     */
+    public Set<String> keySet() {
+        return this.valueMap.keySet();
+    }
+
+    /**
+     * @see java.util.Map#size()
+     */
+    public int size() {
+        return this.valueMap.size();
+    }
+
+    /**
+     * @see java.util.Map#values()
+     */
+    public Collection<Object> values() {
+        return this.valueMap.values();
+    }
+
+    /**
+     * @see org.apache.sling.api.resource.ValueMap#get(java.lang.String, java.lang.Class)
+     */
+    @SuppressWarnings("unchecked")
+    public <T> T get(final String key, final Class<T> type) {
+        if (type == null) {
+            return (T) get(key);
+        }
+
+        final Object val = this.get(key);
+        if ( val == null ) {
+            return null;
+        }
+        return convertToType(val, type);
+    }
+
+    /**
+     * @see org.apache.sling.api.resource.ValueMap#get(java.lang.String, java.lang.Object)
+     */
+    @SuppressWarnings("unchecked")
+    public <T> T get(final String key,final T defaultValue) {
+        if (defaultValue == null) {
+            return (T) get(key);
+        }
+
+        T value = get(key, (Class<T>) defaultValue.getClass());
+        if (value == null) {
+            value = defaultValue;
+        }
+
+        return value;
+    }
+
+    /**
+     * @see java.util.Map#clear()
+     */
+    public void clear() {
+        throw new UnsupportedOperationException("clear");
+    }
+
+    /**
+     * @see java.util.Map#put(java.lang.Object, java.lang.Object)
+     */
+    public Object put(String key, Object value) {
+        throw new UnsupportedOperationException("put");
+    }
+
+    /**
+     * @see java.util.Map#putAll(java.util.Map)
+     */
+    public void putAll(Map<? extends String, ? extends Object> m) {
+        throw new UnsupportedOperationException("putAll");
+    }
+
+    /**
+     * @see java.util.Map#remove(java.lang.Object)
+     */
+    public Object remove(Object key) {
+        throw new UnsupportedOperationException("remove");
+    }
+
+    /**
+     * Converts the object to the given type.
+     * @param obj object
+     * @param type type
+     * @return the converted object
+     */
+    @SuppressWarnings("unchecked")
+    private <T> T convertToType(final Object obj, final Class<T> type) {
+        // todo: do smarter checks
+        try {
+            if (obj == null) {
+                return null;
+            } else if (type.isAssignableFrom(obj.getClass())) {
+                return (T) obj;
+            } else if (type.isArray()) {
+                return (T) convertToArray(obj, type.getComponentType());
+            } else if (type == String.class) {
+                return (T) String.valueOf(obj);
+            } else if (type == Integer.class) {
+                return (T) (Integer) Integer.parseInt(obj.toString());
+            } else if (type == Long.class) {
+                return (T) (Long) Long.parseLong(obj.toString());
+            } else if (type == Double.class) {
+                return (T) (Double) Double.parseDouble(obj.toString());
+            } else if (type == Boolean.class) {
+                return (T) (Boolean) Boolean.parseBoolean(obj.toString());
+            } else {
+                return null;
+            }
+        } catch (final NumberFormatException e) {
+            return null;
+        }
+    }
+
+    /**
+     * Converts the object to an array of the given type
+     * @param obj tje object or object array
+     * @param type the component type of the array
+     * @return and array of type T
+     */
+    private <T> T[] convertToArray(Object obj, Class<T> type) {
+        List<T> values = new LinkedList<T>();
+        if (obj.getClass().isArray()) {
+            for (Object o: (Object[]) obj) {
+                values.add(convertToType(o, type));
+            }
+        } else {
+            values.add(convertToType(obj, type));
+        }
+        @SuppressWarnings("unchecked")
+        T[] result = (T[]) Array.newInstance(type, values.size());
+        return values.toArray(result);
+    }
+}
diff --git a/src/main/resources/OSGI-INF/metatype/metatype.properties b/src/main/resources/OSGI-INF/metatype/metatype.properties
new file mode 100644
index 0000000..4d380cc
--- /dev/null
+++ b/src/main/resources/OSGI-INF/metatype/metatype.properties
@@ -0,0 +1,42 @@
+#
+#  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.
+#
+
+
+#
+# This file contains localization strings for configuration labels and
+# descriptions as used in the metatype.xml descriptor generated by the
+# the SCR plugin
+
+#
+# Localizations for FsResourceProvider configuration
+factory.name = Apache Sling MongoDB Resource Provider
+factory.description = Configure an instance of the MongoDB \
+ resource provider in terms of provider root and connection
+
+provider.roots.name = Provider Root
+provider.roots.description = Location in the virtual resource tree where the \
+ resources are mapped in. This property must not be an empty string.
+host.name = MongoDB Host
+host.description = The host to connect to.
+
+port.name = MongoDB Port
+port.description = The port to connect to.
+
+db.name MongoDB Database
+db.description = The database to use.

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.