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

Adding restore functionality for versioning support #289

Open
wants to merge 20 commits into
base: versioning
Choose a base branch
from
Open

Adding restore functionality for versioning support #289

wants to merge 20 commits into from

Conversation

danrot
Copy link
Contributor

@danrot danrot commented Jun 11, 2015

This PR adds restoring capabilities to the VersionHandler.

In order to use this functionality with jackalope-doctrine-dbal jackalope/jackalope-doctrine-dbal#283 also needs to be merged.


if ($frozenNode->hasProperty('jcr:frozenMixinTypes')) {
$node->setProperty('jcr:mixinTypes', $frozenNode->getPropertyValue('jcr:frozenMixinTypes'));
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is that property not always set? if not, you would need to delete the mixinTypes from the node in case it has some.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, this property is not always set. Removing non-existing properties is a todo a few lines below, it's an extra section in the specification, and also covers other attributes not set.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, but you treat frozenMixinTypes as special case so you need an elseif to remove mixins if the current node has any but frozen has not

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, you are right, will add that.

@danrot
Copy link
Contributor Author

danrot commented Jun 25, 2015

There are still some TODOs in the code. Almost all of them belong the onParentVersion value.

}
}

public function restoreItem($removeExisting, $versionPath, $path)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please use @inheritDoc when implementing interface methods.

@dbu
Copy link
Member

dbu commented Jul 24, 2015

sorry for the long silence. i mostly commented on some details. can you look at those?

for the TODOs i would prefer NotImplementedExceptions. its fine when not all onParentVersion behaviours are implemented in this PR, but throw exceptions when something should happen but does not. ideally explaining what should happen or at least identifying the spec section that is relevant.

it looks to me like it should be possible to quickly wrap this PR up when just throwing exceptions in those cases.

@danrot
Copy link
Contributor Author

danrot commented Jul 25, 2015

I guess the tests won't work if the OPVs are not implemented... Hopefully I can have a detailed look at your comment next week.

@dbu
Copy link
Member

dbu commented Jul 31, 2015

ping @danrot

@danrot
Copy link
Contributor Author

danrot commented Aug 10, 2015

Sry, I was on holiday, will have a look at this again soonish.

@dbu
Copy link
Member

dbu commented Aug 16, 2015

can you rebase this one on the upstream versioning branch please? it is currently in conflict with it.

@danrot
Copy link
Contributor Author

danrot commented Sep 22, 2015

@dbu I have rebased to the versioning branch, is that one up to date? Hope I did not crash anything while solving the conflicts...

@dbu
Copy link
Member

dbu commented Oct 9, 2015

@danrot can you adress the code style comments i made and ping me again? i think this is almost ready to merge.

@danrot
Copy link
Contributor Author

danrot commented Oct 14, 2015

@dbu Fixed the comments, I think the only thing left now is the correct implementation of the onParentVersion attributes, right? Then the entire feature should be ready to merge, or am I missing something?

@dbu
Copy link
Member

dbu commented Oct 17, 2015

yep, sounds right. can you do that?

@danrot
Copy link
Contributor Author

danrot commented Oct 19, 2015

Yeah, but I can't promise you a due date... should I do that on this PR, or create a new one?

break;
case OnParentVersionAction::INITIALIZE:
case OnParentVersionAction::COMPUTE: // implementation-specific, decided to handle as INITIALIZE
// TODO how to get default value of property?
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hope this one is ok: When there is a OPV of initialize, the default value of the property should be restored. Compute is implementation specific, so I decided to just do the same as for initialize.

The question that remains is how to get this default value, I couldn't find a method on the definition.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

afaik the default value is in the property definition. and usually it would be null...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, there default value is in the property definition, but the handling is not that easy. It's always an array, which should be directly applied to multifield properties, and it should be an array with only one element in case it is a single field property. Is that logic already handled somewhere, so that I could reuse it now?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe look for the code around creating a node or search the codebase for the specific property that defines the default value?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found it:

