diff --git a/README.adoc b/README.adoc
index 3ee3da9..9bf4389 100644
--- a/README.adoc
+++ b/README.adoc
@@ -37,7 +37,7 @@ image:https://api.bintray.com/packages/twcable/aem/Grabbit/images/download.svg[t
* Grabbit Development
-** link:{docsDir}/Prerequisites.adoc[Prerequisites]
+** link:{docsDir}/GettingStarted.adoc[Getting Started]
** link:{docsDir}/Building.adoc[Building from Source]
** link:{docsDir}/RELEASING.adoc[Releasing A New Version]
diff --git a/docs/Prerequisites.adoc b/docs/GettingStarted.adoc
similarity index 84%
rename from docs/Prerequisites.adoc
rename to docs/GettingStarted.adoc
index 73c7cf1..dda7642 100644
--- a/docs/Prerequisites.adoc
+++ b/docs/GettingStarted.adoc
@@ -1,3 +1,7 @@
+== Getting Started
+
+The JCR 2.0 Specification (JSR-283) is included under docs for quick reference
+
== Prerequisites
=== Installing Protocol Buffers Compiler
diff --git a/docs/Running.adoc b/docs/Running.adoc
index c8f01b0..ca5ab33 100644
--- a/docs/Running.adoc
+++ b/docs/Running.adoc
@@ -182,3 +182,19 @@ Invalid:
}
```
+=== Syncing Users and Groups
+
+Grabbit has support for syncing users and groups. One *important note* about syncing users is that you must take care to avoid syncing the _admin user_.
+Jackrabbit will not allow modification of the admin user, so Grabbit will fail on a job that attempts to do so. You can find the path of your admin user
+on your data-warehouse instance or other source instance; and add it as an exclude path as so:
+
+```
+ pathConfigurations :
+ -
+ path : /home/groups
+ -
+ path : /home/users
+ excludePaths:
+ - k/ki9zhpzfe #Admin user
+```
+
diff --git a/docs/jcr-spec.pdf b/docs/jcr-spec.pdf
new file mode 100644
index 0000000..1152363
Binary files /dev/null and b/docs/jcr-spec.pdf differ
diff --git a/gradle.properties b/gradle.properties
index d937a75..25a2624 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -33,6 +33,7 @@ jcr_version = 2.0
jms_version = 3.1.1
jsr305_version = 2.0.0
logback_version = 1.0.4
+oak_version = 1.2.2
objenesis_version = 2.1
protobuf_gradle_plugin_version = 0.9.1
protobuf_version = 2.6.1
@@ -40,6 +41,7 @@ scr_annotations_version = 1.7.0
servlet_api_version = 2.5
slf4j_version = 1.7.6
sling_api_version = 2.9.0
+sling_base_version = 2.2.2
sling_commons_testing_version = 2.0.12
sling_commons_version = 2.2.0
sling_event_version = 3.1.4
diff --git a/gradle/dependencies.gradle b/gradle/dependencies.gradle
index 332bf76..5b72953 100644
--- a/gradle/dependencies.gradle
+++ b/gradle/dependencies.gradle
@@ -19,6 +19,7 @@ dependencies {
// Apache Sling libraries
compile "org.apache.sling:org.apache.sling.api:${sling_api_version}"
+ compile "org.apache.sling:org.apache.sling.jcr.base:${sling_base_version}"
compile "org.apache.sling:org.apache.sling.jcr.resource:${sling_jcr_resource_version}"
// Apache Felix libraries
@@ -33,6 +34,8 @@ dependencies {
// Working with the JCR
compile "javax.jcr:jcr:${jcr_version}"
compile "org.apache.jackrabbit:jackrabbit-jcr-commons:${jackrabbit_version}"
+ compile "org.apache.jackrabbit:jackrabbit-api:${jackrabbit_version}"
+ compile "org.apache.jackrabbit:oak-core:${oak_version}"
compile "org.apache.sling:org.apache.sling.jcr.api:${sling_commons_version}"
// Logging
diff --git a/gradle/packageExclusions.gradle b/gradle/packageExclusions.gradle
index 5adfb2a..42267a3 100644
--- a/gradle/packageExclusions.gradle
+++ b/gradle/packageExclusions.gradle
@@ -29,11 +29,14 @@ configurations.cq_package {
exclude group: 'org.apache.felix', module: 'org.osgi.compendium'
exclude group: 'org.apache.commons', module: 'commons-lang3'
- exclude group: 'org.apache.jackrabbit', module:'jackrabbit-jcr-commons'
+ exclude group: 'org.apache.jackrabbit', module: 'jackrabbit-jcr-commons'
+ exclude group: 'org.apache.jackrabbit', module: 'jackrabbit-api'
+ exclude group: 'org.apache.jackrabbit', module: 'oak-core'
exclude group: 'commons-io', module: 'commons-io'
//Exclude Apache Sling Libraries
exclude group: 'org.apache.sling', module: 'org.apache.sling.api'
+ exclude group: 'org.apache.sling', module: 'org.apache.sling.jcr.base'
exclude group: 'org.apache.sling', module:'org.apache.sling.jcr.resource'
exclude group: 'org.apache.sling', module: 'org.apache.sling.jcr.api'
diff --git a/src/main/content/SLING-INF/content/apps/grabbit/config/org.apache.sling.commons.log.LogManager.factory.config-com.twcable.grabbit.client.batch.xml b/src/main/content/SLING-INF/content/apps/grabbit/config/org.apache.sling.commons.log.LogManager.factory.config-com.twcable.grabbit.client.batch.xml
index e7739e2..e2557f2 100644
--- a/src/main/content/SLING-INF/content/apps/grabbit/config/org.apache.sling.commons.log.LogManager.factory.config-com.twcable.grabbit.client.batch.xml
+++ b/src/main/content/SLING-INF/content/apps/grabbit/config/org.apache.sling.commons.log.LogManager.factory.config-com.twcable.grabbit.client.batch.xml
@@ -14,7 +14,7 @@
org.apache.sling.commons.log.names
- com.twcable.grabbit.client.batch
+ com.twcable.grabbit.client
String
diff --git a/src/main/content/SLING-INF/content/apps/grabbit/config/org.apache.sling.commons.log.LogManager.factory.config-com.twcable.grabbit.server.batch.xml b/src/main/content/SLING-INF/content/apps/grabbit/config/org.apache.sling.commons.log.LogManager.factory.config-com.twcable.grabbit.server.batch.xml
index 9fe6384..b5750d7 100644
--- a/src/main/content/SLING-INF/content/apps/grabbit/config/org.apache.sling.commons.log.LogManager.factory.config-com.twcable.grabbit.server.batch.xml
+++ b/src/main/content/SLING-INF/content/apps/grabbit/config/org.apache.sling.commons.log.LogManager.factory.config-com.twcable.grabbit.server.batch.xml
@@ -14,7 +14,7 @@
org.apache.sling.commons.log.names
- com.twcable.grabbit.server.batch
+ com.twcable.grabbit.server
String
diff --git a/src/main/groovy/com/twcable/grabbit/client/batch/steps/jcrnodes/JcrNodesReader.groovy b/src/main/groovy/com/twcable/grabbit/client/batch/steps/jcrnodes/JcrNodesReader.groovy
index d33a9b1..b5ed0ba 100644
--- a/src/main/groovy/com/twcable/grabbit/client/batch/steps/jcrnodes/JcrNodesReader.groovy
+++ b/src/main/groovy/com/twcable/grabbit/client/batch/steps/jcrnodes/JcrNodesReader.groovy
@@ -18,6 +18,7 @@ package com.twcable.grabbit.client.batch.steps.jcrnodes
import com.twcable.grabbit.client.batch.ClientBatchJobContext
import com.twcable.grabbit.proto.NodeProtos
+import com.twcable.grabbit.proto.NodeProtos.Node as ProtoNode
import groovy.transform.CompileStatic
import groovy.util.logging.Slf4j
import org.springframework.batch.item.ItemReader
@@ -32,11 +33,11 @@ import org.springframework.batch.item.UnexpectedInputException
@Slf4j
@CompileStatic
@SuppressWarnings("GrMethodMayBeStatic")
-class JcrNodesReader implements ItemReader {
+class JcrNodesReader implements ItemReader {
@Override
NodeProtos.Node read() throws Exception, UnexpectedInputException, ParseException, NonTransientResourceException {
- NodeProtos.Node nodeProto = NodeProtos.Node.parseDelimitedFrom(theInputStream())
+ ProtoNode nodeProto = ProtoNode.parseDelimitedFrom(theInputStream())
if (!nodeProto) {
log.info "Received all data from Server"
return null
diff --git a/src/main/groovy/com/twcable/grabbit/client/batch/steps/jcrnodes/JcrNodesWriter.groovy b/src/main/groovy/com/twcable/grabbit/client/batch/steps/jcrnodes/JcrNodesWriter.groovy
index 85fe218..8d62167 100644
--- a/src/main/groovy/com/twcable/grabbit/client/batch/steps/jcrnodes/JcrNodesWriter.groovy
+++ b/src/main/groovy/com/twcable/grabbit/client/batch/steps/jcrnodes/JcrNodesWriter.groovy
@@ -17,7 +17,7 @@
package com.twcable.grabbit.client.batch.steps.jcrnodes
import com.twcable.grabbit.client.batch.ClientBatchJobContext
-import com.twcable.grabbit.jcr.JcrNodeDecorator
+import com.twcable.grabbit.jcr.JCRNodeDecorator
import com.twcable.grabbit.jcr.ProtoNodeDecorator
import com.twcable.grabbit.proto.NodeProtos.Node as ProtoNode
import groovy.transform.CompileStatic
@@ -88,14 +88,8 @@ class JcrNodesWriter implements ItemWriter, ItemWriteListener {
}
private static void writeToJcr(ProtoNode nodeProto, Session session) {
- JcrNodeDecorator jcrNode = new ProtoNodeDecorator(nodeProto).writeToJcr(session)
+ JCRNodeDecorator jcrNode = ProtoNodeDecorator.createFrom(nodeProto).writeToJcr(session)
jcrNode.setLastModified()
- // This will processed all mandatory child nodes only
- if(nodeProto.mandatoryChildNodeList && nodeProto.mandatoryChildNodeList.size() > 0) {
- for(ProtoNode childNode: nodeProto.mandatoryChildNodeList) {
- writeToJcr(childNode, session)
- }
- }
}
private Session theSession() {
diff --git a/src/main/groovy/com/twcable/grabbit/jcr/AuthorizableProtoNodeDecorator.groovy b/src/main/groovy/com/twcable/grabbit/jcr/AuthorizableProtoNodeDecorator.groovy
new file mode 100644
index 0000000..09f13e3
--- /dev/null
+++ b/src/main/groovy/com/twcable/grabbit/jcr/AuthorizableProtoNodeDecorator.groovy
@@ -0,0 +1,266 @@
+/*
+ * Copyright 2015 Time Warner Cable, Inc.
+ *
+ * Licensed 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 com.twcable.grabbit.jcr
+
+import com.twcable.grabbit.proto.NodeProtos.Node as ProtoNode
+import com.twcable.grabbit.security.AuthorizablePrincipal
+import com.twcable.grabbit.security.InsufficientGrabbitPrivilegeException
+import com.twcable.grabbit.util.CryptoUtil
+import groovy.transform.CompileStatic
+import groovy.util.logging.Slf4j
+import java.lang.reflect.Field
+import java.lang.reflect.Method
+import java.lang.reflect.ReflectPermission
+import java.util.regex.Pattern
+import javax.annotation.Nonnull
+import javax.jcr.Session
+import org.apache.jackrabbit.api.security.user.Authorizable
+import org.apache.jackrabbit.api.security.user.Group
+import org.apache.jackrabbit.api.security.user.User
+import org.apache.jackrabbit.api.security.user.UserManager
+import org.apache.jackrabbit.value.StringValue
+import org.apache.sling.jcr.base.util.AccessControlUtil
+
+
+/**
+ * This class wraps a serialized node that represents an Authorizable. Authorizables are special system protected nodes, that can only be written under certain
+ * trees, and can not be written directly by a client.
+ */
+@CompileStatic
+@Slf4j
+class AuthorizableProtoNodeDecorator extends ProtoNodeDecorator {
+
+
+ protected AuthorizableProtoNodeDecorator(@Nonnull ProtoNode node, @Nonnull Collection protoProperties) {
+ this.innerProtoNode = node
+ this.protoProperties = protoProperties
+ }
+
+
+ @Override
+ JCRNodeDecorator writeToJcr(@Nonnull Session session) {
+ if(!checkSecurityPermissions()) {
+ throw new InsufficientGrabbitPrivilegeException("JVM Permissions needed by Grabbit to sync Users/Groups were not found. See log for specific permissions needed, and add these to your security manager; or do not sync users and groups." +
+ "Unfortunately, the way Jackrabbit goes about certain things requires us to do a bit of hacking in order to sync Authorizables securely, and efficiently.")
+ }
+ Authorizable existingAuthorizable = findAuthorizable(session)
+ Authorizable newAuthorizable = existingAuthorizable ? updateAuthorizable(existingAuthorizable, session) : createNewAuthorizable(session)
+ writeAuthorizablePieces(newAuthorizable, session)
+ return new JCRNodeDecorator(session.getNode(newAuthorizable.getPath()))
+ }
+
+
+ /**
+ * @return a new authorizable from this serialized node
+ */
+ private Authorizable createNewAuthorizable(final Session session) {
+ final UserManager userManager = getUserManager(session)
+ if(isUserType()) {
+ //We set a temporary password for now, and then set the real password later in setPasswordForUser(). See the method for why.
+ final newUser = userManager.createUser(authorizableID, Long.toString(CryptoUtil.generateNextId()), new AuthorizablePrincipal(authorizableID), getIntermediateAuthorizablePath())
+ //This is a special protected property for disabling user access
+ if(hasProperty('rep:disabled')) {
+ newUser.disable(getStringValueFrom('rep:disabled'))
+ }
+ //AEM writes this property directly on the user node for some reason. One known use is for setting leads on MCM campaigns.
+ final authorizableCategory = 'cq:authorizableCategory'
+ if(hasProperty(authorizableCategory)) {
+ newUser.setProperty(authorizableCategory, new StringValue(getStringValueFrom(authorizableCategory)))
+ }
+ session.save()
+ //Special users may not have passwords, such as anonymous users
+ if(hasProperty('rep:password')) {
+ setPasswordForUser(newUser, session)
+ }
+ return newUser
+ }
+ final Group group = userManager.createGroup(authorizableID, new AuthorizablePrincipal(authorizableID), getIntermediateAuthorizablePath())
+ session.save()
+ return group
+ }
+
+
+ /**
+ * From a client API perspective, there is really no way to truly update an existing authorizable node. All of the node properties are protected, and there is no
+ * known way to update them. Here we remove the existing authorizable as denoted by the authorizableID, and recreate it.
+ * @return new instance of updated authorizable
+ */
+ private Authorizable updateAuthorizable(final Authorizable authorizable, final Session session) {
+ authorizable.remove()
+ session.save()
+ createNewAuthorizable(session)
+ }
+
+
+ /**
+ * Authorizable pieces (nodes that live under Authorizables - profile, preferences, etc) get sent with the authorizable node instead of streamed independently because we do not know the client's new
+ * authorizable UUID node name at runtime. In other words, authorizables can live under different node names from server to server
+ */
+ private void writeAuthorizablePieces(final Authorizable authorizable, final Session session) {
+ innerProtoNode.mandatoryChildNodeList.each {
+ //We replace the incoming server authorizable path, with the new authorizable path
+ createFrom(it, it.name.replaceFirst(Pattern.quote(getName()), authorizable.getPath())).writeToJcr(session)
+ }
+ session.save()
+ }
+
+
+ private Authorizable findAuthorizable(final Session session) {
+ final UserManager userManager = getUserManager(session)
+ return userManager.getAuthorizable(getAuthorizableID())
+ }
+
+
+ private String getAuthorizableID() {
+ return protoProperties.find { it.isAuthorizableIDType() }.stringValue
+ }
+
+
+ private String getIntermediateAuthorizablePath() {
+ final pathTokens = getName().tokenize('/')
+ //remove last index, as this is the Authorizable node name
+ pathTokens.remove(pathTokens.size() - 1)
+ return "/${pathTokens.join('/')}"
+ }
+
+
+ private boolean isUserType() {
+ return protoProperties.any { it.userType }
+ }
+
+
+ /**
+ * Some JVM's have a SecurityManager set, which based on configuration, can potentially inhibit our hack {@code setPasswordForUser(User, Session)} from working.
+ * We need to check security permissions before proceeding
+ * @return true if we can sync this Authorizable
+ */
+ private boolean checkSecurityPermissions() {
+ final SecurityManager securityManager = getSecurityManager()
+ //If no security manager is present, then we are in the clear; otherwise, we need to check certain permissions
+ if(!securityManager){
+ log.debug "No SecurityManager found on this JVM. Sync of Users/Groups can continue"
+ return true
+ }
+ final issues = []
+ final badPermissions = false
+ log.debug "SecurityManager found on this JVM. Checking permissions.."
+ try {
+ //Needed to reflect on members for which this class does not normally have access to
+ securityManager.checkPermission(new ReflectPermission('suppressAccessChecks'))
+ }
+ catch(SecurityException ex) {
+ issues << 'suppressAccessChecks'
+ badPermissions = true
+ }
+ try {
+ //Needed to access all declared members of a class, including protected or private
+ securityManager.checkPermission(new RuntimePermission('accessDeclaredMembers'))
+ }
+ catch(SecurityException ex) {
+ issues << 'accessDeclaredMembers'
+ badPermissions = true
+ }
+ try {
+ //Needed to access classes directly within a potentially system protected package
+ securityManager.checkPermission(new RuntimePermission('accessClassInPackage.{org.apache.jackrabbit.oak.security.user}'))
+ }
+ catch(SecurityException ex) {
+ issues << 'accessClassInPackage.{org.apache.jackrabbit.oak.security.user}'
+ badPermissions = true
+ }
+ if(badPermissions) {
+ log.warn "A SecurityManager is enabled for this JVM, and permissions are not sufficient for Grabbit to sync Authorizables (Users/Groups). You must enable ${issues.join(', ')} permissions in your SecurityManager to use this functionality" +
+ "Check https://docs.oracle.com/javase/7/docs/api/java/lang/RuntimePermission.html and https://docs.oracle.com/javase/7/docs/api/java/lang/reflect/ReflectPermission.html to see what these permissions enable"
+ return false
+ }
+ else {
+ log.debug "Permissions check successful"
+ return true
+ }
+ }
+
+ /**
+ * Mostly for ease of mocking/testing
+ * @return the system's security manager, or null if one is not present
+ */
+ SecurityManager getSecurityManager() {
+ return System.getSecurityManager()
+ }
+
+ /**
+ * Mostly for ease of mocking/testing
+ */
+ UserManager getUserManager(final Session session) {
+ return AccessControlUtil.getUserManager(session)
+ }
+
+
+ /**
+ * Normally we would call org.apache.jackrabbit.oak.jcr.delegate.UserDelegator.changePassword(String password) to change a password (this is what is publicly available through the Jackrabbit API)
+ * However, this method ALWAYS rehashes the password argument which is of no use to us, since we are trying to transfer an already hashed password.
+ *
+ * Internally, org.apache.jackrabbit.oak.jcr.delegate.UserDelegator calls it's delegate's org.apache.jackrabbit.oak.security.user.UserImpl.changePassword(String password)
+ * which calls org.apache.jackrabbit.oak.security.user.UserManagerImpl.setPassword(Tree tree, String userId, String password, boolean forceHash) with forceHash always set to true
+ * We really need forcehash set to false for our case, but this isn't publicly available. Here, we access internal objects to do this manipulation. org.apache.jackrabbit.oak.security.user.UserManagerImpl
+ * simply ensures that forcehash is false, and that the password is not plain text, and it sets the password as-is.
+ *
+ * @throws IllegalStateException if security permissions required to run this are not there. @{code checkSecurityPermissions()} should be called before calling this method
+ **/
+ void setPasswordForUser(final User user, final Session session) {
+ if(!checkSecurityPermissions()) throw new IllegalStateException("Security check failed for Grabbit. Can not set user passwords")
+ //As a consumer we have access to org.apache.jackrabbit.oak.jcr.delegate.UserManagerDelegator below
+ final userManager = getUserManager(session)
+ Class userManagerDelegatorClass = userManager.getClass()
+ //Reach into the class of this delegator, and grab the core Jackrabbit object we delegate to
+ Field userManagerDelegateField = userManagerDelegatorClass.getDeclaredField('userManagerDelegate')
+ //The delegate field is private, so we need to make it accessible. Security checks above are imperative for this to work
+ userManagerDelegateField.setAccessible(true)
+ //Here we have a handle to the internal class org.apache.jackrabbit.oak.security.user.UserManagerImpl
+ final userManagerDelegate = userManagerDelegateField.get(userManager)
+ final userManagerDelegateClass = userManagerDelegate.getClass()
+ //We need to set the 'setPassword' method as accessible. Again, security checks above are imperative for this to work
+ Method setPasswordMethod = userManagerDelegateClass.getDeclaredMethod('setPassword', Class.forName('org.apache.jackrabbit.oak.api.Tree', true, userManagerDelegateClass.getClassLoader()), String, String, boolean)
+ setPasswordMethod.setAccessible(true)
+ /**
+ * Step two. We need access to the internal Authorizable object's tree in order to call the internal setPassword method
+ * User is an instance of org.apache.jackrabbit.oak.jcr.delegate.UserDelegator. We need to get the delegate off of this class's super class org.apache.jackrabbit.oak.jcr.delegate.AuthorizableDelegator
+ */
+ Class authorizableDelegateClass = user.getClass().getSuperclass()
+ Field authorizableDelegateField = authorizableDelegateClass.getDeclaredField('delegate')
+ authorizableDelegateField.setAccessible(true)
+ final authorizable = authorizableDelegateField.get(user)
+ //Internal org.apache.jackrabbit.oak.security.user.AuthorizableImpl object. We can access the protected tree here
+ Method getTreeMethod = authorizable.getClass().getSuperclass().getDeclaredMethod('getTree')
+ getTreeMethod.setAccessible(true)
+
+ /**
+ * The last argument where we are passing in 'false' in the secret sauce we need. This parameter is forceHash. As long as forceHash is false, and the password is not
+ * clear-text, which it isn't since we got it from another Jackrabbit instance, we can set the password as-is.
+ */
+ setPasswordMethod.invoke(userManagerDelegate, getTreeMethod.invoke(authorizable), getAuthorizableID(), getStringValueFrom('rep:password'), false)
+ session.save()
+ }
+
+
+ /**
+ * An instance wrapper for ease of mocking
+ * @see super.createFrom
+ */
+ ProtoNodeDecorator createFrom(final ProtoNode protoNode, final String nameOverride) {
+ super.createFrom(protoNode, nameOverride)
+ }
+}
diff --git a/src/main/groovy/com/twcable/grabbit/jcr/DefaultProtoNodeDecorator.groovy b/src/main/groovy/com/twcable/grabbit/jcr/DefaultProtoNodeDecorator.groovy
new file mode 100644
index 0000000..64635e0
--- /dev/null
+++ b/src/main/groovy/com/twcable/grabbit/jcr/DefaultProtoNodeDecorator.groovy
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2015 Time Warner Cable, Inc.
+ *
+ * Licensed 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 com.twcable.grabbit.jcr
+
+import com.twcable.grabbit.proto.NodeProtos.Node as ProtoNode
+import com.twcable.grabbit.proto.NodeProtos.Value as ProtoValue
+import groovy.transform.CompileStatic
+import groovy.util.logging.Slf4j
+import java.util.regex.Pattern
+import javax.annotation.Nonnull
+import javax.jcr.Node as JCRNode
+import javax.jcr.Session
+import org.apache.jackrabbit.commons.JcrUtils
+
+
+import static org.apache.jackrabbit.JcrConstants.JCR_MIXINTYPES
+import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE
+
+@CompileStatic
+@Slf4j
+class DefaultProtoNodeDecorator extends ProtoNodeDecorator {
+
+ private final String nameOverride
+
+ protected DefaultProtoNodeDecorator(@Nonnull ProtoNode node, @Nonnull Collection protoProperties, String nameOverride) {
+ this.innerProtoNode = node
+ this.protoProperties = protoProperties
+ this.nameOverride = nameOverride
+ }
+
+
+ @Override
+ JCRNodeDecorator writeToJcr(@Nonnull Session session) {
+ final jcrNode = getOrCreateNode(session)
+ //Write mixin types first to avoid InvalidConstraintExceptions
+ final mixinProperty = getMixinProperty()
+ if(mixinProperty) {
+ addMixins(mixinProperty, jcrNode)
+ }
+ //Then add other properties
+ writableProperties.each { it.writeToNode(jcrNode) }
+
+ if(innerProtoNode.mandatoryChildNodeList && innerProtoNode.mandatoryChildNodeList.size() > 0) {
+ for(ProtoNode childNode: innerProtoNode.mandatoryChildNodeList) {
+ //Mandatory children must inherit any name overrides from their parent (if they exist)
+ createFrom(childNode, childNode.getName().replaceFirst(Pattern.quote(innerProtoNode.name), getName())).writeToJcr(session)
+ }
+ }
+ return new JCRNodeDecorator(jcrNode)
+ }
+
+
+ private ProtoPropertyDecorator getMixinProperty() {
+ protoProperties.find { it.isMixinType() }
+ }
+
+
+ private Collection getWritableProperties() {
+ protoProperties.findAll { !(it.name in [JCR_PRIMARYTYPE, JCR_MIXINTYPES]) }
+ }
+
+
+ @Override
+ String getName() {
+ nameOverride ?: innerProtoNode.getName()
+ }
+
+
+ /**
+ * This method is rather succinct, but helps isolate this JcrUtils static method call
+ * so that we can get better test coverage.
+ * @param session to create or get the node path for
+ * @return the newly created, or found node
+ */
+ JCRNode getOrCreateNode(Session session) {
+ JcrUtils.getOrCreateByPath(getName(), primaryType.getStringValue(), session)
+ }
+
+
+ /**
+ * If a property can be added as a mixin, adds it to the given node
+ * @param property
+ * @param node
+ */
+ private static void addMixins(ProtoPropertyDecorator property, JCRNode node) {
+ property.valuesList.each { ProtoValue value ->
+ if (node.canAddMixin(value.stringValue)) {
+ node.addMixin(value.stringValue)
+ log.debug "Added mixin ${value.stringValue} for : ${node.name}."
+ }
+ else {
+ log.warn "Encountered invalid mixin type while unmarshalling for Proto value : ${value}"
+ }
+ }
+ }
+
+}
diff --git a/src/main/groovy/com/twcable/grabbit/jcr/JCRNodeDecorator.groovy b/src/main/groovy/com/twcable/grabbit/jcr/JCRNodeDecorator.groovy
index d3be775..0aa6e27 100644
--- a/src/main/groovy/com/twcable/grabbit/jcr/JCRNodeDecorator.groovy
+++ b/src/main/groovy/com/twcable/grabbit/jcr/JCRNodeDecorator.groovy
@@ -19,49 +19,74 @@ import com.twcable.grabbit.proto.NodeProtos.Node as ProtoNode
import com.twcable.grabbit.proto.NodeProtos.Node.Builder as ProtoNodeBuilder
import com.twcable.grabbit.proto.NodeProtos.Property as ProtoProperty
import groovy.transform.CompileStatic
-
import groovy.util.logging.Slf4j
-import org.apache.jackrabbit.value.DateValue
-
import javax.annotation.Nonnull
import javax.annotation.Nullable
+import javax.jcr.ItemNotFoundException
import javax.jcr.Node as JCRNode
-import javax.jcr.Property
+import javax.jcr.PathNotFoundException
import javax.jcr.Property as JcrProperty
import javax.jcr.RepositoryException
import javax.jcr.nodetype.ItemDefinition
+import org.apache.jackrabbit.commons.flat.TreeTraverser
+import org.apache.jackrabbit.value.DateValue
-import static org.apache.jackrabbit.JcrConstants.*
+
+import static org.apache.jackrabbit.JcrConstants.JCR_CREATED
+import static org.apache.jackrabbit.JcrConstants.JCR_LASTMODIFIED
+import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE
+import static org.apache.jackrabbit.commons.flat.TreeTraverser.ErrorHandler
+import static org.apache.jackrabbit.commons.flat.TreeTraverser.InclusionPolicy
+import static org.apache.jackrabbit.oak.spi.security.authorization.accesscontrol.AccessControlConstants.AC_NODETYPE_NAMES
@CompileStatic
@Slf4j
-class JcrNodeDecorator {
+class JCRNodeDecorator {
@Delegate
JCRNode innerNode
+ private final Collection properties
//Evaluated in a lazy fashion
- private Collection immediateChildNodes
+ private Collection immediateChildNodes
+ private List childNodeList
- JcrNodeDecorator(@Nonnull JCRNode node) {
+ JCRNodeDecorator(@Nonnull JCRNode node) {
if(!node) throw new IllegalArgumentException("node must not be null!")
this.innerNode = node
+ Collection innerProperties = node.properties.toList()
+ this.properties = innerProperties.collect { JcrProperty property ->
+ new JcrPropertyDecorator(property, this)
+ }
}
/**
* @return this node's immediate children, empty if none
*/
- Collection getImmediateChildNodes() {
+ Collection getImmediateChildNodes() {
if(!immediateChildNodes) {
- immediateChildNodes = (getNodes().collect { JCRNode node -> new JcrNodeDecorator(node) } ?: []) as Collection
+ immediateChildNodes = (getNodes().collect { JCRNode node -> new JCRNodeDecorator(node) } ?: []) as Collection
}
return immediateChildNodes
}
+ List getChildNodeList() {
+ if(!childNodeList) {
+ childNodeList = (getChildNodeIterator().collect { JCRNode node -> new JCRNodeDecorator(node) } ?: []) as List
+ }
+ return childNodeList
+ }
+
+
+ Iterator getChildNodeIterator() {
+ return TreeTraverser.nodeIterator(innerNode, ErrorHandler.IGNORE, new NoRootInclusionPolicy(this))
+ }
+
+
void setLastModified() {
final lastModified = new DateValue(Calendar.instance)
try {
@@ -77,18 +102,25 @@ class JcrNodeDecorator {
}
- String getPrimaryType() {
- innerNode.getProperty(JCR_PRIMARYTYPE).string
- }
-
-
/**
* Identify all required child nodes
- * @return list of immediate required child nodes that must exist with this node, or null if no children
+ * @return list of immediate required child nodes that must be transported with this node, or an empty collection if no required nodes
*/
@Nullable
- Collection getRequiredChildNodes() {
- return hasMandatoryChildNodes() ? getImmediateChildNodes().findAll{ JcrNodeDecorator childJcrNode -> childJcrNode.isRequiredNode() } : null
+ Collection getRequiredChildNodes() {
+ if(isAuthorizableType()){
+ return getChildNodeList().findAll { JCRNodeDecorator childJcrNode -> !childJcrNode.isLoginToken() && !childJcrNode.isACType() }
+ }
+ return getMandatoryChildren()
+ }
+
+
+ /**
+ * Some nodes must be saved together, per node definition
+ */
+ @Nonnull
+ private Collection getMandatoryChildren() {
+ return hasMandatoryChildNodes() ? getImmediateChildNodes().findAll{ JCRNodeDecorator childJcrNode -> childJcrNode.isMandatoryNode() } : []
}
@@ -102,10 +134,10 @@ class JcrNodeDecorator {
/**
- * This node is a required node of some parent node definition
+ * This node is a mandatory required node of some parent node definition
* @return true if mandatory, false if not
*/
- boolean isRequiredNode() {
+ boolean isMandatoryNode() {
return definition.isMandatory()
}
@@ -117,7 +149,7 @@ class JcrNodeDecorator {
final ProtoNodeBuilder protoNodeBuilder = ProtoNode.newBuilder()
protoNodeBuilder.setName(path)
protoNodeBuilder.addAllProperties(getProtoProperties())
- requiredChildNodes?.each {
+ requiredChildNodes.each {
protoNodeBuilder.addMandatoryChildNode(it.toProtoNode())
}
return protoNodeBuilder.build()
@@ -128,11 +160,8 @@ class JcrNodeDecorator {
*/
@Nonnull
private Collection getProtoProperties() {
- final List properties = properties.toList()
- return properties.findResults { JcrProperty jcrProperty ->
- JcrPropertyDecorator decoratedProperty = new JcrPropertyDecorator(jcrProperty)
- decoratedProperty.isTransferable() ? decoratedProperty.toProtoProperty() : null
- }
+ final Collection transferableProperties = properties.findAll{ it.isTransferable() }
+ return transferableProperties.collect{ it.toProtoProperty() }
}
/**
@@ -156,6 +185,43 @@ class JcrNodeDecorator {
}
+ /**
+ * Authorizable nodes can be unique from server to server, so associated profiles, preferences, etc need to be sent with.
+ * @return true if this node lives under an authorizable
+ */
+ boolean isAuthorizablePart() {
+ try {
+ JCRNodeDecorator parent = new JCRNodeDecorator(getParent())
+ while(!parent.isAuthorizableType()) {
+ parent = new JCRNodeDecorator(parent.getParent())
+ }
+ return true
+ } catch(PathNotFoundException | ItemNotFoundException ex) {
+ return false
+ }
+ }
+
+
+ String getPrimaryType() {
+ innerNode.getProperty(JCR_PRIMARYTYPE).string
+ }
+
+
+ boolean isAuthorizableType() {
+ return primaryType == 'rep:User' || primaryType == 'rep:Group'
+ }
+
+ boolean isACType() {
+ AC_NODETYPE_NAMES.contains(primaryType)
+ }
+
+
+ boolean isLoginToken() {
+ final primaryType = getPrimaryType()
+ return (primaryType == 'rep:Unstructured' && name.tokenize('/')[-1] == '.tokens') || primaryType == 'rep:Token'
+ }
+
+
Object asType(Class clazz) {
if(clazz == JCRNode) {
return innerNode
@@ -164,4 +230,37 @@ class JcrNodeDecorator {
super.asType(clazz)
}
}
+
+ @Override
+ boolean equals(Object obj) {
+ if (this.is(obj)) return true
+ if (getClass() != obj.class) return false
+
+ JCRNodeDecorator that = (JCRNodeDecorator)obj
+
+ return this.hashCode() == that.hashCode()
+ }
+
+ @Override
+ int hashCode() {
+ return innerNode.getName().hashCode()
+ }
+
+ @CompileStatic
+ private static class NoRootInclusionPolicy implements InclusionPolicy {
+
+ final JCRNodeDecorator rootNode
+
+ NoRootInclusionPolicy(JCRNode rootNode) {
+ this.rootNode = new JCRNodeDecorator(rootNode)
+ }
+
+
+ @Override
+ boolean include(JCRNode node) {
+ final JCRNodeDecorator candidateNode = new JCRNodeDecorator(node)
+ //Don't include the root, and dont' include mandatory nodes as they are held within their parent
+ return (!rootNode.equals(candidateNode)) && (!candidateNode.isMandatoryNode())
+ }
+ }
}
diff --git a/src/main/groovy/com/twcable/grabbit/jcr/JcrPropertyDecorator.groovy b/src/main/groovy/com/twcable/grabbit/jcr/JcrPropertyDecorator.groovy
index af667dc..b258db8 100644
--- a/src/main/groovy/com/twcable/grabbit/jcr/JcrPropertyDecorator.groovy
+++ b/src/main/groovy/com/twcable/grabbit/jcr/JcrPropertyDecorator.groovy
@@ -24,7 +24,6 @@ import groovy.transform.CompileStatic
import groovy.util.logging.Slf4j
import javax.annotation.Nonnull
-import javax.jcr.Property
import javax.jcr.Property as JCRProperty
import javax.jcr.Value
@@ -38,8 +37,11 @@ class JcrPropertyDecorator {
@Delegate
JCRProperty innerProperty
- JcrPropertyDecorator(Property property) {
+ private final JCRNodeDecorator nodeOwner
+
+ JcrPropertyDecorator(JCRProperty property, JCRNodeDecorator nodeOwner) {
this.innerProperty = property
+ this.nodeOwner = nodeOwner
}
/**
@@ -58,7 +60,7 @@ class JcrPropertyDecorator {
return true
}
- !definition.isProtected()
+ return nodeOwner.isAuthorizableType() ?: !definition.isProtected()
}
/**
diff --git a/src/main/groovy/com/twcable/grabbit/jcr/ProtoNodeDecorator.groovy b/src/main/groovy/com/twcable/grabbit/jcr/ProtoNodeDecorator.groovy
index 750eab3..24d65a8 100644
--- a/src/main/groovy/com/twcable/grabbit/jcr/ProtoNodeDecorator.groovy
+++ b/src/main/groovy/com/twcable/grabbit/jcr/ProtoNodeDecorator.groovy
@@ -13,93 +13,46 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
package com.twcable.grabbit.jcr
import com.twcable.grabbit.proto.NodeProtos.Node as ProtoNode
-import com.twcable.grabbit.proto.NodeProtos.Value as ProtoValue
import groovy.transform.CompileStatic
-import groovy.util.logging.Slf4j
-import org.apache.jackrabbit.commons.JcrUtils
import javax.annotation.Nonnull
-import javax.jcr.Node as JCRNode
import javax.jcr.Session
-import static org.apache.jackrabbit.JcrConstants.JCR_MIXINTYPES
-import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE
-
@CompileStatic
-@Slf4j
-class ProtoNodeDecorator {
+abstract class ProtoNodeDecorator {
@Delegate
- ProtoNode innerProtoNode
+ protected ProtoNode innerProtoNode
- Collection protoProperties
+ protected Collection protoProperties
+ abstract JCRNodeDecorator writeToJcr(@Nonnull Session session)
- ProtoNodeDecorator(@Nonnull ProtoNode node) {
+ static ProtoNodeDecorator createFrom(@Nonnull ProtoNode node, String nameOverride = null) {
if(!node) throw new IllegalArgumentException("node must not be null!")
- this.innerProtoNode = node
- this.protoProperties = node.propertiesList.collect { new ProtoPropertyDecorator(it) }
- }
-
-
- String getPrimaryType() {
- protoProperties.find { it.isPrimaryType() }.value.stringValue
- }
-
-
- ProtoPropertyDecorator getMixinProperty() {
- protoProperties.find { it.isMixinType() }
- }
-
-
- Collection getWritableProperties() {
- protoProperties.findAll { !(it.name in [JCR_PRIMARYTYPE, JCR_MIXINTYPES]) }
- }
-
-
- JcrNodeDecorator writeToJcr(@Nonnull Session session) {
- final jcrNode = getOrCreateNode(session)
- //Write mixin types first to avoid InvalidConstraintExceptions
- final mixinProperty = getMixinProperty()
- if(mixinProperty) {
- addMixins(mixinProperty, jcrNode)
+ final protoProperties = node.propertiesList.collect { new ProtoPropertyDecorator(it) }
+ final primaryType = protoProperties.find { it.primaryType }
+ if(primaryType.isUserType() || primaryType.isGroupType()) {
+ return new AuthorizableProtoNodeDecorator(node, protoProperties)
}
- //Then add other properties
- writableProperties.each { it.writeToNode(jcrNode) }
+ return new DefaultProtoNodeDecorator(node, protoProperties, nameOverride)
+ }
- return new JcrNodeDecorator(jcrNode)
+ boolean hasProperty(String propertyName) {
+ propertiesList.any{ it.name == propertyName }
}
- /**
- * This method is rather succinct, but helps isolate this JcrUtils static method call
- * so that we can get better test coverage.
- * @param session to create or get the node path for
- * @return the newly created, or found node
- */
- JCRNode getOrCreateNode(Session session) {
- JcrUtils.getOrCreateByPath(innerProtoNode.name, primaryType, session)
+ protected ProtoPropertyDecorator getPrimaryType() {
+ protoProperties.find { it.isPrimaryType() }
}
- /**
- * If a property can be added as a mixin, adds it to the given node
- * @param property
- * @param node
- */
- private static void addMixins(ProtoPropertyDecorator property, JCRNode node) {
- property.valuesList.each { ProtoValue value ->
- if (node.canAddMixin(value.stringValue)) {
- node.addMixin(value.stringValue)
- log.debug "Added mixin ${value.stringValue} for : ${node.name}."
- }
- else {
- log.warn "Encountered invalid mixin type while unmarshalling for Proto value : ${value}"
- }
- }
+ protected String getStringValueFrom(String propertyName) {
+ protoProperties.find { it.name == propertyName }.stringValue
}
-
}
diff --git a/src/main/groovy/com/twcable/grabbit/jcr/ProtoPropertyDecorator.groovy b/src/main/groovy/com/twcable/grabbit/jcr/ProtoPropertyDecorator.groovy
index eeb7a4e..a7aa862 100644
--- a/src/main/groovy/com/twcable/grabbit/jcr/ProtoPropertyDecorator.groovy
+++ b/src/main/groovy/com/twcable/grabbit/jcr/ProtoPropertyDecorator.groovy
@@ -38,6 +38,7 @@ class ProtoPropertyDecorator {
@Delegate
ProtoProperty innerProtoProperty
+
ProtoPropertyDecorator(@Nonnull ProtoProperty protoProperty) {
this.innerProtoProperty = protoProperty
}
@@ -82,7 +83,28 @@ class ProtoPropertyDecorator {
innerProtoProperty.name == JCR_MIXINTYPES
}
- ProtoValue getValue() {
+
+ boolean isUserType() {
+ getStringValue() == 'rep:User'
+ }
+
+
+ boolean isGroupType() {
+ getStringValue() == 'rep:Group'
+ }
+
+
+ boolean isAuthorizableIDType() {
+ innerProtoProperty.name == 'rep:authorizableId'
+ }
+
+
+ String getStringValue() {
+ getValue().stringValue
+ }
+
+
+ private ProtoValue getValue() {
innerProtoProperty.valuesList.first()
}
diff --git a/src/main/groovy/com/twcable/grabbit/security/AuthorizablePrincipal.groovy b/src/main/groovy/com/twcable/grabbit/security/AuthorizablePrincipal.groovy
new file mode 100644
index 0000000..101ba2b
--- /dev/null
+++ b/src/main/groovy/com/twcable/grabbit/security/AuthorizablePrincipal.groovy
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2015 Time Warner Cable, Inc.
+ *
+ * Licensed 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 com.twcable.grabbit.security
+
+import groovy.transform.CompileStatic
+
+import javax.annotation.Nonnull
+import java.security.Principal
+
+
+@CompileStatic
+class AuthorizablePrincipal implements Principal {
+
+ final private String principalName
+
+ AuthorizablePrincipal(@Nonnull final String principalName) {
+ this.principalName = principalName
+ }
+
+ @Override
+ boolean equals(Object other) {
+ if(other == null) return false
+ return this.hashCode() == other.hashCode()
+ }
+
+ @Override
+ String getName() {
+ return principalName
+ }
+
+ @Override
+ int hashCode() {
+ return principalName.hashCode()
+ }
+}
diff --git a/src/main/groovy/com/twcable/grabbit/security/InsufficientGrabbitPrivilegeException.groovy b/src/main/groovy/com/twcable/grabbit/security/InsufficientGrabbitPrivilegeException.groovy
new file mode 100644
index 0000000..a397d40
--- /dev/null
+++ b/src/main/groovy/com/twcable/grabbit/security/InsufficientGrabbitPrivilegeException.groovy
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2015 Time Warner Cable, Inc.
+ *
+ * Licensed 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 com.twcable.grabbit.security
+
+import groovy.transform.CompileStatic
+import groovy.transform.InheritConstructors
+
+@InheritConstructors
+@CompileStatic
+class InsufficientGrabbitPrivilegeException extends RuntimeException {}
diff --git a/src/main/groovy/com/twcable/grabbit/server/batch/steps/jcrnodes/JcrNodesProcessor.groovy b/src/main/groovy/com/twcable/grabbit/server/batch/steps/jcrnodes/JcrNodesProcessor.groovy
index 76051e6..7f4bb65 100644
--- a/src/main/groovy/com/twcable/grabbit/server/batch/steps/jcrnodes/JcrNodesProcessor.groovy
+++ b/src/main/groovy/com/twcable/grabbit/server/batch/steps/jcrnodes/JcrNodesProcessor.groovy
@@ -17,7 +17,7 @@
package com.twcable.grabbit.server.batch.steps.jcrnodes
import com.twcable.grabbit.DateUtil
-import com.twcable.grabbit.jcr.JcrNodeDecorator
+import com.twcable.grabbit.jcr.JCRNodeDecorator
import com.twcable.grabbit.proto.NodeProtos.Node as ProtoNode
import groovy.transform.CompileStatic
import groovy.util.logging.Slf4j
@@ -47,10 +47,10 @@ class JcrNodesProcessor implements ItemProcessor {
@Nullable
ProtoNode process(JcrNode jcrNode) throws Exception {
- JcrNodeDecorator decoratedNode = new JcrNodeDecorator(jcrNode)
+ JCRNodeDecorator decoratedNode = new JCRNodeDecorator(jcrNode)
//TODO: Access Control Lists nodes are not supported right now.
- if (decoratedNode.path.contains("rep:policy")) {
+ if (decoratedNode.isACType()) {
log.info "Ignoring current node ${decoratedNode.innerNode}"
return null
}
@@ -66,7 +66,7 @@ class JcrNodesProcessor implements ItemProcessor {
}
// Skip this node because it has already been processed by its parent
- if(decoratedNode.isRequiredNode()) {
+ if(decoratedNode.isMandatoryNode() || decoratedNode.isAuthorizablePart()) {
return null
} else {
// Build parent node
diff --git a/src/main/groovy/com/twcable/grabbit/server/services/RootNodeWithMandatoryIterator.groovy b/src/main/groovy/com/twcable/grabbit/server/services/RootNodeWithMandatoryIterator.groovy
index 063dd52..e882259 100644
--- a/src/main/groovy/com/twcable/grabbit/server/services/RootNodeWithMandatoryIterator.groovy
+++ b/src/main/groovy/com/twcable/grabbit/server/services/RootNodeWithMandatoryIterator.groovy
@@ -16,7 +16,7 @@
package com.twcable.grabbit.server.services
-import com.twcable.grabbit.jcr.JcrNodeDecorator
+import com.twcable.grabbit.jcr.JCRNodeDecorator
import groovy.transform.CompileStatic
import groovy.transform.TailRecursive
@@ -31,13 +31,13 @@ import javax.jcr.Node as JcrNode
final class RootNodeWithMandatoryIterator implements Iterator {
private boolean doneRoot
- private JcrNodeDecorator rootNode
- private Iterator immediateChildren
- private Iterator mandatoryWriteNodes
+ private JCRNodeDecorator rootNode
+ private Iterator immediateChildren
+ private Iterator mandatoryWriteNodes
public RootNodeWithMandatoryIterator(JcrNode root) {
- this.rootNode = new JcrNodeDecorator(root)
+ this.rootNode = new JCRNodeDecorator(root)
this.doneRoot = false
//Get all immediate children that are not mandatory write nodes. We will handle those by iterating over mandatoryWriteNodes
immediateChildren = getNonMandatoryChildren(this.rootNode).iterator()
@@ -76,20 +76,20 @@ final class RootNodeWithMandatoryIterator implements Iterator {
}
- private static Collection getNonMandatoryChildren(final JcrNodeDecorator node) {
- node.getImmediateChildNodes().findAll { !it.isRequiredNode() }
+ private static Collection getNonMandatoryChildren(final JCRNodeDecorator node) {
+ node.getImmediateChildNodes().findAll { !it.isMandatoryNode() }
}
@TailRecursive
- private static Collection getMandatoryChildren(final JcrNodeDecorator currentNode, final Collection nodesToAdd) {
+ private static Collection getMandatoryChildren(final JCRNodeDecorator currentNode, final Collection nodesToAdd) {
if(!currentNode.hasMandatoryChildNodes()) {
return nodesToAdd
}
final mandatoryNodes = currentNode.getRequiredChildNodes()
- return mandatoryNodes.collectMany { JcrNodeDecorator mandatoryNode ->
+ return mandatoryNodes.collectMany { JCRNodeDecorator mandatoryNode ->
return getMandatoryChildren(mandatoryNode, (nodesToAdd << mandatoryNode))
}
}
diff --git a/src/test/groovy/com/twcable/grabbit/client/GrabbitJobServletSpec.groovy b/src/test/groovy/com/twcable/grabbit/client/GrabbitJobServletSpec.groovy
index 86dc95d..25fad75 100644
--- a/src/test/groovy/com/twcable/grabbit/client/GrabbitJobServletSpec.groovy
+++ b/src/test/groovy/com/twcable/grabbit/client/GrabbitJobServletSpec.groovy
@@ -32,10 +32,9 @@ import spock.lang.Specification
import spock.lang.Subject
import spock.lang.Unroll
-import javax.annotation.Nonnull
-import javax.servlet.ServletInputStream
import static com.twcable.grabbit.client.servlets.GrabbitJobServlet.ALL_JOBS_ID
+import static com.twcable.grabbit.testutil.StubInputStream.inputStream
import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST
import static javax.servlet.http.HttpServletResponse.SC_OK
@@ -186,7 +185,7 @@ class GrabbitJobServletSpec extends Specification {
def "Can create a new job successfully from configuration"() {
given:
File configFile = new File(this.class.getResource("test_config.yaml").getFile())
- final inputStream = new StubServletInputStream(configFile)
+ final inputStream = inputStream(configFile)
final clientService = Mock(ClientService) {
initiateGrab(_, _) >> [123L]
}
@@ -230,36 +229,7 @@ class GrabbitJobServletSpec extends Specification {
1 * response.setStatus(SC_BAD_REQUEST)
where:
- inputStream << [new StubServletInputStream(" "), new StubServletInputStream("foo: 'foo'")]
+ inputStream << [inputStream(" "), inputStream("foo: 'foo'")]
//One causes SnakeYAML to produce a null config map, and the other does not pass our validations (missing values)
}
-
- class StubServletInputStream extends ServletInputStream {
-
-
- private final int byte_length
- private final byte[] bytes
- private int byte_index = 0
-
- StubServletInputStream(@Nonnull final String data) {
- bytes = data as byte[]
- byte_length = bytes.length
- }
-
- StubServletInputStream(@Nonnull final File fromFile) {
- bytes = fromFile.readBytes()
- byte_length = bytes.length
- }
-
- @Override
- int read() throws IOException {
- if (byte_index <= byte_length - 1) {
- final thisByte = bytes[byte_index] as int
- byte_index++
- return thisByte
- }
- return -1
- }
- }
-
}
diff --git a/src/test/groovy/com/twcable/grabbit/jcr/AuthorizableProtoNodeDecoratorSpec.groovy b/src/test/groovy/com/twcable/grabbit/jcr/AuthorizableProtoNodeDecoratorSpec.groovy
new file mode 100644
index 0000000..e858d71
--- /dev/null
+++ b/src/test/groovy/com/twcable/grabbit/jcr/AuthorizableProtoNodeDecoratorSpec.groovy
@@ -0,0 +1,339 @@
+/*
+ * Copyright 2015 Time Warner Cable, Inc.
+ *
+ * Licensed 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 com.twcable.grabbit.jcr
+
+import com.twcable.grabbit.proto.NodeProtos.Node as ProtoNode
+import com.twcable.grabbit.proto.NodeProtos.Node.Builder as ProtoNodeBuilder
+import com.twcable.grabbit.proto.NodeProtos.Property as ProtoProperty
+import com.twcable.grabbit.proto.NodeProtos.Value as ProtoValue
+import com.twcable.grabbit.security.AuthorizablePrincipal
+import com.twcable.grabbit.security.InsufficientGrabbitPrivilegeException
+import java.lang.reflect.ReflectPermission
+import javax.jcr.Node
+import javax.jcr.Property
+import javax.jcr.PropertyIterator
+import javax.jcr.Session
+import org.apache.jackrabbit.api.security.user.Authorizable
+import org.apache.jackrabbit.api.security.user.Group
+import org.apache.jackrabbit.api.security.user.User
+import org.apache.jackrabbit.api.security.user.UserManager
+import org.apache.jackrabbit.value.StringValue
+import spock.lang.Specification
+import spock.lang.Unroll
+
+
+import static javax.jcr.PropertyType.STRING
+import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE
+
+
+class AuthorizableProtoNodeDecoratorSpec extends Specification {
+
+ AuthorizableProtoNodeDecorator theProtoNodeDecorator(boolean forUser, boolean hasProfile, boolean hasPreferences, Closure configuration = null){
+ ProtoNodeBuilder nodeBuilder = ProtoNode.newBuilder()
+ nodeBuilder.setName(forUser ? "/home/users/u/user1" : "/home/groups/g/group1")
+
+ ProtoProperty primaryTypeProperty = ProtoProperty
+ .newBuilder()
+ .setName(JCR_PRIMARYTYPE)
+ .setType(STRING)
+ .setMultiple(false)
+ .addValues(ProtoValue.newBuilder().setStringValue(forUser ? 'rep:User' : 'rep:Group'))
+ .build()
+ nodeBuilder.addProperties(primaryTypeProperty)
+
+ ProtoProperty disabledProperty = ProtoProperty
+ .newBuilder()
+ .setName('rep:disabled')
+ .setType(STRING)
+ .setMultiple(false)
+ .addValues(ProtoValue.newBuilder().setStringValue('Reason for disabling'))
+ .build()
+ nodeBuilder.addProperties(disabledProperty)
+
+ ProtoProperty authorizableIdProperty = ProtoProperty
+ .newBuilder()
+ .setName('rep:authorizableId')
+ .setType(STRING)
+ .setMultiple(false)
+ .addValues(ProtoValue.newBuilder().setStringValue('authorizableID'))
+ .build()
+ nodeBuilder.addProperties(authorizableIdProperty)
+
+ ProtoProperty authorizableCategory = ProtoProperty
+ .newBuilder()
+ .setName('cq:authorizableCategory')
+ .setType(STRING)
+ .setMultiple(false)
+ .addValues(ProtoValue.newBuilder().setStringValue('mcm'))
+ .build()
+ nodeBuilder.addProperties(authorizableCategory)
+
+ ProtoProperty simplePrimaryType = ProtoProperty
+ .newBuilder()
+ .setName(JCR_PRIMARYTYPE)
+ .setType(STRING)
+ .setMultiple(false)
+ .addValues(ProtoValue.newBuilder().setStringValue('nt:unstructured'))
+ .build()
+ nodeBuilder.addProperties(authorizableCategory)
+ if(hasPreferences) {
+ ProtoNode preferenceNode = ProtoNode.newBuilder()
+ .setName("${nodeBuilder.getName()}/preferences")
+ .addProperties(simplePrimaryType)
+ .build()
+ nodeBuilder.addMandatoryChildNode(preferenceNode)
+ }
+ if(hasProfile) {
+ ProtoNode profileNode = ProtoNode
+ .newBuilder()
+ .setName("${nodeBuilder.getName()}/profile")
+ .addProperties(simplePrimaryType)
+ .build()
+ nodeBuilder.addMandatoryChildNode(profileNode)
+ }
+ final properties = [new ProtoPropertyDecorator(primaryTypeProperty), new ProtoPropertyDecorator(disabledProperty), new ProtoPropertyDecorator(authorizableIdProperty), new ProtoPropertyDecorator(authorizableCategory)]
+ return GroovySpy(AuthorizableProtoNodeDecorator, constructorArgs: [nodeBuilder.build(), properties], configuration)
+ }
+
+
+ def "Throws an InsufficientGrabbitPrivilegeException if JVM permissions are not present"() {
+ when:
+ final protoNodeDecorator = theProtoNodeDecorator(false, false, false) {
+ it.getSecurityManager() >> Mock(SecurityManager) {
+ it.checkPermission(permission) >> {
+ throw new SecurityException()
+ }
+ }
+ }
+
+ protoNodeDecorator.writeToJcr(Mock(Session))
+
+ then:
+ thrown(InsufficientGrabbitPrivilegeException)
+
+ where:
+ permission << [new ReflectPermission('suppressAccessChecks'), new RuntimePermission('accessDeclaredMembers'), new RuntimePermission('accessClassInPackage.{org.apache.jackrabbit.oak.security.user}')]
+ }
+
+
+ def "Passes security check if all JVM permissions are present"() {
+ when:
+ final session = Mock(Session) {
+ it.getNode('authorizablePath') >> Mock(Node) {
+ getProperty(JCR_PRIMARYTYPE) >> Mock(Property) {
+ it.getString() >> 'nt:unstructured'
+ }
+ getProperties() >> Mock(PropertyIterator) {
+ it.toList() >> []
+ }
+ }
+ }
+ final protoNodeDecorator = theProtoNodeDecorator(false, false, false) {
+ it.getSecurityManager() >> Mock(SecurityManager) {
+ it.checkPermission(permission) >> {
+ return
+ }
+ }
+ it.getUserManager(session) >> Mock(UserManager) {
+ it.createGroup(_, _, _) >> Mock(Group) {
+ it.getPath() >> 'authorizablePath'
+ }
+ }
+ }
+
+ protoNodeDecorator.writeToJcr(session)
+
+ then:
+ notThrown(InsufficientGrabbitPrivilegeException)
+
+ where:
+ permission << [new ReflectPermission('suppressAccessChecks'), new RuntimePermission('accessDeclaredMembers'), new RuntimePermission('accessClassInPackage.{org.apache.jackrabbit.oak.security.user}')]
+ }
+
+
+ def "Passes security check if no SecurityManager is found on the JVM"() {
+ when:
+ final session = Mock(Session) {
+ it.getNode('authorizablePath') >> Mock(Node) {
+ getProperty(JCR_PRIMARYTYPE) >> Mock(Property) {
+ it.getString() >> 'nt:unstructured'
+ }
+ getProperties() >> Mock(PropertyIterator) {
+ it.toList() >> []
+ }
+ }
+ }
+ final protoNodeDecorator = theProtoNodeDecorator(false, false, false) {
+ it.getSecurityManager() >> null
+ it.getUserManager(session) >> Mock(UserManager) {
+ it.createGroup(_, _, _) >> Mock(Group) {
+ it.getPath() >> 'authorizablePath'
+ }
+ }
+ }
+
+ protoNodeDecorator.writeToJcr(session)
+
+ then:
+ notThrown(InsufficientGrabbitPrivilegeException)
+ }
+
+
+ def "getSecurityManager() will retrieve the JVM's security manager"() {
+ given:
+ final protoNodeDecorator = theProtoNodeDecorator(false, false, false)
+
+ expect:
+ System.getSecurityManager() == protoNodeDecorator.getSecurityManager()
+ }
+
+
+ def "writeToJcr() will create a new underlying User, and return it's node within a JcrNodeDecorator"(){
+ given:
+ final node = Mock(Node) {
+ getProperty(JCR_PRIMARYTYPE) >> Mock(Property) {
+ it.getString() >> 'nt:unstructured'
+ }
+ getProperties() >> Mock(PropertyIterator) {
+ it.toList() >> []
+ }
+ }
+ final session = Mock(Session) {
+ it.getNode('newUserPath') >> node
+ }
+ final newUser = Mock(User) {
+ 1 * it.disable('Reason for disabling')
+ 1 * it.setProperty('cq:authorizableCategory', new StringValue('mcm'))
+ it.getPath() >> 'newUserPath'
+ }
+ final protoNodeDecorator = theProtoNodeDecorator(true, false, false) {
+ it.getName() >> '/home/users/auth_folder/user'
+ it.getSecurityManager() >> null
+ it.setPasswordForUser(newUser, session) >> {
+ return
+ }
+ it.getUserManager(session) >> Mock(UserManager) {
+ it.getAuthorizable('authorizableID') >> null
+ 1 * it.createUser('authorizableID', _, new AuthorizablePrincipal('authorizableID'), '/home/users/auth_folder') >> newUser
+ }
+ }
+
+ when:
+ final userNode = protoNodeDecorator.writeToJcr(session)
+
+ then:
+ userNode.getInnerNode() == node
+ }
+
+
+ def "writeToJcr() will create a new underlying Group, and return it's node within a JcrNodeDecorator"(){
+ given:
+ final node = Mock(Node) {
+ getProperty(JCR_PRIMARYTYPE) >> Mock(Property) {
+ it.getString() >> 'nt:unstructured'
+ }
+ getProperties() >> Mock(PropertyIterator) {
+ it.toList() >> []
+ }
+ }
+ final session = Mock(Session) {
+ it.getNode('newGroupPath') >> node
+ }
+ final newGroup = Mock(Group) {
+ it.getPath() >> 'newGroupPath'
+ }
+ final protoNodeDecorator = theProtoNodeDecorator(false, false, false) {
+ it.getName() >> '/home/groups/auth_folder/group'
+ it.getSecurityManager() >> null
+ it.getUserManager(session) >> Mock(UserManager) {
+ it.getAuthorizable('authorizableID') >> null
+ 1 * it.createGroup('authorizableID', new AuthorizablePrincipal('authorizableID'), '/home/groups/auth_folder') >> newGroup
+ }
+ }
+
+ when:
+ final groupNode = protoNodeDecorator.writeToJcr(session)
+
+ then:
+ groupNode.getInnerNode() == node
+ }
+
+ @Unroll
+ def "Updates profile on an authorizable if it exists. Exists: #exists"() {
+ when:
+ final node = Mock(Node) {
+ getProperty(JCR_PRIMARYTYPE) >> Mock(Property) {
+ it.getString() >> 'rep:User'
+ }
+ getProperties() >> Mock(PropertyIterator) {
+ it.toList() >> []
+ }
+ }
+ final session = Mock(Session) {
+ it.getNode('/home/users/u/newuser') >> node
+ }
+ final protoNodeDecorator = theProtoNodeDecorator(false, exists, false) {
+ it.getSecurityManager() >> null
+ it.getUserManager(session) >> Mock(UserManager) {
+ it.getAuthorizable('authorizableID') >> Mock(Authorizable)
+ it.createGroup('authorizableID', _, _) >> Mock(Group) {
+ it.getPath() >> '/home/users/u/newuser'
+ }
+ }
+ (exists ? 1 : 0) * it.createFrom(_ as ProtoNode, '/home/users/u/newuser/profile') >> Mock(ProtoNodeDecorator)
+ }
+
+ then:
+ protoNodeDecorator.writeToJcr(session)
+
+
+ where:
+ exists << [false, true]
+ }
+
+ @Unroll
+ def "Updates preferences on an authorizable if it exists. Exists: #exists"() {
+ when:
+ final node = Mock(Node) {
+ getProperty(JCR_PRIMARYTYPE) >> Mock(Property) {
+ it.getString() >> 'rep:User'
+ }
+ getProperties() >> Mock(PropertyIterator) {
+ it.toList() >> []
+ }
+ }
+ final session = Mock(Session) {
+ it.getNode('/home/users/u/newuser') >> node
+ }
+ final protoNodeDecorator = theProtoNodeDecorator(false, false, exists) {
+ it.getSecurityManager() >> null
+ it.getUserManager(session) >> Mock(UserManager) {
+ it.getAuthorizable('authorizableID') >> Mock(Authorizable)
+ it.createGroup('authorizableID', _, _) >> Mock(Group) {
+ it.getPath() >> '/home/users/u/newuser'
+ }
+ }
+ (exists ? 1 : 0) * it.createFrom(_ as ProtoNode, '/home/users/u/newuser/preferences') >> Mock(ProtoNodeDecorator)
+ }
+
+ then:
+ protoNodeDecorator.writeToJcr(session)
+
+
+ where:
+ exists << [false, true]
+ }
+}
diff --git a/src/test/groovy/com/twcable/grabbit/jcr/DefaultProtoNodeDecoratorSpec.groovy b/src/test/groovy/com/twcable/grabbit/jcr/DefaultProtoNodeDecoratorSpec.groovy
new file mode 100644
index 0000000..2724778
--- /dev/null
+++ b/src/test/groovy/com/twcable/grabbit/jcr/DefaultProtoNodeDecoratorSpec.groovy
@@ -0,0 +1,145 @@
+/*
+ * Copyright 2015 Time Warner Cable, Inc.
+ *
+ * Licensed 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 com.twcable.grabbit.jcr
+
+import com.day.cq.commons.jcr.JcrConstants
+import com.twcable.grabbit.proto.NodeProtos
+import com.twcable.grabbit.proto.NodeProtos.Node as ProtoNode
+import com.twcable.grabbit.proto.NodeProtos.Node.Builder as ProtoNodeBuilder
+import com.twcable.grabbit.proto.NodeProtos.Property as ProtoProperty
+import spock.lang.Specification
+
+import javax.jcr.Node
+import javax.jcr.Property
+import javax.jcr.PropertyIterator
+import javax.jcr.Session
+
+import static javax.jcr.PropertyType.STRING
+import static org.apache.jackrabbit.JcrConstants.JCR_MIXINTYPES
+import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE
+
+
+class DefaultProtoNodeDecoratorSpec extends Specification {
+
+ ProtoNode decoratedProtoNode
+ ProtoProperty mixinProperty
+ ProtoProperty someOtherProperty
+
+ def setup() {
+ ProtoNodeBuilder nodeBuilder = ProtoNode.newBuilder()
+ nodeBuilder.setName("somenode")
+
+ ProtoProperty primaryTypeProperty = NodeProtos.Property
+ .newBuilder()
+ .setName(JCR_PRIMARYTYPE)
+ .setType(STRING)
+ .setMultiple(false)
+ .addValues(NodeProtos.Value.newBuilder().setStringValue(JcrConstants.NT_UNSTRUCTURED))
+ .build()
+ nodeBuilder.addProperties(primaryTypeProperty)
+
+ mixinProperty = NodeProtos.Property
+ .newBuilder()
+ .setName(JCR_MIXINTYPES)
+ .setType(STRING)
+ .setMultiple(true)
+ .addAllValues(
+ [
+ NodeProtos.Value.newBuilder().setStringValue("somemixintype").build(),
+ NodeProtos.Value.newBuilder().setStringValue("unwritablemixin").build()
+ ]
+ )
+ .build()
+ nodeBuilder.addProperties(mixinProperty)
+
+
+ someOtherProperty = NodeProtos.Property
+ .newBuilder()
+ .setName("someproperty")
+ .setType(STRING)
+ .setMultiple(false)
+ .addValues(NodeProtos.Value.newBuilder().setStringValue("somevalue"))
+ .build()
+ nodeBuilder.addProperties(someOtherProperty)
+
+ decoratedProtoNode = nodeBuilder.build()
+ }
+
+
+ def "Can get primary type"() {
+ when:
+ final protoNodeDecorator = DefaultProtoNodeDecorator.createFrom(decoratedProtoNode)
+
+ then:
+ protoNodeDecorator.getPrimaryType().getStringValue() == JcrConstants.NT_UNSTRUCTURED
+ }
+
+
+ def "can get mixin property"() {
+ given:
+ final protoNodeDecorator = DefaultProtoNodeDecorator.createFrom(decoratedProtoNode)
+
+ when:
+ final property = protoNodeDecorator.getMixinProperty()
+
+ then:
+ property.valuesCount == 2
+ property.name == JCR_MIXINTYPES
+ }
+
+
+ def "Can get just writable properties"() {
+ given:
+ final protoNodeDecorator = DefaultProtoNodeDecorator.createFrom(decoratedProtoNode)
+
+ when:
+ final Collection properties = protoNodeDecorator.getWritableProperties()
+
+ then:
+ properties.size() == 1
+ properties[0].stringValue == "somevalue"
+ }
+
+ def "Can write this ProtoNodeDecorator to the JCR"() {
+ given:
+ final session = Mock(Session)
+ final jcrNodeRepresentation = Mock(Node) {
+ canAddMixin('somemixintype') >> true
+ canAddMixin('unwritablemixin') >> false
+ 1 * addMixin('somemixintype')
+ getProperty(JCR_PRIMARYTYPE) >> Mock(Property) {
+ getString() >> 'nt:unstructured'
+ }
+ getProperties() >> Mock(PropertyIterator) {
+ toList() >> []
+ }
+ }
+ final protoPropertyDecorators = [
+ new ProtoPropertyDecorator(mixinProperty),
+ new ProtoPropertyDecorator(someOtherProperty)
+ ]
+ final protoNodeDecorator = Spy(DefaultProtoNodeDecorator, constructorArgs: [decoratedProtoNode, protoPropertyDecorators, null]) {
+ getOrCreateNode(session) >> {
+ return jcrNodeRepresentation
+ }
+ }
+
+ final jcrNodeDecorator = protoNodeDecorator.writeToJcr(session)
+
+ expect:
+ jcrNodeDecorator != null
+ }
+}
diff --git a/src/test/groovy/com/twcable/grabbit/jcr/JCRNodeDecoratorSpec.groovy b/src/test/groovy/com/twcable/grabbit/jcr/JCRNodeDecoratorSpec.groovy
index 6b5904e..b83b375 100644
--- a/src/test/groovy/com/twcable/grabbit/jcr/JCRNodeDecoratorSpec.groovy
+++ b/src/test/groovy/com/twcable/grabbit/jcr/JCRNodeDecoratorSpec.groovy
@@ -15,18 +15,28 @@
*/
package com.twcable.grabbit.jcr
-import com.day.cq.commons.jcr.JcrConstants
-import spock.lang.Shared
-import spock.lang.Specification
-
+import com.twcable.grabbit.proto.NodeProtos.Node as ProtoNode
+import javax.jcr.Binary
+import javax.jcr.ItemNotFoundException
import javax.jcr.Node
import javax.jcr.NodeIterator
import javax.jcr.Property
+import javax.jcr.PropertyIterator
import javax.jcr.RepositoryException
+import javax.jcr.Value
import javax.jcr.nodetype.ItemDefinition
import javax.jcr.nodetype.NodeDefinition
import javax.jcr.nodetype.NodeType
+import javax.jcr.nodetype.PropertyDefinition
+import org.apache.jackrabbit.commons.iterator.PropertyIteratorAdapter
+import spock.lang.Shared
+import spock.lang.Specification
+import spock.lang.Unroll
+
+import static com.twcable.grabbit.jcr.JCRNodeDecorator.NoRootInclusionPolicy
+import static com.twcable.grabbit.testutil.StubInputStream.inputStream
+import static javax.jcr.PropertyType.BINARY
import static org.apache.jackrabbit.JcrConstants.JCR_CREATED
import static org.apache.jackrabbit.JcrConstants.JCR_LASTMODIFIED
import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE
@@ -46,93 +56,189 @@ class JCRNodeDecoratorSpec extends Specification {
def "null nodes are not allowed for JCRNodeDecorator construction"() {
when:
- new JcrNodeDecorator(null)
+ new JCRNodeDecorator(null)
then:
thrown(IllegalArgumentException)
}
-
- def "setLastModified() when last modified can be set"() {
+ def "toProtoNode()"() {
given:
Node node = Mock(Node) {
+ getPath() >> "/some/path"
getPrimaryNodeType() >> Mock(NodeType) {
- canSetProperty(JCR_LASTMODIFIED, _) >> { true }
+ getChildNodeDefinitions() >> [
+ Mock(NodeDefinition) {
+ isMandatory() >> true
+ }
+ ].toArray()
+ }
+ getNodes() >> Mock(NodeIterator) {
+ hasNext() >>> true >> false
+ next() >>
+ Mock(Node) {
+ getDefinition() >> Mock(NodeDefinition) {
+ isMandatory() >> true
+ }
+ getProperties() >> Mock(PropertyIterator) {
+ toList() >> []
+ }
+ getPath() >> "path"
+ getProperty(JCR_PRIMARYTYPE) >> Mock(Property) {
+ getString() >> "nt:unstructured"
+ }
+ getPrimaryNodeType() >> Mock(NodeType) {
+ getChildNodeDefinitions() >> [].toArray()
+ }
+ }
}
+ getProperty(JCR_PRIMARYTYPE) >> Mock(Property) {
+ getString() >> "rep:SystemUser"
+ }
+ getProperties() >> new PropertyIteratorAdapter(
+ [
+ Mock(Property) {
+ getName() >> JCR_PRIMARYTYPE
+ getString() >> "rep:SystemUser"
+ getDefinition() >> Mock(PropertyDefinition) {
+ isProtected() >> true
+ }
+ getType() >> BINARY
+ isMultiple() >> false
+ getValue() >> Mock(Value) {
+ getBinary() >> Mock(Binary) {
+ getStream() >> inputStream("test data")
+ }
+ }
+ },
+ Mock(Property) {
+ getName() >> JCR_LASTMODIFIED
+ getString() >> "lastModified"
+ getDefinition() >> Mock(PropertyDefinition) {
+ isProtected() >> false
+ }
+ getType() >> BINARY
+ isMultiple() >> false
+ getValue() >> Mock(Value) {
+ getBinary() >> Mock(Binary) {
+ getStream() >> inputStream("test data")
+ } }
+ },
+ Mock(Property) {
+ getName() >> "protectedProperty"
+ getString() >> "protectedPropertyValue"
+ getDefinition() >> Mock(PropertyDefinition) {
+ isProtected() >> true
+ }
+ getType() >> BINARY
+ isMultiple() >> false
+ getValue() >> Mock(Value) {
+ getBinary() >> Mock(Binary) {
+ getStream() >> inputStream("test data")
+ }
+ }
+ }
+ ].iterator()
+ )
}
when:
- final nodeDecorator = new JcrNodeDecorator(node)
- nodeDecorator.setLastModified()
+ final ProtoNode protoNode = new JCRNodeDecorator(node).toProtoNode()
then:
- 1 * node.setProperty(JCR_LASTMODIFIED, _)
- notThrown(RepositoryException)
+ protoNode.getName() == '/some/path'
+ protoNode.getPropertiesCount() == 1
}
- def "setLastModified() when last modified can not be set"() {
+ def "setLastModified() when last modified can be set"() {
given:
Node node = Mock(Node) {
getPrimaryNodeType() >> Mock(NodeType) {
- canSetProperty(JCR_LASTMODIFIED, _) >> { false }
+ canSetProperty(JCR_LASTMODIFIED, _) >> { true }
+ }
+ getProperty(JCR_PRIMARYTYPE) >> Mock(Property) {
+ getString() >> 'nt:unstructured'
+ }
+ getProperties() >> Mock(PropertyIterator) {
+ toList() >> []
}
}
when:
- final nodeDecorator = new JcrNodeDecorator(node)
+ final nodeDecorator = new JCRNodeDecorator(node)
nodeDecorator.setLastModified()
then:
- 0 * node.setProperty(JCR_LASTMODIFIED, _)
+ 1 * node.setProperty(JCR_LASTMODIFIED, _)
notThrown(RepositoryException)
}
- def "During setLastModified() when something goes wrong with getPrimaryNodeType() we handle this case gracefully"() {
+ def "setLastModified() when last modified can not be set"() {
given:
Node node = Mock(Node) {
- getPrimaryNodeType() >> { throw new RepositoryException() }
+ getPrimaryNodeType() >> Mock(NodeType) {
+ canSetProperty(JCR_LASTMODIFIED, _) >> { false }
+ }
+ getProperty(JCR_PRIMARYTYPE) >> Mock(Property) {
+ getString() >> 'nt:unstructured'
+ }
+ getProperties() >> Mock(PropertyIterator) {
+ toList() >> []
+ }
}
when:
- final nodeDecorator = new JcrNodeDecorator(node)
+ final nodeDecorator = new JCRNodeDecorator(node)
nodeDecorator.setLastModified()
then:
+ 0 * node.setProperty(JCR_LASTMODIFIED, _)
notThrown(RepositoryException)
}
- def "getPrimaryType()"() {
+ def "During setLastModified() when something goes wrong with getPrimaryNodeType() we handle this case gracefully"() {
given:
Node node = Mock(Node) {
+ getPrimaryNodeType() >> { throw new RepositoryException() }
getProperty(JCR_PRIMARYTYPE) >> Mock(Property) {
- getString() >> { JcrConstants.NT_FILE }
+ getString() >> 'nt:unstructured'
+ }
+ getProperties() >> Mock(PropertyIterator) {
+ toList() >> []
}
}
when:
- final nodeDecorator = new JcrNodeDecorator(node)
+ final nodeDecorator = new JCRNodeDecorator(node)
+ nodeDecorator.setLastModified()
then:
- nodeDecorator.getPrimaryType() == JcrConstants.NT_FILE
+ notThrown(RepositoryException)
}
- def "isRequiredNode()"() {
+ def "isMandatoryNode()"() {
given:
Node node = Mock(Node) {
getDefinition() >> Mock(NodeDefinition) {
isMandatory() >> isMandatory
}
+ getProperty(JCR_PRIMARYTYPE) >> Mock(Property) {
+ getString() >> 'nt:unstructured'
+ }
+ getProperties() >> Mock(PropertyIterator) {
+ toList() >> []
+ }
}
when:
- final nodeDecorator = new JcrNodeDecorator(node)
+ final nodeDecorator = new JCRNodeDecorator(node)
then:
- nodeDecorator.isRequiredNode() == isMandatory
+ nodeDecorator.isMandatoryNode() == isMandatory
where:
isMandatory << [true, false]
@@ -148,10 +254,16 @@ class JCRNodeDecoratorSpec extends Specification {
Mock(ItemDefinition) { isMandatory() >> secondDefinition } as NodeDefinition
]
}
+ getProperty(JCR_PRIMARYTYPE) >> Mock(Property) {
+ getString() >> 'nt:unstructured'
+ }
+ getProperties() >> Mock(PropertyIterator) {
+ toList() >> []
+ }
}
when:
- final nodeDecorator = new JcrNodeDecorator(node)
+ final nodeDecorator = new JCRNodeDecorator(node)
then:
nodeDecorator.hasMandatoryChildNodes() == hasMandatoryChildNodes
@@ -166,44 +278,132 @@ class JCRNodeDecoratorSpec extends Specification {
def "getRequiredChildNodes()"() {
given:
- Node node = Mock(Node) {
+ Node nodeWithMandatoryChildren = Mock(Node) {
getNodes() >> Mock(NodeIterator) {
hasNext() >>> true >> false
next() >> Mock(Node) {
+ getProperty(JCR_PRIMARYTYPE) >> Mock(Property) {
+ getString() >> 'nt:resource'
+ }
+ getProperties() >> Mock(PropertyIterator) {
+ toList() >> []
+ }
getDefinition() >> Mock(NodeDefinition) {
isMandatory() >> true
}
}
}
+ getProperty(JCR_PRIMARYTYPE) >> Mock(Property) {
+ getString() >> 'nt:file'
+ }
+ getProperties() >> Mock(PropertyIterator) {
+ toList() >> []
+ }
}
- when: "The node has children"
- final nodeDecorator = Spy(JcrNodeDecorator, constructorArgs: [node]) {
+ Node authorizableNode = Mock(Node) {
+ getProperty(JCR_PRIMARYTYPE) >> Mock(Property) {
+ getString() >> 'rep:User'
+ }
+ getProperties() >> Mock(PropertyIterator) {
+ toList() >> []
+ }
+ }
+
+ when: "The node has mandatory children"
+ final nodeDecorator = Spy(JCRNodeDecorator, constructorArgs: [nodeWithMandatoryChildren]) {
hasMandatoryChildNodes() >> true
+ isAuthorizableType() >> false
}
then:
nodeDecorator.getRequiredChildNodes().size() == 1
- and: "If no child nodes, getRequiredChildNodes() returns null"
+ and: "The node has authorizable pieces"
+
+ when:
+ final otherNodeDecorator = Spy(JCRNodeDecorator, constructorArgs: [authorizableNode]) {
+ hasMandatoryChildNodes() >> false
+ isAuthorizableType() >> true
+ getChildNodeIterator() >> Mock(Iterator) {
+ hasNext() >>> true >> true >> true >> true >> false
+ next() >>>
+ Mock(Node) {
+ getName() >> "/home/users/u/user/preferences"
+ getProperty(JCR_PRIMARYTYPE) >> Mock(Property) {
+ getString() >> 'nt:unstructured'
+ }
+ getProperty('sling:resourceType') >> Mock(Property) {
+ getString() >> 'cq:Preferences'
+ }
+ getProperties() >> Mock(PropertyIterator) {
+ toList() >> []
+ }
+ } >>
+ Mock(Node) {
+ getName() >> "/home/users/u/user/profile"
+ getProperty(JCR_PRIMARYTYPE) >> Mock(Property) {
+ getString() >> 'nt:unstructured'
+ }
+ getProperty('sling:resourceType') >> Mock(Property) {
+ getString() >> 'cq/security/components/profile'
+ }
+ getProperties() >> Mock(PropertyIterator) {
+ toList() >> []
+ }
+ } >>
+ Mock(Node) {
+ getName() >> "/home/users/u/user/.tokens"
+ getProperty(JCR_PRIMARYTYPE) >> Mock(Property) {
+ getString() >> 'rep:Unstructured'
+ }
+ getProperties() >> Mock(PropertyIterator) {
+ toList() >> []
+ }
+ } >>
+ Mock(Node) {
+ getName() >> "/home/users/u/user/rep:policy"
+ getProperty(JCR_PRIMARYTYPE) >> Mock(Property) {
+ getString() >> 'rep:ACL'
+ }
+ getProperties() >> Mock(PropertyIterator) {
+ toList() >> []
+ }
+ }
+ }
+ }
+
+ then:
+ otherNodeDecorator.getRequiredChildNodes().size() == 2
+
+ and: "If no child nodes, getRequiredChildNodes() returns an empty collection"
when:
- final otherNodeDecorator = Spy(JcrNodeDecorator, constructorArgs: [Mock(Node)]) {
+ final yetAnotherNodeDecorator = Spy(JCRNodeDecorator, constructorArgs: [nodeWithMandatoryChildren]) {
hasMandatoryChildNodes() >> false
+ isAuthorizableType() >> false
}
then:
- otherNodeDecorator.getRequiredChildNodes() == null
+ yetAnotherNodeDecorator.getRequiredChildNodes() == []
}
def "Can adapt the decorator back to the wrapped node"() {
given:
- final node = Mock(Node)
- final nodeDecorator = new JcrNodeDecorator(node)
+ final node = Mock(Node) {
+ getProperty(JCR_PRIMARYTYPE) >> Mock(Property) {
+ getString() >> 'nt:unstructured'
+ }
+ getProperties() >> Mock(PropertyIterator) {
+ toList() >> []
+ }
+ }
+ final nodeDecorator = new JCRNodeDecorator(node)
expect:
(nodeDecorator as Node) == node
+ (nodeDecorator as JCRNodeDecorator) == nodeDecorator
}
def "Get modified date for a node"() {
@@ -221,8 +421,14 @@ class JCRNodeDecoratorSpec extends Specification {
getProperty(JCR_CREATED) >> Mock(Property) {
getDate() >> jcrCreatedDate
}
+ getProperty(JCR_PRIMARYTYPE) >> Mock(Property) {
+ getString() >> 'nt:unstructured'
+ }
+ getProperties() >> Mock(PropertyIterator) {
+ toList() >> []
+ }
}
- final nodeDecorator = new JcrNodeDecorator(node)
+ final nodeDecorator = new JCRNodeDecorator(node)
expect:
nodeDecorator.getModifiedOrCreatedDate() == modifiedDate
@@ -234,4 +440,189 @@ class JCRNodeDecoratorSpec extends Specification {
false | false | true | jcrCreatedDate.time
false | false | false | null
}
+
+ def "isAuthorizablePart()"() {
+ given:
+ final nodeThatIsPart = Mock(Node) {
+ getProperties() >> Mock(PropertyIterator) {
+ toList() >> []
+ }
+ getParent() >> Mock(Node) {
+ getProperties() >> Mock(PropertyIterator) {
+ toList() >> []
+ }
+ getProperty(JCR_PRIMARYTYPE) >> Mock(Property) {
+ getString() >> "nt:unstructured"
+ }
+ getParent() >> Mock(Node) {
+ getProperties() >> Mock(PropertyIterator) {
+ toList() >> []
+ }
+ getProperty(JCR_PRIMARYTYPE) >> Mock(Property) {
+ getString() >> "rep:User"
+ }
+ }
+ }
+ }
+
+ final nodeThatIsNotPart = Mock(Node) {
+ getProperties() >> Mock(PropertyIterator) {
+ toList() >> []
+ }
+ getParent() >> Mock(Node) {
+ getProperties() >> Mock(PropertyIterator) {
+ toList() >> []
+ }
+ getProperty(JCR_PRIMARYTYPE) >> Mock(Property) {
+ getString() >> "nt:unstructured"
+ }
+ getParent() >> { throw new ItemNotFoundException() }
+ }
+ }
+
+ when:
+ final JCRNodeDecorator jcrNodeDecorator = new JCRNodeDecorator(nodeThatIsPart)
+
+ then:
+ jcrNodeDecorator.isAuthorizablePart()
+
+ and: "!isAuthorizablePart()"
+
+ when:
+ final JCRNodeDecorator jcrNodeDecoratorTwo = new JCRNodeDecorator(nodeThatIsNotPart)
+
+ then:
+ !jcrNodeDecoratorTwo.isAuthorizablePart()
+ }
+
+ @Unroll
+ def "isAuthorizableType() for primary type #primaryType is expected #expected"() {
+ given:
+ final node = Mock(Node) {
+ getProperty(JCR_PRIMARYTYPE) >> Mock(Property) {
+ getString() >> primaryType
+ }
+ getProperties() >> Mock(PropertyIterator) {
+ toList() >> []
+ }
+ }
+
+ when:
+ final JCRNodeDecorator jcrNodeDecorator = new JCRNodeDecorator(node)
+
+ then:
+ jcrNodeDecorator.isAuthorizableType() == expected
+
+ where:
+ primaryType | expected
+ 'rep:User' | true
+ 'rep:Group' | true
+ 'unknown' | false
+ }
+
+
+ @Unroll
+ def "isLoginToken() for primary type #primaryType and name #name is expected #expected"() {
+ given:
+ final node = Mock(Node) {
+ getProperty(JCR_PRIMARYTYPE) >> Mock(Property) {
+ getString() >> primaryType
+ }
+ getName() >> name
+ getProperties() >> Mock(PropertyIterator) {
+ toList() >> []
+ }
+ }
+
+ when:
+ final JCRNodeDecorator jcrNodeDecorator = new JCRNodeDecorator(node)
+
+ then:
+ jcrNodeDecorator.isLoginToken() == expected
+
+ where:
+ primaryType | name | expected
+ 'rep:Unstructured' | '/home/users/u/user/.tokens' | true
+ 'nt:unstructured' | '/home/users/u/user/.tokens' | false
+ 'rep:Unstructured' | '/home/users/u/user/other' | false
+ 'rep:Token' | '/home/users/u/user/.tokens/token' | true
+ 'unknown' | 'unknown' | false
+ }
+
+
+ def "equals()"() {
+ given:
+ final JCRNodeDecorator decoratorOne = new JCRNodeDecorator(Mock(Node){
+ getProperties() >> Mock(PropertyIterator) {
+ toList() >> []
+ }
+ getName() >> 'decoratorOne'
+ })
+
+ final JCRNodeDecorator otherDecoratorOneInstance = new JCRNodeDecorator(Mock(Node){
+ getProperties() >> Mock(PropertyIterator) {
+ toList() >> []
+ }
+ getName() >> 'decoratorOne'
+ })
+
+ final JCRNodeDecorator decoratorTwo = new JCRNodeDecorator(Mock(Node){
+ getProperties() >> Mock(PropertyIterator) {
+ toList() >> []
+ }
+ getName() >> 'decoratorTwo'
+ })
+
+ expect:
+ decoratorOne.equals(decoratorOne)
+ decoratorOne.equals(otherDecoratorOneInstance)
+ !decoratorOne.equals(decoratorTwo)
+ !decoratorOne.equals(Mock(Node))
+ }
+
+
+ def "NoRootInclusionPolicy behavior"() {
+ given:
+ final rootNode = Mock(Node) {
+ getName() >> "/path/root"
+ getProperties() >> Mock(PropertyIterator) {
+ toList() >> []
+ }
+ }
+
+ when:
+ final NoRootInclusionPolicy policy = new NoRootInclusionPolicy(rootNode)
+
+ then:
+ policy.include(node) == expectedValue
+
+ where:
+ node << [
+ Mock(Node) {
+ getName() >> "/path/root"
+ getProperties() >> Mock(PropertyIterator) {
+ toList() >> []
+ }
+ },
+ Mock(Node) {
+ getName() >> "/path/root/node"
+ getDefinition() >> Mock(NodeDefinition) {
+ isMandatory() >> false
+ }
+ getProperties() >> Mock(PropertyIterator) {
+ toList() >> []
+ }
+ },
+ Mock(Node) {
+ getName() >> "/path/root/node"
+ getDefinition() >> Mock(NodeDefinition) {
+ isMandatory() >> true
+ }
+ getProperties() >> Mock(PropertyIterator) {
+ toList() >> []
+ }
+ }
+ ]
+ expectedValue << [false, true, false]
+ }
}
diff --git a/src/test/groovy/com/twcable/grabbit/jcr/JcrPropertyDecoratorSpec.groovy b/src/test/groovy/com/twcable/grabbit/jcr/JcrPropertyDecoratorSpec.groovy
index b2c110b..ca7e326 100644
--- a/src/test/groovy/com/twcable/grabbit/jcr/JcrPropertyDecoratorSpec.groovy
+++ b/src/test/groovy/com/twcable/grabbit/jcr/JcrPropertyDecoratorSpec.groovy
@@ -16,10 +16,11 @@
package com.twcable.grabbit.jcr
-import spock.lang.Specification
-
import javax.jcr.Property
import javax.jcr.nodetype.PropertyDefinition
+import spock.lang.Specification
+import spock.lang.Unroll
+
import static org.apache.jackrabbit.JcrConstants.JCR_LASTMODIFIED
import static org.apache.jackrabbit.JcrConstants.JCR_MIXINTYPES
@@ -28,6 +29,7 @@ import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE
@SuppressWarnings("GroovyAssignabilityCheck")
class JcrPropertyDecoratorSpec extends Specification {
+ @Unroll
def "check if property is transferable"() {
given:
Property property = Mock(Property) {
@@ -36,19 +38,26 @@ class JcrPropertyDecoratorSpec extends Specification {
isProtected() >> protectedFlag
}
}
+ final nodeOwner = Mock(JCRNodeDecorator) {
+ isAuthorizableType() >> authorizableType
+ }
when:
- final propertyDecorator = new JcrPropertyDecorator(property)
+ final propertyDecorator = new JcrPropertyDecorator(property, nodeOwner)
then:
expectedOutput == propertyDecorator.isTransferable()
where:
- propertyName | protectedFlag | expectedOutput
- JCR_LASTMODIFIED | true | false
- JCR_PRIMARYTYPE | false | true
- JCR_MIXINTYPES | false | true
- "otherProperty" | true | false
- "otherProperty" | false | true
+ propertyName | protectedFlag | expectedOutput | authorizableType
+ JCR_LASTMODIFIED | true | false | false
+ JCR_PRIMARYTYPE | false | true | false
+ JCR_MIXINTYPES | false | true | false
+ 'otherProperty' | true | false | false
+ 'otherProperty' | false | true | false
+ 'protectedProperty' | true | true | true
+ 'protectedProperty' | true | true | true
+
+
}
}
diff --git a/src/test/groovy/com/twcable/grabbit/jcr/ProtoNodeDecoratorSpec.groovy b/src/test/groovy/com/twcable/grabbit/jcr/ProtoNodeDecoratorSpec.groovy
index 4b1c3dd..3ce6735 100644
--- a/src/test/groovy/com/twcable/grabbit/jcr/ProtoNodeDecoratorSpec.groovy
+++ b/src/test/groovy/com/twcable/grabbit/jcr/ProtoNodeDecoratorSpec.groovy
@@ -15,146 +15,80 @@
*/
package com.twcable.grabbit.jcr
-import com.day.cq.commons.jcr.JcrConstants
+import com.twcable.grabbit.proto.NodeProtos
import com.twcable.grabbit.proto.NodeProtos.Node as ProtoNode
-
-/*
- * Copyright 2015 Time Warner Cable, Inc.
- *
- * Licensed 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.
- */
import com.twcable.grabbit.proto.NodeProtos.Node.Builder as ProtoNodeBuilder
-import com.twcable.grabbit.proto.NodeProtos.Property as ProtoProperty
-import com.twcable.grabbit.proto.NodeProtos.Value as ProtoValue
import spock.lang.Specification
-import javax.jcr.Node
-import javax.jcr.Session
import static javax.jcr.PropertyType.STRING
-import static org.apache.jackrabbit.JcrConstants.JCR_MIXINTYPES
import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE
@SuppressWarnings("GroovyAccessibility")
class ProtoNodeDecoratorSpec extends Specification {
- ProtoNode decoratedProtoNode
+ def "ProtoNodeDecorator can not be constructed with a null ProtoNode"() {
+ when:
+ ProtoNodeDecorator.createFrom(null)
+ then:
+ thrown(IllegalArgumentException)
+ }
- def setup() {
- ProtoNodeBuilder nodeBuilder = ProtoNode.newBuilder()
- nodeBuilder.setName("somenode")
- ProtoProperty primaryTypeProperty = ProtoProperty
+ def "Can create a regular DefaultProtoNodeDecorator"() {
+ given:
+ ProtoNodeBuilder nodeBuilder = ProtoNode.newBuilder()
+ nodeBuilder.setName("user")
+ NodeProtos.Property primaryTypeProperty = NodeProtos.Property
.newBuilder()
.setName(JCR_PRIMARYTYPE)
.setType(STRING)
.setMultiple(false)
- .addValues(ProtoValue.newBuilder().setStringValue(JcrConstants.NT_UNSTRUCTURED))
+ .addValues(NodeProtos.Value.newBuilder().setStringValue('nt:unstructured'))
.build()
nodeBuilder.addProperties(primaryTypeProperty)
+ final protoNodeDecorator = ProtoNodeDecorator.createFrom(nodeBuilder.build())
- ProtoProperty mixinTypeProperty = ProtoProperty
- .newBuilder()
- .setName(JCR_MIXINTYPES)
- .setType(STRING)
- .setMultiple(true)
- .addAllValues(
- [
- ProtoValue.newBuilder().setStringValue("somemixintype").build(),
- ProtoValue.newBuilder().setStringValue("unwritablemixin").build()
- ]
- )
- .build()
- nodeBuilder.addProperties(mixinTypeProperty)
+ expect:
+ protoNodeDecorator instanceof DefaultProtoNodeDecorator
+ }
- ProtoProperty someOtherProperty = ProtoProperty
+ def "Can create an AuthorizableProtoNodeDecorator with wrapped User node"() {
+ given:
+ NodeProtos.Node.Builder nodeBuilder = NodeProtos.Node.newBuilder()
+ nodeBuilder.setName("user")
+ NodeProtos.Property userProperty = NodeProtos.Property
.newBuilder()
- .setName("someproperty")
+ .setName(JCR_PRIMARYTYPE)
.setType(STRING)
.setMultiple(false)
- .addValues(ProtoValue.newBuilder().setStringValue("somevalue"))
+ .addValues(NodeProtos.Value.newBuilder().setStringValue('rep:User'))
.build()
- nodeBuilder.addProperties(someOtherProperty)
+ nodeBuilder.addProperties(userProperty)
+ final protoNodeDecorator = DefaultProtoNodeDecorator.createFrom(nodeBuilder.build())
- decoratedProtoNode = nodeBuilder.build()
+ expect:
+ protoNodeDecorator instanceof AuthorizableProtoNodeDecorator
}
- def "ProtoNodeDecorator can not be constructed with a null ProtoNode"() {
- when:
- new ProtoNodeDecorator(null)
-
- then:
- thrown(IllegalArgumentException)
- }
-
-
- def "Can get primary type"() {
- when:
- final protoNodeDecorator = new ProtoNodeDecorator(decoratedProtoNode)
-
- then:
- protoNodeDecorator.getPrimaryType() == JcrConstants.NT_UNSTRUCTURED
- }
-
-
- def "can get mixin property"() {
- given:
- final protoNodeDecorator = new ProtoNodeDecorator(decoratedProtoNode)
-
- when:
- final property = protoNodeDecorator.getMixinProperty()
-
- then:
- property.valuesCount == 2
- property.name == JCR_MIXINTYPES
- }
-
-
- def "Can get just writable properties"() {
+ def "Can create an AuthorizableProtoNodeDecorator with wrapped Group node"() {
given:
- final protoNodeDecorator = new ProtoNodeDecorator(decoratedProtoNode)
-
- when:
- final properties = protoNodeDecorator.getWritableProperties()
-
- then:
- properties.size() == 1
- properties[0].value.stringValue == "somevalue"
- }
-
-
- def "Can write the decorated node to the JCR"() {
- given:
- final session = Mock(Session)
- final node = Mock(Node) {
- canAddMixin("somemixintype") >> { true }
- canAddMixin("unwritablemixin") >> { false }
- }
-
- final protoNodeDecorator = Spy(ProtoNodeDecorator, constructorArgs: [decoratedProtoNode]) {
- getOrCreateNode(session) >> { node }
- }
-
- when:
- protoNodeDecorator.writeToJcr(session)
+ NodeProtos.Node.Builder nodeBuilder = NodeProtos.Node.newBuilder()
+ nodeBuilder.setName("group")
+ NodeProtos.Property groupProperty = NodeProtos.Property
+ .newBuilder()
+ .setName(JCR_PRIMARYTYPE)
+ .setType(STRING)
+ .setMultiple(false)
+ .addValues(NodeProtos.Value.newBuilder().setStringValue('rep:Group'))
+ .build()
+ nodeBuilder.addProperties(groupProperty)
+ final protoNodeDecorator = DefaultProtoNodeDecorator.createFrom(nodeBuilder.build())
- then:
- //Only one mixin should be valid
- 1 * node.addMixin("somemixintype")
- //Only one other property that needs to be written
- 1 * node.setProperty(_, _, _)
+ expect:
+ protoNodeDecorator instanceof AuthorizableProtoNodeDecorator
}
}
diff --git a/src/test/groovy/com/twcable/grabbit/security/AuthorizablePrincipalSpec.groovy b/src/test/groovy/com/twcable/grabbit/security/AuthorizablePrincipalSpec.groovy
new file mode 100644
index 0000000..abc9b16
--- /dev/null
+++ b/src/test/groovy/com/twcable/grabbit/security/AuthorizablePrincipalSpec.groovy
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2015 Time Warner Cable, Inc.
+ *
+ * Licensed 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 com.twcable.grabbit.security
+
+import spock.lang.Specification
+
+class AuthorizablePrincipalSpec extends Specification {
+
+ def "Can get the name of an AuthorizablePrincipal"() {
+ given:
+ final authorizablePrincipal = new AuthorizablePrincipal('principalName')
+
+ expect:
+ authorizablePrincipal.getName() == 'principalName'
+ }
+
+ def "One AuthorizablePrincipal is equal to another"() {
+ expect:
+ assert new AuthorizablePrincipal('one').equals(new AuthorizablePrincipal('one'))
+ assert !(new AuthorizablePrincipal('two').equals(new AuthorizablePrincipal('three')))
+ assert !(new AuthorizablePrincipal('two').equals(null))
+ }
+}
diff --git a/src/test/groovy/com/twcable/grabbit/server/batch/steps/jcrnodes/JcrNodesProcessorSpec.groovy b/src/test/groovy/com/twcable/grabbit/server/batch/steps/jcrnodes/JcrNodesProcessorSpec.groovy
index e951d42..780ee67 100644
--- a/src/test/groovy/com/twcable/grabbit/server/batch/steps/jcrnodes/JcrNodesProcessorSpec.groovy
+++ b/src/test/groovy/com/twcable/grabbit/server/batch/steps/jcrnodes/JcrNodesProcessorSpec.groovy
@@ -19,12 +19,14 @@ package com.twcable.grabbit.server.batch.steps.jcrnodes
import com.twcable.grabbit.proto.NodeProtos
import com.twcable.jackalope.NodeBuilder as FakeNodeBuilder
import com.twcable.jackalope.impl.jcr.ValueImpl
+import javax.jcr.ItemNotFoundException
import org.apache.jackrabbit.JcrConstants
import org.apache.jackrabbit.commons.iterator.NodeIteratorAdapter
import org.apache.jackrabbit.commons.iterator.PropertyIteratorAdapter
import spock.lang.Specification
import spock.lang.Subject
import javax.jcr.Node as JcrNode
+import javax.jcr.Property
import javax.jcr.Property as JcrProperty
import javax.jcr.PropertyIterator
import javax.jcr.nodetype.NodeDefinition
@@ -39,6 +41,8 @@ import javax.jcr.NodeIterator
import spock.lang.Shared
import spock.lang.Unroll
+import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE
+
@Subject(JcrNodesProcessor)
class JcrNodesProcessorSpec extends Specification {
@@ -157,6 +161,10 @@ class JcrNodesProcessorSpec extends Specification {
getPrimaryNodeType() >> Mock(NodeType) {
getChildNodeDefinitions() >> childDefinitions.toArray()
}
+ getProperty(JCR_PRIMARYTYPE) >> Mock(Property) {
+ getString() >> primaryType
+ }
+ getParent() >> { throw new ItemNotFoundException() }
}
children.each { JcrNode child ->
@@ -201,6 +209,7 @@ class JcrNodesProcessorSpec extends Specification {
JcrNode node = Mock(JcrNode) { //mocks a parent with updated lastModified property
getPath() >> "testParent"
+ getParent() >> { throw new ItemNotFoundException() }
getProperties() >> propertyIterator(primaryTypeProperty(JcrConstants.NT_FILE))
hasProperty(JcrConstants.JCR_LASTMODIFIED) >> true
getProperty(JcrConstants.JCR_LASTMODIFIED) >> Mock(JcrProperty){
@@ -214,6 +223,9 @@ class JcrNodesProcessorSpec extends Specification {
isMandatory() >> true //has mandatory child
}
}
+ getProperty(JCR_PRIMARYTYPE) >> Mock(Property) {
+ getString() >> 'nt:file'
+ }
getNodes() >> Mock(NodeIterator) {
hasNext() >>> true >> false
@@ -228,6 +240,9 @@ class JcrNodesProcessorSpec extends Specification {
isMandatory() >> false //has no children
}
}
+ getProperty(JCR_PRIMARYTYPE) >> Mock(Property) {
+ getString() >> 'nt:unstructured'
+ }
}
}
}
diff --git a/src/test/groovy/com/twcable/grabbit/server/services/RootNodeWithMandatoryIteratorSpec.groovy b/src/test/groovy/com/twcable/grabbit/server/services/RootNodeWithMandatoryIteratorSpec.groovy
index d35c857..79b64bc 100644
--- a/src/test/groovy/com/twcable/grabbit/server/services/RootNodeWithMandatoryIteratorSpec.groovy
+++ b/src/test/groovy/com/twcable/grabbit/server/services/RootNodeWithMandatoryIteratorSpec.groovy
@@ -23,11 +23,14 @@ import spock.lang.Subject
import javax.jcr.Node
import javax.jcr.Node as JcrNode
import javax.jcr.NodeIterator
+import javax.jcr.Property
+import javax.jcr.PropertyIterator
import javax.jcr.nodetype.NodeDefinition
import javax.jcr.nodetype.NodeType
import static com.twcable.jackalope.JCRBuilder.node
import static com.twcable.jackalope.JCRBuilder.property
+import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE
@Subject(RootNodeWithMandatoryIterator)
class RootNodeWithMandatoryIteratorSpec extends Specification {
@@ -89,6 +92,12 @@ class RootNodeWithMandatoryIteratorSpec extends Specification {
isMandatory() >> true
}
getPrimaryNodeType() >> nodeTypeNoMandatory
+ getProperty(JCR_PRIMARYTYPE) >> Mock(Property) {
+ getString() >> 'nt:unstructured'
+ }
+ getProperties() >> Mock(PropertyIterator) {
+ toList() >> []
+ }
}
final child1 = Mock(Node) {
@@ -100,12 +109,24 @@ class RootNodeWithMandatoryIteratorSpec extends Specification {
hasNext() >>> [true, false]
next() >> child3
}
+ getProperty(JCR_PRIMARYTYPE) >> Mock(Property) {
+ getString() >> 'nt:unstructured'
+ }
+ getProperties() >> Mock(PropertyIterator) {
+ toList() >> []
+ }
}
final child2 = Mock(Node) {
getDefinition() >> Mock(NodeDefinition) {
isMandatory() >> false
}
+ getProperty(JCR_PRIMARYTYPE) >> Mock(Property) {
+ getString() >> 'nt:unstructured'
+ }
+ getProperties() >> Mock(PropertyIterator) {
+ toList() >> []
+ }
}
final rootNode = Mock(Node) {
@@ -114,6 +135,12 @@ class RootNodeWithMandatoryIteratorSpec extends Specification {
next() >>> [child1, child2]
}
getPrimaryNodeType() >> nodeTypeWithMandatory
+ getProperty(JCR_PRIMARYTYPE) >> Mock(Property) {
+ getString() >> 'nt:unstructured'
+ }
+ getProperties() >> Mock(PropertyIterator) {
+ toList() >> []
+ }
}
when:
diff --git a/src/test/groovy/com/twcable/grabbit/testutil/StubInputStream.groovy b/src/test/groovy/com/twcable/grabbit/testutil/StubInputStream.groovy
new file mode 100644
index 0000000..00ce366
--- /dev/null
+++ b/src/test/groovy/com/twcable/grabbit/testutil/StubInputStream.groovy
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2015 Time Warner Cable, Inc.
+ *
+ * Licensed 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 com.twcable.grabbit.testutil
+
+import javax.annotation.Nonnull
+import javax.servlet.ServletInputStream
+
+class StubInputStream extends ServletInputStream {
+
+
+ private final int byte_length
+ private final byte[] bytes
+ private int byte_index = 0
+
+ private StubInputStream(@Nonnull final String data) {
+ bytes = data as byte[]
+ byte_length = bytes.length
+ }
+
+ private StubInputStream(@Nonnull final File fromFile) {
+ bytes = fromFile.readBytes()
+ byte_length = bytes.length
+ }
+
+ static InputStream inputStream(@Nonnull final String data) {
+ return new StubInputStream(data)
+ }
+
+ static InputStream inputStream(@Nonnull final File fromFile) {
+ return new StubInputStream(fromFile)
+ }
+
+ @Override
+ int read() throws IOException {
+ if (byte_index <= byte_length - 1) {
+ final thisByte = bytes[byte_index] as int
+ byte_index++
+ return thisByte
+ }
+ return -1
+ }
+}