Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue 3 handle mixin property #227

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
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.ProtoNodeDecorator
import com.twcable.grabbit.proto.NodeProtos.Node as ProtoNode
import groovy.transform.CompileStatic
Expand Down Expand Up @@ -71,7 +70,8 @@ class JcrNodesWriter implements ItemWriter<ProtoNode>, ItemWriteListener {
void write(List<? extends ProtoNode> nodeProtos) throws Exception {
Session session = theSession()
for (ProtoNode nodeProto : nodeProtos) {
writeToJcr(nodeProto, session)
ProtoNodeDecorator protoNodeDecorator = new ProtoNodeDecorator(nodeProto)
protoNodeDecorator.writeToJcr(session)
}
}

Expand All @@ -87,16 +87,7 @@ class JcrNodesWriter implements ItemWriter<ProtoNode>, ItemWriteListener {
return retVal
}

private static void writeToJcr(ProtoNode nodeProto, Session session) {
JcrNodeDecorator jcrNode = new ProtoNodeDecorator(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() {
ClientBatchJobContext.session
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,22 @@ 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.commons.JcrUtils
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.Property as JcrProperty
import javax.jcr.RepositoryException
import javax.jcr.Session
import javax.jcr.nodetype.ItemDefinition
import javax.jcr.version.VersionException

import static javax.jcr.nodetype.NodeType.*
import static org.apache.jackrabbit.JcrConstants.*

@CompileStatic
Expand All @@ -45,11 +49,41 @@ class JcrNodeDecorator {
private Collection<JcrNodeDecorator> immediateChildNodes


JcrNodeDecorator(@Nonnull JCRNode node) {
JcrNodeDecorator(@Nonnull final JCRNode node) {
if(!node) throw new IllegalArgumentException("node must not be null!")
this.innerNode = node
}

static JcrNodeDecorator createFromProtoNode(@Nonnull final ProtoNodeDecorator protoNode, @Nonnull final Session session) {
final JcrNodeDecorator theDecorator = new JcrNodeDecorator(getOrCreateNode(protoNode, session))
final mandatoryNodes = protoNode.getMandatoryChildNodes()
mandatoryNodes?.each {
it.writeToJcr(session)
}
//If a version exception is thrown,
try {
protoNode.writableProperties.each { it.writeToNode(innerNode) }
}
catch(VersionException ex) {
JcrNodeDecorator checkedOutNode = theDecorator.checkoutNode()
if (checkedOutNode) {
protoNode.writableProperties.each { it.writeToNode(innerNode) }
checkedOutNode.checkinNode()
}
}
theDecorator.setLastModified()
return theDecorator
}

/**
* 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
*/
private static JCRNode getOrCreateNode(ProtoNodeDecorator protoNode, Session session) {
JcrUtils.getOrCreateByPath(protoNode.name, protoNode.primaryType, session)
}

/**
* @return this node's immediate children, empty if none
Expand Down Expand Up @@ -135,6 +169,48 @@ class JcrNodeDecorator {
}
}

private void checkinNode() {
try {
this.session.workspace.versionManager.checkin(this.path)
}
catch (RepositoryException e) {
log.warn("Error checking in node ${this.path}")
}
}

/**
* Finds out nearest versionable ancestor for a node and performs a checkout
*/
private JcrNodeDecorator checkoutNode() {
try {
JcrNodeDecorator decoratedVersionableAncestor = findVersionableAncestor()
if (decoratedVersionableAncestor && !decoratedVersionableAncestor.isCheckedOut()) {
decoratedVersionableAncestor.session.workspace.versionManager.checkout(decoratedVersionableAncestor.path)
return decoratedVersionableAncestor
}
}
catch (RepositoryException exception) {
log.warn "Could not checkout node ${this.path}, ${exception.message}"
}
return null
}

private JcrNodeDecorator findVersionableAncestor() {
if (isVersionable()) {
return this
}
try {
JcrNodeDecorator parentDecoratedNode = new JcrNodeDecorator(this.parent)
return parentDecoratedNode.findVersionableAncestor()
} catch (ItemNotFoundException e) {
return null
}
}

private boolean isVersionable() {
return mixinNodeTypes.any{it in [MIX_SIMPLE_VERSIONABLE, MIX_VERSIONABLE]}
}

/**
* Returns the "jcr:lastModified", "cq:lastModified" or "jcr:created" date property
* for current Jcr Node
Expand Down
45 changes: 5 additions & 40 deletions src/main/groovy/com/twcable/grabbit/jcr/ProtoNodeDecorator.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -56,50 +56,15 @@ class ProtoNodeDecorator {


Collection<ProtoPropertyDecorator> getWritableProperties() {
protoProperties.findAll { !(it.name in [JCR_PRIMARYTYPE, JCR_MIXINTYPES]) }
protoProperties.findAll { !(it.name in [JCR_PRIMARYTYPE]) }
}


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

return new JcrNodeDecorator(jcrNode)
List<ProtoNodeDecorator> getMandatoryChildNodes() {
return mandatoryChildNodeList.collect { new ProtoNodeDecorator(it) }
}


/**
* 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)
}


/**
* 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}"
}
}
JcrNodeDecorator writeToJcr(@Nonnull Session session) {
return JcrNodeDecorator.createFromProtoNode(this, session)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import javax.jcr.Node as JCRNode
import javax.jcr.PropertyType
import javax.jcr.Value
import javax.jcr.ValueFormatException
import javax.jcr.version.VersionException

import static org.apache.jackrabbit.JcrConstants.JCR_MIXINTYPES
import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE
Expand All @@ -43,8 +44,11 @@ class ProtoPropertyDecorator {
}


void writeToNode(@Nonnull JCRNode node) {
if(primaryType || mixinType) throw new IllegalStateException("Refuse to write jcr:primaryType or jcr:mixinType as normal properties. These are not allowed")
void writeToNode(@Nonnull JCRNode node) throws VersionException {
if(primaryType) throw new IllegalStateException("Refuse to write jcr:primaryType. This can not be written after node creation")
if(mixinType) {
writeMixinTypeToNode(node)
}
try {
if (multiple) {
node.setProperty(this.name, getPropertyValues(), this.type)
Expand Down Expand Up @@ -72,6 +76,18 @@ class ProtoPropertyDecorator {
}
}

void writeMixinTypeToNode(@Nonnull JCRNode node) {
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}"
}
}
}


boolean isPrimaryType() {
innerProtoProperty.name == JCR_PRIMARYTYPE
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
package com.twcable.grabbit.jcr

import com.day.cq.commons.jcr.JcrConstants
import com.twcable.jackalope.NodeBuilder
import spock.lang.Ignore
import spock.lang.Shared
import spock.lang.Specification

Expand All @@ -27,12 +29,12 @@ import javax.jcr.nodetype.ItemDefinition
import javax.jcr.nodetype.NodeDefinition
import javax.jcr.nodetype.NodeType

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 com.twcable.jackalope.JCRBuilder.node
import static com.twcable.jackalope.JCRBuilder.property
import static org.apache.jackrabbit.JcrConstants.*

@SuppressWarnings("GroovyAssignabilityCheck")
class JCRNodeDecoratorSpec extends Specification {
class JcrNodeDecoratorSpec extends Specification {
@Shared
static Calendar jcrModifiedDate = Calendar.getInstance()
static Calendar cqModifiedDate = Calendar.getInstance()
Expand Down Expand Up @@ -234,4 +236,32 @@ class JCRNodeDecoratorSpec extends Specification {
false | false | true | jcrCreatedDate.time
false | false | false | null
}

/*
* Needs mixin implementation in Jackalope
* https://github.com/TWCable/jackalope/pull/7
*/
@Ignore
def "Checkout nearest versionable node"() {
given:
NodeBuilder fakeNodeBuilder =
node("a",
node("b", property("jcr:mixinTypes", ["mix:versionable"].toArray()),
property("jcr:primaryType", "cq:Page"),
node("c",
node("d")),

),
)

Node parentNode = fakeNodeBuilder.build();

final nodeDecorator = new JcrNodeDecorator(parentNode.getNode("b/c/d"))

when:
nodeDecorator.checkoutNode()

then:
parentNode.getNode("b").getProperty("jcr:isCheckedOut") == true
}
}