$defaultValues = $propertyDef->getDefaultValues();

But I don't really know where this should be extracted...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we could just push the logic into the PropertyDefinition class as a method determineDefaultValue, with an annotation that this is @internal. the typehint on

foreach ($nodeTypeDefinition->getDeclaredPropertyDefinitions() as $propertyDef) {
/* @var $propertyDef PropertyDefinitionInterface */
should be changed to the concrete class (and move above the foreach and get a second * so its seen by IDEs)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

check if you always need the exception to be thrown or if the method should return false and we check the return value to know what to do.

erm, actually i guess its the whole isAutocreated part, right? and that only makes sense if the property actually is auto created? we could move the whole switch statement into he node type then.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so, because the values for the other properties, which have a special handling in the switch block are not handled by the version at all.

I also dont think it's about the isAutoCreated part, since the spec doesn't even mention it:

If P has an OPV of INITIALIZE then, if N/P has a default value (either defined in the node type of N or implementation-defined) its value is changed to that default value. If N/P has no default value then it is left unchanged.

I think it might also be possible that a property is not auto-created but still have a default value, isn't it? So I think I would go with your first suggestion (the internal determineDefaultValue method on the PropertyDefinition).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

right, makes sense to me. i did not check the spec, but your reasoning makes sense to me.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, then I'll implement it like that. But I have another question: I would also like to write a test for that, and I would have to add my own node type for this. Hasn't there something like this be done already? I don't know where in the tests I can create my own node-types...

@dbu
Copy link
Member

dbu commented Nov 23, 2015

btw, did you see that label support is being improved? #302 this will be implemented for jackalope-jackrabbit in master, but could also affect the dbal implementation.

@danrot
Copy link
Contributor Author

danrot commented Jan 5, 2016

@dbu I will consider the new label support, and see if it changes anything for me.

However, I wanted to continue today, but I am having the same issue now as in a different PR. When I am trying to run the tests I am getting loads of these error:

DOMDocument::loadXML(): attributes construct error in Entity, line: 1

According to php -i I still have the libxml version which should not be problematic:

libxml

libXML support => active
libXML Compiled Version => 2.9.2
libXML Loaded Version => 20902
libXML streams => enabled

Do you still have no idea what could be causing this?

@danrot
Copy link
Contributor Author

danrot commented Jan 5, 2016

@dbu Maybe I should try to getting my branches up to date first... Can you maybe update the versioning branches my PRs in jackalope/jackalope and jackalope/doctrine-dbal are targeting?

throw new NotImplementedException('Check for $removeExisting not implemented yet');
}

