[maven-release-plugin]  copy for tag jackrabbit-oak-1.1.2

git-svn-id: https://svn.apache.org/repos/asf/jackrabbit/oak/tags/jackrabbit-oak-1.1.2@1637127 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt
index 2933c50..20f8df9 100644
--- a/RELEASE-NOTES.txt
+++ b/RELEASE-NOTES.txt
@@ -58,6 +58,11 @@
     authorizable id
     [OAK-2248] - IndexOutOfBoundsException in
     o.a.j.o.scalability.ScalabilityNodeRelationshipSuite
+    [OAK-2249] - Query with mixed full-text, "and", "or" conditions
+    fails
+    [OAK-2250] - Lucene Index property definition is ignored if its
+    not in includePropertyNames config
+    [OAK-2255] - PermissionStoreImpl refresh root on flush
 
 Improvement
 
@@ -66,6 +71,7 @@
     [OAK-2119] - AggregateIndex should support AdvanceQueryIndex
     [OAK-2125] - Integrate Lucene logging with Slf4j
     [OAK-2173] - Allow specifying custom attributes in IndexPlan
+    [OAK-2178] - Lucene index boosting on certain fields
     [OAK-2189] - TarMK cold standby: update OSGi config example files
     [OAK-2194] - Print tar file graph in segment explorer
     [OAK-2201] - Make blobSize in OakDirectory configurable
@@ -86,7 +92,9 @@
     [OAK-2230] - Execution Stats for async indexing
     [OAK-2233] - Add UserIdCredentials to Oak user management
     extension
+    [OAK-2241] - Support native queries for non-full-text indexes
     [OAK-2245] - UserImporter should always set the rep:authorizableID
+    [OAK-2253] - Index node only if configured property changed
 
 New Feature
 
diff --git a/oak-authorization-cug/pom.xml b/oak-authorization-cug/pom.xml
index 378e296..d30d53f 100644
--- a/oak-authorization-cug/pom.xml
+++ b/oak-authorization-cug/pom.xml
@@ -134,6 +134,15 @@
       <artifactId>jsr305</artifactId>
     </dependency>
 
+    <!-- Test dependencies -->
+    <dependency>
+      <groupId>org.apache.jackrabbit</groupId>
+      <artifactId>oak-core</artifactId>
+      <version>${project.version}</version>
+      <classifier>tests</classifier>
+      <scope>test</scope>
+    </dependency>
+
   </dependencies>
 
 </project>
