[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
}