// TODO handle different onParentVersion values
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dbu I still have to get here the onParentVersion attribute of the $frozenChildNode. The $frozenChildNode says correctly that it is a nt:unstructure (at least that's correct for the VersionManagerTest::testRestoreWithChildren test, I am currently investigating), but $frozenChildNode->getDefinition() returns the definition for nt:frozenNode.

It looks to me like this is caused by the algorithm used in getDefinition, it takes the parent into account. Looks to me like this doesn't work for frozen nodes.

From what I've seen until now the onParentVersion attribute also depends on the parent, so that's probably the reason getDefinition makes use of the parent. So what I probably would need is to set the parent on my own. Any elegant way to do so?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does the spec say anything about node types of children of that frozenNode? maybe the whole subtree should be frozen? or the children would retain their original node type.

either way, getNodeType and getDefinition have to be consistent, i am surprised this is not the case. smells like we have code duplication between getDefinition and getNodeType... do you have the java jackrabbit client running? then you could try to recreate the situation and see what jackrabbit is doing. either the children just keep their node types, or they are frozen too and their original node type is in a separate property, just like for the main frozen node. (the more i think about this, the more sense it would make to freeze the whole subtree. otherwise you could change a child of a frozen node which makes no sense)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You were right, the children have also the node type nt:frozenNode... But that doesn't make my issue easier to solve. I think that means that the logic from getDefinition has to be duplicated in some way, because the one existing does not work with all the frozen* properties, or am I missing something?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it probably means that when restoring, you need to walk the subtree and restore each of the nodes. i don't have in my mind how getDefinition comes into that here? do you need the definition to restore the node? isn't the frozen primary type and the (frozen?) mixins enough? then you could create the child node with those.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right again :-) The specification is just written a bit strange IMO. [Here](http://www.day.com/specs/jcr/2.0/15_Versioning.html#15.7.6 Restoring Child Nodes) it says that nodes are only copied over if the OPV of the node is COPY or VERSION, but later it says that there will never be a different value on these nodes. So there is no need to check this.

It would make things even easier if the subtree of this node is not frozen at all, since it would be a simple copy command then. But I guess this is still required, because it might be possible that the child of the frozen node is changed by some userland code otherwise, right?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, i think the whole subtree must be frozen to avoid accidentally overwriting. if you want to be sure, best reproduce the situation with jackalope-jackrabbit and check the node types of the children. but afaik a read-only can not be enforced by a parent node so all sub nodes need to be transformed to frozen and back to not frozen.

@ahilles107
Copy link

Any news here?

@danrot
Copy link
Contributor Author

danrot commented Jul 28, 2016

I am sry, currently I am very busy... IIRC it's not too much work to finish this, but I think I won't find time to finish it before october 😞

@dbu
Copy link
Member

dbu commented Jul 28, 2016

@ahilles107 would you be interested in trying to take over? if you want to, i can take this branch over into the jackalope repository so you can do pull requests against it.

@danrot
Copy link
Contributor Author

danrot commented Dec 13, 2016

Good News: Since I've been implementing the versioning feature for Sulu, I will now also get some time to work on this, so that we can use the versioning feature also with jackalope-doctrine-dbal :-) Hope I can finish it...

@danrot
Copy link
Contributor Author

danrot commented Dec 13, 2016

@dbu Is there anything new that should be rebased into the versioning branch?

@dbu
Copy link
Member

dbu commented Dec 13, 2016

there is one conflict with master in the versioning branch. but i guess its better to wrap this PR up first before we rebase. otherwise we might get a huge mess with the old versions of the commits that are here in your restore branch.

$this->session->removeItem($childNodePath);
}

// TODO handle onParentVersion?
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I rewrote the code, but leave the comment here for now. The onParentVersion has to be handled in some way, but maybe this information can be inferred in a different way. The only possible OPVs here is COPY and VERSION, and when it is VERSION the resulting node has a different node type, if I understand the specification on 3.13.9 correct:

Under full versioning, if C is mix:versionable, then a special nt:versionedChild node with a reference to the version history of C is substituted in place of C as a child of the frozen node. 

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sounds right and would need special handling on restore.

@danrot
Copy link
Contributor Author

danrot commented Dec 13, 2016

@dbu We already said that the children of the frozen nodes must also be frozen nodes, but that is not that easy. Should that be done in a separate PR, or should I fix it in here?

@dbu
Copy link
Member

dbu commented Dec 13, 2016

lets try to keep this step as small as possible - its big enough already. so if we don't create the children as frozen nodes yet, lets postpone that to a separate PR and do both in a later PR

@danrot
Copy link
Contributor Author

danrot commented Dec 13, 2016

Ok, so the following steps won't be addressed in this PR:

  • restore primary type (we said we won't do that as long as the primary type can't change)
  • handle identifier collisions
  • handle remove existing
  • Create children of frozen node also as frozen node

And the following should be fixed before this is being merged:

  • handle OPVs when traversing child nodes on current node
  • restore the default value on an OPV of INITIALIZE or COMPUTE (I think that should go into this PR)

Would be nice to know if you agree.