\ No newline at end of file
diff --git a/oak-authorization-cug/src/main/java/org/apache/jackrabbit/oak/spi/security/authorization/cug/CugExclude.java b/oak-authorization-cug/src/main/java/org/apache/jackrabbit/oak/spi/security/authorization/cug/CugExclude.java
index 0da4247..30cff6d 100644
--- a/oak-authorization-cug/src/main/java/org/apache/jackrabbit/oak/spi/security/authorization/cug/CugExclude.java
+++ b/oak-authorization-cug/src/main/java/org/apache/jackrabbit/oak/spi/security/authorization/cug/CugExclude.java
@@ -25,14 +25,22 @@
 import org.apache.jackrabbit.oak.spi.security.principal.SystemUserPrincipal;
 
 /**
- * CugExclude... TODO
+ * Interface that allows to exclude certain principals from the CUG evaluation.
+ * For the excluded principals the closed user group policies will be ignored.
  */
 public interface CugExclude {
 
     boolean isExcluded(@Nonnull Set<Principal> principals);
 
-    CugExclude DEFAULT = new Default();
-
+    /**
+     * Default implementation of the {@link CugExclude} interface that exclude
+     * the following principal classes from CUG evaluation:
+     * <ul>
+     *     <li>{@link org.apache.jackrabbit.oak.spi.security.principal.AdminPrincipal AdminPrincipals}</li>
+     *     <li>{@link org.apache.jackrabbit.oak.spi.security.principal.SystemPrincipal SystemPrincipal}</li>
+     *     <li>{@link org.apache.jackrabbit.oak.spi.security.principal.SystemUserPrincipal SystemUserPrincipal}</li>
+     * </ul>
+     */
     class Default implements CugExclude {
 
         @Override
diff --git a/oak-authorization-cug/src/main/java/org/apache/jackrabbit/oak/spi/security/authorization/cug/CugPolicy.java b/oak-authorization-cug/src/main/java/org/apache/jackrabbit/oak/spi/security/authorization/cug/CugPolicy.java
index f3c2062..c499e58 100644
--- a/oak-authorization-cug/src/main/java/org/apache/jackrabbit/oak/spi/security/authorization/cug/CugPolicy.java
+++ b/oak-authorization-cug/src/main/java/org/apache/jackrabbit/oak/spi/security/authorization/cug/CugPolicy.java
@@ -28,11 +28,35 @@
  */
 public interface CugPolicy extends JackrabbitAccessControlPolicy {
 
+    /**
+     * Returns the set of {@code Principal}s that are allowed to access the items
+     * in the restricted area defined by this policy.
+     *
+     * @return The set of {@code Principal}s that are allowed to access the
+     * restricted area.
+     */
     @Nonnull
     Set<Principal> getPrincipals();
 
+    /**
+     * Add {@code Principal}s that are allowed to access the restricted area.
+     *
+     * @param principals The {@code Principal}s that are granted read access.
+     * @return {@code true} if this policy was modified; {@code false} otherwise.
+     * @throws AccessControlException If any of the specified principals is
+     * invalid.
+     */
     boolean addPrincipals(@Nonnull Principal... principals) throws AccessControlException;
 
-    boolean removePrincipals(@Nonnull Principal... principals);
+    /**
+     * Remove the specified {@code Principal}s for the set of allowed principals
+     * thus revoking their ability to read items in the restricted area defined
+     * by this policy.
+     *
+     * @param principals The {@code Principal}s for which access should be revoked.
+     * @return {@code true} if this policy was modified; {@code false} otherwise.
+     * @throws  AccessControlException If an error occurs.
+     */
+    boolean removePrincipals(@Nonnull Principal... principals) throws AccessControlException;
 
 }
diff --git a/oak-authorization-cug/src/main/java/org/apache/jackrabbit/oak/spi/security/authorization/cug/impl/CugAccessControlManager.java b/oak-authorization-cug/src/main/java/org/apache/jackrabbit/oak/spi/security/authorization/cug/impl/CugAccessControlManager.java
index f2ab076..82a8458 100644
--- a/oak-authorization-cug/src/main/java/org/apache/jackrabbit/oak/spi/security/authorization/cug/impl/CugAccessControlManager.java
+++ b/oak-authorization-cug/src/main/java/org/apache/jackrabbit/oak/spi/security/authorization/cug/impl/CugAccessControlManager.java
@@ -50,6 +50,7 @@
 import org.apache.jackrabbit.oak.spi.security.authorization.permission.Permissions;
 import org.apache.jackrabbit.oak.spi.security.principal.PrincipalConfiguration;
 import org.apache.jackrabbit.oak.spi.security.principal.PrincipalImpl;
+import org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeConstants;
 import org.apache.jackrabbit.oak.util.TreeUtil;
 import org.apache.jackrabbit.util.Text;
 import org.slf4j.Logger;
@@ -58,7 +59,8 @@
 import static org.apache.jackrabbit.oak.plugins.nodetype.NodeTypeConstants.NODE_TYPES_PATH;
 
 /**
- * CugAccessControlManager... TODO
+ * Implementation of the {@link org.apache.jackrabbit.api.security.JackrabbitAccessControlManager}
+ * interface that allows to create, modify and remove closed user group policies.
  */
 class CugAccessControlManager extends AbstractAccessControlManager implements CugConstants {
 
@@ -80,7 +82,7 @@
     @Override
     public Privilege[] getSupportedPrivileges(@Nullable String absPath) throws RepositoryException {
         if (isSupportedPath(getOakPath(absPath))) {
-            return new Privilege[] {privilegeFromName(Privilege.JCR_READ)};
+            return new Privilege[] {privilegeFromName(PrivilegeConstants.JCR_READ)};
         } else {
             return new Privilege[0];
         }
@@ -190,10 +192,17 @@
 
     //--------------------------------------------------------------------------
 
-    private boolean isSupportedPath(@Nullable String oakPath) {
+    private boolean isSupportedPath(@Nullable String oakPath) throws RepositoryException {
+        checkValidPath(oakPath);
         return CugUtil.isSupportedPath(oakPath, config);
     }
 
+    private void checkValidPath(@Nullable String oakPath) throws RepositoryException {
+        if (oakPath != null) {
+            getTree(oakPath, Permissions.NO_PERMISSION, false);
+        }
+    }
+
     @CheckForNull
     private CugPolicy getCugPolicy(@Nonnull String oakPath) throws RepositoryException {
         return getCugPolicy(oakPath, getTree(oakPath, Permissions.READ_ACCESS_CONTROL, true));
diff --git a/oak-authorization-cug/src/main/java/org/apache/jackrabbit/oak/spi/security/authorization/cug/impl/CugConfiguration.java b/oak-authorization-cug/src/main/java/org/apache/jackrabbit/oak/spi/security/authorization/cug/impl/CugConfiguration.java
index eef4c76..1c87480 100644
--- a/oak-authorization-cug/src/main/java/org/apache/jackrabbit/oak/spi/security/authorization/cug/impl/CugConfiguration.java
+++ b/oak-authorization-cug/src/main/java/org/apache/jackrabbit/oak/spi/security/authorization/cug/impl/CugConfiguration.java
@@ -17,7 +17,6 @@
 package org.apache.jackrabbit.oak.spi.security.authorization.cug.impl;
 
 import java.io.IOException;
-import java.io.InputStream;
 import java.security.Principal;
 import java.security.PrivilegedActionException;
 import java.security.PrivilegedExceptionAction;
@@ -32,6 +31,7 @@
 
 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.PropertyOption;
@@ -41,16 +41,11 @@
 import org.apache.jackrabbit.oak.api.ContentRepository;
 import org.apache.jackrabbit.oak.api.ContentSession;
 import org.apache.jackrabbit.oak.api.Root;
-import org.apache.jackrabbit.oak.api.Tree;
 import org.apache.jackrabbit.oak.core.SystemRoot;
 import org.apache.jackrabbit.oak.namepath.NamePathMapper;
 import org.apache.jackrabbit.oak.plugins.memory.MemoryNodeStore;
 import org.apache.jackrabbit.oak.plugins.name.NamespaceEditorProvider;
-import org.apache.jackrabbit.oak.plugins.nodetype.NodeTypeConstants;
-import org.apache.jackrabbit.oak.plugins.nodetype.ReadOnlyNodeTypeManager;
 import org.apache.jackrabbit.oak.plugins.nodetype.TypeEditorProvider;
-import org.apache.jackrabbit.oak.plugins.nodetype.write.NodeTypeRegistry;
-import org.apache.jackrabbit.oak.spi.security.authorization.cug.CugExclude;
 import org.apache.jackrabbit.oak.spi.commit.CompositeEditorProvider;
 import org.apache.jackrabbit.oak.spi.commit.EditorHook;
 import org.apache.jackrabbit.oak.spi.lifecycle.RepositoryInitializer;
@@ -62,6 +57,7 @@
 import org.apache.jackrabbit.oak.spi.security.SecurityProvider;
 import org.apache.jackrabbit.oak.spi.security.authentication.SystemSubject;
 import org.apache.jackrabbit.oak.spi.security.authorization.AuthorizationConfiguration;
+import org.apache.jackrabbit.oak.spi.security.authorization.cug.CugExclude;
 import org.apache.jackrabbit.oak.spi.security.authorization.permission.AggregatedPermissionProvider;
 import org.apache.jackrabbit.oak.spi.security.authorization.permission.ControlFlag;
 import org.apache.jackrabbit.oak.spi.security.authorization.permission.EmptyPermissionProvider;
@@ -76,7 +72,10 @@
 import org.apache.jackrabbit.oak.spi.state.NodeStore;
 import org.apache.jackrabbit.oak.spi.xml.ProtectedItemImporter;
 
-@Component()
+@Component(metatype = true,
+        label = "CUG Configuration",
+        description = "Component to enable Allows to exclude principal(s) with the configured name(s) from CUG evaluation.",
+        policy = ConfigurationPolicy.REQUIRE)
 @Service({AuthorizationConfiguration.class, SecurityConfiguration.class})
 @Properties({
         @Property(name = CugConstants.PARAM_CUG_SUPPORTED_PATHS,
@@ -105,15 +104,18 @@
     @Reference
     private ContentRepository repository;
 
+    /**
+     * Reference to services implementing {@link org.apache.jackrabbit.oak.spi.security.authorization.cug.CugExclude}.
+     */
     @Reference
-    private CugExclude exclude = CugExclude.DEFAULT;
+    private CugExclude exclude = new CugExclude.Default();
 
     public CugConfiguration() {
         super();
     }
 
-    public CugConfiguration(@Nonnull SecurityProvider securityProvider, @Nonnull ConfigurationParameters config) {
-        super(securityProvider, config);
+    public CugConfiguration(@Nonnull SecurityProvider securityProvider) {
+        super(securityProvider, securityProvider.getParameters(NAME));
     }
 
     @Override
@@ -162,7 +164,7 @@
                         new EditorHook(new CompositeEditorProvider(
                                 new NamespaceEditorProvider(),
                                 new TypeEditorProvider())));
-                if (registerCugNodeTypes(root)) {
+                if (CugUtil.registerCugNodeTypes(root)) {
                     NodeState target = store.getRoot();
                     target.compareAgainstBaseState(base, new ApplyDiff(builder));
                 }
@@ -193,7 +195,7 @@
                 }
             });
             final Root root = systemSession.getLatestRoot();
-            if (registerCugNodeTypes(root)) {
+            if (CugUtil.registerCugNodeTypes(root)) {
                 root.commit();
             }
         } finally {
@@ -205,7 +207,7 @@
 
     //--------------------------------------------------------------------------
     private CugExclude getExclude() {
-        return (exclude == null) ? CugExclude.DEFAULT : exclude;
+        return (exclude == null) ? new CugExclude.Default() : exclude;
     }
 
     private static boolean isAdmin(@Nonnull Set<Principal> principals) {
@@ -216,29 +218,4 @@
         }
         return false;
     }
-
-    private static boolean registerCugNodeTypes(@Nonnull final Root root) {
-        try {
-            ReadOnlyNodeTypeManager ntMgr = new ReadOnlyNodeTypeManager() {
-                @Override
-                protected Tree getTypes() {
-                    return root.getTree(NodeTypeConstants.NODE_TYPES_PATH);
-                }
-            };
-            if (!ntMgr.hasNodeType(NT_REP_CUG_POLICY)) {
-                InputStream stream = CugConfiguration.class.getResourceAsStream("cug_nodetypes.cnd");
-                try {
-                    NodeTypeRegistry.register(root, stream, "cug node types");
-                    return true;
-                } finally {
-                    stream.close();
-                }
-            }
-        } catch (IOException e) {
-            throw new IllegalStateException("Unable to read cug node types", e);
-        } catch (RepositoryException e) {
-            throw new IllegalStateException("Unable to read cug node types", e);
-        }
-        return false;
-    }
 }
\ No newline at end of file
diff --git a/oak-authorization-cug/src/main/java/org/apache/jackrabbit/oak/spi/security/authorization/cug/impl/CugConstants.java b/oak-authorization-cug/src/main/java/org/apache/jackrabbit/oak/spi/security/authorization/cug/impl/CugConstants.java
index d74b3e2..56cdf06 100644
--- a/oak-authorization-cug/src/main/java/org/apache/jackrabbit/oak/spi/security/authorization/cug/impl/CugConstants.java
+++ b/oak-authorization-cug/src/main/java/org/apache/jackrabbit/oak/spi/security/authorization/cug/impl/CugConstants.java
@@ -17,25 +17,52 @@
 package org.apache.jackrabbit.oak.spi.security.authorization.cug.impl;
 
 /**
- * CugConstants... TODO
+ * Constants for the Closed User Group (CUG) feature.
  */
 interface CugConstants {
 
+    /**
+     * The name of the mixin type that defines the CUG policy node.
+     */
     String MIX_REP_CUG_MIXIN = "rep:CugMixin";
 
+    /**
+     * The primary node type name of the CUG policy node.
+     */
     String NT_REP_CUG_POLICY = "rep:CugPolicy";
 
+    /**
+     * The name of the CUG policy node.
+     */
     String REP_CUG_POLICY = "rep:cugPolicy";
 
+    /**
+     * The name of the property that stores the principal names that are allowed
+     * to access the restricted area defined by the CUG (closed user group).
+     */
     String REP_PRINCIPAL_NAMES = "rep:principalNames";
 
+    /**
+     * Name of the configuration option that specifies the subtrees that allow
+     * to define closed user groups.
+     *
+     * <ul>
+     *     <li>Value Type: String</li>
+     *     <li>Default: -</li>
+     *     <li>Multiple: true</li>
+     * </ul>
+     */
     String PARAM_CUG_SUPPORTED_PATHS = "cugSupportedPaths";
 
-    String PARAM_CUG_ENABLED = "cugEnabled";
-
     /**
+     * Name of the configuration option that specifies if CUG content must
+     * be respected for permission evaluation.
      *
+     * <ul>
+     *     <li>Value Type: boolean</li>
+     *     <li>Default: false</li>
+     *     <li>Multiple: false</li>
+     * </ul>
      */
-    String PARAM_CUG_EXCLUDE = "cugExclude";
-
+    String PARAM_CUG_ENABLED = "cugEnabled";
 }
\ No newline at end of file
diff --git a/oak-authorization-cug/src/main/java/org/apache/jackrabbit/oak/spi/security/authorization/cug/impl/CugContext.java b/oak-authorization-cug/src/main/java/org/apache/jackrabbit/oak/spi/security/authorization/cug/impl/CugContext.java
index a0c9fe3..c73fbba 100644
--- a/oak-authorization-cug/src/main/java/org/apache/jackrabbit/oak/spi/security/authorization/cug/impl/CugContext.java
+++ b/oak-authorization-cug/src/main/java/org/apache/jackrabbit/oak/spi/security/authorization/cug/impl/CugContext.java
@@ -25,7 +25,7 @@
 import org.apache.jackrabbit.util.Text;
 
 /**
- * CugContext... TODO
+ * CUG specific {@code Context} implementation.
  */
 final class CugContext implements Context, CugConstants {
 
@@ -55,8 +55,8 @@
             PropertyState p = location.getProperty();
             return (p == null) ? definesTree(tree) : definesProperty(tree, p);
         } else {
-            String name = Text.getName(location.getPath());
-            return REP_PRINCIPAL_NAMES.equals(name) || REP_CUG_POLICY.equals(name);
+            String path = location.getPath();
+            return REP_CUG_POLICY.equals(Text.getName(path)) || path.endsWith(REP_CUG_POLICY + '/' + REP_PRINCIPAL_NAMES);
         }
     }
 }
\ No newline at end of file
diff --git a/oak-authorization-cug/src/main/java/org/apache/jackrabbit/oak/spi/security/authorization/cug/impl/CugExcludeImpl.java b/oak-authorization-cug/src/main/java/org/apache/jackrabbit/oak/spi/security/authorization/cug/impl/CugExcludeImpl.java
index 714a566..0241ac4 100644
--- a/oak-authorization-cug/src/main/java/org/apache/jackrabbit/oak/spi/security/authorization/cug/impl/CugExcludeImpl.java
+++ b/oak-authorization-cug/src/main/java/org/apache/jackrabbit/oak/spi/security/authorization/cug/impl/CugExcludeImpl.java
@@ -17,61 +17,53 @@
 package org.apache.jackrabbit.oak.spi.security.authorization.cug.impl;
 
 import java.security.Principal;
+import java.util.Collections;
 import java.util.Map;
 import java.util.Set;
 import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-import javax.jcr.RepositoryException;
 
-import com.google.common.base.Function;
-import com.google.common.base.Predicates;
-import com.google.common.collect.Iterables;
+import com.google.common.collect.ImmutableSet;
 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.Service;
-import org.apache.jackrabbit.api.security.principal.ItemBasedPrincipal;
 import org.apache.jackrabbit.oak.commons.PropertiesUtil;
 import org.apache.jackrabbit.oak.spi.security.authorization.cug.CugExclude;
-import org.apache.jackrabbit.oak.spi.security.principal.PrincipalImpl;
 
 /**
- * CugExcludeImpl... TODO
+ * Extension of the default {@link org.apache.jackrabbit.oak.spi.security.authorization.cug.CugExclude}
+ * implementation that allow to specify additional principal names to be excluded
+ * from CUG evaluation.
+ *
+ * Note: this component is requires a configuration (i.e. a configured list of
+ * principal names) in order to be activated.
  */
-@Component()
+@Component(metatype = true,
+        label = "Exclude Principals from CUG",
+        description = "Allows to exclude principal(s) with the configured name(s) from CUG evaluation.",
+        policy = ConfigurationPolicy.REQUIRE)
 @Service({CugExclude.class})
 @Properties({
         @Property(name = "principalNames",
                 label = "Principal Names",
                 description = "Name of principals that are always excluded from CUG evaluation.",
-                cardinality = Integer.MAX_VALUE),
-        @Property(name = "principalPaths",
-                label = "Principal Paths",
-                description = "Path pattern for principals that are always excluded from CUG evaluation",
                 cardinality = Integer.MAX_VALUE)
 })
 public class CugExcludeImpl extends CugExclude.Default {
 
-    private String[] principalNames = new String[0];
-    private String[] principalPaths = new String[0];
+    private Set<String> principalNames = Collections.emptySet();
 
     @Override
     public boolean isExcluded(@Nonnull Set<Principal> principals) {
         if (super.isExcluded(principals)) {
             return true;
         }
-        for (String principalName : principalNames) {
-            if (principals.contains(new PrincipalImpl(principalName))) {
-                return true;
-            }
-        }
-        if (principalPaths.length > 0) {
-            for (String principalPath : getPrincipalPaths(principals)) {
-                for (String path : principalPaths) {
-                    if (principalPath.startsWith(path)) {
-                        return true;
-                    }
+        if (!principalNames.isEmpty()) {
+            for (Principal p : principals) {
+                if (principalNames.contains(p.getName())) {
+                    return true;
                 }
             }
         }
@@ -80,25 +72,6 @@
 
     @Activate
     protected void activate(Map<String, Object> properties) {
-        principalNames = PropertiesUtil.toStringArray(properties.get("principalNames"), new String[0]);
-        principalPaths = PropertiesUtil.toStringArray(properties.get("principalPaths"), new String[0]);
-    }
-
-    private static Iterable<String> getPrincipalPaths(@Nonnull final Iterable<Principal> principals) {
-        return Iterables.filter(Iterables.transform(principals, new Function<Principal, String>() {
-            @Nullable
-            @Override
-            public String apply(@Nullable Principal input) {
-                if (input instanceof ItemBasedPrincipal) {
-                    try {
-                        return ((ItemBasedPrincipal) input).getPath();
-                    } catch (RepositoryException e) {
-                        return null;
-                    }
-                } else {
-                    return null;
-                }
-            }
-        }), Predicates.notNull());
+        principalNames = ImmutableSet.copyOf(PropertiesUtil.toStringArray(properties.get("principalNames"), new String[0]));
     }
 }
\ No newline at end of file
diff --git a/oak-authorization-cug/src/main/java/org/apache/jackrabbit/oak/spi/security/authorization/cug/impl/CugImporter.java b/oak-authorization-cug/src/main/java/org/apache/jackrabbit/oak/spi/security/authorization/cug/impl/CugImporter.java
index dbd339b..ab2f27b 100644
--- a/oak-authorization-cug/src/main/java/org/apache/jackrabbit/oak/spi/security/authorization/cug/impl/CugImporter.java
+++ b/oak-authorization-cug/src/main/java/org/apache/jackrabbit/oak/spi/security/authorization/cug/impl/CugImporter.java
@@ -45,7 +45,7 @@
 import org.slf4j.LoggerFactory;
 
 /**
- * CugImporter... TODO
+ * CUG specific implementation of the {@code ProtectedPropertyImporter}.
  */
 class CugImporter implements ProtectedPropertyImporter, CugConstants {
 
@@ -98,11 +98,12 @@
                 if (principal == null) {
                     switch (importBehavior) {
                         case ImportBehavior.IGNORE:
-                            log.debug("Unknown principal " + principalName + " -> Ignoring this ACE.");
+                            log.debug("Ignoring unknown principal with name '" + principalName + "'.");
                             break;
                         case ImportBehavior.ABORT:
-                            throw new AccessControlException("Unknown principal " + principalName);
+                            throw new AccessControlException("Unknown principal '" + principalName + "'.");
                         case ImportBehavior.BESTEFFORT:
+                            log.debug("Importing unknown principal '" + principalName + '\'');
                             principalNames.add(principalName);
                             break;
                         default:
diff --git a/oak-authorization-cug/src/main/java/org/apache/jackrabbit/oak/spi/security/authorization/cug/impl/CugPolicyImpl.java b/oak-authorization-cug/src/main/java/org/apache/jackrabbit/oak/spi/security/authorization/cug/impl/CugPolicyImpl.java
index f135171..0df2c85 100644
--- a/oak-authorization-cug/src/main/java/org/apache/jackrabbit/oak/spi/security/authorization/cug/impl/CugPolicyImpl.java
+++ b/oak-authorization-cug/src/main/java/org/apache/jackrabbit/oak/spi/security/authorization/cug/impl/CugPolicyImpl.java
@@ -16,7 +16,6 @@
  */
 package org.apache.jackrabbit.oak.spi.security.authorization.cug.impl;
 
-import java.lang.IllegalArgumentException;
 import java.security.Principal;
 import java.util.Collections;
 import java.util.HashSet;
@@ -35,7 +34,8 @@
 import org.slf4j.LoggerFactory;
 
 /**
- * CugPolicyImpl... TODO
+ * Implementation of the {@link org.apache.jackrabbit.oak.spi.security.authorization.cug.CugPolicy}
+ * interface that respects the configured {@link org.apache.jackrabbit.oak.spi.xml.ImportBehavior}.
  */
 class CugPolicyImpl implements CugPolicy {
 
@@ -48,11 +48,14 @@
 
     private final Set<Principal> principals = new HashSet<Principal>();
 
-    CugPolicyImpl(@Nonnull String oakPath, @Nonnull NamePathMapper namePathMapper, PrincipalManager principalManager, int importBehavior) {
+    CugPolicyImpl(@Nonnull String oakPath, @Nonnull NamePathMapper namePathMapper,
+                  @Nonnull PrincipalManager principalManager, int importBehavior) {
         this(oakPath, namePathMapper, principalManager, importBehavior, Collections.<Principal>emptySet());
     }
 
-    CugPolicyImpl(@Nonnull String oakPath, @Nonnull NamePathMapper namePathMapper, PrincipalManager principalManager, int importBehavior, @Nonnull Set<Principal> principals) {
+    CugPolicyImpl(@Nonnull String oakPath, @Nonnull NamePathMapper namePathMapper,
+                  @Nonnull PrincipalManager principalManager, int importBehavior,
+                  @Nonnull Set<Principal> principals) {
         this.oakPath = oakPath;
         this.namePathMapper = namePathMapper;
         this.principalManager = principalManager;
diff --git a/oak-authorization-cug/src/main/java/org/apache/jackrabbit/oak/spi/security/authorization/cug/impl/CugUtil.java b/oak-authorization-cug/src/main/java/org/apache/jackrabbit/oak/spi/security/authorization/cug/impl/CugUtil.java
index 27f9cb9..775e391 100644
--- a/oak-authorization-cug/src/main/java/org/apache/jackrabbit/oak/spi/security/authorization/cug/impl/CugUtil.java
+++ b/oak-authorization-cug/src/main/java/org/apache/jackrabbit/oak/spi/security/authorization/cug/impl/CugUtil.java
@@ -16,11 +16,21 @@
  */
 package org.apache.jackrabbit.oak.spi.security.authorization.cug.impl;
 
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.Principal;
+import javax.annotation.CheckForNull;
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
+import javax.jcr.RepositoryException;
 
+import org.apache.jackrabbit.api.security.principal.PrincipalManager;
 import org.apache.jackrabbit.oak.api.PropertyState;
+import org.apache.jackrabbit.oak.api.Root;
 import org.apache.jackrabbit.oak.api.Tree;
+import org.apache.jackrabbit.oak.plugins.nodetype.NodeTypeConstants;
+import org.apache.jackrabbit.oak.plugins.nodetype.ReadOnlyNodeTypeManager;
+import org.apache.jackrabbit.oak.plugins.nodetype.write.NodeTypeRegistry;
 import org.apache.jackrabbit.oak.spi.security.ConfigurationParameters;
 import org.apache.jackrabbit.oak.spi.xml.ImportBehavior;
 import org.apache.jackrabbit.oak.spi.xml.ProtectedItemImporter;
@@ -28,19 +38,18 @@
 import org.apache.jackrabbit.util.Text;
 
 /**
- * CugUtil... TODO
+ * Utility methods for this CUG implementation package.
  */
-final class CugUtil {
+final class CugUtil implements CugConstants {
 
     private CugUtil(){}
 
-
     public static boolean definesCug(@Nonnull Tree tree) {
-        return tree.exists() && CugConstants.NT_REP_CUG_POLICY.equals(TreeUtil.getPrimaryTypeName(tree));
+        return tree.exists() && NT_REP_CUG_POLICY.equals(TreeUtil.getPrimaryTypeName(tree));
     }
 
     public static boolean definesCug(@Nonnull Tree tree, @Nonnull PropertyState property) {
-        return CugConstants.REP_PRINCIPAL_NAMES.equals(property.getName()) && definesCug(tree);
+        return REP_PRINCIPAL_NAMES.equals(property.getName()) && definesCug(tree);
     }
 
     public static boolean isSupportedPath(@Nullable String oakPath, @Nonnull ConfigurationParameters config) {
@@ -60,4 +69,29 @@
         String importBehaviorStr = config.getConfigValue(ProtectedItemImporter.PARAM_IMPORT_BEHAVIOR, ImportBehavior.NAME_ABORT);
         return ImportBehavior.valueFromString(importBehaviorStr);
     }
+
+    public static boolean registerCugNodeTypes(@Nonnull final Root root) {
+        try {
+            ReadOnlyNodeTypeManager ntMgr = new ReadOnlyNodeTypeManager() {
+                @Override
+                protected Tree getTypes() {
+                    return root.getTree(NodeTypeConstants.NODE_TYPES_PATH);
+                }
+            };
+            if (!ntMgr.hasNodeType(NT_REP_CUG_POLICY)) {
+                InputStream stream = CugConfiguration.class.getResourceAsStream("cug_nodetypes.cnd");
+                try {
+                    NodeTypeRegistry.register(root, stream, "cug node types");
+                    return true;
+                } finally {
+                    stream.close();
+                }
+            }
+        } catch (IOException e) {
+            throw new IllegalStateException("Unable to read cug node types", e);
+        } catch (RepositoryException e) {
+            throw new IllegalStateException("Unable to read cug node types", e);
+        }
+        return false;
+    }
 }
\ No newline at end of file
diff --git a/oak-authorization-cug/src/test/java/org/apache/jackrabbit/oak/spi/security/authorization/cug/impl/AbstractCugTest.java b/oak-authorization-cug/src/test/java/org/apache/jackrabbit/oak/spi/security/authorization/cug/impl/AbstractCugTest.java
new file mode 100644
index 0000000..903641a
--- /dev/null
+++ b/oak-authorization-cug/src/test/java/org/apache/jackrabbit/oak/spi/security/authorization/cug/impl/AbstractCugTest.java
@@ -0,0 +1,84 @@
+/*
+ * 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.jackrabbit.oak.spi.security.authorization.cug.impl;
+
+import java.util.Iterator;
+import java.util.Set;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.apache.jackrabbit.api.security.JackrabbitAccessControlManager;
+import org.apache.jackrabbit.oak.AbstractSecurityTest;
+import org.apache.jackrabbit.oak.api.Root;
+import org.apache.jackrabbit.oak.security.SecurityProviderImpl;
+import org.apache.jackrabbit.oak.spi.security.ConfigurationParameters;
+import org.apache.jackrabbit.oak.spi.security.SecurityConfiguration;
+import org.apache.jackrabbit.oak.spi.security.SecurityProvider;
+import org.apache.jackrabbit.oak.spi.security.authorization.AuthorizationConfiguration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Base class for CUG related test that setup the authorization configuration
+ * to expose the CUG specific implementations of {@code AccessControlManager}
+ * and {@code PermissionProvider}.
+ */
+public class AbstractCugTest extends AbstractSecurityTest {
+
+    @Override
+    protected SecurityProvider getSecurityProvider() {
+        if (securityProvider == null) {
+            securityProvider = new CugSecurityProvider(getSecurityConfigParameters());
+        }
+        return securityProvider;
+    }
+
+    private final class CugSecurityProvider extends SecurityProviderImpl {
+
+        private AuthorizationConfiguration cugConfiguration;
+
+        private CugSecurityProvider(@Nonnull ConfigurationParameters configuration) {
+            super(configuration);
+            cugConfiguration = new CugConfiguration(this);
+        }
+
+        @Nonnull
+        @Override
+        public Iterable<? extends SecurityConfiguration> getConfigurations() {
+            Set<SecurityConfiguration> configs = (Set<SecurityConfiguration>) super.getConfigurations();
+
+            Iterator<SecurityConfiguration> it = configs.iterator();
+            while (it.hasNext()) {
+                if (it.next() instanceof AuthorizationConfiguration) {
+                    it.remove();
+                }
+            }
+            configs.add(cugConfiguration);
+            return configs;
+        }
+
+        @Nonnull
+        @Override
+        public <T> T getConfiguration(@Nonnull Class<T> configClass) {
+            if (AuthorizationConfiguration.class == configClass) {
+                return (T) cugConfiguration;
+            } else {
+                return super.getConfiguration(configClass);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/oak-authorization-cug/src/test/java/org/apache/jackrabbit/oak/spi/security/authorization/cug/impl/CugAccessControlManagerTest.java b/oak-authorization-cug/src/test/java/org/apache/jackrabbit/oak/spi/security/authorization/cug/impl/CugAccessControlManagerTest.java
index 40f8037..5be895b 100644
--- a/oak-authorization-cug/src/test/java/org/apache/jackrabbit/oak/spi/security/authorization/cug/impl/CugAccessControlManagerTest.java
+++ b/oak-authorization-cug/src/test/java/org/apache/jackrabbit/oak/spi/security/authorization/cug/impl/CugAccessControlManagerTest.java
@@ -16,7 +16,320 @@
  */
 package org.apache.jackrabbit.oak.spi.security.authorization.cug.impl;
 
-public class CugAccessControlManagerTest {
+import java.security.Principal;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.annotation.Nonnull;
+import javax.jcr.PathNotFoundException;
+import javax.jcr.RepositoryException;
+import javax.jcr.security.AccessControlException;
+import javax.jcr.security.AccessControlPolicy;
+import javax.jcr.security.AccessControlPolicyIterator;
+import javax.jcr.security.NamedAccessControlPolicy;
+import javax.jcr.security.Privilege;
 
-    // TODO
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import org.apache.jackrabbit.oak.api.PropertyState;
+import org.apache.jackrabbit.oak.api.Tree;
+import org.apache.jackrabbit.oak.api.Type;
+import org.apache.jackrabbit.oak.namepath.NamePathMapper;
+import org.apache.jackrabbit.oak.plugins.nodetype.NodeTypeConstants;
+import org.apache.jackrabbit.oak.spi.security.ConfigurationParameters;
+import org.apache.jackrabbit.oak.spi.security.authorization.AuthorizationConfiguration;
+import org.apache.jackrabbit.oak.spi.security.authorization.cug.CugPolicy;
+import org.apache.jackrabbit.oak.spi.security.principal.EveryonePrincipal;
+import org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeConstants;
+import org.apache.jackrabbit.oak.spi.xml.ImportBehavior;
+import org.apache.jackrabbit.oak.util.NodeUtil;
+import org.apache.jackrabbit.oak.util.TreeUtil;
+import org.junit.Test;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+public class CugAccessControlManagerTest extends AbstractCugTest {
+
+    private static final String SUPPORTED_PATH = "/content";
+    private static final String UNSUPPORTED_PATH = "/testNode";
+    private static final String INVALID_PATH = "/path/to/non/existing/tree";
+    private static final ConfigurationParameters CUG_CONFIG = ConfigurationParameters.of(CugConstants.PARAM_CUG_SUPPORTED_PATHS, SUPPORTED_PATH);
+
+    private CugAccessControlManager cugAccessControlManager;
+
+    @Override
+    public void before() throws Exception {
+        super.before();
+
+        NodeUtil rootNode = new NodeUtil(root.getTree("/"));
+        NodeUtil content = rootNode.addChild("content", NodeTypeConstants.NT_OAK_UNSTRUCTURED);
+        content.addChild("subtree", NodeTypeConstants.NT_OAK_UNSTRUCTURED);
+        rootNode.addChild("testNode", NodeTypeConstants.NT_OAK_UNSTRUCTURED);
+        root.commit();
+
+        cugAccessControlManager = new CugAccessControlManager(root, NamePathMapper.DEFAULT, getSecurityProvider());
+    }
+
+    @Override
+    public void after() throws Exception {
+        try {
+            root.getTree(SUPPORTED_PATH).remove();
+            root.getTree(UNSUPPORTED_PATH).remove();
+            root.commit();
+        } finally {
+            super.after();
+        }
+    }
+
+    @Override
+    protected ConfigurationParameters getSecurityConfigParameters() {
+        return ConfigurationParameters.of(ImmutableMap.of(AuthorizationConfiguration.NAME, CUG_CONFIG));
+    }
+
+    private CugPolicy createCug(@Nonnull String path) {
+        return new CugPolicyImpl(path, NamePathMapper.DEFAULT, getPrincipalManager(root), ImportBehavior.ABORT);
+    }
+
+    private CugPolicy getApplicableCug(@Nonnull String path) throws RepositoryException {
+        return (CugPolicy) cugAccessControlManager.getApplicablePolicies(path).nextAccessControlPolicy();
+    }
+
+    @Test
+    public void testGetSupportedPrivileges() throws Exception {
+        Privilege[] readPrivs = privilegesFromNames(PrivilegeConstants.JCR_READ);
+        Map<String, Privilege[]> pathMap = ImmutableMap.of(
+                SUPPORTED_PATH, readPrivs,
+                SUPPORTED_PATH + "/subtree", readPrivs,
+                UNSUPPORTED_PATH, new Privilege[0],
+                NodeTypeConstants.NODE_TYPES_PATH, new Privilege[0]
+        );
+
+        for (String path : pathMap.keySet()) {
+            Privilege[] expected = pathMap.get(path);
+            assertArrayEquals(expected, cugAccessControlManager.getSupportedPrivileges(path));
+        }
+    }
+
+    @Test(expected = PathNotFoundException.class)
+    public void testGetSupportedPrivilegesInvalidPath() throws Exception {
+        cugAccessControlManager.getSupportedPrivileges(INVALID_PATH);
+    }
+
+    @Test
+    public void testGetApplicablePolicies() throws Exception {
+        AccessControlPolicyIterator it = cugAccessControlManager.getApplicablePolicies(SUPPORTED_PATH);
+        assertTrue(it.hasNext());
+
+        AccessControlPolicy policy = cugAccessControlManager.getApplicablePolicies(SUPPORTED_PATH).nextAccessControlPolicy();
+        assertTrue(policy instanceof CugPolicyImpl);
+
+    }
+
+    @Test
+    public void testGetApplicablePoliciesAfterSet() throws Exception {
+        cugAccessControlManager.setPolicy(SUPPORTED_PATH, getApplicableCug(SUPPORTED_PATH));
+        AccessControlPolicyIterator it = cugAccessControlManager.getApplicablePolicies(SUPPORTED_PATH);
+        assertFalse(it.hasNext());
+    }
+
+    @Test(expected = PathNotFoundException.class)
+    public void testGetApplicablePoliciesInvalidPath() throws Exception {
+        cugAccessControlManager.getApplicablePolicies(INVALID_PATH);
+    }
+
+    @Test
+    public void testGetApplicablePoliciesUnsupportedPath() throws Exception {
+        AccessControlPolicyIterator it = cugAccessControlManager.getApplicablePolicies(UNSUPPORTED_PATH);
+        assertFalse(it.hasNext());
+    }
+
+    @Test
+    public void testGetPolicies() throws Exception {
+        AccessControlPolicy[] policies = cugAccessControlManager.getPolicies(SUPPORTED_PATH);
+        assertEquals(0, policies.length);
+    }
+
+    @Test
+    public void testGetPoliciesAfterSet() throws Exception {
+        cugAccessControlManager.setPolicy(SUPPORTED_PATH, getApplicableCug(SUPPORTED_PATH));
+
+        AccessControlPolicy[] policies = cugAccessControlManager.getPolicies(SUPPORTED_PATH);
+        assertEquals(1, policies.length);
+        assertTrue(policies[0] instanceof CugPolicyImpl);
+    }
+
+    @Test(expected = PathNotFoundException.class)
+    public void testGetPoliciesInvalidPath() throws Exception {
+        cugAccessControlManager.getPolicies(INVALID_PATH);
+    }
+
+    @Test
+    public void testGetPoliciesUnsupportedPath() throws Exception {
+        AccessControlPolicy[] policies = cugAccessControlManager.getPolicies(UNSUPPORTED_PATH);
+        assertEquals(0, policies.length);
+    }
+
+    @Test
+    public void testSetPolicy() throws Exception {
+        CugPolicy cug = getApplicableCug(SUPPORTED_PATH);
+        cug.addPrincipals(EveryonePrincipal.getInstance());
+
+        cugAccessControlManager.setPolicy(SUPPORTED_PATH, cug);
+        AccessControlPolicy[] policies = cugAccessControlManager.getPolicies(SUPPORTED_PATH);
+        assertEquals(1, policies.length);
+        AccessControlPolicy policy = policies[0];
+        assertTrue(policy instanceof CugPolicyImpl);
+        Set<Principal> principals = ((CugPolicy) policy).getPrincipals();
+        assertEquals(1, principals.size());
+        assertEquals(EveryonePrincipal.getInstance(), principals.iterator().next());
+    }
+
+
+    @Test
+    public void testSetPolicyPersisted() throws Exception {
+        CugPolicy cug = getApplicableCug(SUPPORTED_PATH);
+        cug.addPrincipals(EveryonePrincipal.getInstance());
+        cugAccessControlManager.setPolicy(SUPPORTED_PATH, cug);
+        root.commit();
+
+        Tree tree = root.getTree(SUPPORTED_PATH);
+        assertTrue(TreeUtil.isNodeType(tree, CugConstants.MIX_REP_CUG_MIXIN, root.getTree(NodeTypeConstants.NODE_TYPES_PATH)));
+        Tree cugTree = tree.getChild(CugConstants.REP_CUG_POLICY);
+        assertTrue(cugTree.exists());
+        assertEquals(CugConstants.NT_REP_CUG_POLICY, TreeUtil.getPrimaryTypeName(cugTree));
+
+        PropertyState prop = cugTree.getProperty(CugConstants.REP_PRINCIPAL_NAMES);
+        assertNotNull(prop);
+        assertTrue(prop.isArray());
+        assertEquals(Type.STRINGS, prop.getType());
+        assertEquals(1, prop.count());
+        assertEquals(EveryonePrincipal.NAME, prop.getValue(Type.STRING, 0));
+    }
+
+    @Test
+    public void testSetInvalidPolicy() throws Exception {
+        List<AccessControlPolicy> invalidPolicies = ImmutableList.of(
+                new AccessControlPolicy() {},
+                new NamedAccessControlPolicy() {
+                    public String getName() {
+                        return "name";
+                    }
+                },
+                InvalidCug.INSTANCE
+        );
+
+        for (AccessControlPolicy policy : invalidPolicies) {
+            try {
+                cugAccessControlManager.setPolicy(SUPPORTED_PATH, policy);
+                fail("Invalid cug policy must be detected.");
+            } catch (AccessControlException e) {
+                // success
+            }
+        }
+    }
+
+    @Test(expected = PathNotFoundException.class)
+    public void testSetPolicyInvalidPath() throws Exception {
+        cugAccessControlManager.setPolicy(INVALID_PATH, createCug(INVALID_PATH));
+    }
+
+    @Test(expected = AccessControlException.class)
+    public void testSetPolicyUnsupportedPath() throws Exception {
+        cugAccessControlManager.setPolicy(UNSUPPORTED_PATH, createCug(UNSUPPORTED_PATH));
+    }
+
+    @Test(expected = AccessControlException.class)
+    public void testSetPolicyPathMismatch() throws Exception {
+        cugAccessControlManager.setPolicy(SUPPORTED_PATH, createCug(SUPPORTED_PATH + "/subtree"));
+    }
+
+    @Test
+    public void testRemovePolicy() throws Exception {
+        CugPolicy cug = getApplicableCug(SUPPORTED_PATH);
+        cugAccessControlManager.setPolicy(SUPPORTED_PATH, cug);
+        cugAccessControlManager.removePolicy(SUPPORTED_PATH, cugAccessControlManager.getPolicies(SUPPORTED_PATH)[0]);
+
+        assertArrayEquals(new AccessControlPolicy[0], cugAccessControlManager.getPolicies(SUPPORTED_PATH));
+    }
+
+    @Test
+    public void testRemovePolicyPersisted() throws Exception {
+        CugPolicy cug = getApplicableCug(SUPPORTED_PATH);
+        cugAccessControlManager.setPolicy(SUPPORTED_PATH, cug);
+        root.commit();
+        cugAccessControlManager.removePolicy(SUPPORTED_PATH, cugAccessControlManager.getPolicies(SUPPORTED_PATH)[0]);
+        root.commit();
+
+        Tree tree = root.getTree(SUPPORTED_PATH);
+        assertFalse(tree.hasChild(CugConstants.REP_CUG_POLICY));
+    }
+
+    @Test
+    public void testRemoveInvalidPolicy() throws Exception {
+        List<AccessControlPolicy> invalidPolicies = ImmutableList.of(
+                new AccessControlPolicy() {},
+                new NamedAccessControlPolicy() {
+                    public String getName() {
+                        return "name";
+                    }
+                },
+                InvalidCug.INSTANCE
+        );
+
+        for (AccessControlPolicy policy : invalidPolicies) {
+            try {
+                cugAccessControlManager.removePolicy(SUPPORTED_PATH, policy);
+                fail("Invalid cug policy must be detected.");
+            } catch (AccessControlException e) {
+                // success
+            }
+        }
+    }
+
+    @Test(expected = PathNotFoundException.class)
+    public void testRemovePolicyInvalidPath() throws Exception {
+        cugAccessControlManager.removePolicy(INVALID_PATH, createCug(INVALID_PATH));
+    }
+
+    @Test(expected = AccessControlException.class)
+    public void testRemovePolicyUnsupportedPath() throws Exception {
+        cugAccessControlManager.removePolicy(UNSUPPORTED_PATH, createCug(UNSUPPORTED_PATH));
+    }
+
+    @Test(expected = AccessControlException.class)
+    public void testRemovePolicyPathMismatch() throws Exception {
+        cugAccessControlManager.removePolicy(SUPPORTED_PATH, createCug(SUPPORTED_PATH + "/subtree"));
+    }
+
+    private static final class InvalidCug implements CugPolicy {
+
+        private static final InvalidCug INSTANCE = new InvalidCug();
+
+        @Nonnull
+        @Override
+        public Set<Principal> getPrincipals() {
+            return Collections.emptySet();
+        }
+
+        @Override
+        public boolean addPrincipals(@Nonnull Principal... principals) {
+            return false;
+        }
+
+        @Override
+        public boolean removePrincipals(@Nonnull Principal... principals) {
+            return false;
+        }
+
+        @Override
+        public String getPath() {
+            return null;
+        }
+    }
 }
\ No newline at end of file
diff --git a/oak-authorization-cug/src/test/java/org/apache/jackrabbit/oak/spi/security/authorization/cug/impl/CugExcludeDefaultTest.java b/oak-authorization-cug/src/test/java/org/apache/jackrabbit/oak/spi/security/authorization/cug/impl/CugExcludeDefaultTest.java
new file mode 100644
index 0000000..205846a
--- /dev/null
+++ b/oak-authorization-cug/src/test/java/org/apache/jackrabbit/oak/spi/security/authorization/cug/impl/CugExcludeDefaultTest.java
@@ -0,0 +1,102 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.oak.spi.security.authorization.cug.impl;
+
+import java.security.Principal;
+import java.util.HashSet;
+import java.util.Set;
+
+import com.google.common.collect.ImmutableSet;
+import org.apache.jackrabbit.api.security.principal.ItemBasedPrincipal;
+import org.apache.jackrabbit.oak.spi.security.authorization.cug.CugExclude;
+import org.apache.jackrabbit.oak.spi.security.principal.AdminPrincipal;
+import org.apache.jackrabbit.oak.spi.security.principal.PrincipalImpl;
+import org.apache.jackrabbit.oak.spi.security.principal.SystemPrincipal;
+import org.apache.jackrabbit.oak.spi.security.principal.SystemUserPrincipal;
+import org.junit.Test;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+public class CugExcludeDefaultTest {
+
+    private CugExclude exclude = new CugExclude.Default();
+
+    @Test
+    public void testSystemPrincipal() {
+        Set<Principal> principals = ImmutableSet.<Principal>of(SystemPrincipal.INSTANCE);
+        assertTrue(exclude.isExcluded(principals));
+    }
+
+    @Test
+    public void testAdminPrincipal() {
+        Set<Principal> principals = ImmutableSet.<Principal>of(new AdminPrincipal() {
+            @Override
+            public String getName() {
+                return "admin";
+            }
+        });
+        assertTrue(exclude.isExcluded(principals));
+    }
+
+    @Test
+    public void testSystemUserPrincipal() {
+        Set<Principal> principals = ImmutableSet.<Principal>of(new SystemUserPrincipal() {
+            @Override
+            public String getName() {
+                return "test";
+            }
+        });
+        assertTrue(exclude.isExcluded(principals));
+    }
+
+    @Test
+    public void testPrincipals() {
+        Set<Principal> principals = new HashSet<Principal>();
+        principals.add(new PrincipalImpl("test"));
+        principals.add(new ItemBasedPrincipal() {
+            @Override
+            public String getPath() {
+                return "/path";
+            }
+
+            @Override
+            public String getName() {
+                return "test";
+            }
+        });
+
+        assertFalse(exclude.isExcluded(principals));
+        for (Principal p : principals) {
+            assertFalse(exclude.isExcluded(ImmutableSet.of(p)));
+        }
+    }
+
+    @Test
+    public void testMixedPrincipals() {
+        Set<Principal> principals = new HashSet<Principal>();
+        principals.add(new PrincipalImpl("test"));
+        principals.add(new SystemUserPrincipal() {
+            @Override
+            public String getName() {
+                return "test";
+            }
+        });
+
+        assertTrue(exclude.isExcluded(principals));
+    }
+}
diff --git a/oak-authorization-cug/src/test/java/org/apache/jackrabbit/oak/spi/security/authorization/cug/impl/CugExcludeImplTest.java b/oak-authorization-cug/src/test/java/org/apache/jackrabbit/oak/spi/security/authorization/cug/impl/CugExcludeImplTest.java
index 686b454..8d689a1 100644
--- a/oak-authorization-cug/src/test/java/org/apache/jackrabbit/oak/spi/security/authorization/cug/impl/CugExcludeImplTest.java
+++ b/oak-authorization-cug/src/test/java/org/apache/jackrabbit/oak/spi/security/authorization/cug/impl/CugExcludeImplTest.java
@@ -16,7 +16,46 @@
  */
 package org.apache.jackrabbit.oak.spi.security.authorization.cug.impl;
 
+import java.security.Principal;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import org.apache.jackrabbit.oak.spi.security.principal.PrincipalImpl;
+import org.junit.Test;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
 public class CugExcludeImplTest {
 
-    // TODO
+    private CugExcludeImpl exclude = new CugExcludeImpl();
+    private Set<Principal> principals = ImmutableSet.<Principal>of(new PrincipalImpl("test"));
+
+    @Test
+    public void testEmpty() {
+        assertFalse(exclude.isExcluded(principals));
+    }
+
+    @Test
+    public void testEmpty2() {
+        exclude.activate(Collections.<String, Object>emptyMap());
+        assertFalse(exclude.isExcluded(principals));
+    }
+
+    @Test
+    public void testExcludeTest() {
+        Map<String, Object> m = ImmutableMap.<String, Object>of("principalNames", new String[] {"a","b","c","test"});
+        exclude.activate(m);
+        assertTrue(exclude.isExcluded(principals));
+    }
+
+    @Test
+    public void testExcludeAnother() {
+        Map<String, Object> m = ImmutableMap.<String, Object>of("principalNames", new String[] {"a","b","c","test"});
+        exclude.activate(m);
+        assertFalse(exclude.isExcluded(ImmutableSet.<Principal>of(new PrincipalImpl("another"))));
+    }
 }
\ No newline at end of file
diff --git a/oak-authorization-cug/src/test/java/org/apache/jackrabbit/oak/spi/security/authorization/cug/impl/CugPermissionProviderTest.java b/oak-authorization-cug/src/test/java/org/apache/jackrabbit/oak/spi/security/authorization/cug/impl/CugPermissionProviderTest.java
index ee33b2a..fb1d10f 100644
--- a/oak-authorization-cug/src/test/java/org/apache/jackrabbit/oak/spi/security/authorization/cug/impl/CugPermissionProviderTest.java
+++ b/oak-authorization-cug/src/test/java/org/apache/jackrabbit/oak/spi/security/authorization/cug/impl/CugPermissionProviderTest.java
@@ -16,7 +16,7 @@
  */
 package org.apache.jackrabbit.oak.spi.security.authorization.cug.impl;
 
-public class CugPermissionProviderTest {
+public class CugPermissionProviderTest extends AbstractCugTest {
 
     // TODO
 }
\ No newline at end of file
diff --git a/oak-authorization-cug/src/test/java/org/apache/jackrabbit/oak/spi/security/authorization/cug/impl/CugPolicyImplTest.java b/oak-authorization-cug/src/test/java/org/apache/jackrabbit/oak/spi/security/authorization/cug/impl/CugPolicyImplTest.java
index 85a476b..2f8c26e 100644
--- a/oak-authorization-cug/src/test/java/org/apache/jackrabbit/oak/spi/security/authorization/cug/impl/CugPolicyImplTest.java
+++ b/oak-authorization-cug/src/test/java/org/apache/jackrabbit/oak/spi/security/authorization/cug/impl/CugPolicyImplTest.java
@@ -16,37 +16,160 @@
  */
 package org.apache.jackrabbit.oak.spi.security.authorization.cug.impl;
 
-import org.junit.Test;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import java.security.Principal;
+import java.util.Iterator;
+import java.util.Set;
+import javax.annotation.Nonnull;
+import javax.jcr.security.AccessControlException;
 
-/**
- * CugPolicyImplTest... TODO
- */
-public class CugPolicyImplTest {
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import org.apache.jackrabbit.api.security.principal.PrincipalManager;
+import org.apache.jackrabbit.oak.AbstractSecurityTest;
+import org.apache.jackrabbit.oak.namepath.LocalNameMapper;
+import org.apache.jackrabbit.oak.namepath.NamePathMapper;
+import org.apache.jackrabbit.oak.namepath.NamePathMapperImpl;
+import org.apache.jackrabbit.oak.spi.security.authorization.cug.CugPolicy;
+import org.apache.jackrabbit.oak.spi.security.principal.EveryonePrincipal;
+import org.apache.jackrabbit.oak.spi.security.principal.PrincipalImpl;
+import org.apache.jackrabbit.oak.spi.xml.ImportBehavior;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertTrue;
+
+public class CugPolicyImplTest extends AbstractSecurityTest {
+
+    private String path = "/testPath";
+    private PrincipalManager principalManager;
+    private Principal testPrincipal = new PrincipalImpl("test");
+    Set<Principal> principals = ImmutableSet.of(testPrincipal);
+
+    @Override
+    public void before() throws Exception {
+        super.before();
+
+        principalManager = getPrincipalManager(root);
+    }
+
+    private CugPolicyImpl createEmptyCugPolicy() {
+        return new CugPolicyImpl(path, NamePathMapper.DEFAULT, principalManager, ImportBehavior.ABORT);
+    }
+
+    private CugPolicyImpl createCugPolicy(@Nonnull Set<Principal> principals) {
+        return new CugPolicyImpl(path, NamePathMapper.DEFAULT, principalManager, ImportBehavior.ABORT, principals);
+    }
 
     @Test
     public void testGetPrincipals() {
-        // TODO
+        CugPolicyImpl cug = createCugPolicy(principals);
+
+        Set<Principal> principalSet = cug.getPrincipals();
+        assertFalse(principalSet.isEmpty());
+        assertEquals(principals, principalSet);
+        assertNotSame(principals, principalSet);
+    }
+
+    @Test
+    public void testGetPrincipals2() {
+        CugPolicyImpl empty = createEmptyCugPolicy();
+
+        assertTrue(empty.getPrincipals().isEmpty());
     }
 
     @Test
     public void testGetPrincipalNames() {
-        // TODO
+        CugPolicyImpl cug = createCugPolicy(principals);
+
+        Iterator<String> it = cug.getPrincipalNames().iterator();
+        assertTrue(it.hasNext());
+        assertEquals("test", it.next());
+        assertFalse(it.hasNext());
     }
 
     @Test
-    public void testAddPrincipals() {
-        // TODO
+    public void testGetPrincipalNames2() {
+        CugPolicyImpl empty = createEmptyCugPolicy();
+
+        assertFalse(empty.getPrincipalNames().iterator().hasNext());
     }
 
     @Test
-    public void testRemovePrincipals() {
-        // TODO
+    public void testAddPrincipals() throws Exception {
+        CugPolicy empty = createEmptyCugPolicy();
+        assertTrue(empty.addPrincipals(EveryonePrincipal.getInstance()));
+        assertFalse(empty.addPrincipals(EveryonePrincipal.getInstance()));
+
+        CugPolicy cug = createCugPolicy(principals);
+        assertTrue(cug.addPrincipals(EveryonePrincipal.getInstance()));
+        assertFalse(cug.addPrincipals(EveryonePrincipal.getInstance()));
+    }
+
+    @Test(expected = AccessControlException.class)
+    public void testAddInvalidPrincipalsAbort() throws Exception {
+        CugPolicy cug = new CugPolicyImpl(path, NamePathMapper.DEFAULT, principalManager, ImportBehavior.ABORT);
+        cug.addPrincipals(
+                EveryonePrincipal.getInstance(),
+                new PrincipalImpl("unknown"));
+    }
+
+    @Test
+    public void testAddInvalidPrincipalsBestEffort() throws Exception {
+        CugPolicy cug = new CugPolicyImpl(path, NamePathMapper.DEFAULT, principalManager, ImportBehavior.BESTEFFORT, principals);
+        assertTrue(cug.addPrincipals(
+                EveryonePrincipal.getInstance(),
+                new PrincipalImpl("unknown")));
+
+        Set<Principal> principalSet = cug.getPrincipals();
+        assertEquals(3, principalSet.size());
+    }
+
+    @Test
+    public void testAddInvalidPrincipalsIgnore() throws Exception {
+        CugPolicy cug = new CugPolicyImpl(path, NamePathMapper.DEFAULT, principalManager, ImportBehavior.IGNORE, principals);
+        assertTrue(cug.addPrincipals(
+                new PrincipalImpl("unknown"),
+                EveryonePrincipal.getInstance()));
+
+        Set<Principal> principalSet = cug.getPrincipals();
+        assertEquals(2, principalSet.size());
+        assertFalse(principalSet.contains(new PrincipalImpl("unknown")));
+    }
+
+    @Test
+    public void testAddContainedPrincipal() throws Exception {
+        CugPolicy cug = new CugPolicyImpl(path, NamePathMapper.DEFAULT, principalManager, ImportBehavior.BESTEFFORT, principals);
+        assertFalse(cug.addPrincipals(
+                new PrincipalImpl("test")));
+
+        assertEquals(principals, cug.getPrincipals());
+    }
+
+    @Test
+    public void testRemovePrincipals() throws Exception {
+        CugPolicy cug = new CugPolicyImpl(path, NamePathMapper.DEFAULT, principalManager,
+                ImportBehavior.BESTEFFORT,
+                ImmutableSet.of(testPrincipal, EveryonePrincipal.getInstance()));
+
+        assertFalse(cug.removePrincipals(new PrincipalImpl("unknown")));
+        assertTrue(cug.removePrincipals(testPrincipal, EveryonePrincipal.getInstance(), new PrincipalImpl("unknown")));
+        assertTrue(cug.getPrincipals().isEmpty());
     }
 
     @Test
     public void testGetPath() {
-        // TODO
+        CugPolicy empty = createEmptyCugPolicy();
+        assertEquals(path, empty.getPath());
+    }
+
+    @Test
+    public void testGetPathWithRemapping() {
+        String oakPath = "/oak:testPath";
+        NamePathMapper mapper = new NamePathMapperImpl(new LocalNameMapper(root, ImmutableMap.of("quercus", "http://jackrabbit.apache.org/oak/ns/1.0")));
+
+        CugPolicy empty = new CugPolicyImpl(oakPath, mapper, principalManager, ImportBehavior.ABORT);
+        assertEquals("/quercus:testPath", empty.getPath());
     }
 }
\ No newline at end of file
diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBDocumentSerializer.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBDocumentSerializer.java
index 6b1e6ee..5dcce1f 100644
--- a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBDocumentSerializer.java
+++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBDocumentSerializer.java
@@ -31,6 +31,7 @@
 import org.apache.jackrabbit.oak.plugins.document.Document;
 import org.apache.jackrabbit.oak.plugins.document.DocumentStore;
 import org.apache.jackrabbit.oak.plugins.document.DocumentStoreException;
+import org.apache.jackrabbit.oak.plugins.document.NodeDocument;
 import org.apache.jackrabbit.oak.plugins.document.Revision;
 import org.apache.jackrabbit.oak.plugins.document.StableRevisionComparator;
 import org.apache.jackrabbit.oak.plugins.document.UpdateOp;
@@ -52,6 +53,8 @@
     private static final String MODIFIED = "_modified";
     private static final String MODCOUNT = "_modCount";
     private static final String ID = "_id";
+    private static final String HASBINARY = NodeDocument.HAS_BINARY_FLAG;
+
     private final Comparator<Revision> comparator = StableRevisionComparator.REVERSE;
 
     public RDBDocumentSerializer(DocumentStore store, Set<String> columnProperties) {
@@ -197,6 +200,9 @@
         doc.put(ID, row.getId());
         doc.put(MODIFIED, row.getModified());
         doc.put(MODCOUNT, row.getModcount());
+        if (row.hasBinaryProperties()) {
+            doc.put(HASBINARY, NodeDocument.HAS_BINARY_VAL);
+        }
 
         JSONParser jp = new JSONParser();
 
diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBDocumentStore.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBDocumentStore.java
index ec248e8..a7c213f 100755
--- a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBDocumentStore.java
+++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBDocumentStore.java
@@ -345,12 +345,16 @@
     // for DBs that prefer "limit" over "fetch first"
     private boolean needsLimit = false;
 
+    // for DBs that do not support CASE in SELECT (currently all)
+    private boolean allowsCaseInSelect = true;
+
     // set of supported indexed properties
     private static Set<String> INDEXEDPROPERTIES = new HashSet<String>(Arrays.asList(new String[] { MODIFIED,
             NodeDocument.HAS_BINARY_FLAG }));
 
     // set of properties not serialized to JSON
-    private static Set<String> COLUMNPROPERTIES = new HashSet<String>(Arrays.asList(new String[] { ID, MODIFIED, MODCOUNT }));
+    private static Set<String> COLUMNPROPERTIES = new HashSet<String>(Arrays.asList(new String[] { ID,
+            NodeDocument.HAS_BINARY_FLAG, MODIFIED, MODCOUNT }));
 
     private RDBDocumentSerializer SR = new RDBDocumentSerializer(this, COLUMNPROPERTIES);
 
@@ -458,10 +462,10 @@
 
     private <T extends Document> T readDocumentCached(final Collection<T> collection, final String id, int maxCacheAge) {
         if (collection != Collection.NODES) {
-            return readDocumentUncached(collection, id);
+            return readDocumentUncached(collection, id, null);
         } else {
             CacheValue cacheKey = new StringValue(id);
-            NodeDocument doc;
+            NodeDocument doc = null;
             if (maxCacheAge > 0) {
                 // first try without lock
                 doc = nodesCache.getIfPresent(cacheKey);
@@ -473,6 +477,7 @@
             }
             try {
                 Lock lock = getAndLock(id);
+                final NodeDocument cachedDoc = doc;
                 try {
                     if (maxCacheAge == 0) {
                         invalidateCache(collection, id);
@@ -481,7 +486,7 @@
                         doc = nodesCache.get(cacheKey, new Callable<NodeDocument>() {
                             @Override
                             public NodeDocument call() throws Exception {
-                                NodeDocument doc = (NodeDocument) readDocumentUncached(collection, id);
+                                NodeDocument doc = (NodeDocument) readDocumentUncached(collection, id, cachedDoc);
                                 if (doc != null) {
                                     doc.seal();
                                 }
@@ -491,7 +496,7 @@
                         if (maxCacheAge == 0 || maxCacheAge == Integer.MAX_VALUE) {
                             break;
                         }
-                        if (System.currentTimeMillis() - doc.getCreated() < maxCacheAge) {
+                        if (System.currentTimeMillis() - doc.getLastCheckTime() < maxCacheAge) {
                             break;
                         }
                         // too old: invalidate, try again
@@ -566,7 +571,7 @@
                 // this is an edge case, so it's ok to bypass the cache
                 // (avoiding a race condition where the DB is already updated
                 // but the cache is not)
-                oldDoc = readDocumentUncached(collection, update.getId());
+                oldDoc = readDocumentUncached(collection, update.getId(), null);
                 if (oldDoc == null) {
                     // something else went wrong
                     LOG.error("insert failed, but document " + update.getId() + " is not present, aborting", ex);
@@ -606,7 +611,7 @@
                             if (lastmodcount == newmodcount) {
                                 // cached copy did not change so it probably was updated by
                                 // a different instance, get a fresh one
-                                oldDoc = readDocumentUncached(collection, update.getId());
+                                oldDoc = readDocumentUncached(collection, update.getId(), null);
                             }
                         }
 
@@ -751,13 +756,29 @@
     }
 
     @CheckForNull
-    private <T extends Document> T readDocumentUncached(Collection<T> collection, String id) {
+    private <T extends Document> T readDocumentUncached(Collection<T> collection, String id, NodeDocument cachedDoc) {
         Connection connection = null;
         String tableName = getTable(collection);
         try {
+            long lastmodcount = -1;
+            if (cachedDoc != null && cachedDoc.getModCount() != null) {
+                lastmodcount = cachedDoc.getModCount().longValue();
+            }
             connection = getConnection();
-            RDBRow row = dbRead(connection, tableName, id);
-            return row != null ? SR.fromRow(collection, row) : null;
+            RDBRow row = dbRead(connection, tableName, id, lastmodcount);
+            if (row == null) {
+                return null;
+            }
+            else {
+                if (lastmodcount == row.getModcount()) {
+                    // we can re-use the cached document
+                    cachedDoc.markUpToDate(System.currentTimeMillis());
+                    return (T)cachedDoc;
+                }
+                else {
+                    return SR.fromRow(collection, row);
+                }
+            }
         } catch (Exception ex) {
             throw new DocumentStoreException(ex);
         } finally {
@@ -951,17 +972,39 @@
     }
 
     @CheckForNull
-    private RDBRow dbRead(Connection connection, String tableName, String id) throws SQLException {
-        PreparedStatement stmt = connection.prepareStatement("select MODIFIED, MODCOUNT, DATA, BDATA from " + tableName + " where ID = ?");
+    private RDBRow dbRead(Connection connection, String tableName, String id, long lastmodcount) throws SQLException {
+        PreparedStatement stmt;
+        boolean useCaseStatement = lastmodcount != -1 && allowsCaseInSelect;
+        if (useCaseStatement) {
+            // either we don't have a previous version of the document
+            // or the database does not support CASE in SELECT
+            stmt = connection.prepareStatement("select MODIFIED, MODCOUNT, HASBINARY, DATA, BDATA from " + tableName
+                    + " where ID = ?");
+        } else {
+            // the case statement causes the actual row data not to be
+            // sent in case we already have it
+            stmt = connection
+                    .prepareStatement("select MODIFIED, MODCOUNT, HASBINARY, case MODCOUNT when ? then null else DATA end as DATA, "
+                            + "case MODCOUNT when ? then null else BDATA end as BDATA from " + tableName + " where ID = ?");
+        }
+
         try {
-            stmt.setString(1, id);
+            if (useCaseStatement) {
+                stmt.setString(1, id);
+            }
+            else {
+                stmt.setLong(1, lastmodcount);
+                stmt.setLong(2, lastmodcount);
+                stmt.setString(3, id);
+            }
             ResultSet rs = stmt.executeQuery();
             if (rs.next()) {
                 long modified = rs.getLong(1);
                 long modcount = rs.getLong(2);
-                String data = rs.getString(3);
-                byte[] bdata = rs.getBytes(4);
-                return new RDBRow(id, modified, modcount, data, bdata);
+                long hasBinary = rs.getLong(3);
+                String data = rs.getString(4);
+                byte[] bdata = rs.getBytes(5);
+                return new RDBRow(id, hasBinary == 1, modified, modcount, data, bdata);
             } else {
                 return null;
             }
@@ -982,12 +1025,11 @@
 
     private List<RDBRow> dbQuery(Connection connection, String tableName, String minId, String maxId, String indexedProperty,
             long startValue, int limit) throws SQLException {
-        String t = "select ID, MODIFIED, MODCOUNT, DATA, BDATA from " + tableName + " where ID > ? and ID < ?";
+        String t = "select ID, MODIFIED, MODCOUNT, HASBINARY, DATA, BDATA from " + tableName + " where ID > ? and ID < ?";
         if (indexedProperty != null) {
             if (MODIFIED.equals(indexedProperty)) {
                 t += " and MODIFIED >= ?";
-            }
-            else if (NodeDocument.HAS_BINARY_FLAG.equals(indexedProperty)) {
+            } else if (NodeDocument.HAS_BINARY_FLAG.equals(indexedProperty)) {
                 if (startValue != NodeDocument.HAS_BINARY_VAL) {
                     throw new DocumentStoreException("unsupported value for property " + NodeDocument.HAS_BINARY_FLAG);
                 }
@@ -996,7 +1038,7 @@
         }
         t += " order by ID";
         if (limit != Integer.MAX_VALUE) {
-            t += this.needsConcat ? (" LIMIT " + limit) : (" FETCH FIRST " + limit + " ROWS ONLY");
+            t += this.needsLimit ? (" LIMIT " + limit) : (" FETCH FIRST " + limit + " ROWS ONLY");
         }
         PreparedStatement stmt = connection.prepareStatement(t);
         List<RDBRow> result = new ArrayList<RDBRow>();
@@ -1018,9 +1060,10 @@
                 }
                 long modified = rs.getLong(2);
                 long modcount = rs.getLong(3);
-                String data = rs.getString(4);
-                byte[] bdata = rs.getBytes(5);
-                result.add(new RDBRow(id, modified, modcount, data, bdata));
+                long hasBinary = rs.getLong(4);
+                String data = rs.getString(5);
+                byte[] bdata = rs.getBytes(6);
+                result.add(new RDBRow(id, hasBinary == 1, modified, modcount, data, bdata));
             }
         } finally {
             stmt.close();
diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBRow.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBRow.java
index 43cd098..c10d292 100644
--- a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBRow.java
+++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBRow.java
@@ -16,39 +16,58 @@
  */
 package org.apache.jackrabbit.oak.plugins.document.rdb;
 
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+
 /**
  * Container for the information in a RDB database column.
+ * <p>
+ * Note that the String "data" and the byte[] "bdata" may be null
+ * when the SQL SELECT request was conditional on "modcount" being
+ * unchanged.
  */
 public class RDBRow {
 
-    private final String id, data;
+    private final String id;
+    private final boolean hasBinaryProperties;
     private final long modified, modcount;
+    private final String data;
     private final byte[] bdata;
 
-    public RDBRow(String id, long modified, long modcount, String data, byte[] bdata) {
+    public RDBRow(String id, boolean hasBinaryProperties, long modified, long modcount, String data, byte[] bdata) {
         this.id = id;
+        this.hasBinaryProperties = hasBinaryProperties;
         this.modified = modified;
         this.modcount = modcount;
         this.data = data;
         this.bdata = bdata;
     }
 
+    @Nonnull
     public String getId() {
         return id;
     }
 
+    public boolean hasBinaryProperties() {
+        return hasBinaryProperties;
+    }
+
+    @CheckForNull
     public String getData() {
         return data;
     }
 
+    @Nonnull
     public long getModified() {
         return modified;
     }
 
+    @Nonnull
     public long getModcount() {
         return modcount;
     }
 
+    @CheckForNull
     public byte[] getBdata() {
         return bdata;
     }
diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/FullTextSearchImpl.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/FullTextSearchImpl.java
index f922007..f93ed0e 100644
--- a/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/FullTextSearchImpl.java
+++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/FullTextSearchImpl.java
@@ -48,7 +48,7 @@
      * instead, as in the spec, using double quotes.
      */
     public static final boolean JACKRABBIT_2_SINGLE_QUOTED_PHRASE = true;
-    
+
     private final String selectorName;
     private final String relativePath;
     private final String propertyName;
@@ -251,8 +251,12 @@
     public void restrict(FilterImpl f) {
         if (propertyName != null) {
             if (f.getSelector().equals(selector)) {
-                String pn = normalizePropertyName(propertyName);
-                f.restrictProperty(pn, Operator.NOT_EQUAL, null);
+                String p = propertyName;
+                if (relativePath != null) {
+                    p = PathUtils.concat(p, relativePath);
+                }                
+                p = normalizePropertyName(p);
+                f.restrictProperty(p, Operator.NOT_EQUAL, null);
             }
         }
         f.restrictFulltextCondition(fullTextSearchExpression.currentValue().getValue(Type.STRING));
diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/NativeFunctionImpl.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/NativeFunctionImpl.java
index 6a2f057..083d7ec 100644
--- a/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/NativeFunctionImpl.java
+++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/NativeFunctionImpl.java
@@ -23,7 +23,8 @@
 
 import org.apache.jackrabbit.oak.api.PropertyValue;
 import org.apache.jackrabbit.oak.query.index.FilterImpl;
-import org.apache.jackrabbit.oak.spi.query.QueryIndex.FulltextQueryIndex;
+
+import static org.apache.jackrabbit.oak.spi.query.QueryIndex.NativeQueryIndex;
 
 /**
  * A native function condition.
@@ -61,7 +62,7 @@
         // disable evaluation if a fulltext index is used,
         // and because we don't know how to process native
         // conditions
-        if (!(selector.getIndex() instanceof FulltextQueryIndex)) {
+        if (!(selector.getIndex() instanceof NativeQueryIndex)) {
             throw new IllegalArgumentException("No full-text index was found that can process the condition " + toString());
         }
         // we assume the index only returns the requested entries
diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/query/index/FilterImpl.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/query/index/FilterImpl.java
index 728973c..6f61a58 100644
--- a/oak-core/src/main/java/org/apache/jackrabbit/oak/query/index/FilterImpl.java
+++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/query/index/FilterImpl.java
@@ -401,7 +401,7 @@
             buff.append("query=").append(queryStatement);
         }
         if (fullTextConstraint != null) {
-            buff.append("fullText=").append(fullTextConstraint);
+            buff.append(" fullText=").append(fullTextConstraint);
         }
         buff.append(", path=").append(getPathPlan());
         if (!propertyRestrictions.isEmpty()) {
diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/query/xpath/Statement.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/query/xpath/Statement.java
index 6113c0c..9763c13 100644
--- a/oak-core/src/main/java/org/apache/jackrabbit/oak/query/xpath/Statement.java
+++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/query/xpath/Statement.java
@@ -83,9 +83,7 @@
     }
     
     private static void addToUnionList(Expression condition,  ArrayList<Expression> unionList) {
-        if (condition.containsFullTextCondition()) {
-            // do not use union
-        } else if (condition instanceof OrCondition) {
+        if (condition instanceof OrCondition) {
             OrCondition or = (OrCondition) condition;
             // conditions of type                
             // @x = 1 or @y = 2
diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/permission/PermissionStoreImpl.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/permission/PermissionStoreImpl.java
index 3c4b35e..d394a4a 100644
--- a/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/permission/PermissionStoreImpl.java
+++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/permission/PermissionStoreImpl.java
@@ -48,31 +48,29 @@
      */
     private static final Logger log = LoggerFactory.getLogger(PermissionStoreImpl.class);
 
-    private Tree permissionsTree;
-
     private final String workspaceName;
 
     private final RestrictionProvider restrictionProvider;
 
-    private final PrivilegeBitsProvider privilegeBitsProvider;
-
-    private PrivilegeBits allBits;
-
     private final Map<String, Tree> principalTreeMap = new HashMap<String, Tree>();
 
+    private Tree permissionsTree;
+    private PrivilegeBits allBits;
+
     public PermissionStoreImpl(Root root, String workspaceName, RestrictionProvider restrictionProvider) {
-        this.permissionsTree = PermissionUtil.getPermissionsRoot(root, workspaceName);
         this.workspaceName = workspaceName;
         this.restrictionProvider = restrictionProvider;
-        this.privilegeBitsProvider = new PrivilegeBitsProvider(root);
-
-        allBits = privilegeBitsProvider.getBits(PrivilegeConstants.JCR_ALL);
+        reset(root);
     }
 
-    protected void flush(Root root) {
-        this.permissionsTree = PermissionUtil.getPermissionsRoot(root, workspaceName);
-        this.principalTreeMap.clear();
-        allBits = privilegeBitsProvider.getBits(PrivilegeConstants.JCR_ALL);
+    protected void flush(@Nonnull Root root) {
+        principalTreeMap.clear();
+        reset(root);
+    }
+
+    private void reset(@Nonnull Root root) {
+        permissionsTree = PermissionUtil.getPermissionsRoot(root, workspaceName);
+        allBits = new PrivilegeBitsProvider(root).getBits(PrivilegeConstants.JCR_ALL);
     }
 
     @CheckForNull
@@ -194,4 +192,4 @@
     private static boolean isJcrAll(PropertyState property) {
         return property.count() == 1 && property.getValue(Type.LONG, 0) == DYNAMIC_ALL_BITS;
     }
-}
\ No newline at end of file
+}
diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/Cursors.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/Cursors.java
index 74060a1..10a459e 100644
--- a/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/Cursors.java
+++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/Cursors.java
@@ -439,7 +439,12 @@
         ConcatCursor(List<Cursor> cursors, QueryEngineSettings settings) {
             this.cursors = cursors;
             this.settings = settings;
-            this.currentCursor = cursors.remove(0);
+            if (cursors.size() == 0) {
+                init = true;
+                closed = true;
+            } else {
+                this.currentCursor = cursors.remove(0);
+            }
         }
 
         @Override
diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/QueryIndex.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/QueryIndex.java
index 1f7d378..a5c8bf8 100644
--- a/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/QueryIndex.java
+++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/QueryIndex.java
@@ -115,12 +115,19 @@
     String getIndexName();
 
     /**
-     * A maker interface which means this index supports may support more than
+     *  A marker interface which means this index supports executing native queries
+     */
+    public interface NativeQueryIndex {
+
+    }
+
+    /**
+     * A marker interface which means this index supports may support more than
      * just the minimal fulltext query syntax. If this index is used, then the
      * query engine does not verify the fulltext constraint(s) for the given
      * selector.
      */
-    public interface FulltextQueryIndex extends QueryIndex {
+    public interface FulltextQueryIndex extends QueryIndex, NativeQueryIndex {
 
         /**
          * Returns the NodeAggregator responsible for providing the aggregation
diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/xml/ProtectedItemImporter.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/xml/ProtectedItemImporter.java
index 4e070fd..c26463e 100644
--- a/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/xml/ProtectedItemImporter.java
+++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/xml/ProtectedItemImporter.java
@@ -56,7 +56,8 @@
     boolean init(@Nonnull Session session, @Nonnull Root root,
             @Nonnull NamePathMapper namePathMapper,
             boolean isWorkspaceImport, int uuidBehavior,
-            @Nonnull ReferenceChangeTracker referenceTracker, SecurityProvider securityProvider);
+            @Nonnull ReferenceChangeTracker referenceTracker,
+            @Nonnull SecurityProvider securityProvider);
 
     /**
      * Post processing protected reference properties underneath a protected
diff --git a/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/BasicDocumentStoreTest.java b/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/BasicDocumentStoreTest.java
index 5a9d057..026c979 100644
--- a/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/BasicDocumentStoreTest.java
+++ b/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/BasicDocumentStoreTest.java
@@ -523,6 +523,31 @@
     }
 
     @Test
+    public void testPerfReadBigDoc() {
+        String id = this.getClass().getName() + ".testReadBigDoc";
+        long duration = 1000;
+        int cnt = 0;
+
+        super.ds.remove(Collection.NODES, Collections.singletonList(id));
+        UpdateOp up = new UpdateOp(id, true);
+        up.set("_id", id);
+        for (int i = 0; i < 100; i++) {
+            up.set("foo" + i, generateString(1024, true));
+        }
+        assertTrue(super.ds.create(Collection.NODES, Collections.singletonList(up)));
+        removeMe.add(id);
+
+        long end = System.currentTimeMillis() + duration;
+        while (System.currentTimeMillis() < end) {
+            NodeDocument d = super.ds.find(Collection.NODES, id, 10); // allow 10ms old entries
+            cnt += 1;
+        }
+
+        LOG.info("big doc read from " + super.dsname + " was "
+                + cnt + " in " + duration + "ms (" + (cnt / (duration / 1000f)) + "/s)");
+    }
+
+    @Test
     public void testUpdatePerfSmall() {
         updatePerf(16, false);
     }
diff --git a/oak-core/src/test/java/org/apache/jackrabbit/oak/query/AbstractQueryTest.java b/oak-core/src/test/java/org/apache/jackrabbit/oak/query/AbstractQueryTest.java
index a50352b..83168f7 100644
--- a/oak-core/src/test/java/org/apache/jackrabbit/oak/query/AbstractQueryTest.java
+++ b/oak-core/src/test/java/org/apache/jackrabbit/oak/query/AbstractQueryTest.java
@@ -71,6 +71,7 @@
 
     protected static final String TEST_INDEX_NAME = "test-index";
     protected static final String SQL2 = QueryEngineImpl.SQL2;
+    protected static final String XPATH = QueryEngineImpl.XPATH;
 
     protected QueryEngine qe;
     protected ContentSession session;
@@ -231,6 +232,10 @@
     }
 
     protected List<String> executeQuery(String query, String language, boolean pathsOnly) {
+        return executeQuery(query, language, pathsOnly, false);
+    }
+
+    protected List<String> executeQuery(String query, String language, boolean pathsOnly, boolean skipSort) {
         long time = System.currentTimeMillis();
         List<String> lines = new ArrayList<String>();
         try {
@@ -242,7 +247,7 @@
                 }
                 lines.add(r);
             }
-            if (!query.contains("order by")) {
+            if (!query.contains("order by") && !skipSort) {
                 Collections.sort(lines);
             }
         } catch (ParseException e) {
@@ -263,13 +268,19 @@
 
     protected List<String> assertQuery(String sql, String language,
             List<String> expected) {
-        List<String> paths = executeQuery(sql, language, true);
+        return assertQuery(sql, language, expected, false);
+    }
+
+    protected List<String> assertQuery(String sql, String language,
+                                       List<String> expected, boolean skipSort) {
+        List<String> paths = executeQuery(sql, language, true, skipSort);
         for (String p : expected) {
             assertTrue("Expected path " + p + " not found", paths.contains(p));
         }
         assertEquals("Result set size is different", expected.size(),
                 paths.size());
         return paths;
+
     }
 
     protected void setTraversalEnabled(boolean traversalEnabled) {
diff --git a/oak-core/src/test/resources/org/apache/jackrabbit/oak/query/xpath.txt b/oak-core/src/test/resources/org/apache/jackrabbit/oak/query/xpath.txt
index 8119e72..579bef2 100644
--- a/oak-core/src/test/resources/org/apache/jackrabbit/oak/query/xpath.txt
+++ b/oak-core/src/test/resources/org/apache/jackrabbit/oak/query/xpath.txt
@@ -237,8 +237,11 @@
 select [jcr:path], [jcr:score], *
   from [nt:base] as a
   where [jcr:primaryType] = 'nt:unstructured'
-  and (contains(*, 'hello')
-  or contains(*, 'world'))
+  and contains(*, 'hello')
+  union select [jcr:path], [jcr:score], *
+  from [nt:base] as a
+  where [jcr:primaryType] = 'nt:unstructured'
+  and contains(*, 'world')
   /* xpath ... */
 
 xpath2sql //*[(((@jcr:primaryType = 'nt:unstructured')
@@ -249,9 +252,14 @@
 select [jcr:path], [jcr:score], *
   from [nt:base] as a
   where [jcr:primaryType] = 'nt:unstructured'
-  and (contains(*, 'hello')
-  or contains(*, 'world'))
-  or [content] = '/data'
+  and contains(*, 'hello')
+  union select [jcr:path], [jcr:score], *
+  from [nt:base] as a
+  where [jcr:primaryType] = 'nt:unstructured'
+  and contains(*, 'world')
+  union select [jcr:path], [jcr:score], *
+  from [nt:base] as a
+  where [content] = '/data'
   and [jcr:primaryType] = 'nt:folder'
   /* xpath ... */
 
@@ -590,10 +598,18 @@
 select [jcr:path], [jcr:score], *
   from [rep:Authorizable] as a
   where contains([profile/givenName/*], '**')
-  or contains([profile/familyName/*], '**')
-  or contains([profile/email/*], '**')
-  or [rep:principalName] like '%%'
-  or name(a) like '%%'
+  union select [jcr:path], [jcr:score], *
+  from [rep:Authorizable] as a
+  where contains([profile/familyName/*], '**')
+  union select [jcr:path], [jcr:score], *
+  from [rep:Authorizable] as a
+  where contains([profile/email/*], '**')
+  union select [jcr:path], [jcr:score], *
+  from [rep:Authorizable] as a
+  where [rep:principalName] like '%%'
+  union select [jcr:path], [jcr:score], *
+  from [rep:Authorizable] as a
+  where name(a) like '%%'
   order by [rep:principalName]
   /* xpath ... */
 
@@ -607,10 +623,18 @@
 select [jcr:path], [jcr:score], *
   from [rep:Authorizable] as a
   where contains([profile/givenName/*], '**')
-  or contains([profile/familyName/*], '**')
-  or contains([profile/email/*], '**')
-  or [rep:principalName] like '%%'
-  or name(a) like '%%'
+  union select [jcr:path], [jcr:score], *
+  from [rep:Authorizable] as a
+  where contains([profile/familyName/*], '**')
+  union select [jcr:path], [jcr:score], *
+  from [rep:Authorizable] as a
+  where contains([profile/email/*], '**')
+  union select [jcr:path], [jcr:score], *
+  from [rep:Authorizable] as a
+  where [rep:principalName] like '%%'
+  union select [jcr:path], [jcr:score], *
+  from [rep:Authorizable] as a
+  where name(a) like '%%'
   order by [rep:principalName]
   /* xpath ... */
 
@@ -672,10 +696,18 @@
 select [jcr:path], [jcr:score], *
   from [rep:Authorizable] as a
   where contains([profile/givenName], '**')
-  or contains([profile/familyName], '**')
-  or contains([profile/email], '**')
-  or [rep:principalName] like '%%'
-  or name(a) like '%%'
+  union select [jcr:path], [jcr:score], *
+  from [rep:Authorizable] as a
+  where contains([profile/familyName], '**')
+  union select [jcr:path], [jcr:score], *
+  from [rep:Authorizable] as a
+  where contains([profile/email], '**')
+  union select [jcr:path], [jcr:score], *
+  from [rep:Authorizable] as a
+  where [rep:principalName] like '%%'
+  union select [jcr:path], [jcr:score], *
+  from [rep:Authorizable] as a
+  where name(a) like '%%'
   order by [rep:principalName]
   /* xpath ... */
 
diff --git a/oak-doc/src/site/markdown/query/lucene-copy-on-read.png b/oak-doc/src/site/markdown/query/lucene-copy-on-read.png
new file mode 100644
index 0000000..c7dee12
--- /dev/null
+++ b/oak-doc/src/site/markdown/query/lucene-copy-on-read.png
Binary files differ
diff --git a/oak-doc/src/site/markdown/query/lucene-index-mbean.png b/oak-doc/src/site/markdown/query/lucene-index-mbean.png
new file mode 100644
index 0000000..f29cada
--- /dev/null
+++ b/oak-doc/src/site/markdown/query/lucene-index-mbean.png
Binary files differ
diff --git a/oak-doc/src/site/markdown/query/lucene-osgi-config.png b/oak-doc/src/site/markdown/query/lucene-osgi-config.png
new file mode 100644
index 0000000..2b37df3
--- /dev/null
+++ b/oak-doc/src/site/markdown/query/lucene-osgi-config.png
Binary files differ
diff --git a/oak-doc/src/site/markdown/query/lucene.md b/oak-doc/src/site/markdown/query/lucene.md
new file mode 100644
index 0000000..eb072a9
--- /dev/null
+++ b/oak-doc/src/site/markdown/query/lucene.md
@@ -0,0 +1,438 @@
+<!--
+   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.
+  -->
+
+## Lucene Index
+
+Oak supports Lucene based indexes to support both property constraint and full
+text constraints
+
+### The Lucene Full-Text Index
+
+The full-text index handles the 'contains' type of queries:
+
+    //*[jcr:contains(., 'text')]
+
+If a full-text index is configured, then all queries that have a full-text condition
+use the full-text index, no matter if there are other conditions that are indexed,
+and no matter if there is a path restriction.
+
+If no full-text index is configured, then queries with full-text conditions
+may not work as expected. (The query engine has a basic verification in place 
+for full-text conditions, but it does not support all features that Lucene does,
+and it traverses all nodes if there are no indexed constraints).
+
+The full-text index update is asynchronous via a background thread, 
+see `Oak#withAsyncIndexing`.
+This means that some full-text searches will not work for a small window of time: 
+the background thread runs every 5 seconds, plus the time is takes to run the diff 
+and to run the text-extraction process. 
+
+The async update status is now reflected on the `oak:index` node with the help of 
+a few properties, see [OAK-980](https://issues.apache.org/jira/browse/OAK-980)
+
+TODO Node aggregation [OAK-828](https://issues.apache.org/jira/browse/OAK-828)
+
+The index definition node for a lucene-based full-text index:
+
+* must be of type `oak:QueryIndexDefinition`
+* must have the `type` property set to __`lucene`__
+* must contain the `async` property set to the value `async`, this is what sends the 
+index update process to a background thread
+
+_Optionally_ you can add
+
+ * what subset of property types to be included in the index via the  
+ `includePropertyTypes` property
+ * a blacklist of property names: what property to be excluded from the index
+   via the `excludePropertyNames` property
+ * the `reindex` flag which when set to `true`, triggers a full content re-index.
+
+Example:
+
+    {
+      NodeBuilder index = root.child("oak:index");
+      index.child("lucene")
+        .setProperty("jcr:primaryType", "oak:QueryIndexDefinition", Type.NAME)
+        .setProperty("type", "lucene")
+        .setProperty("async", "async")
+        .setProperty(PropertyStates.createProperty("includePropertyTypes", ImmutableSet.of(
+            PropertyType.TYPENAME_STRING, PropertyType.TYPENAME_BINARY), Type.STRINGS))
+        .setProperty(PropertyStates.createProperty("excludePropertyNames", ImmutableSet.of( 
+            "jcr:createdBy", "jcr:lastModifiedBy"), Type.STRINGS))
+        .setProperty("reindex", true);
+    }
+
+__Note__ The Oak Lucene index will only index _Strings_ and _Binaries_ by default. 
+If you need to add another data type, you need to add it to the  
+_includePropertyTypes_ setting, and don't forget to set the _reindex_ flag to true.
+
+### Lucene Property Index (Since 1.0.8)
+
+Oak uses Lucene for creating index to support queries which involve property 
+constraint that is not full-text
+
+    select * from [nt:base] where [alias] = '/admin'
+
+To define a property index on a subtree for above query you have to add an 
+index definition 
+
+```js
+"uuid" : {
+        "jcr:primaryType": "oak:QueryIndexDefinition",
+        "type": "lucene",
+        "async": "async",
+        "fulltextEnabled": false,
+        "includePropertyNames": ["alias"]
+    }
+```
+The index definition node for a lucene-based full-text index:
+
+* must be of type `oak:QueryIndexDefinition`
+* must have the `type` property set to __`lucene`__
+* must contain the `async` property set to the value `async`, this is what sends the 
+index update process to a background thread
+* must have `fulltextEnabled` set to `false`
+* must provide a whitelist of property names which should be indexed via 
+`includePropertyNames`
+
+_Note that compared to [Property Index](query.html#property-index) Lucene 
+Property Index is always configured in Async mode hence it might lag behind 
+in reflecting the current repository state while performing the query_
+
+Taking another example. 
+
+```
+select
+    *
+from
+    [app:Asset] as a
+where
+    [jcr:content/jcr:lastModified] > cast('2014-10-01T00:00:00.000+02:00' as date)
+    and [jcr:content/metadata/format] = 'image'
+order by
+    jcr:content/jcr:lastModified
+```
+
+To enable faster execution for above query you can create following Lucene 
+property index 
+
+```
+"assetIndex":
+{
+  "jcr:primaryType":"oak:QueryIndexDefinition",
+  "declaringNodeTypes":"app:Asset",
+  "includePropertyNames":["jcr:content/jcr:lastModified" , 
+      "jcr:content/metadata/format"],
+  "type":"lucene",
+  "async":"async",
+  "reindex":true,
+  "fulltextEnabled":false,
+  "orderedProps":["jcr:content/jcr:lastModified"]
+  "properties":	{
+    "jcr:primaryType":"oak:Unstructured",
+    "jcr:content": {
+      "jcr:primaryType":"oak:Unstructured",
+      "jcr:lastModified":	{
+        "jcr:primaryType":"oak:Unstructured",
+        "type":"Date"
+      }
+    }
+  }	
+}
+```
+
+Above index definition makes use of various features supported by property index
+
+* `declaringNodeTypes` - As the query involves nodes of type `app:Asset` 
+index is restricted to only index nodes of type `app:Asset`
+* `orderedProps` - As the query performs sorting via `order by` clause index is
+configured with property names which are used in sorting
+* `properties` - For ordering to work properly we need to tell the type of 
+property
+
+For implementation details refer to [OAK-2005][OAK-2005]. Following sections 
+would provide more details about supported features
+
+### Index Definition
+
+Lucene index definition is managed via `NodeStore` and supports following 
+attributes
+
+type
+: Required and should always be `lucene`
+
+async
+: Required and should always be `async`
+
+fulltextEnabled
+: For Lucene based property index this should *always* be set to `false`
+
+declaringNodeTypes
+: Node type names whose properties should be indexed. If not specified then all
+  nodes would indexed if they have properties defined in `includePropertyNames`.
+  For smaller and efficient indexes its recommended that `declaringNodeTypes`
+   should be specified according to your query needs
+
+includePropertyNames
+: List of property name which should be indexed. Property name can be 
+  relative e.g. `jcr:content/jcr:lastModified`
+
+orderedProps
+: List of property names which would be used in the `order by` clause of the 
+  query
+   
+includePropertyTypes
+: Used in Lucene Fulltext Index
+: For full text index defaults to `String, Binary`
+: List of property types which should be indexed. The values can be one 
+  specified in [PropertyType Names][1]
+  
+[blobSize][OAK-2201]
+: Default value 32768 (32kb)
+: Size in bytes used for splitting the index files when storing them in NodeStore
+
+functionName
+: Name to be used to enable index usage with [native query support](#native-query)
+
+### Property Definition
+
+In some cases property specific configurations are required. For example 
+typically while performing order by in query user does not specify the 
+property type. In such cases you need to specify the property type explicitly.
+
+Property definition nodes are created as per there property name under 
+`properties` node of index definition node. For relative properties you would
+need to create the required path structure under `properties` node. For e.g.
+for property `jcr:content/metadata/format` you need to create property node at
+path `<index definition node>/properties/jcr:content/jcr:lastModified`
+
+```
+"properties":
+  {
+    "jcr:primaryType":"oak:Unstructured",
+    "jcr:content":
+    {
+      "jcr:primaryType":"oak:Unstructured",
+      "jcr:lastModified":
+      {
+        "jcr:primaryType":"oak:Unstructured",
+        "type":"Date"
+      }
+    }
+  }	
+```
+
+type
+: JCR Property type. Can be one of `Date`, `Boolean`, `Double` or `Long`
+
+boost
+: The boost value. Defaults to 1.0
+: Since 1.0.9
+
+### Ordering
+
+Lucene property index provides efficient sorting support based on Lucene 
+DocValue fields. To configure specify the list of property names which can be
+used in the `order by` clause as part of `orderedProps` property.
+
+If the property is of type other than string then you must specify the property
+definition with `type` details
+
+Refer to [Lucene based Sorting][OAK-2196] for more details. 
+
+<a name="osgi-config"></a>
+### LuceneIndexProvider Configuration
+
+Some of the runtime aspects of the Oak Lucene support can be configured via OSGi
+configuration. The configuration needs to be done for PID `org.apache
+.jackrabbit.oak.plugins.index.lucene.LuceneIndexProviderService`
+
+![OSGi Configuration](lucene-osgi-config.png)
+
+enableCopyOnReadSupport
+: Enable copying of Lucene index to local file system to improve query 
+performance. See [Copy Indexes On Read](#copy-on-read)
+
+localIndexDir
+: Directory to be used for when copy index files to local file system. To be 
+specified when `enableCopyOnReadSupport` is enabled
+
+debug
+: Boolean value. Defaults to `false`
+: If enabled then Lucene logging would be integrated with Slf4j
+
+<a name="non-root-index"></a>
+### Non Root Index Definitions
+
+Lucene index definition can be defined at any location in repository and need
+not always be defined at root. For example if your query involves path 
+restrictions like
+
+    select * from [app:Asset] as a where ISDESCENDANTNODE(a, '/content/companya') and [format] = 'image'
+    
+Then you can create the required index definition say `assetIndex` at 
+`/content/companya/oak:index/assetIndex`. In such a case that index would 
+contain data for the subtree under `/content/companya`
+
+<a name="native-query"></a>
+### Native Query and Index Selection
+
+Oak query engine supports native queries like
+
+    //*[rep:native('lucene', 'name:(Hello OR World)')]
+
+If multiple Lucene based indexes are enabled on the system and you need to 
+make use of specific Lucene index like `/oak:index/assetIndex` then you can 
+specify the index name via `functionName` attribute on index definition. 
+
+For example for assetIndex definition like 
+
+```
+{
+  "jcr:primaryType":"oak:QueryIndexDefinition",
+  "type":"lucene",
+  ...
+  "functionName" : "lucene-assetIndex",
+}
+```
+
+Executing following query would ensure that Lucene index from `assetIndex` 
+should be used
+
+    //*[rep:native('lucene-assetIndex', 'name:(Hello OR World)')]
+
+### Persisting indexes to FileSystem
+
+By default Lucene indexes are stored in the `NodeStore`. If required they can
+be stored on the file system directly
+
+```
+{
+  "jcr:primaryType":"oak:QueryIndexDefinition",
+  "type":"lucene",
+  ...
+  "persistence" : "file",
+  "path" : "/path/to/store/index"
+}
+```
+To store the Lucene index in the file system, in the Lucene index definition 
+node, set the property `persistence` to `file`, and set the property `path` 
+to the directory where the index should be stored. Then start reindexing by 
+setting `reindex` to `true`.
+
+Note that this setup would only for those non cluster `NodeStore`. If the 
+backend `NodeStore` supports clustering then index data would not be 
+accessible on other cluster nodes
+
+<a name="copy-on-read"></a>
+### CopyOnRead
+
+Lucene indexes are stored in `NodeStore`. Oak Lucene provides a custom directory
+implementation which enables Lucene to load index from `NodeStore`. This 
+might cause performance degradation if the `NodeStore` storage is remote. For
+such case Oak Lucene provide a `CopyOnReadDirectory` which copies the index 
+content to a local directory and enables Lucene to make use of local 
+directory based indexes while performing queries.
+
+At runtime various details related to copy on read features are exposed via
+`CopyOnReadStats` MBean. Indexes at JCR path e.g. `/oak:index/assetIndex` 
+would be copied to `<index dir>/<hash of jcr path>`. To determine mapping 
+between local index directory and JCR path refer to the MBean details
+
+![CopyOnReadStats](lucene-copy-on-read.png)
+  
+For more details refer to [OAK-1724][OAK-1724]. This feature can be enabled via
+[Lucene Index provider service configuration](#osgi-config)
+
+### Lucene Index MBeans
+
+Oak Lucene registers a JMX bean `LuceneIndex` which provide details about the 
+index content e.g. size of index, number of documents present in index etc
+
+![Lucene Index MBean](lucene-index-mbean.png)
+
+<a name="luke"></a>
+### Analyzing created Lucene Index
+
+[Luke]  is a handy development and diagnostic tool, which accesses already 
+existing Lucene indexes and allows you to display index details. In Oak 
+Lucene index files are stored in `NodeStore` and hence not directly 
+accessible. To enable analyzing the index files via Luke follow below 
+mentioned steps
+
+1. Download the Luke version which includes the matching Lucene jars used by 
+   Oak. As of Oak 1.0.8 release the Lucene version used is 4.7.1. So download
+    the jar from [here](https://github.com/DmitryKey/luke/releases)
+     
+        $wget https://github.com/DmitryKey/luke/releases/download/4.7.0/luke-with-deps.jar
+        
+2. Use the [Oak Console][oak-console] to dump the Lucene index from `NodeStore`
+   to filesystem directory. Use the `lc dump` command
+   
+        $ java -jar oak-run-*.jar console /path/to/oak/repository
+        Apache Jackrabbit Oak 1.1-SNAPSHOT
+        Jackrabbit Oak Shell (Apache Jackrabbit Oak 1.1-SNAPSHOT, JVM: 1.7.0_55)
+        Type ':help' or ':h' for help.
+        -------------------------------------------------------------------------
+        /> lc info /oak:index/lucene
+        Index size : 74.1 MB
+        Number of documents : 235708
+        Number of deleted documents : 231
+        /> lc 
+        dump   info   
+        /> lc dump /path/to/dump/index/lucene /oak:index/lucene
+        Copying Lucene indexes to [/path/to/dump/index/lucene]
+        Copied 74.1 MB in 1.209 s
+        /> lc dump /path/to/dump/index/slingAlias /oak:index/slingAlias
+        Copying Lucene indexes to [/path/to/dump/index/lucene-index/slingAlias]
+        Copied 8.5 MB in 218.7 ms
+        />
+       
+3. Post dump open the index via Luke. Oak Lucene uses a [custom 
+   Codec][OAK-1737]. So oak-lucene jar needs to be included in Luke classpath
+   for it to display the index details
+
+        $ java -XX:MaxPermSize=512m luke-with-deps.jar:oak-lucene-1.0.8.jar org.getoptuke.Luke
+        
+From the Luke UI shown you can access various details.
+
+### Index performance
+
+Following are some best practices to get good performance from Lucene based 
+indexes
+
+1. Make use on [non root indexes](#non-root-index). If you query always 
+  perform search under certain paths then create index definition under those 
+  paths only. This might be helpful in multi tenant deployment where each tenant 
+  data is stored under specific repository path and all queries are made under 
+  those path.
+   
+2. Index only required data. Depending on your requirement you can create 
+   multiple Lucene indexes. For example if in majority of cases you are 
+   querying on various properties specified under `<node>/jcr:content/metadata`
+   where node belong to certain specific nodeType then create single index 
+   definition listing all such properties and restrict it that nodeType. You 
+   can the size of index via mbean
+
+[1]: http://www.day.com/specs/jsr170/javadocs/jcr-2.0/constant-values.html#javax.jcr.PropertyType.TYPENAME_STRING
+[OAK-2201]: https://issues.apache.org/jira/browse/OAK-2201
+[OAK-1724]: https://issues.apache.org/jira/browse/OAK-1724
+[OAK-2196]: https://issues.apache.org/jira/browse/OAK-2196
+[OAK-2005]: https://issues.apache.org/jira/browse/OAK-2005
+[OAK-1737]: https://issues.apache.org/jira/browse/OAK-1737 
+[luke]: https://code.google.com/p/luke/
+[oak-console]: https://github.com/apache/jackrabbit-oak/tree/trunk/oak-run#console
\ No newline at end of file
diff --git a/oak-doc/src/site/markdown/query.md b/oak-doc/src/site/markdown/query/query.md
similarity index 87%
rename from oak-doc/src/site/markdown/query.md
rename to oak-doc/src/site/markdown/query/query.md
index c2aab50..5fe1083 100644
--- a/oak-doc/src/site/markdown/query.md
+++ b/oak-doc/src/site/markdown/query/query.md
@@ -179,6 +179,7 @@
 result. There are exceptions however, where all data is read in memory when the query
 is executed: when using a full-text index, and when using an "order by" clause.
 
+<a name="property-index"></a>
 ### The Property Index
 
 Is useful whenever there is a query with a property constraint that is not full-text:
@@ -295,67 +296,9 @@
   define it as asynchronous by providing `async=async` in the index
   definition. This is to avoid cluster merges.
 
-### The Lucene Full-Text Index
+### The Lucene Index
 
-The full-text index handles the 'contains' type of queries:
-
-    //*[jcr:contains(., 'text')]
-
-If a full-text index is configured, then all queries that have a full-text condition
-use the full-text index, no matter if there are other conditions that are indexed,
-and no matter if there is a path restriction.
-
-If no full-text index is configured, then queries with full-text conditions
-may not work as expected. (The query engine has a basic verification in place 
-for full-text conditions, but it does not support all features that Lucene does,
-and it traverses all nodes if there are no indexed constraints).
-
-The full-text index update is asynchronous via a background thread, 
-see `Oak#withAsyncIndexing`.
-This means that some full-text searches will not work for a small window of time: 
-the background thread runs every 5 seconds, plus the time is takes to run the diff 
-and to run the text-extraction process. 
-
-The async update status is now reflected on the `oak:index` node with the help of 
-a few properties, see [OAK-980](https://issues.apache.org/jira/browse/OAK-980)
-
-TODO Node aggregation [OAK-828](https://issues.apache.org/jira/browse/OAK-828)
-
-The index definition node for a lucene-based full-text index:
-
-* must be of type `oak:QueryIndexDefinition`
-* must have the `type` property set to __`lucene`__
-* must contain the `async` property set to the value `async`, this is what sends the 
-index update process to a background thread
-
-_Optionally_ you can add
-
- * what subset of property types to be included in the index via the `includePropertyTypes` property
- * a blacklist of property names: what property to be excluded from the index via the `excludePropertyNames` property
- * the `reindex` flag which when set to `true`, triggers a full content re-index.
-
-Example:
-
-    {
-      NodeBuilder index = root.child("oak:index");
-      index.child("lucene")
-        .setProperty("jcr:primaryType", "oak:QueryIndexDefinition", Type.NAME)
-        .setProperty("type", "lucene")
-        .setProperty("async", "async")
-        .setProperty(PropertyStates.createProperty("includePropertyTypes", ImmutableSet.of(
-            PropertyType.TYPENAME_STRING, PropertyType.TYPENAME_BINARY), Type.STRINGS))
-        .setProperty(PropertyStates.createProperty("excludePropertyNames", ImmutableSet.of( 
-            "jcr:createdBy", "jcr:lastModifiedBy"), Type.STRINGS))
-        .setProperty("reindex", true);
-    }
-
-__Note__ The Oak Lucene index will only index _Strings_ and _Binaries_ by default. If you need to add another data type, you need to add it to the  _includePropertyTypes_ setting, and don't forget to set the _reindex_ flag to true.
-
-To store the Lucene index in the file system, 
-in the Lucene index definition node, 
-set the property "persistence" to "file", and 
-set the property "path" to the directory where the index should be stored. 
-Then start reindexing by setting "reindex" to "true" (a boolean).
+Refer to [Lucene Index](lucene.html) for details.
 
 ### The Solr Index
 
diff --git a/oak-doc/src/site/site.xml b/oak-doc/src/site/site.xml
index efe7d64..e15ce06 100644
--- a/oak-doc/src/site/site.xml
+++ b/oak-doc/src/site/site.xml
@@ -39,7 +39,7 @@
       <item href="nodestore/overview.html" name="NodeStore and MicroKernel API" />
     </menu>
     <menu name="Features and Plugins">
-      <item href="query.html" name="Query" />
+      <item href="query/query.html" name="Query" />
       <item href="security/overview.html" name="Security" />
       <item href="plugins/blobstore.html" name="BlobStore" />
       <item href="clustering.html" name="Clustering" />
diff --git a/oak-jcr/pom.xml b/oak-jcr/pom.xml
index 5a7158f..a2d4507 100644
--- a/oak-jcr/pom.xml
+++ b/oak-jcr/pom.xml
@@ -123,6 +123,7 @@
       org.apache.jackrabbit.core.query.DerefTest#testRewrite                                         <!-- OAK-321 -->
       org.apache.jackrabbit.core.query.DerefTest#testDerefToVersionNode                              <!-- OAK-321 -->
       org.apache.jackrabbit.core.query.DerefTest#testMultipleDeref                                   <!-- OAK-321 -->
+      org.apache.jackrabbit.core.query.FulltextQueryTest#testMultipleOrExpressions                   <!-- OAK-2249 -->
       org.apache.jackrabbit.core.query.FulltextQueryTest#testContainsPropScopeSQL                    <!-- OAK-902 -->
       org.apache.jackrabbit.core.query.FulltextQueryTest#testContainsPropScopeXPath                  <!-- OAK-902 -->
       org.apache.jackrabbit.core.query.XPathAxisTest#testIndex0Descendant                            <!-- OAK-322 -->
diff --git a/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexDefinition.java b/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexDefinition.java
index 9dd4b00..a89b6ae 100644
--- a/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexDefinition.java
+++ b/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexDefinition.java
@@ -285,7 +285,8 @@
     private Map<String, PropertyDefinition> collectPropertyDefns(NodeBuilder defn) {
         Map<String, PropertyDefinition> propDefns = newHashMap();
         NodeBuilder propNode = defn.getChildNode(LuceneIndexConstants.PROP_NODE);
-        for (String propName : Iterables.concat(includes, orderedProps)) {
+        //Include all immediate child nodes to 'properties' node by default
+        for (String propName : Iterables.concat(includes, orderedProps, propNode.getChildNodeNames())) {
             NodeBuilder propDefnNode;
             if (relativeProps.containsKey(propName)) {
                 propDefnNode = relativeProps.get(propName).getPropDefnNode(propNode);
@@ -293,7 +294,7 @@
                 propDefnNode = propNode.getChildNode(propName);
             }
 
-            if (propDefnNode.exists()) {
+            if (propDefnNode.exists() && !propDefns.containsKey(propName)) {
                 propDefns.put(propName, new PropertyDefinition(this, propName, propDefnNode));
             }
         }
diff --git a/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexConstants.java b/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexConstants.java
index 799d519..ef3c0cf 100644
--- a/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexConstants.java
+++ b/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexConstants.java
@@ -100,4 +100,10 @@
      * Child node name under which property details are provided
      */
     String PROP_NODE = "properties";
+
+    /**
+     * Field boost factor
+     */
+    String FIELD_BOOST = "boost";
+
 }
diff --git a/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexEditor.java b/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexEditor.java
index 051c4780..31cd837 100644
--- a/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexEditor.java
+++ b/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexEditor.java
@@ -176,19 +176,19 @@
 
     @Override
     public void propertyAdded(PropertyState after) {
-        markPropertiesChanged();
+        markPropertyChanged(after.getName());
         checkForRelativePropertyChange(after.getName());
     }
 
     @Override
     public void propertyChanged(PropertyState before, PropertyState after) {
-        markPropertiesChanged();
+        markPropertyChanged(before.getName());
         checkForRelativePropertyChange(before.getName());
     }
 
     @Override
     public void propertyDeleted(PropertyState before) {
-        markPropertiesChanged();
+        markPropertyChanged(before.getName());
         checkForRelativePropertyChange(before.getName());
     }
 
@@ -338,7 +338,13 @@
                         !context.skipTokenization(pname),
                         context.isStored(pname)));
                 if (context.isFullTextEnabled()) {
-                    fields.add(newFulltextField(value));
+                    Field field = newFulltextField(value);
+                    boolean hasBoost = context.getDefinition().getPropDefn(pname) != null &&
+                            context.getDefinition().getPropDefn(pname).hasFieldBoost();
+                    if (hasBoost) {
+                        field.setBoost((float)context.getDefinition().getPropDefn(pname).fieldBoost());
+                    }
+                    fields.add(field);
                 }
                 dirty = true;
             }
@@ -499,7 +505,7 @@
             }
 
             if (p != null) {
-                p.markPropertiesChanged();
+                p.relativePropertyChanged();
             }
         }
     }
@@ -511,7 +517,13 @@
         return changedRelativeProps;
     }
 
-    private void markPropertiesChanged() {
+    private void markPropertyChanged(String name) {
+        if (!propertiesChanged && context.getDefinition().includeProperty(name)) {
+            propertiesChanged = true;
+        }
+    }
+
+    private void relativePropertyChanged() {
         propertiesChanged = true;
     }
 
diff --git a/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndex.java b/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndex.java
index be0d207..14ac3e9 100644
--- a/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndex.java
+++ b/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndex.java
@@ -107,6 +107,7 @@
 import static org.apache.jackrabbit.oak.plugins.index.lucene.TermFactory.newPathTerm;
 import static org.apache.jackrabbit.oak.query.QueryImpl.JCR_PATH;
 import static org.apache.jackrabbit.oak.spi.query.QueryIndex.AdvancedQueryIndex;
+import static org.apache.jackrabbit.oak.spi.query.QueryIndex.NativeQueryIndex;
 import static org.apache.lucene.search.BooleanClause.Occur.MUST;
 import static org.apache.lucene.search.BooleanClause.Occur.MUST_NOT;
 import static org.apache.lucene.search.BooleanClause.Occur.SHOULD;
@@ -150,7 +151,7 @@
  * @see org.apache.jackrabbit.oak.spi.query.QueryIndex
  *
  */
-public class LucenePropertyIndex implements AdvancedQueryIndex, QueryIndex {
+public class LucenePropertyIndex implements AdvancedQueryIndex, QueryIndex, NativeQueryIndex {
 
     private static final Logger LOG = LoggerFactory
             .getLogger(LucenePropertyIndex.class);
diff --git a/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/PropertyDefinition.java b/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/PropertyDefinition.java
index b60cdcf..20b7377 100644
--- a/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/PropertyDefinition.java
+++ b/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/PropertyDefinition.java
@@ -21,6 +21,7 @@
 
 import javax.jcr.PropertyType;
 
+import org.apache.jackrabbit.oak.api.Type;
 import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -31,6 +32,8 @@
     private final NodeBuilder definition;
 
     private final int propertyType;
+    private double fieldBoost;
+    private boolean hasFieldBoost;
 
     public PropertyDefinition(IndexDefinition idxDefn, String name, NodeBuilder defn) {
         this.name = name;
@@ -46,9 +49,20 @@
             }
         }
         this.propertyType = type;
+        this.hasFieldBoost = defn.hasProperty(LuceneIndexConstants.FIELD_BOOST) && (defn.getProperty(LuceneIndexConstants.FIELD_BOOST).getType().tag() == Type.DOUBLE.tag());
+        // if !hasFieldBoost then setting default lucene field boost = 1.0
+        fieldBoost = hasFieldBoost ? defn.getProperty(LuceneIndexConstants.FIELD_BOOST).getValue(Type.DOUBLE) : 1.0;
     }
 
     public int getPropertyType() {
         return propertyType;
     }
+
+    public boolean hasFieldBoost() {
+        return hasFieldBoost;
+    }
+
+    public double fieldBoost() {
+        return fieldBoost;
+    }
 }
diff --git a/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/util/MoreLikeThisHelper.java b/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/util/MoreLikeThisHelper.java
index b5c3fa5..62bdd9b 100644
--- a/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/util/MoreLikeThisHelper.java
+++ b/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/util/MoreLikeThisHelper.java
@@ -94,7 +94,7 @@
                                 fieldNames.add(f.name());
                             }
                         }
-                        String[] docFields = fieldNames.toArray(new String[0]);
+                        String[] docFields = fieldNames.toArray(new String[fieldNames.size()]);
                         mlt.setFieldNames(docFields);
                         moreLikeThisQuery = mlt.like(d.doc);
                     }
diff --git a/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexDefinitionTest.java b/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexDefinitionTest.java
index 0a764e9..5b9347f 100644
--- a/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexDefinitionTest.java
+++ b/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexDefinitionTest.java
@@ -93,6 +93,17 @@
     }
 
     @Test
+    public void propertyDefinitionWithExcludes() throws Exception{
+        builder.child(PROP_NODE).child("foo").setProperty(LuceneIndexConstants.PROP_TYPE, PropertyType.TYPENAME_DATE);
+        IndexDefinition defn = new IndexDefinition(builder);
+
+        assertTrue(defn.hasPropertyDefinition("foo"));
+        assertFalse(defn.hasPropertyDefinition("bar"));
+
+        assertEquals(PropertyType.DATE, defn.getPropDefn("foo").getPropertyType());
+    }
+
+    @Test
     public void codecConfig() throws Exception{
         IndexDefinition defn = new IndexDefinition(builder);
         assertNotNull(defn.getCodec());
diff --git a/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexAggregationTest2.java b/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexAggregationTest2.java
index 55d749a..7ebc1cf 100644
--- a/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexAggregationTest2.java
+++ b/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexAggregationTest2.java
@@ -16,13 +16,18 @@
  */
 package org.apache.jackrabbit.oak.plugins.index.lucene;
 
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.collect.ImmutableList.of;
 import static com.google.common.collect.Lists.newArrayList;
+import static org.apache.jackrabbit.JcrConstants.JCR_CONTENT;
 import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE;
 import static org.apache.jackrabbit.JcrConstants.JCR_SYSTEM;
 import static org.apache.jackrabbit.JcrConstants.NT_FILE;
 import static org.apache.jackrabbit.JcrConstants.NT_UNSTRUCTURED;
 import static org.apache.jackrabbit.oak.api.Type.NAME;
 import static org.apache.jackrabbit.oak.api.Type.STRING;
+import static org.apache.jackrabbit.oak.api.Type.STRINGS;
 import static org.apache.jackrabbit.oak.plugins.nodetype.NodeTypeConstants.JCR_NODE_TYPES;
 import static org.junit.Assert.fail;
 
@@ -31,6 +36,8 @@
 import java.util.Collections;
 import java.util.List;
 
+import javax.annotation.Nonnull;
+
 import org.apache.jackrabbit.oak.Oak;
 import org.apache.jackrabbit.oak.api.ContentRepository;
 import org.apache.jackrabbit.oak.api.Root;
@@ -57,6 +64,7 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.common.base.Strings;
 import com.google.common.collect.Lists;
 
 public class LuceneIndexAggregationTest2 extends AbstractQueryTest {
@@ -217,4 +225,85 @@
         setTraversalEnabled(true);
     }
 
+    @Test
+    public void oak2249() throws Exception {
+        setTraversalEnabled(false);
+        final String statement = "//element(*, test:Asset)[ " +
+            "( " +
+                "jcr:contains(., 'summer') " +
+                "or " + 
+                "jcr:content/metadata/@tags = 'namespace:season/summer' " +
+            ") and " +
+                "jcr:contains(jcr:content/metadata/@format, 'image') " +
+            "]"; 
+        
+        Tree content = root.getTree("/").addChild("content");
+        List<String> expected = newArrayList();
+        
+        Tree metadata = createAssetStructure(content, "tagged");
+        metadata.setProperty("tags", of("namespace:season/summer"), STRINGS);
+        metadata.setProperty("format", "image/jpeg", STRING);
+        expected.add("/content/tagged");
+        
+        metadata = createAssetStructure(content, "titled");
+        metadata.setProperty("title", "Lorem summer ipsum", STRING);
+        metadata.setProperty("format", "image/jpeg", STRING);
+        expected.add("/content/titled");
+
+        metadata = createAssetStructure(content, "summer-node");
+        metadata.setProperty("format", "image/jpeg", STRING);
+        expected.add("/content/summer-node");
+        
+        // the following is NOT expected
+        metadata = createAssetStructure(content, "winter-node");
+        metadata.setProperty("tags", of("namespace:season/winter"), STRINGS);
+        metadata.setProperty("title", "Lorem winter ipsum", STRING);
+        metadata.setProperty("format", "image/jpeg", STRING);
+
+        root.commit();
+        
+        assertQuery(statement, "xpath", expected);
+        setTraversalEnabled(true);
+    }
+    
+    /**
+     * <p>
+     * convenience method that create an "asset" structure like
+     * </p>
+     * 
+     * <pre>
+     *  "parent" : {
+     *      "nodeName" : {
+     *          "jcr:primaryType" : "test:Asset",
+     *          "jcr:content" : {
+     *              "jcr:primaryType" : "test:AssetContent",
+     *              "metatada" : {
+     *                  "jcr:primaryType" : "nt:unstructured"
+     *              }
+     *          }
+     *      }
+     *  }
+     * </pre>
+     * 
+     * <p>
+     *  and returns the {@code metadata} node
+     * </p>
+     * 
+     * @param parent the parent under which creating the node
+     * @param nodeName the node name to be used
+     * @return the {@code metadata} node. See above for details
+     */
+    private static Tree createAssetStructure(@Nonnull final Tree parent, 
+                                             @Nonnull final String nodeName) {
+        checkNotNull(parent);
+        checkArgument(!Strings.isNullOrEmpty(nodeName), "nodeName cannot be null or empty");
+        
+        Tree node = parent.addChild(nodeName);
+        node.setProperty(JCR_PRIMARYTYPE, NT_TEST_ASSET, NAME);
+        node = node.addChild(JCR_CONTENT);
+        node.setProperty(JCR_PRIMARYTYPE, NT_TEST_ASSETCONTENT, NAME);
+        node = node.addChild("metadata");
+        node.setProperty(JCR_PRIMARYTYPE, NT_UNSTRUCTURED, NAME);
+        return node;
+    }
 }
diff --git a/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexEditorTest.java b/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexEditorTest.java
index a6ff7e9..105f2fd 100644
--- a/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexEditorTest.java
+++ b/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexEditorTest.java
@@ -153,6 +153,46 @@
         assertEquals(2, getSearcher().getIndexReader().numDocs());
     }
 
+    /**
+     * 1. Index property foo in /test
+     * 2. Then modify some other property in /test
+     *
+     * This should not cause the index to be updated
+     */
+    @Test
+    public void nonIncludedPropertyChange() throws Exception {
+        NodeBuilder index = builder.child(INDEX_DEFINITIONS_NAME);
+        NodeBuilder nb = newLuceneIndexDefinition(index, "lucene",
+                of(TYPENAME_STRING));
+        nb.setProperty(LuceneIndexConstants.FULL_TEXT_ENABLED, false);
+        nb.setProperty(createProperty(INCLUDE_PROPERTY_NAMES, of("foo"),
+                STRINGS));
+
+        NodeState before = builder.getNodeState();
+        builder.child("test").setProperty("foo", "fox is jumping");
+        builder.child("test2").setProperty("foo", "bird is chirping");
+        NodeState after = builder.getNodeState();
+
+        NodeState indexed = HOOK.processCommit(before, after, CommitInfo.EMPTY);
+        tracker.update(indexed);
+
+        assertEquals(2, getSearcher().getIndexReader().numDocs());
+
+        assertEquals("/test", getPath(new TermQuery(new Term("foo", "fox is jumping"))));
+
+        releaseIndexNode();
+        before = indexed;
+        builder = before.builder();
+        builder.child("test").setProperty("bar", "kite is flying");
+        after = builder.getNodeState();
+        indexed = HOOK.processCommit(before, after, CommitInfo.EMPTY);
+        tracker.update(indexed);
+
+        assertEquals(2, getSearcher().getIndexReader().numDocs());
+        assertEquals("change in non included property should not cause " +
+                "index update",0, getSearcher().getIndexReader().numDeletedDocs());
+    }
+
     @Test
     public void testLuceneWithRelativeProperty() throws Exception {
         NodeBuilder index = builder.child(INDEX_DEFINITIONS_NAME);
diff --git a/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndexTest.java b/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndexTest.java
index c17e8a5..57f0292 100644
--- a/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndexTest.java
+++ b/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndexTest.java
@@ -359,7 +359,6 @@
         assertQuery("select [jcr:path] from [nt:base] where propa like '%ty'", asList("/test/a", "/test/b"));
     }
 
-    @Ignore("OAK-2241")
     @Test
     public void nativeQueries() throws Exception {
         Tree idx = createIndex("test1", of("propa", "propb"));
@@ -667,8 +666,49 @@
         assertOrderedQuery("select [jcr:path] from [nt:base] where [bar] = 'baz' order by [foo] asc, [baz] desc", getSortedPaths(tuples));
     }
 
+    @Test
+    public void indexTimeFieldBoost() throws Exception {
+
+        // Index Definition
+        Tree idx = createIndex("test1", of("propa", "propb", "propc"));
+        idx.setProperty(LuceneIndexConstants.FULL_TEXT_ENABLED, true);
+        Tree propNode = idx.addChild(PROP_NODE);
+        root.commit();
+
+        // property definition for index test1
+        Tree propA = propNode.addChild("propa");
+        propA.setProperty(LuceneIndexConstants.PROP_TYPE, PropertyType.TYPENAME_STRING);
+        propA.setProperty(LuceneIndexConstants.FIELD_BOOST, 2.0);
+
+        Tree propB = propNode.addChild("propb");
+        propB.setProperty(LuceneIndexConstants.PROP_TYPE, PropertyType.TYPENAME_STRING);
+        propB.setProperty(LuceneIndexConstants.FIELD_BOOST, 1.0);
+
+        Tree propC = propNode.addChild("propc");
+        propC.setProperty(LuceneIndexConstants.PROP_TYPE, PropertyType.TYPENAME_STRING);
+        propC.setProperty(LuceneIndexConstants.FIELD_BOOST, 4.0);
+        root.commit();
+
+        // create test data
+        Tree test = root.getTree("/").addChild("test");
+        root.commit();
+        test.addChild("a").setProperty("propa", "foo");
+        test.addChild("b").setProperty("propb", "foo");
+        test.addChild("c").setProperty("propc", "foo");
+        root.commit();
+
+        String queryString = "//* [jcr:contains(., 'foo' )]";
+        // verify results ordering
+        // which should be /test/c (boost = 4.0), /test/a(boost = 2.0), /test/b (1.0)
+        assertOrderedQuery(queryString, asList("/test/c", "/test/a", "/test/b"), XPATH, true);
+    }
+
     private void assertOrderedQuery(String sql, List<String> paths) {
-        List<String> result = executeQuery(sql, SQL2, true);
+        assertOrderedQuery(sql, paths, SQL2, false);
+    }
+
+    private void assertOrderedQuery(String sql, List<String> paths, String language, boolean skipSort) {
+        List<String> result = executeQuery(sql, language, true, skipSort);
         assertEquals(paths, result);
     }
 
diff --git a/oak-run/src/main/groovy/org/apache/jackrabbit/oak/console/commands/LuceneCommand.groovy b/oak-run/src/main/groovy/org/apache/jackrabbit/oak/console/commands/LuceneCommand.groovy
index bd419ff..01635b5 100644
--- a/oak-run/src/main/groovy/org/apache/jackrabbit/oak/console/commands/LuceneCommand.groovy
+++ b/oak-run/src/main/groovy/org/apache/jackrabbit/oak/console/commands/LuceneCommand.groovy
@@ -97,7 +97,7 @@
             //OakDirectory is package scope but Groovy allows us
             //to use it. Good or bad but its helpful debug scripts
             //can access inner classes and prod code cannot. Win win :)
-            return new OakDirectory(new ReadOnlyBuilder(data), new IndexDefinition(definition));
+            return new OakDirectory(new ReadOnlyBuilder(data), new IndexDefinition(new ReadOnlyBuilder(definition)));
         }
         return null
     }