You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by cz...@apache.org on 2012/07/30 18:20:18 UTC

svn commit: r1367147 - in /sling/trunk: bundles/api/src/main/java/org/apache/sling/api/resource/ bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/ bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/ bundles/jcr/re...

Author: cziegeler
Date: Mon Jul 30 16:20:17 2012
New Revision: 1367147

URL: http://svn.apache.org/viewvc?rev=1367147&view=rev
Log:
SLING-2530 : Implement CRUD based on resources

Added:
    sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/JcrModifiableValueMap.java   (with props)
Modified:
    sling/trunk/bundles/api/src/main/java/org/apache/sling/api/resource/ModifiableValueMap.java
    sling/trunk/bundles/api/src/main/java/org/apache/sling/api/resource/QueriableResourceProvider.java
    sling/trunk/bundles/api/src/main/java/org/apache/sling/api/resource/ResourceProviderFactory.java
    sling/trunk/bundles/api/src/main/java/org/apache/sling/api/resource/ResourceResolver.java
    sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/JcrModifiablePropertyMap.java
    sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrNodeResource.java
    sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrResourceProvider.java
    sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrResourceProviderFactory.java
    sling/trunk/bundles/jcr/resource/src/test/java/org/apache/sling/jcr/resource/internal/JcrResourceListenerTest.java
    sling/trunk/bundles/jcr/resource/src/test/java/org/apache/sling/jcr/resource/internal/helper/jcr/MockResourceResolver.java
    sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/ResourceResolverImpl.java
    sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/helper/SortedProviderList.java
    sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/tree/ProviderHandler.java
    sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/tree/RootResourceProviderEntry.java
    sling/trunk/bundles/resourceresolver/src/test/java/org/apache/sling/resourceresolver/impl/ResourceResolverImplTest.java
    sling/trunk/bundles/resourceresolver/src/test/java/org/apache/sling/resourceresolver/impl/helper/SortedProviderListTest.java
    sling/trunk/bundles/resourceresolver/src/test/java/org/apache/sling/resourceresolver/impl/tree/ResourceProviderEntryTest.java
    sling/trunk/launchpad/integration-tests/src/main/resources/integration-test/crud/crud-test.jsp

Modified: sling/trunk/bundles/api/src/main/java/org/apache/sling/api/resource/ModifiableValueMap.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/api/src/main/java/org/apache/sling/api/resource/ModifiableValueMap.java?rev=1367147&r1=1367146&r2=1367147&view=diff
==============================================================================
--- sling/trunk/bundles/api/src/main/java/org/apache/sling/api/resource/ModifiableValueMap.java (original)
+++ sling/trunk/bundles/api/src/main/java/org/apache/sling/api/resource/ModifiableValueMap.java Mon Jul 30 16:20:17 2012
@@ -21,42 +21,37 @@ package org.apache.sling.api.resource;
 /**
  * The <code>ModifiableValueMap</code> is an extension
  * of the {@link ValueMap} which allows to modify and
- * persist properties. All changes to this map are transient
- * and only available through this map instance.
- *
- * Changes can be pushed into the transient persistence layer
- * with a call to {@link #update()}. This causes the changes
- * to be stored in the persistence layer for committing. Once
- * {@link ResourceResolver#commit()} is called, the
+ * persist properties. All changes to this map are
+ * stored in the transient layer of the resource resolver
+ * or more precisely in the transient layer of the
+ * resource provider managing this resource.
+ * <p>
+ * Once{@link ResourceResolver#commit()} is called, the
  * changes are finally persisted.
- *
- * Note, that each time you call {@link Resource#adaptTo(Class)}
- * you get a new map instance which does not share modified
- * properties with other representations until {@link #update()}
- * is called. In general, to avoid confusion, it's better to use
- * one modifiable value map for a resource per resource resolver.
- *
+ * <p>
+ * The modifiable value map is only changeable through
+ * one of these methods
+ * <ul>
+ *  <li>{@link #put(String, Object)}</li>
+ *  <li>{@link #putAll(java.util.Map)}</li>
+ *  <li>{@link #remove(Object)}</li>
+ * </ul>
+ * <p>
+ * The map is not modifiable through the collection provided
+ * by
+ * <ul>
+ *  <li>{@link #entrySet()}</li>
+ *  <li>{@link #keySet()}</li>
+ *  <li>{@link #values()}</li>
+ * </ul>
+ * or one of these methods:
+ * <ul>
+ *  <li>{@link #clear()}</li>
+ * </ul>
+ * <p>
  * @since 2.2
  */
 public interface ModifiableValueMap extends ValueMap {
 
-    /**
-     * Persists the changes in the transient persistence layer.
-     * Once update is called this map has no temporary changes
-     * any more.
-     * A call to {@link ResourceResolver#commit()} is required
-     * to permanently persist the changes.
-     * @throws PersistenceException If the changes can't be persisted.
-     */
-    void update() throws PersistenceException;
-
-    /**
-     * Revert temporary changes.
-     */
-    void revert();
-
-    /**
-     * Are there any temporary changes?
-     */
-    boolean hasChanges();
+    // just a marker
 }

Modified: sling/trunk/bundles/api/src/main/java/org/apache/sling/api/resource/QueriableResourceProvider.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/api/src/main/java/org/apache/sling/api/resource/QueriableResourceProvider.java?rev=1367147&r1=1367146&r2=1367147&view=diff
==============================================================================
--- sling/trunk/bundles/api/src/main/java/org/apache/sling/api/resource/QueriableResourceProvider.java (original)
+++ sling/trunk/bundles/api/src/main/java/org/apache/sling/api/resource/QueriableResourceProvider.java Mon Jul 30 16:20:17 2012
@@ -29,14 +29,19 @@ import java.util.Map;
  * {@link ResourceProvider} interface and {@link ResourceProvider}s
  * returned through a {@link ResourceProviderFactory}.
  *