if (!$frozenChildNode->isNodeType('nt:versionedChild')) {
$this->restoreFromNode($node, $frozenChildNode);
} else {
// TODO handle restoring from frozenChildNode with OPV of VERSION
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the check is the correct one, but I would not implement restoring from a child with an OPV of VERSION at the moment, because it would make the PR too big.

@dbu Another question is if this must be implemented for the entire Versioning stuff to be merged, or if just throwing a NotImplementedException here would be enough.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't imagine that a lot of system want to rely on that anyway, because the specification is not really clear about what happens here:

Under full versioning each child node C of N where C has an OPV of VERSION and C is versionable, is represented in F not as a copy of N/C but as special node of type nt:versionedChild containing a reference to the version history of C. On restore, N/C in the workspace is replaced by a version of C. The determination of which version of C to use is implementation-dependent (see §15.7.5 Chained Versions on Restore).

(See 15.7.6 on https://docs.adobe.com/content/docs/en/spec/jcr/2.0/15_Versioning.html)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i am ok with leaving parts out, as long as we throw the NotImplementedException and document what we support or not.

continue;
}

// TODO That's the correct behavior for the OPVs COPY, VERSION and ABORT, handle others as well
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not a 100% sure if there is anything needed. The specification at https://docs.adobe.com/content/docs/en/spec/jcr/2.0/15_Versioning.html chapter 15.7.6 says this:

  • If C has an OPV of COPY, VERSION or ABORT then N/C is removed. Note that while a node with a child item of OPV ABORT cannot be versioned, it is legal for a previously versioned node to have such a child item added to it and then for it to be restored to the state that it had before that item was added, as this step indicates.
  • If C has an OPV of IGNORE then no change is made to N/C.
  • If C has an OPV of INITIALIZE then N/C is re-initialized as if it were newly created, as defined in its node type.
  • If C has an OPV of COMPUTE then N/C may be re-initialized according to an implementation-specific mechanism.

But when I check 3.13.9 on https://docs.adobe.com/content/docs/en/spec/jcr/2.0/3_Repository_Model.html it only handles the OPV of COPY and VERSION, so checking this value here is probably not necessary, am I right?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does 15.7.6 not apply here? i would think that IGNORE would mean that we don't do the delete that is below. initialize/compute should delete it and then create a new one with default values. why do you think that would not be needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sry, you were right, I mixed that up with a frozen node, but this is not what is handled here.

case OnParentVersionAction::ABORT:
$property->remove();
break;
case OnParentVersionAction::INITIALIZE:
Copy link
Contributor Author

@danrot danrot Dec 15, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed the COMPUTE value which was here before, because it made problems with some internal fields (in my case jcr:mixinTypes has an OPV of COMPUTE, and when changing these fields I got a few error message later on).

Another thing is that the spec states following:

If P has an OPV of COMPUTE then the value of N/P may be changed according to an implementation-specific mechanism.

Since I also don't see a need to change these values, I think we are fine to don't do so. @dbu Do you agree?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

implementation-specific means we are free to do whatever the easiest solution is :-)

$childNode->remove();
break;
case OnParentVersionAction::INITIALIZE:
// TODO reinitialize node as defined in its node type
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that's the only thing left before we can merge this. This behavior here should be similar to the one for properties. The code autocreating the child nodes is currently in the NodeProcessor. Should I also extract that to a determineDefaultValue to the NodeDefinition?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it as easy as for a property? what is the "default value" of a node? a node is not just a value.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, if I look at the code of the NodeProcessor I'd say it's just about adding a node and apply some value to the required properties. Was thinking if it would be enough to just add the node with the primary NodeType and let the system do the rest. Not sure if that will crash something, but then the NodeProcessor also wouldn't work. I guess I just give it a try.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My idea here was to simply call $childNode->remove() and recreate the node. But the problem is that if the $childNode is defined as mandatory, I can't remove it... Any idea how else I could handle that?

@dbu
Copy link
Member

dbu commented Dec 15, 2016 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants