Skip to content

Commit b01b3ce

Browse files
author
Jeff Bornemann
committed
More progress
1 parent 875b576 commit b01b3ce

11 files changed

Lines changed: 317 additions & 70 deletions

src/main/groovy/com/twcable/grabbit/client/batch/steps/jcrnodes/JcrNodesWriter.groovy

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -90,12 +90,6 @@ class JcrNodesWriter implements ItemWriter<ProtoNode>, ItemWriteListener {
9090
private static void writeToJcr(ProtoNode nodeProto, Session session) {
9191
JcrNodeDecorator jcrNode = ProtoNodeDecorator.createFrom(nodeProto).writeToJcr(session)
9292
jcrNode.setLastModified()
93-
// This will processed all mandatory child nodes only
94-
if(nodeProto.mandatoryChildNodeList && nodeProto.mandatoryChildNodeList.size() > 0) {
95-
for(ProtoNode childNode: nodeProto.mandatoryChildNodeList) {
96-
writeToJcr(childNode, session)
97-
}
98-
}
9993
}
10094

10195
private Session theSession() {

src/main/groovy/com/twcable/grabbit/jcr/AuthorizableProtoNodeDecorator.groovy

Lines changed: 57 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,18 @@ import com.twcable.grabbit.security.AuthorizablePrincipal
55
import com.twcable.grabbit.security.InsufficientGrabbitPrivilegeException
66
import groovy.transform.CompileStatic
77
import groovy.util.logging.Slf4j
8+
import java.lang.reflect.Field
9+
import java.lang.reflect.Method
10+
import java.lang.reflect.ReflectPermission
11+
import javax.annotation.Nonnull
12+
import javax.jcr.Session
813
import org.apache.jackrabbit.api.security.user.Authorizable
14+
import org.apache.jackrabbit.api.security.user.Group
915
import org.apache.jackrabbit.api.security.user.User
1016
import org.apache.jackrabbit.api.security.user.UserManager
1117
import org.apache.jackrabbit.value.StringValue
1218
import org.apache.sling.jcr.base.util.AccessControlUtil
1319

14-
import javax.annotation.Nonnull
15-
import javax.jcr.Session
16-
import java.lang.reflect.Field
17-
import java.lang.reflect.Method
18-
import java.lang.reflect.ReflectPermission
19-
2020
/*
2121
* Copyright 2015 Time Warner Cable, Inc.
2222
*
@@ -59,9 +59,10 @@ class AuthorizableProtoNodeDecorator extends ProtoNodeDecorator {
5959
authorizable = createNewAuthorizable(session)
6060
}
6161
else {
62-
updateAuthorizable(authorizable, session)
62+
authorizable = updateAuthorizable(authorizable, session)
6363
}
64-
session.save()
64+
updateProfile(authorizable, session)
65+
updatePreferences(authorizable, session)
6566
return new JcrNodeDecorator(session.getNode(authorizable.getPath()))
6667
}
6768

@@ -74,6 +75,7 @@ class AuthorizableProtoNodeDecorator extends ProtoNodeDecorator {
7475
if(isUserType()) {
7576
//We set a temporary password for now, and then set the real password later in setPasswordForUser(). See the method for why.
7677
final newUser = userManager.createUser(authorizableID, 'temp', new AuthorizablePrincipal(authorizableID), getIntermediateAuthorizablePath())
78+
session.save()
7779
//This is a special protected property for disabling user access
7880
if(hasProperty('rep:disabled')) {
7981
newUser.disable(getStringValueFrom('rep:disabled'))
@@ -83,27 +85,55 @@ class AuthorizableProtoNodeDecorator extends ProtoNodeDecorator {
8385
if(hasProperty(authorizableCategory)) {
8486
newUser.setProperty(authorizableCategory, new StringValue(getStringValueFrom(authorizableCategory)))
8587
}
88+
session.save()
8689
//Special users may not have passwords, such as anonymous users
8790
if(hasProperty('rep:password')) {
8891
setPasswordForUser(newUser, session)
8992
}
9093
return newUser
9194
}
92-
return userManager.createGroup(authorizableID, new AuthorizablePrincipal(authorizableID), getIntermediateAuthorizablePath())
95+
final Group group = userManager.createGroup(authorizableID, new AuthorizablePrincipal(authorizableID), getIntermediateAuthorizablePath())
96+
session.save()
97+
return group
9398
}
9499

95100

96101
/**
97-
* From a client API perspective, there is really no way to truely update an existing authorizable node. All of the properties are protected, and there is no
102+
* From a client API perspective, there is really no way to truly update an existing authorizable node. All of the properties are protected, and there is no
98103
* known way to update them. What we do here is essentially remove the existing authorizable as denoted by the authorizableID, and recreate it.
104+
* @return new instance of updated authorizable
99105
*/
100-
private void updateAuthorizable(final Authorizable authorizable, final Session session) {
106+
private Authorizable updateAuthorizable(final Authorizable authorizable, final Session session) {
101107
authorizable.remove()
102108
session.save()
103109
createNewAuthorizable(session)
104110
}
105111

106112

113+
/**
114+
* profile nodes live with authorizables as a way of collecting personal information such as email, and first and last name.
115+
* This gets sent with the authorizable node instead of streamed independently because we do not know the client's new
116+
* authorizable UUID node name at runtime. In other words, authorizables live under different node names from server to server
117+
*/
118+
private void updateProfile(final Authorizable authorizable, final Session session) {
119+
final ProtoNode incomingProfileNode = innerProtoNode.mandatoryChildNodeList.find { it.name.contains('profile') }
120+
if(!incomingProfileNode) return
121+
createFrom(incomingProfileNode, "${authorizable.getPath()}/profile").writeToJcr(session)
122+
session.save()
123+
}
124+
125+
126+
/**
127+
* @see updateProfile() for a related explanation to this method.
128+
*/
129+
private void updatePreferences(final Authorizable authorizable, final Session session) {
130+
final ProtoNode incomingPreferencesNode = innerProtoNode.mandatoryChildNodeList.find { it.name.contains('preferences') }
131+
if(!incomingPreferencesNode) return
132+
createFrom(incomingPreferencesNode, "${authorizable.getPath()}/preferences").writeToJcr(session)
133+
session.save()
134+
}
135+
136+
107137
private Authorizable findAuthorizable(final Session session) {
108138
final UserManager userManager = getUserManager(session)
109139
return userManager.getAuthorizable(getAuthorizableID())
@@ -218,9 +248,9 @@ class AuthorizableProtoNodeDecorator extends ProtoNodeDecorator {
218248
Method setPasswordMethod = userManagerDelegateClass.getDeclaredMethod('setPassword', Class.forName('org.apache.jackrabbit.oak.api.Tree', true, userManagerDelegateClass.getClassLoader()), String, String, boolean)
219249
setPasswordMethod.setAccessible(true)
220250
/**
221-
* Step two. We need access to the internal Authorizable object's tree in order to call the internal setPassword method
222-
* 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
223-
*/
251+
* Step two. We need access to the internal Authorizable object's tree in order to call the internal setPassword method
252+
* 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
253+
*/
224254
Class authorizableDelegateClass = user.getClass().getSuperclass()
225255
Field authorizableDelegateField = authorizableDelegateClass.getDeclaredField('delegate')
226256
authorizableDelegateField.setAccessible(true)
@@ -230,9 +260,19 @@ class AuthorizableProtoNodeDecorator extends ProtoNodeDecorator {
230260
getTreeMethod.setAccessible(true)
231261

232262
/**
233-
* 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
234-
* clear-text, which it isn't since we got it from another Jackrabbit instance, we can set the password as-is.
235-
*/
263+
* 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
264+
* clear-text, which it isn't since we got it from another Jackrabbit instance, we can set the password as-is.
265+
*/
236266
setPasswordMethod.invoke(userManagerDelegate, getTreeMethod.invoke(authorizable), getAuthorizableID(), getStringValueFrom('rep:password'), false)
267+
session.save()
268+
}
269+
270+
271+
/**
272+
* An instance wrapper for ease of mocking
273+
* @see ProtoNodeDecorator.createFrom
274+
*/
275+
ProtoNodeDecorator createFrom(final ProtoNode protoNode, final String nameOverride) {
276+
super.createFrom(protoNode, nameOverride)
237277
}
238278
}

src/main/groovy/com/twcable/grabbit/jcr/DefaultProtoNodeDecorator.groovy

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,12 @@ import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE
3232
@Slf4j
3333
class DefaultProtoNodeDecorator extends ProtoNodeDecorator {
3434

35+
private final String nameOverride
3536

36-
protected DefaultProtoNodeDecorator(@Nonnull ProtoNode node, @Nonnull Collection<ProtoPropertyDecorator> protoProperties) {
37+
protected DefaultProtoNodeDecorator(@Nonnull ProtoNode node, @Nonnull Collection<ProtoPropertyDecorator> protoProperties, String nameOverride) {
3738
this.innerProtoNode = node
3839
this.protoProperties = protoProperties
40+
this.nameOverride = nameOverride
3941
}
4042

4143

@@ -50,6 +52,12 @@ class DefaultProtoNodeDecorator extends ProtoNodeDecorator {
5052
//Then add other properties
5153
writableProperties.each { it.writeToNode(jcrNode) }
5254

55+
if(innerProtoNode.mandatoryChildNodeList && innerProtoNode.mandatoryChildNodeList.size() > 0) {
56+
for(ProtoNode childNode: innerProtoNode.mandatoryChildNodeList) {
57+
createFrom(childNode).writeToJcr(session)
58+
}
59+
}
60+
5361
return new JcrNodeDecorator(jcrNode)
5462
}
5563

@@ -64,14 +72,20 @@ class DefaultProtoNodeDecorator extends ProtoNodeDecorator {
6472
}
6573

6674

75+
@Override
76+
String getName() {
77+
nameOverride ?: innerProtoNode.getName()
78+
}
79+
80+
6781
/**
6882
* This method is rather succinct, but helps isolate this JcrUtils static method call
6983
* so that we can get better test coverage.
7084
* @param session to create or get the node path for
7185
* @return the newly created, or found node
7286
*/
7387
JCRNode getOrCreateNode(Session session) {
74-
JcrUtils.getOrCreateByPath(innerProtoNode.name, primaryType.getStringValue(), session)
88+
JcrUtils.getOrCreateByPath(getName(), primaryType.getStringValue(), session)
7589
}
7690

7791

src/main/groovy/com/twcable/grabbit/jcr/JCRNodeDecorator.groovy

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import groovy.util.logging.Slf4j
2323
import javax.annotation.Nonnull
2424
import javax.annotation.Nullable
2525
import javax.jcr.Node as JCRNode
26+
import javax.jcr.PathNotFoundException
2627
import javax.jcr.Property as JcrProperty
2728
import javax.jcr.RepositoryException
2829
import javax.jcr.nodetype.ItemDefinition
@@ -40,7 +41,7 @@ class JcrNodeDecorator {
4041
@Delegate
4142
JCRNode innerNode
4243

43-
private Collection<JcrPropertyDecorator> properties
44+
private final Collection<JcrPropertyDecorator> properties
4445

4546
//Evaluated in a lazy fashion
4647
private Collection<JcrNodeDecorator> immediateChildNodes
@@ -49,10 +50,9 @@ class JcrNodeDecorator {
4950
JcrNodeDecorator(@Nonnull JCRNode node) {
5051
if(!node) throw new IllegalArgumentException("node must not be null!")
5152
this.innerNode = node
52-
String primaryType = node.getProperty(JCR_PRIMARYTYPE).string
5353
Collection<JcrProperty> innerProperties = node.properties.toList()
5454
this.properties = innerProperties.collect { JcrProperty property ->
55-
new JcrPropertyDecorator(property, primaryType)
55+
new JcrPropertyDecorator(property, this)
5656
}
5757
}
5858

@@ -85,11 +85,19 @@ class JcrNodeDecorator {
8585

8686
/**
8787
* Identify all required child nodes
88-
* @return list of immediate required child nodes that must exist with this node, or null if no children
88+
* @return list of immediate required child nodes that must be transported with this node, or null if no required nodes
8989
*/
9090
@Nullable
9191
Collection<JcrNodeDecorator> getRequiredChildNodes() {
92-
return hasMandatoryChildNodes() ? getImmediateChildNodes().findAll{ JcrNodeDecorator childJcrNode -> childJcrNode.isRequiredNode() } : null
92+
Collection<JcrNodeDecorator> requiredNodes
93+
if(isAuthorizableType()) {
94+
requiredNodes = getImmediateChildNodes().findAll { JcrNodeDecorator childJcrNode -> childJcrNode.isAuthorizablePart()}
95+
}
96+
else {
97+
requiredNodes = hasMandatoryChildNodes() ? getImmediateChildNodes().findAll{ JcrNodeDecorator childJcrNode -> childJcrNode.isRequiredNode() } : null
98+
}
99+
100+
return requiredNodes ?: null
93101
}
94102

95103

@@ -154,6 +162,34 @@ class JcrNodeDecorator {
154162
}
155163

156164

165+
/**
166+
* Authorizable nodes are unique on each server, so associated profiles and preferences need to be sent with.
167+
* @return true if this node is part of an authorizable node conglomerate
168+
*/
169+
boolean isAuthorizablePart() {
170+
final String resourceType
171+
try {
172+
resourceType = getProperty('sling:resourceType')?.string
173+
}
174+
catch(PathNotFoundException ex) {
175+
return false
176+
}
177+
final String name = innerNode.getName()
178+
return (name == 'preferences' && resourceType == 'cq:Preferences') ||
179+
(name == 'profile' && resourceType == 'cq/security/components/profile')
180+
}
181+
182+
183+
String getPrimaryType() {
184+
innerNode.getProperty(JCR_PRIMARYTYPE).string
185+
}
186+
187+
188+
boolean isAuthorizableType() {
189+
return primaryType == 'rep:User' || primaryType == 'rep:Group'
190+
}
191+
192+
157193
Object asType(Class clazz) {
158194
if(clazz == JCRNode) {
159195
return innerNode

src/main/groovy/com/twcable/grabbit/jcr/JcrPropertyDecorator.groovy

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,11 @@ class JcrPropertyDecorator {
3737
@Delegate
3838
JCRProperty innerProperty
3939

40-
private final String nodeOwnerPrimaryType
40+
private final JcrNodeDecorator nodeOwner
4141

42-
JcrPropertyDecorator(JCRProperty property, String nodeOwnerPrimaryType) {
42+
JcrPropertyDecorator(JCRProperty property, JcrNodeDecorator nodeOwner) {
4343
this.innerProperty = property
44-
this.nodeOwnerPrimaryType = nodeOwnerPrimaryType
44+
this.nodeOwner = nodeOwner
4545
}
4646

4747
/**
@@ -60,7 +60,7 @@ class JcrPropertyDecorator {
6060
return true
6161
}
6262

63-
if(nodeOwnerPrimaryType in ['rep:User', 'rep:Group']) {
63+
if(nodeOwner.isAuthorizableType()) {
6464
return true
6565
}
6666
else {

src/main/groovy/com/twcable/grabbit/jcr/ProtoNodeDecorator.groovy

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,14 @@ abstract class ProtoNodeDecorator {
3232

3333
abstract JcrNodeDecorator writeToJcr(@Nonnull Session session)
3434

35-
static ProtoNodeDecorator createFrom(@Nonnull ProtoNode node) {
35+
static ProtoNodeDecorator createFrom(@Nonnull ProtoNode node, String nameOverride = null) {
3636
if(!node) throw new IllegalArgumentException("node must not be null!")
3737
final protoProperties = node.propertiesList.collect { new ProtoPropertyDecorator(it) }
3838
final primaryType = protoProperties.find { it.primaryType }
3939
if(primaryType.isUserType() || primaryType.isGroupType()) {
4040
return new AuthorizableProtoNodeDecorator(node, protoProperties)
4141
}
42-
return new DefaultProtoNodeDecorator(node, protoProperties)
42+
return new DefaultProtoNodeDecorator(node, protoProperties, nameOverride)
4343
}
4444

4545
boolean hasProperty(String propertyName) {

src/main/groovy/com/twcable/grabbit/server/batch/steps/jcrnodes/JcrNodesProcessor.groovy

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ class JcrNodesProcessor implements ItemProcessor<JcrNode, ProtoNode> {
6666
}
6767

6868
// Skip this node because it has already been processed by its parent
69-
if(decoratedNode.isRequiredNode()) {
69+
if(decoratedNode.isRequiredNode() || decoratedNode.isAuthorizablePart()) {
7070
return null
7171
} else {
7272
// Build parent node

0 commit comments

Comments
 (0)