- * TODO - what should the resource provider do, if the language is not supported?
- * TODO - is returning null allowed?
- *
  * @since 2.2.0
  */
 public interface QueriableResourceProvider {
 
     /**
+     * The name of the service registration property containing the supported
+     * languages of the resource provider (value is "provider.query.languages").
+     * If the resource provider is delivered by a {@link ResourceProviderFactory}
+     * this property should be declared on the factory.
+     */
+    String LANGUAGES = "provider.query.languages";
+
+    /**
      * Searches for resources using the given query formulated in the given
      * language.
      * <p>
@@ -50,10 +55,11 @@ public interface QueriableResourceProvid
      * @param query The query string to use to find the resources.
      * @param language The language in which the query is formulated.
      * @return An <code>Iterator</code> of {@link Resource} objects matching the
-     *         query.
+     *         query. If no resources match, <code>null</code> might be
+     *         returned instead of an empty iterator.
      * @throws QuerySyntaxException If the query is not syntactically correct
-     *             according to the query language indicator of if the query
-     *             language is not supported.
+     *             according to the query language indicator or if the query
+     *             language is not supported as specified in {@link #LANGUAGES}.
      * @throws org.apache.sling.api.SlingException If an error occurrs querying
      *             for the resources.
      * @throws IllegalStateException if this resource provider has already been
@@ -78,10 +84,11 @@ public interface QueriableResourceProvid
      * @param query The query string to use to find the resources.
      * @param language The language in which the query is formulated.
      * @return An <code>Iterator</code> of <code>Map</code> instances providing
-     *         access to the query result.
+     *         access to the query result. If no resources match, <code>null</code>
+     *         might be returned instead of an empty iterator.
      * @throws QuerySyntaxException If the query is not syntactically correct
-     *             according to the query language indicator of if the query
-     *             language is not supported.
+     *             according to the query language indicator or if the query
+     *             language is not supported as specified in {@link #LANGUAGES}.
      * @throws org.apache.sling.api.SlingException If an error occurrs querying
      *             for the resources.
      * @throws IllegalStateException if this resource provider has already been

Modified: sling/trunk/bundles/api/src/main/java/org/apache/sling/api/resource/ResourceProviderFactory.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/api/src/main/java/org/apache/sling/api/resource/ResourceProviderFactory.java?rev=1367147&r1=1367146&r2=1367147&view=diff
==============================================================================
--- sling/trunk/bundles/api/src/main/java/org/apache/sling/api/resource/ResourceProviderFactory.java (original)
+++ sling/trunk/bundles/api/src/main/java/org/apache/sling/api/resource/ResourceProviderFactory.java Mon Jul 30 16:20:17 2012
@@ -22,7 +22,12 @@ import java.util.Map;
 
 /**
  * The <code>ResourceProviderFactory</code> defines the service API to get and
- * create <code>ResourceProviders</code>s.
+ * create <code>ResourceProviders</code>s dynamically on a per usage base.
+ *
+ * Instead of sharing a resource provider between resource resolvers, the
+ * factory allows to create an own instance of a resource provider per resource
+ * resolver.
+ * The factory also supports authentication.
  * <p>
  * If the resource provider is not used anymore and implements the {@link DynamicResourceProvider}
  * interface, the close method should be called.
@@ -45,8 +50,8 @@ public interface ResourceProviderFactory
      * Generally this map will contain a user name and password to authenticate.
      * <p>
      * If the <code>authenticationInfo</code> map is <code>null</code> the
-     * <code>ResourceProvider</code> returned will generally not be authenticated and only provide
-     * minimal privileges, if any at all.
+     * <code>ResourceProvider</code> returned will generally not be authenticated
+     * and only provide minimal privileges, if any at all.
      *
      * @param authenticationInfo
      *            A map of further credential information which may be used by

Modified: sling/trunk/bundles/api/src/main/java/org/apache/sling/api/resource/ResourceResolver.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/api/src/main/java/org/apache/sling/api/resource/ResourceResolver.java?rev=1367147&r1=1367146&r2=1367147&view=diff
==============================================================================
--- sling/trunk/bundles/api/src/main/java/org/apache/sling/api/resource/ResourceResolver.java (original)
+++ sling/trunk/bundles/api/src/main/java/org/apache/sling/api/resource/ResourceResolver.java Mon Jul 30 16:20:17 2012
@@ -367,12 +367,14 @@ public interface ResourceResolver extend
      * provided by the query result.
      *
      * @param query The query string to use to find the resources.
-     * @param language The language in which the query is formulated.
+     * @param language The language in which the query is formulated. The
+     *                 language should always be specified. However for
+     *                 compatibility with older version, if no language
+     *                 is specified, "xpath" is used.
      * @return An <code>Iterator</code> of {@link Resource} objects matching the
      *         query.
      * @throws QuerySyntaxException If the query is not syntactically correct
-     *             according to the query language indicator of if the query
-     *             language is not supported.
+     *             according to the query language indicator.
      * @throws org.apache.sling.api.SlingException If an error occurrs querying
      *             for the resources.
      * @throws IllegalStateException if this resource resolver has already been
@@ -395,12 +397,14 @@ public interface ResourceResolver extend
      * for a value of property type <em>Boolean</em>.
      *
      * @param query The query string to use to find the resources.
-     * @param language The language in which the query is formulated.
+     * @param language The language in which the query is formulated. The
+     *                 language should always be specified. However for
+     *                 compatibility with older version, if no language
+     *                 is specified, "xpath" is used.
      * @return An <code>Iterator</code> of <code>Map</code> instances providing
      *         access to the query result.
      * @throws QuerySyntaxException If the query is not syntactically correct
-     *             according to the query language indicator of if the query
-     *             language is not supported.
+     *             according to the query language indicator.
      * @throws org.apache.sling.api.SlingException If an error occurrs querying
      *             for the resources.
      * @throws IllegalStateException if this resource resolver has already been
@@ -506,7 +510,10 @@ public interface ResourceResolver extend
      * Delete the resource
      * @param resource The resource to delete
      *
-     * @throws PersistenceException, NullPointerException, UnsupportedOperationException
+     * @throws NullPointerException if the resource parameter is null
+     * @throws UnsupportedOperationException If the resource provider does not allow to
+     *                                       delete this resource.
+     * @throws PersistenceException If the operation fails.
      */
     void delete(Resource resource)
     throws PersistenceException;
@@ -514,13 +521,17 @@ public interface ResourceResolver extend
     /**
      * Add a child resource to the given parent resource
      * @param parent The parent resource
-     * @param name   The name of the child resource
+     * @param name   The name of the child resource - this is a plain name, not a path!
      * @param properties Optional properties for the resource
      * @return The new resource
      *
-     * @throws PersistenceException, NullPointerException, UnsupportedOperationException
+     * @throws NullPointerException if the resource parameter or name parameter is null
+     * @throws IllegalArgumentException if the name contains a slash
+     * @throws UnsupportedOperationException If the resource provider does not allow to
+     *                                       create a resource at that location.
+     * @throws PersistenceException If the operation fails.
      */
-    Resource addChild(Resource parent, String name, Map<String, Object> properties)
+    Resource create(Resource parent, String name, Map<String, Object> properties)
     throws PersistenceException;
 
     /**

Modified: sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/JcrModifiablePropertyMap.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/JcrModifiablePropertyMap.java?rev=1367147&r1=1367146&r2=1367147&view=diff
==============================================================================
--- sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/JcrModifiablePropertyMap.java (original)
+++ sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/JcrModifiablePropertyMap.java Mon Jul 30 16:20:17 2012
@@ -28,7 +28,6 @@ import javax.jcr.RepositoryException;
 import javax.jcr.Value;
 import javax.jcr.nodetype.NodeType;
 
-import org.apache.sling.api.resource.ModifiableValueMap;
 import org.apache.sling.api.resource.PersistableValueMap;
 import org.apache.sling.api.resource.PersistenceException;
 import org.apache.sling.jcr.resource.internal.helper.JcrPropertyMapCacheEntry;
@@ -39,7 +38,7 @@ import org.apache.sling.jcr.resource.int
  */
 public final class JcrModifiablePropertyMap
     extends JcrPropertyMap
-    implements PersistableValueMap, ModifiableValueMap {
+    implements PersistableValueMap {
 
     /** Set of removed and changed properties. */
     private Set<String> changedProperties;
@@ -177,29 +176,13 @@ public final class JcrModifiableProperty
     /**
      * @see org.apache.sling.api.resource.PersistableValueMap#save()
      */
+    @SuppressWarnings("javadoc")
     public void save() throws PersistenceException {
         if ( this.changedProperties == null || this.changedProperties.size() == 0 ) {
             // nothing has changed
             return;
         }
         try {
-            this.update();
-            getNode().getSession().save();
-        } catch (final RepositoryException re) {
-            throw new PersistenceException("Unable to persist changes.", re);
-        }
-    }
-
-    /**
-     * @see org.apache.sling.api.resource.ModifiableValueMap#update()
-     */
-    public void update() throws PersistenceException {
-        if ( this.changedProperties == null || this.changedProperties.size() == 0 ) {
-            // nothing has changed
-            return;
-        }
-
-        try {
             final Node node = getNode();
             // check for mixin types
             if ( this.changedProperties.contains(MIXIN_TYPES) ) {
@@ -227,23 +210,10 @@ public final class JcrModifiableProperty
                     }
                 }
             }
+            getNode().getSession().save();
             this.reset();
         } catch (final RepositoryException re) {
             throw new PersistenceException("Unable to persist changes.", re, getPath(), null);
         }
     }
-
-    /**
-     * @see org.apache.sling.api.resource.ModifiableValueMap#revert()
-     */
-    public void revert() {
-        this.reset();
-    }
-
-    /**
-     * @see org.apache.sling.api.resource.ModifiableValueMap#hasChanges()
-     */
-    public boolean hasChanges() {
-        return this.changedProperties != null && this.changedProperties.size() > 0;
-    }
 }

