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 <[email protected]> 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 "[email protected]" <[email protected]>.
