use of org.structr.web.diff.InvertibleModificationOperation in project structr by structr.
the class FtpFilePageWrapper method createOutputStream.
@Override
public OutputStream createOutputStream(long offset) throws IOException {
final Page origPage = page;
OutputStream out = new ByteArrayOutputStream() {
@Override
public void flush() throws IOException {
final String source = toString();
final App app = StructrApp.getInstance();
try (Tx tx = app.tx()) {
// parse page from modified source
Page modifiedPage = Importer.parsePageFromSource(page.getSecurityContext(), source, "__FTP_Temporary_Page__");
final List<InvertibleModificationOperation> changeSet = Importer.diffNodes(origPage, modifiedPage);
for (final InvertibleModificationOperation op : changeSet) {
// execute operation
op.apply(app, origPage, modifiedPage);
}
app.delete(modifiedPage);
tx.success();
} catch (FrameworkException fex) {
logger.warn("", fex);
}
super.flush();
}
};
return out;
}
use of org.structr.web.diff.InvertibleModificationOperation in project structr by structr.
the class DiffTest method testDiff.
private String testDiff(final String source, final Function<String, String> modifier) {
Settings.JsonIndentation.setValue(true);
Settings.HtmlIndentation.setValue(true);
final StringBuilder buf = new StringBuilder();
String sourceHtml = null;
try {
// create page from source
final Page sourcePage = Importer.parsePageFromSource(securityContext, source, "test");
// render page into HTML string
try (final Tx tx = app.tx()) {
sourceHtml = sourcePage.getContent(RenderContext.EditMode.RAW);
tx.success();
}
// modify HTML string with transformation function
final String modifiedHtml = modifier.apply(sourceHtml);
// parse page from modified source
final Page modifiedPage = Importer.parsePageFromSource(securityContext, modifiedHtml, "Test");
// create and apply diff operations
try (final Tx tx = app.tx()) {
final List<InvertibleModificationOperation> changeSet = Importer.diffNodes(sourcePage, modifiedPage);
for (final InvertibleModificationOperation op : changeSet) {
System.out.println(op);
// execute operation
op.apply(app, sourcePage, modifiedPage);
System.out.println("############################################################################################");
// System.out.println(sourcePage.getContent(RenderContext.EditMode.NONE));
}
tx.success();
}
// render modified page into buffer
try (final Tx tx = app.tx()) {
buf.append(sourcePage.getContent(RenderContext.EditMode.NONE));
tx.success();
}
} catch (Throwable t) {
logger.warn("", t);
}
return buf.toString();
}
use of org.structr.web.diff.InvertibleModificationOperation in project structr by structr.
the class SaveNodeCommand method processMessage.
@Override
public void processMessage(final WebSocketMessage webSocketData) {
final String nodeId = webSocketData.getId();
final Map<String, Object> nodeData = webSocketData.getNodeData();
final String modifiedHtml = (String) nodeData.get("source");
final SecurityContext securityContext = getWebSocket().getSecurityContext();
final App app = StructrApp.getInstance(securityContext);
Page modifiedNode = null;
DOMNode sourceNode = (DOMNode) getNode(nodeId);
if (sourceNode != null) {
TransactionCommand.registerNodeCallback(sourceNode, callback);
try {
// parse page from modified source
modifiedNode = Importer.parsePageFromSource(securityContext, modifiedHtml, "__SaveNodeCommand_Temporary_Page__");
DOMNode targetNode = modifiedNode;
if (!(sourceNode instanceof Page)) {
targetNode = (DOMNode) modifiedNode.getFirstChild().getNextSibling().getFirstChild().getNextSibling().getFirstChild();
}
final List<InvertibleModificationOperation> changeSet = Importer.diffNodes(sourceNode, targetNode);
for (final InvertibleModificationOperation op : changeSet) {
// execute operation
op.apply(app, sourceNode.getClosestPage(), modifiedNode);
}
} catch (Throwable t) {
logger.warn("", t);
// send exception
getWebSocket().send(MessageBuilder.status().code(422).message(t.toString()).build(), true);
}
try {
app.delete(modifiedNode);
} catch (FrameworkException ex) {
logger.warn("", ex);
}
} else {
// send exception
getWebSocket().send(MessageBuilder.status().code(422).message("Cannot save page").build(), true);
}
}
use of org.structr.web.diff.InvertibleModificationOperation in project structr by structr.
the class Importer method diffNodes.
public static List<InvertibleModificationOperation> diffNodes(final DOMNode sourceNode, final DOMNode modifiedNode) {
if (sourceNode == null) {
logger.warn("Source node was null, returning empty change set.");
return Collections.EMPTY_LIST;
}
if (modifiedNode == null) {
logger.warn("Modified node was null, returning empty change set.");
return Collections.EMPTY_LIST;
}
final List<InvertibleModificationOperation> changeSet = new LinkedList<>();
final Map<String, DOMNode> indexMappedExistingNodes = new LinkedHashMap<>();
final Map<String, DOMNode> hashMappedExistingNodes = new LinkedHashMap<>();
final Map<DOMNode, Integer> depthMappedExistingNodes = new LinkedHashMap<>();
final Map<String, DOMNode> indexMappedNewNodes = new LinkedHashMap<>();
final Map<String, DOMNode> hashMappedNewNodes = new LinkedHashMap<>();
final Map<DOMNode, Integer> depthMappedNewNodes = new LinkedHashMap<>();
InvertibleModificationOperation.collectNodes(sourceNode, indexMappedExistingNodes, hashMappedExistingNodes, depthMappedExistingNodes);
InvertibleModificationOperation.collectNodes(modifiedNode, indexMappedNewNodes, hashMappedNewNodes, depthMappedNewNodes);
// iterate over existing nodes and try to find deleted ones
for (final Iterator<Map.Entry<String, DOMNode>> it = hashMappedExistingNodes.entrySet().iterator(); it.hasNext(); ) {
final Map.Entry<String, DOMNode> existingNodeEntry = it.next();
final DOMNode existingNode = existingNodeEntry.getValue();
final String existingHash = existingNode.getIdHash();
// check for deleted nodes ignoring Page nodes
if (!hashMappedNewNodes.containsKey(existingHash) && !(existingNode instanceof Page)) {
changeSet.add(new DeleteOperation(hashMappedExistingNodes, existingNode));
}
}
// iterate over new nodes and try to find new ones
for (final Iterator<Map.Entry<String, DOMNode>> it = indexMappedNewNodes.entrySet().iterator(); it.hasNext(); ) {
final Map.Entry<String, DOMNode> newNodeEntry = it.next();
final DOMNode newNode = newNodeEntry.getValue();
// if newNode is a content element, do not rely on local hash property
String newHash = newNode.getDataHash();
if (newHash == null) {
newHash = newNode.getIdHash();
}
// check for deleted nodes ignoring Page nodes
if (!hashMappedExistingNodes.containsKey(newHash) && !(newNode instanceof Page)) {
final DOMNode newParent = newNode.getParent();
changeSet.add(new CreateOperation(hashMappedExistingNodes, getHashOrNull(newParent), getSiblingHashes(newNode), newNode, depthMappedNewNodes.get(newNode)));
}
}
// compare all new nodes with all existing nodes
for (final Map.Entry<String, DOMNode> newNodeEntry : indexMappedNewNodes.entrySet()) {
final String newTreeIndex = newNodeEntry.getKey();
final DOMNode newNode = newNodeEntry.getValue();
for (final Map.Entry<String, DOMNode> existingNodeEntry : indexMappedExistingNodes.entrySet()) {
final String existingTreeIndex = existingNodeEntry.getKey();
final DOMNode existingNode = existingNodeEntry.getValue();
DOMNode newParent = null;
int equalityBitmask = 0;
if (newTreeIndex.equals(existingTreeIndex)) {
equalityBitmask |= 1;
}
if (newNode.getIdHashOrProperty().equals(existingNode.getIdHash())) {
equalityBitmask |= 2;
}
if (newNode.contentEquals(existingNode)) {
equalityBitmask |= 4;
}
switch(equalityBitmask) {
case // same tree index (1), same node (2), same content (4) => node is completely unmodified
7:
break;
case // same content (2), same node (4), NOT same tree index => node has moved
6:
newParent = newNode.getParent();
changeSet.add(new MoveOperation(hashMappedExistingNodes, getHashOrNull(newParent), getSiblingHashes(newNode), newNode, existingNode));
break;
case // same tree index (1), NOT same node, same content (5) => node was deleted and restored, maybe the identification information was lost
5:
break;
case // NOT same tree index, NOT same node, same content (4) => different node, content is equal by chance?
4:
break;
case // same tree index, same node, NOT same content => node was modified but not moved
3:
changeSet.add(new UpdateOperation(hashMappedExistingNodes, existingNode, newNode));
break;
case // NOT same tree index, same node (2), NOT same content => node was moved and changed
2:
newParent = newNode.getParent();
changeSet.add(new UpdateOperation(hashMappedExistingNodes, existingNode, newNode));
changeSet.add(new MoveOperation(hashMappedExistingNodes, getHashOrNull(newParent), getSiblingHashes(newNode), newNode, existingNode));
break;
case // same tree index (1), NOT same node, NOT same content => ignore
1:
break;
case // NOT same tree index, NOT same node, NOT same content => ignore
0:
break;
}
}
}
return changeSet;
}
Aggregations