Added: sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/JcrModifiableValueMap.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/JcrModifiableValueMap.java?rev=1367147&view=auto
==============================================================================
--- sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/JcrModifiableValueMap.java (added)
+++ sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/JcrModifiableValueMap.java Mon Jul 30 16:20:17 2012
@@ -0,0 +1,670 @@
+/*
+ * 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.jcr.resource.internal;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Serializable;
+import java.lang.reflect.Array;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.PropertyIterator;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+import javax.jcr.ValueFormatException;
+
+import org.apache.jackrabbit.util.ISO9075;
+import org.apache.jackrabbit.util.Text;
+import org.apache.sling.api.resource.ModifiableValueMap;
+import org.apache.sling.jcr.resource.internal.helper.JcrPropertyMapCacheEntry;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This implementation of the value map allows to change
+ * the properies.
+ *
+ * TODO : This adds a lot of duplicate code - we should consolidate.
+ */
+public final class JcrModifiableValueMap
+    implements ModifiableValueMap {
+
+    /** Default logger */
+    private static Logger LOGGER = LoggerFactory.getLogger(JcrModifiableValueMap.class);
+
+    /** The underlying node. */
+    private final Node node;
+
+    /** A cache for the properties. */
+    final Map<String, JcrPropertyMapCacheEntry> cache;
+
+    /** A cache for the values. */
+    final Map<String, Object> valueCache;
+
+    /** Has the node been read completly? */
+    boolean fullyRead;
+
+    private final ClassLoader dynamicClassLoader;
+
+    /**
+     * Constructor
+     * @param node The underlying node.
+     * @param dynamicCL Dynamic class loader for loading serialized objects.
+     */
+    public JcrModifiableValueMap(final Node node, final ClassLoader dynamicCL) {
+        this.node = node;
+        this.cache = new LinkedHashMap<String, JcrPropertyMapCacheEntry>();
+        this.valueCache = new LinkedHashMap<String, Object>();
+        this.fullyRead = false;
+        this.dynamicClassLoader = dynamicCL;
+    }
+
+    /**
+     * Get the node.
+     */
+    private Node getNode() {
+        return node;
+    }
+
+    // ---------- ValueMap
+
+    private String checkKey(final String key) {
+        if ( key == null ) {
+            throw new NullPointerException("Key must not be null.");
+        }
+        if ( key.startsWith("./") ) {
+            return key.substring(2);
+        }
+        return key;
+    }
+
+    /**
+     * @see org.apache.sling.api.resource.ValueMap#get(java.lang.String, java.lang.Class)
+     */
+    @SuppressWarnings("unchecked")
+    public <T> T get(final String aKey, final Class<T> type) {
+        final String key = checkKey(aKey);
+        if (type == null) {
+            return (T) get(key);
+        }
+
+        final JcrPropertyMapCacheEntry entry = this.read(key);
+        if ( entry == null ) {
+            return null;
+        }
+        return convertToType(entry, type);
+    }
+
+    /**
+     * @see org.apache.sling.api.resource.ValueMap#get(java.lang.String, java.lang.Object)
+     */
+    @SuppressWarnings("unchecked")
+    public <T> T get(final String aKey,final T defaultValue) {
+        final String key = checkKey(aKey);
+        if (defaultValue == null) {
+            return (T) get(key);
+        }
+
+        // special handling in case the default value implements one
+        // of the interface types supported by the convertToType method
+        Class<T> type = (Class<T>) normalizeClass(defaultValue.getClass());
+
+        T value = get(key, type);
+        if (value == null) {
+            value = defaultValue;
+        }
+
+        return value;
+    }
+
+    // ---------- Map
+
+    /**
+     * @see java.util.Map#get(java.lang.Object)
+     */
+    public Object get(final Object aKey) {
+        final String key = checkKey(aKey.toString());
+        final JcrPropertyMapCacheEntry entry = this.read(key);
+        final Object value = (entry == null ? null : entry.getDefaultValueOrNull());
+        return value;
+    }
+
+    /**
+     * @see java.util.Map#containsKey(java.lang.Object)
+     */
+    public boolean containsKey(final Object key) {
+        return get(key) != null;
+    }
+
+    /**
+     * @see java.util.Map#containsValue(java.lang.Object)
+     */
+    public boolean containsValue(final Object value) {
+        readFully();
+        return valueCache.containsValue(value);
+    }
+
+    /**
+     * @see java.util.Map#isEmpty()
+     */
+    public boolean isEmpty() {
+        return size() == 0;
+    }
+
+    /**
+     * @see java.util.Map#size()
+     */
+    public int size() {
+        readFully();
+        return cache.size();
+    }
+
+    /**
+     * @see java.util.Map#entrySet()
+     */
+    public Set<java.util.Map.Entry<String, Object>> entrySet() {
+        readFully();
+        final Map<String, Object> sourceMap;
+        if (cache.size() == valueCache.size()) {
+            sourceMap = valueCache;
+        } else {
+            sourceMap = transformEntries(cache);
+        }
+        return Collections.unmodifiableSet(sourceMap.entrySet());
+    }
+
+    /**
+     * @see java.util.Map#keySet()
+     */
+    public Set<String> keySet() {
+        readFully();
+        return Collections.unmodifiableSet(cache.keySet());
+    }
+
+    /**
+     * @see java.util.Map#values()
+     */
+    public Collection<Object> values() {
+        readFully();
+        final Map<String, Object> sourceMap;
+        if (cache.size() == valueCache.size()) {
+            sourceMap = valueCache;
+        } else {
+            sourceMap = transformEntries(cache);
+        }
+        return Collections.unmodifiableCollection(sourceMap.values());
+    }
+
+    /**
+     * Return the path of the current node.
+     *
+     * @throws IllegalStateException If a repository exception occurs
+     */
+    public String getPath() {
+        try {
+            return node.getPath();
+        } catch (final RepositoryException e) {
+            throw new IllegalStateException(e);
+        }
+    }
+
+    // ---------- Helpers to access the node's property ------------------------
+
+    /**
+     * Put a single property into the cache
+     * @param prop
+     * @return
+     * @throws IllegalArgumentException if a repository exception occurs
+     */
+    private JcrPropertyMapCacheEntry cacheProperty(final Property prop) {
+        try {
+            // calculate the key
+            final String name = prop.getName();
+            String key = null;
+            if ( name.indexOf("_x") != -1 ) {
+                // for compatiblity with older versions we use the (wrong)
+                // ISO9075 path encoding
+                key = ISO9075.decode(name);
+                if ( key.equals(name) ) {
+                    key = null;
+                }
+            }
+            if ( key == null ) {
+                key = Text.unescapeIllegalJcrChars(name);
+            }
+            JcrPropertyMapCacheEntry entry = cache.get(key);
+            if ( entry == null ) {
+                entry = new JcrPropertyMapCacheEntry(prop);
+                cache.put(key, entry);
+
+                final Object defaultValue = entry.getDefaultValue();
+                if (defaultValue != null) {
+                    valueCache.put(key, entry.getDefaultValue());
+                }
+            }
+            return entry;
+        } catch (final RepositoryException re) {
+            throw new IllegalArgumentException(re);
+        }
+    }
+
+    /**
+     * Read a single property.
+     * @throws IllegalArgumentException if a repository exception occurs
+     */
+    JcrPropertyMapCacheEntry read(final String name) {
+        // check for empty key
+        if ( name.length() == 0 ) {
+            return null;
+        }
+        // if the name is a path, we should handle this differently
+        if ( name.indexOf('/') != -1 ) {
+            // first a compatibility check with the old (wrong) ISO9075
+            // encoding
+            final String path = ISO9075.encodePath(name);
+            try {
+                if ( node.hasProperty(path) ) {
+                    return new JcrPropertyMapCacheEntry(node.getProperty(path));
+                }
+            } catch (final RepositoryException re) {
+                throw new IllegalArgumentException(re);
+            }
+            // now we do a proper segment by segment encoding
+            final StringBuilder sb = new StringBuilder();
+            int pos = 0;
+            int lastPos = -1;
+            while ( pos < name.length() ) {
+                if ( name.charAt(pos) == '/' ) {
+                    if ( lastPos + 1 < pos ) {
+                        sb.append(Text.escapeIllegalJcrChars(name.substring(lastPos + 1, pos)));
+                    }
+                    sb.append('/');
+                    lastPos = pos;
+                }
+                pos++;
+            }
+            if ( lastPos + 1 < pos ) {
+                sb.append(Text.escapeIllegalJcrChars(name.substring(lastPos + 1)));
+            }
+            final String newPath = sb.toString();
+            try {
+                if ( node.hasProperty(newPath) ) {
+                    return new JcrPropertyMapCacheEntry(node.getProperty(newPath));
+                }
+            } catch (final RepositoryException re) {
+                throw new IllegalArgumentException(re);
+            }
+
+            return null;
+        }
+
+        // if the node has been completely read we can directly return
+        if ( fullyRead ) {
+            return cache.get(name);
+        }
+
+        try {
+            final String key = escapeKeyName(name);
+            if (node.hasProperty(key)) {
+                final Property prop = node.getProperty(key);
+                return cacheProperty(prop);
+            }
+        } catch (final RepositoryException re) {
+            throw new IllegalArgumentException(re);
+        }
+
+        try {
+            // for compatiblity with older versions we use the (wrong) ISO9075 path
+            // encoding
+            final String oldKey = ISO9075.encodePath(name);
+            if (node.hasProperty(oldKey)) {
+                final Property prop = node.getProperty(oldKey);
+                return cacheProperty(prop);
+            }
+        } catch (final RepositoryException re) {
+            // we ignore this
+        }
+
+        // property not found
+        return null;
+    }
+
+    /**
+     * Handles key name escaping by taking into consideration if it contains a
+     * registered prefix
+     *
+     * @param key
+     * @return escaped key name
+     */
+    protected String escapeKeyName(final String key) throws RepositoryException {
+        final int indexOfPrefix = key.indexOf(':');
+        // check if colon is neither the first nor the last character
+        if (indexOfPrefix > 0 && key.length() > indexOfPrefix + 1) {
+            final String prefix = key.substring(0, indexOfPrefix);
+            for (final String existingPrefix : getNode().getSession()
+                    .getNamespacePrefixes()) {
+                if (existingPrefix.equals(prefix)) {
+                    return prefix
+                            + ":"
+                            + Text.escapeIllegalJcrChars(key
+                                    .substring(indexOfPrefix + 1));
+                }
+            }
+        }
+        return Text.escapeIllegalJcrChars(key);
+    }
+
+    /**
+     * Read all properties.
+     * @throws IllegalArgumentException if a repository exception occurs
+     */
+    void readFully() {
+        if (!fullyRead) {
+            try {
+                final PropertyIterator pi = node.getProperties();
+                while (pi.hasNext()) {
+                    final Property prop = pi.nextProperty();
+                    this.cacheProperty(prop);
+                }
+                fullyRead = true;
+            } catch (final RepositoryException re) {
+                throw new IllegalArgumentException(re);
+            }
+        }
+    }
+
+    // ---------- Implementation helper
+
+    @SuppressWarnings("unchecked")
+    private <T> T convertToType(final JcrPropertyMapCacheEntry entry, Class<T> type) {
+        T result = null;
+
+        try {
+            final boolean array = type.isArray();
+
+            if (entry.isMulti) {
+
+                if (array) {
+
+                    result = (T) convertToArray(entry,
+                        type.getComponentType());
+
+                } else if (entry.values.length > 0) {
+
+                    result = convertToType(entry, -1, entry.values[0], type);
+
+                }
+
+            } else {
+
+                if (array) {
+
+                    result = (T) convertToArray(entry,
+                            type.getComponentType());
+
+                } else {
+
+                    result = convertToType(entry, -1, entry.values[0], type);
+
+                }
+            }
+
+        } catch (ValueFormatException vfe) {
+            LOGGER.info("converToType: Cannot convert value of " + entry.getDefaultValueOrNull()
+                + " to " + type, vfe);
+        } catch (RepositoryException re) {
+            LOGGER.info("converToType: Cannot get value of " + entry.getDefaultValueOrNull(), re);
+        }
+
+        // fall back to nothing
+        return result;
+    }
+
+    private <T> T[] convertToArray(final JcrPropertyMapCacheEntry entry, Class<T> type)
+    throws ValueFormatException, RepositoryException {
+        List<T> values = new ArrayList<T>();
+        for (int i = 0; i < entry.values.length; i++) {
+            T value = convertToType(entry, i, entry.values[i], type);
+            if (value != null) {
+                values.add(value);
+            }
+        }
+
+        @SuppressWarnings("unchecked")
+        T[] result = (T[]) Array.newInstance(type, values.size());
+
+        return values.toArray(result);
+    }
+
+    @SuppressWarnings("unchecked")
+    private <T> T convertToType(final JcrPropertyMapCacheEntry entry,
+                                final int index,
+                                final Value jcrValue,
+                                final Class<T> type)
+    throws ValueFormatException, RepositoryException {
+        Object defaultValue = entry.getDefaultValue();
+        if ( type.isInstance(defaultValue) ) {
+            return (T) defaultValue;
+        }
+
+        if (String.class == type) {
+            return (T) jcrValue.getString();
+
+        } else if (Byte.class == type) {
+            return (T) Byte.valueOf((byte) jcrValue.getLong());
+
+        } else if (Short.class == type) {
+            return (T) Short.valueOf((short) jcrValue.getLong());
+
+        } else if (Integer.class == type) {
+            return (T) Integer.valueOf((int) jcrValue.getLong());
+
+        } else if (Long.class == type) {
+            if (jcrValue.getType() == PropertyType.BINARY) {
+                if (index == -1) {
+                    return (T) Long.valueOf(entry.property.getLength());
+                }
+                return (T) Long.valueOf(entry.property.getLengths()[index]);
+            }
+            return (T) Long.valueOf(jcrValue.getLong());
+
+        } else if (Float.class == type) {
+            return (T) Float.valueOf((float) jcrValue.getDouble());
+
+        } else if (Double.class == type) {
+            return (T) Double.valueOf(jcrValue.getDouble());
+
+        } else if (BigDecimal.class == type) {
+            return (T) jcrValue.getDecimal();
+
+        } else if (Boolean.class == type) {
+            return (T) Boolean.valueOf(jcrValue.getBoolean());
+
+        } else if (Date.class == type) {
+            return (T) jcrValue.getDate().getTime();
+
+        } else if (Calendar.class == type) {
+            return (T) jcrValue.getDate();
+
+        } else if (Value.class == type) {
+            return (T) jcrValue;
+
+        } else if (Property.class == type) {
+            return (T) entry.property;
+
+        } else if (Serializable.class.isAssignableFrom(type)
+                && jcrValue.getType() == PropertyType.BINARY) {
+            ObjectInputStream ois = null;
+            try {
+                ois = new ObjectInputStream(jcrValue.getBinary().getStream(), this.dynamicClassLoader);
+                final Object obj = ois.readObject();
+                if ( type.isInstance(obj) ) {
+                    return (T)obj;
+                }
+            } catch (ClassNotFoundException cnfe) {
+                 // ignore and use fallback
+            } catch (IOException ioe) {
+                // ignore and use fallback
+            } finally {
+                if ( ois != null ) {
+                    try {
+                        ois.close();
+                    } catch (IOException ignore) {
+                        // ignore
+                    }
+                }
+            }
+        }
+
+        // fallback in case of unsupported type
+        return null;
+    }
+
+    private Class<?> normalizeClass(Class<?> type) {
+        if (Calendar.class.isAssignableFrom(type)) {
+            type = Calendar.class;
+        } else if (Date.class.isAssignableFrom(type)) {
+            type = Date.class;
+        } else if (Value.class.isAssignableFrom(type)) {
+            type = Value.class;
+        } else if (Property.class.isAssignableFrom(type)) {
+            type = Property.class;
+        }
+        return type;
+    }
+
+    private Map<String, Object> transformEntries( Map<String, JcrPropertyMapCacheEntry> map) {
+
+        Map<String, Object> transformedEntries = new LinkedHashMap<String, Object>(map.size());
+        for ( Map.Entry<String, JcrPropertyMapCacheEntry> entry : map.entrySet() )
+            transformedEntries.put(entry.getKey(), entry.getValue().getDefaultValueOrNull());
+
+        return transformedEntries;
+    }
+
+    /**
+     * This is an extended version of the object input stream which uses the
+     * thread context class loader.
+     */
+    private static class ObjectInputStream extends java.io.ObjectInputStream {
+
+        private ClassLoader classloader;
+
+        public ObjectInputStream(final InputStream in, final ClassLoader classLoader) throws IOException {
+            super(in);
+            this.classloader = classLoader;
+        }
+
+        /**
+         * @see java.io.ObjectInputStream#resolveClass(java.io.ObjectStreamClass)
+         */
+        @Override
+        protected Class<?> resolveClass(java.io.ObjectStreamClass classDesc) throws IOException, ClassNotFoundException {
+            if ( this.classloader != null ) {
+                return this.classloader.loadClass(classDesc.getName());
+            }
+            return super.resolveClass(classDesc);
+        }
+    }
+
+    // ---------- Map
+
+    /**
+     * @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 aKey, Object value) {
+        final String key = checkKey(aKey);
+        if ( key.indexOf('/') != -1 ) {
+            throw new IllegalArgumentException("Invalid key: " + key);
+        }
+        if ( value == null ) {
+            throw new NullPointerException("Value should not be null (key = " + key + ")");
+        }
+        readFully();
+        final Object oldValue = this.get(key);
+        try {
+            final JcrPropertyMapCacheEntry entry = new JcrPropertyMapCacheEntry(value, getNode().getSession());
+            this.cache.put(key, entry);
+            final String name = escapeKeyName(key);
+            if ( entry.isMulti ) {
+                node.setProperty(name, entry.values);
+            } else {
+                node.setProperty(name, entry.values[0]);
+            }
+        } catch (final RepositoryException re) {
+            throw new IllegalArgumentException("Value for key " + key + " can't be put into node: " + value, re);
+        }
+        this.valueCache.put(key, value);
+
+        return oldValue;
+    }
+
+    /**
+     * @see java.util.Map#putAll(java.util.Map)
+     */
+    public void putAll(Map<? extends String, ? extends Object> t) {
+        readFully();
+        if ( t != null ) {
+            final Iterator<?> i = t.entrySet().iterator();
+            while (i.hasNext() ) {
+                @SuppressWarnings("unchecked")
+                final Map.Entry<? extends String, ? extends Object> entry = (Map.Entry<? extends String, ? extends Object>) i.next();
+                put(entry.getKey(), entry.getValue());
+            }
+        }
+    }
+
+    /**
+     * @see java.util.Map#remove(java.lang.Object)
+     */
+    public Object remove(Object aKey) {
+        final String key = checkKey(aKey.toString());
+        readFully();
+        final Object oldValue = this.cache.remove(key);
+        this.valueCache.remove(key);
+        try {
+            final String name = escapeKeyName(key);
+            this.node.setProperty(name, (String)null);
+        } catch (final RepositoryException re) {
+            throw new IllegalArgumentException("Value for key " + key + " can't be removed from node.", re);
+        }
+
+        return oldValue;
+    }
+}

Propchange: sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/JcrModifiableValueMap.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/JcrModifiableValueMap.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision rev url

Propchange: sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/JcrModifiableValueMap.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Modified: sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrNodeResource.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrNodeResource.java?rev=1367147&r1=1367146&r2=1367147&view=diff
==============================================================================
--- sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrNodeResource.java (original)
+++ sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrNodeResource.java Mon Jul 30 16:20:17 2012
@@ -50,6 +50,7 @@ import org.apache.sling.api.resource.Val
 import org.apache.sling.jcr.resource.JcrModifiablePropertyMap;
 import org.apache.sling.jcr.resource.JcrPropertyMap;
 import org.apache.sling.jcr.resource.JcrResourceConstants;
+import org.apache.sling.jcr.resource.internal.JcrModifiableValueMap;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -127,7 +128,7 @@ public class JcrNodeResource extends Jcr
             return (Type) getURL(); // unchecked cast
         } else if (type == Map.class || type == ValueMap.class) {
             return (Type) new JcrPropertyMap(getNode(), this.dynamicClassLoader); // unchecked cast
-        } else if (type == PersistableValueMap.class || type == ModifiableValueMap.class ) {
+        } else if (type == PersistableValueMap.class ) {
             // check write
             try {
                 getNode().getSession().checkPermission(getNode().getPath(),
@@ -136,6 +137,23 @@ public class JcrNodeResource extends Jcr
             } catch (AccessControlException ace) {
                 // the user has no write permission, cannot adapt
                 LOGGER.debug(
+                    "adaptTo(PersistableValueMap): Cannot set properties on {}",
+                    this);
+            } catch (RepositoryException e) {
+                // some other problem, cannot adapt
+                LOGGER.debug(
+                    "adaptTo(PersistableValueMap): Unexpected problem for {}",
+                    this);
+            }
+        } else if (type == ModifiableValueMap.class ) {
+            // check write
+            try {
+                getNode().getSession().checkPermission(getNode().getPath(),
+                    "set_property");
+                return (Type) new JcrModifiableValueMap(getNode(), this.dynamicClassLoader);
+            } catch (AccessControlException ace) {
+                // the user has no write permission, cannot adapt
+                LOGGER.debug(
                     "adaptTo(ModifiableValueMap): Cannot set properties on {}",
                     this);
             } catch (RepositoryException e) {

Modified: sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrResourceProvider.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrResourceProvider.java?rev=1367147&r1=1367146&r2=1367147&view=diff
==============================================================================
--- sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrResourceProvider.java (original)
+++ sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrResourceProvider.java Mon Jul 30 16:20:17 2012
@@ -49,8 +49,8 @@ import org.apache.sling.api.resource.Res
 import org.apache.sling.api.resource.ResourceProvider;
 import org.apache.sling.api.resource.ResourceResolver;
 import org.apache.sling.api.resource.ResourceResolverFactory;
-import org.apache.sling.jcr.resource.JcrModifiablePropertyMap;
 import org.apache.sling.jcr.resource.JcrResourceUtil;
+import org.apache.sling.jcr.resource.internal.JcrModifiableValueMap;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -391,13 +391,16 @@ public class JcrResourceProvider
 
             if ( properties != null ) {
                 // create modifiable map
-                final JcrModifiablePropertyMap jcrMap = new JcrModifiablePropertyMap(node, this.dynamicClassLoader);
+                final JcrModifiableValueMap jcrMap = new JcrModifiableValueMap(node, this.dynamicClassLoader);
                 for(final Map.Entry<String, Object> entry : properties.entrySet()) {
                     if ( !"jcr:primaryType".equals(entry.getKey()) ) {
-                        jcrMap.put(entry.getKey(), entry.getValue());
+                        try {
+                            jcrMap.put(entry.getKey(), entry.getValue());
+                        } catch (final IllegalArgumentException iae) {
+                            throw new PersistenceException(iae.getMessage(), iae, path, entry.getKey());
+                        }
                     }
                 }
-                jcrMap.update();
             }
 
             return new JcrNodeResource(resolver, node, this.dynamicClassLoader);

Modified: sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrResourceProviderFactory.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrResourceProviderFactory.java?rev=1367147&r1=1367146&r2=1367147&view=diff
==============================================================================
--- sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrResourceProviderFactory.java (original)
+++ sling/trunk/bundles/jcr/resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrResourceProviderFactory.java Mon Jul 30 16:20:17 2012
@@ -25,6 +25,7 @@ import javax.jcr.Credentials;
 import javax.jcr.RepositoryException;
 import javax.jcr.Session;
 import javax.jcr.SimpleCredentials;
+import javax.jcr.query.Query;
 
 import org.apache.felix.scr.annotations.Component;
 import org.apache.felix.scr.annotations.Properties;
@@ -34,6 +35,7 @@ import org.apache.felix.scr.annotations.
 import org.apache.felix.scr.annotations.ReferencePolicy;
 import org.apache.felix.scr.annotations.Service;
 import org.apache.sling.api.resource.LoginException;
+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.api.resource.ResourceResolver;
@@ -53,7 +55,8 @@ import org.osgi.framework.Constants;
     @Property(name=ResourceProviderFactory.PROPERTY_REQUIRED, boolValue=true),
     @Property(name=ResourceProvider.ROOTS, value="/"),
     @Property(name = Constants.SERVICE_DESCRIPTION, value = "Apache Sling JCR Resource Provider Factory"),
-    @Property(name = Constants.SERVICE_VENDOR, value = "The Apache Software Foundation")
+    @Property(name = Constants.SERVICE_VENDOR, value = "The Apache Software Foundation"),
+    @Property(name = QueriableResourceProvider.LANGUAGES, value = {Query.XPATH, Query.SQL, Query.JCR_SQL2})
 })
 public class JcrResourceProviderFactory implements ResourceProviderFactory {
 

Modified: sling/trunk/bundles/jcr/resource/src/test/java/org/apache/sling/jcr/resource/internal/JcrResourceListenerTest.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/jcr/resource/src/test/java/org/apache/sling/jcr/resource/internal/JcrResourceListenerTest.java?rev=1367147&r1=1367146&r2=1367147&view=diff
==============================================================================
--- sling/trunk/bundles/jcr/resource/src/test/java/org/apache/sling/jcr/resource/internal/JcrResourceListenerTest.java (original)
+++ sling/trunk/bundles/jcr/resource/src/test/java/org/apache/sling/jcr/resource/internal/JcrResourceListenerTest.java Mon Jul 30 16:20:17 2012
@@ -206,7 +206,7 @@ public class JcrResourceListenerTest ext
                 // TODO Auto-generated method stub
             }
 
-            public Resource addChild(Resource parent, String name, Map<String, Object> properties) {
+            public Resource create(Resource parent, String name, Map<String, Object> properties) {
                 // TODO Auto-generated method stub
                 return null;
             }

Modified: sling/trunk/bundles/jcr/resource/src/test/java/org/apache/sling/jcr/resource/internal/helper/jcr/MockResourceResolver.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/jcr/resource/src/test/java/org/apache/sling/jcr/resource/internal/helper/jcr/MockResourceResolver.java?rev=1367147&r1=1367146&r2=1367147&view=diff
==============================================================================
--- sling/trunk/bundles/jcr/resource/src/test/java/org/apache/sling/jcr/resource/internal/helper/jcr/MockResourceResolver.java (original)
+++ sling/trunk/bundles/jcr/resource/src/test/java/org/apache/sling/jcr/resource/internal/helper/jcr/MockResourceResolver.java Mon Jul 30 16:20:17 2012
@@ -130,7 +130,7 @@ public class MockResourceResolver implem
         // TODO Auto-generated method stub
     }
 
-    public Resource addChild(Resource parent, String name, Map<String, Object> properties) {
+    public Resource create(Resource parent, String name, Map<String, Object> properties) {
         // TODO Auto-generated method stub
         return null;
     }

Modified: sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/ResourceResolverImpl.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/ResourceResolverImpl.java?rev=1367147&r1=1367146&r2=1367147&view=diff
==============================================================================
--- sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/ResourceResolverImpl.java (original)
+++ sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/ResourceResolverImpl.java Mon Jul 30 16:20:17 2012
@@ -618,6 +618,8 @@ public class ResourceResolverImpl extend
 
     // ---------- Querying resources
 
+    private static final String DEFAULT_QUERY_LANGUAGE = "xpath";
+
     /**
      * @see org.apache.sling.api.resource.ResourceResolver#findResources(java.lang.String,
      *      java.lang.String)
@@ -626,7 +628,8 @@ public class ResourceResolverImpl extend
         checkClosed();
 
         return new ResourceIteratorDecorator(this.factory.getResourceDecoratorTracker(),
-                        this.factory.getRootProviderEntry().findResources(this.context, this, query, language));
+                        this.factory.getRootProviderEntry().findResources(this.context, this, query,
+                                        language == null ? DEFAULT_QUERY_LANGUAGE : language));
     }
 
     /**
@@ -637,7 +640,8 @@ public class ResourceResolverImpl extend
     throws SlingException {
         checkClosed();
 
-        return this.factory.getRootProviderEntry().queryResources(this.context, query, language);
+        return this.factory.getRootProviderEntry().queryResources(this.context, query,
+                        language == null ? DEFAULT_QUERY_LANGUAGE : language);
     }
 
     /**
@@ -1014,9 +1018,9 @@ public class ResourceResolverImpl extend
     }
 
     /**
-     * @see org.apache.sling.api.resource.ResourceResolver#addChild(org.apache.sling.api.resource.Resource, java.lang.String, Map)
+     * @see org.apache.sling.api.resource.ResourceResolver#create(org.apache.sling.api.resource.Resource, java.lang.String, Map)
      */
-    public Resource addChild(final Resource parent, final String name, final Map<String, Object> properties)
+    public Resource create(final Resource parent, final String name, final Map<String, Object> properties)
     throws PersistenceException {
         // if parent or name is null, we get an NPE as stated in the API
         if ( name == null ) {
@@ -1024,7 +1028,7 @@ public class ResourceResolverImpl extend
         }
         // name should be a name not a path
         if ( name.indexOf("/") != -1 ) {
-            throw new PersistenceException("Name should not contain a slash: " + name, null, parent.getPath(), null);
+            throw new IllegalArgumentException("Name should not contain a slash: " + name);
         }
         final String path;
         if ( parent.getPath().equals("/") ) {

Modified: sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/helper/SortedProviderList.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/helper/SortedProviderList.java?rev=1367147&r1=1367146&r2=1367147&view=diff
==============================================================================
--- sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/helper/SortedProviderList.java (original)
+++ sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/helper/SortedProviderList.java Mon Jul 30 16:20:17 2012
@@ -25,6 +25,7 @@ import java.util.Iterator;
 import java.util.List;
 import java.util.NoSuchElementException;
 
+import org.apache.sling.resourceresolver.impl.tree.ProviderHandler;
 import org.apache.sling.resourceresolver.impl.tree.ResourceProviderFactoryHandler;
 import org.apache.sling.resourceresolver.impl.tree.ResourceProviderHandler;
 
@@ -32,7 +33,6 @@ import org.apache.sling.resourceresolver
  * Helper class to get a sorted list of resource providers which implement
  * a specific feature interface.
  */
-//TODO - Implement sorting
 public class SortedProviderList<T> {
 
     /** The feature interface class. */
@@ -49,11 +49,16 @@ public class SortedProviderList<T> {
         this.genericClass = genericClass;
     }
 
+    public interface Filter<T> {
+
+        boolean select(ProviderHandler handler, T provider);
+    }
+
     /**
      * Return an iterator for the current sorted list of providers
      * implementing the feature interface.
      */
-    public Iterator<T> getProviders(final ResourceResolverContext ctx) {
+    public Iterator<T> getProviders(final ResourceResolverContext ctx, final Filter<T> filter) {
         return new Iterator<T>() {
 
             private final Entry[] list = sortedList;
@@ -65,16 +70,20 @@ public class SortedProviderList<T> {
             private Object seek() {
                 Object result;
                 if ( this.index < list.length ) {
-                    result = list[this.index].object;
+                    final Entry entry = list[this.index];
+                    result = entry.object;
                     this.index++;
                     if ( result instanceof ResourceProviderFactoryHandler ) {
                         result = ((ResourceProviderFactoryHandler)result).getResourceProvider(ctx);
                         if ( !genericClass.isAssignableFrom(result.getClass())) {
                             result = null;
                         }
-                        if ( result == null ) {
-                            result = seek();
-                        }
+                    }
+                    if ( result != null && filter != null && !filter.select(entry.handler, (T)result)) {
+                        result = null;
+                    }
+                    if ( result == null ) {
+                        result = seek();
                     }
                 } else {
                     result = null;
@@ -115,11 +124,10 @@ public class SortedProviderList<T> {
      * Add an object to the list
      */
     private synchronized void addToList(final Object obj,
-                    final String[] roots,
-                    final Long serviceId) {
+                    final ProviderHandler handler) {
         final List<Entry> list = new ArrayList<Entry>();
         list.addAll(Arrays.asList(this.sortedList));
-        list.add(new Entry(obj, roots, serviceId));
+        list.add(new Entry(obj, handler));
         Collections.sort(list);
         this.sortedList = list.toArray(new Entry[list.size()]);
     }
@@ -147,7 +155,7 @@ public class SortedProviderList<T> {
      */
     public void add(final ResourceProviderHandler rpHandler) {
         if ( genericClass.isAssignableFrom(rpHandler.getResourceProvider().getClass())) {
-            this.addToList(rpHandler.getResourceProvider(), rpHandler.getRoots(), rpHandler.getServiceId());
+            this.addToList(rpHandler.getResourceProvider(), rpHandler);
         }
     }
 
@@ -157,7 +165,7 @@ public class SortedProviderList<T> {
      * This will be checked on demand.
      */
     public void add(final ResourceProviderFactoryHandler factory) {
-        this.addToList(factory, factory.getRoots(), factory.getServiceId());
+        this.addToList(factory, factory);
     }
 
     /**
@@ -179,18 +187,20 @@ public class SortedProviderList<T> {
     private static final class Entry implements Comparable<Entry> {
 
         private final String path;
-        private final Long   id;
+
+        public final ProviderHandler handler;
 
         public final Object object;
 
-        public Entry(final Object object, final String[] roots, final Long serviceId) {
-            this.id = serviceId;
+        public Entry(final Object object,
+                     final ProviderHandler handler) {
             this.object = object;
-            if ( roots != null ) {
-                this.path = roots[0];
+            if ( handler.getRoots() != null ) {
+                this.path = handler.getRoots()[0];
             } else {
                 this.path = "";
             }
+            this.handler = handler;
         }
 
         /**
@@ -199,10 +209,9 @@ public class SortedProviderList<T> {
         public int compareTo(final Entry other) {
             int result = this.path.compareTo(other.path);
             if ( result == 0 ) {
-                result = this.id.compareTo(other.id);
+                result = this.handler.getServiceId().compareTo(other.handler.getServiceId());
             }
             return result;
         }
-
     }
 }

Modified: sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/tree/ProviderHandler.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/tree/ProviderHandler.java?rev=1367147&r1=1367146&r2=1367147&view=diff
==============================================================================
--- sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/tree/ProviderHandler.java (original)
+++ sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/tree/ProviderHandler.java Mon Jul 30 16:20:17 2012
@@ -19,10 +19,13 @@ package org.apache.sling.resourceresolve
 
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
+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;
@@ -46,6 +49,9 @@ public abstract class ProviderHandler im
     /** Configured roots. */
     private final String[] roots;
 
+    /** Configured languages for queries. */
+    private final Set<String> queryLanguages;
+
     /** Owns roots? */
     private final boolean ownsRoots;
 
@@ -79,6 +85,23 @@ public abstract class ProviderHandler im
             this.roots = configuredRoots.toArray(new String[configuredRoots.size()]);
         }
         this.ownsRoots = PropertiesUtil.toBoolean(properties.get(ResourceProvider.OWNS_ROOTS), false);
+        final Set<String> configuredLanguages = new HashSet<String>();
+        final String[] languages = PropertiesUtil.toStringArray(properties.get(QueriableResourceProvider.LANGUAGES));
+        if ( languages != null) {
+            for(final String l : languages) {
+                if (l != null) {
+                    String language = l.trim();
+                    if ( language.length() > 0 ) {
+                        configuredLanguages.add(language);
+                    }
+                }
+            }
+        }
+        if ( configuredLanguages.size() == 0 ) {
+            this.queryLanguages = null;
+        } else {
+            this.queryLanguages = configuredLanguages;
+        }
     }
 
     /**
@@ -112,6 +135,13 @@ public abstract class ProviderHandler im
     }
 
     /**
+     * Check if the resource provider supports the language.
+     */
+    public boolean supportsQueryLanguages(final String language) {
+        return this.queryLanguages != null && this.queryLanguages.contains(language);
+    }
+
+    /**
      * @see java.lang.Comparable#compareTo(java.lang.Object)
      */
     public int compareTo(final ProviderHandler other) {

Modified: sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/tree/RootResourceProviderEntry.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/tree/RootResourceProviderEntry.java?rev=1367147&r1=1367146&r2=1367147&view=diff
==============================================================================
--- sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/tree/RootResourceProviderEntry.java (original)
+++ sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/tree/RootResourceProviderEntry.java Mon Jul 30 16:20:17 2012
@@ -105,7 +105,7 @@ public class RootResourceProviderEntry e
      * @see Adaptable
      */
     public <AdapterType> AdapterType adaptTo(final ResourceResolverContext ctx, final Class<AdapterType> type) {
-        final Iterator<Adaptable> i = this.adaptableProviders.getProviders(ctx);
+        final Iterator<Adaptable> i = this.adaptableProviders.getProviders(ctx, null);
         AdapterType result = null;
         while ( result == null && i.hasNext() ) {
             final Adaptable adap = i.next();
@@ -120,7 +120,14 @@ public class RootResourceProviderEntry e
      */
     public Iterator<Resource> findResources(final ResourceResolverContext ctx,
                     final ResourceResolver resolver, final String query, final String language) {
-        final Iterator<QueriableResourceProvider> i = this.queriableProviders.getProviders(ctx);
+        final Iterator<QueriableResourceProvider> i = this.queriableProviders.getProviders(ctx,
+                        new SortedProviderList.Filter<QueriableResourceProvider>() {
+
+                            public boolean select(final ProviderHandler handler, final QueriableResourceProvider provider) {
+                                return handler.supportsQueryLanguages(language);
+                            }
+
+                        });
         return new Iterator<Resource>() {
 
             private Resource nextObject = this.seek();
@@ -180,7 +187,14 @@ public class RootResourceProviderEntry e
      * @see QueriableResourceProvider#queryResources(String, String)
      */
     public Iterator<Map<String, Object>> queryResources(final ResourceResolverContext ctx, final String query, final String language) {
-        final Iterator<QueriableResourceProvider> i = this.queriableProviders.getProviders(ctx);
+        final Iterator<QueriableResourceProvider> i = this.queriableProviders.getProviders(ctx,
+                        new SortedProviderList.Filter<QueriableResourceProvider>() {
+
+            public boolean select(final ProviderHandler handler, final QueriableResourceProvider provider) {
+                return handler.supportsQueryLanguages(language);
+            }
+
+        });
         return new Iterator<Map<String, Object>>() {
 
             private Map<String, Object> nextObject = this.seek();
@@ -246,7 +260,7 @@ public class RootResourceProviderEntry e
         if ( ctx.getAuthenticationInfo() != null ) {
             names.addAll(ctx.getAuthenticationInfo().keySet());
         }
-        final Iterator<AttributableResourceProvider> i = this.attributableProviders.getProviders(ctx);
+        final Iterator<AttributableResourceProvider> i = this.attributableProviders.getProviders(ctx, null);
         while ( i.hasNext() ) {
             final AttributableResourceProvider adap = i.next();
             final Collection<String> newNames = adap.getAttributeNames();
@@ -270,7 +284,7 @@ public class RootResourceProviderEntry e
                 result = ctx.getAuthenticationInfo().get(name);
             }
             if ( result == null ) {
-                final Iterator<AttributableResourceProvider> i = this.attributableProviders.getProviders(ctx);
+                final Iterator<AttributableResourceProvider> i = this.attributableProviders.getProviders(ctx, null);
                 while ( result == null && i.hasNext() ) {
                     final AttributableResourceProvider adap = i.next();
                     result = adap.getAttribute(name);

Modified: sling/trunk/bundles/resourceresolver/src/test/java/org/apache/sling/resourceresolver/impl/ResourceResolverImplTest.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/resourceresolver/src/test/java/org/apache/sling/resourceresolver/impl/ResourceResolverImplTest.java?rev=1367147&r1=1367146&r2=1367147&view=diff
==============================================================================
--- sling/trunk/bundles/resourceresolver/src/test/java/org/apache/sling/resourceresolver/impl/ResourceResolverImplTest.java (original)
+++ sling/trunk/bundles/resourceresolver/src/test/java/org/apache/sling/resourceresolver/impl/ResourceResolverImplTest.java Mon Jul 30 16:20:17 2012
@@ -43,7 +43,6 @@ import javax.servlet.http.HttpSession;
 
 import org.apache.sling.api.resource.AbstractResource;
 import org.apache.sling.api.resource.NonExistingResource;
-import org.apache.sling.api.resource.PersistenceException;
 import org.apache.sling.api.resource.Resource;
 import org.apache.sling.api.resource.ResourceMetadata;
 import org.apache.sling.api.resource.ResourceResolver;
@@ -305,25 +304,25 @@ public class ResourceResolverImplTest {
 
     @Test public void testBasicCrud() throws Exception {
         try {
-            this.resResolver.addChild(null, "a", null);
+            this.resResolver.create(null, "a", null);
             fail("Null parent resource should throw NPE");
         } catch (final NullPointerException npe) {
             // correct
         }
         try {
-            this.resResolver.addChild(new ResourceImpl(), null, null);
+            this.resResolver.create(new ResourceImpl(), null, null);
             fail("Null name should throw NPE");
         } catch (final NullPointerException npe) {
             // correct
         }
         try {
-            this.resResolver.addChild(new ResourceImpl(), "a/b", null);
-            fail("Slash in name should throw persistence exception");
-        } catch (final PersistenceException pe) {
+            this.resResolver.create(new ResourceImpl(), "a/b", null);
+            fail("Slash in name should throw illegal argument exception");
+        } catch (final IllegalArgumentException pe) {
             // correct
         }
         try {
-            this.resResolver.addChild(new ResourceImpl(), "a", null);
+            this.resResolver.create(new ResourceImpl(), "a", null);
             fail("This should be unsupported.");
         } catch (final UnsupportedOperationException uoe) {
             // correct

Modified: sling/trunk/bundles/resourceresolver/src/test/java/org/apache/sling/resourceresolver/impl/helper/SortedProviderListTest.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/resourceresolver/src/test/java/org/apache/sling/resourceresolver/impl/helper/SortedProviderListTest.java?rev=1367147&r1=1367146&r2=1367147&view=diff
==============================================================================
--- sling/trunk/bundles/resourceresolver/src/test/java/org/apache/sling/resourceresolver/impl/helper/SortedProviderListTest.java (original)
+++ sling/trunk/bundles/resourceresolver/src/test/java/org/apache/sling/resourceresolver/impl/helper/SortedProviderListTest.java Mon Jul 30 16:20:17 2012
@@ -153,7 +153,7 @@ public class SortedProviderListTest {
         final SortedProviderList<Adaptable> spl = new SortedProviderList<Adaptable>(Adaptable.class);
         spl.add(new ResourceProviderHandler(rp2, rp2.getProperties()));
 
-        final Iterator<Adaptable> i = spl.getProviders(null);
+        final Iterator<Adaptable> i = spl.getProviders(null, null);
         assertTrue(i.hasNext());
         i.next(); // one entry
         assertFalse(i.hasNext());
@@ -179,7 +179,7 @@ public class SortedProviderListTest {
                     final ResourceResolverContext ctx,
                     final Adaptable... objects) {
         final int expectedCount = objects == null ? 0 : objects.length;
-        final Iterator<Adaptable> i = spl.getProviders(ctx);
+        final Iterator<Adaptable> i = spl.getProviders(ctx, null);
         int count = 0;
         while ( i.hasNext() ) {
             final Adaptable a = i.next();

Modified: sling/trunk/bundles/resourceresolver/src/test/java/org/apache/sling/resourceresolver/impl/tree/ResourceProviderEntryTest.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/resourceresolver/src/test/java/org/apache/sling/resourceresolver/impl/tree/ResourceProviderEntryTest.java?rev=1367147&r1=1367146&r2=1367147&view=diff
==============================================================================
--- sling/trunk/bundles/resourceresolver/src/test/java/org/apache/sling/resourceresolver/impl/tree/ResourceProviderEntryTest.java (original)
+++ sling/trunk/bundles/resourceresolver/src/test/java/org/apache/sling/resourceresolver/impl/tree/ResourceProviderEntryTest.java Mon Jul 30 16:20:17 2012
@@ -303,7 +303,7 @@ public class ResourceProviderEntryTest {
             // TODO Auto-generated method stub
         }
 
-        public Resource addChild(Resource parent, String name, Map<String, Object> properties) {
+        public Resource create(Resource parent, String name, Map<String, Object> properties) {
             // TODO Auto-generated method stub
             return null;
         }

Modified: sling/trunk/launchpad/integration-tests/src/main/resources/integration-test/crud/crud-test.jsp
URL: http://svn.apache.org/viewvc/sling/trunk/launchpad/integration-tests/src/main/resources/integration-test/crud/crud-test.jsp?rev=1367147&r1=1367146&r2=1367147&view=diff
==============================================================================
--- sling/trunk/launchpad/integration-tests/src/main/resources/integration-test/crud/crud-test.jsp (original)
+++ sling/trunk/launchpad/integration-tests/src/main/resources/integration-test/crud/crud-test.jsp Mon Jul 30 16:20:17 2012
@@ -36,7 +36,7 @@
         vm.put("a", request.getParameter("a"));
         vm.put("b", request.getParameter("b"));
         
-        final Resource rsrc = resolver.addChild(resolver.getResource("/"), name, vm);
+        final Resource rsrc = resolver.create(resolver.getResource("/"), name, vm);
         
         resolver.commit();
         %>Resource created at <%= rsrc.getPath() %><%