All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.sirix.service.json.shredder.JsonResourceCopy Maven / Gradle / Ivy

/*
 * Copyright (c) 2011, University of Konstanz, Distributed Systems Group All rights reserved.
 * 

* Redistribution and use in source and binary forms, with or without modification, are permitted * provided that the following conditions are met: * Redistributions of source code must retain the * above copyright notice, this list of conditions and the following disclaimer. * Redistributions * in binary form must reproduce the above copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other materials provided with the distribution. * * Neither the name of the University of Konstanz nor the names of its contributors may be used to * endorse or promote products derived from this software without specific prior written permission. *

* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package io.sirix.service.json.shredder; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; import io.sirix.access.ResourceConfiguration; import io.sirix.access.trx.node.json.InsertOperations; import io.sirix.access.trx.node.json.objectvalue.*; import io.sirix.api.json.JsonNodeReadOnlyTrx; import io.sirix.api.json.JsonNodeTrx; import io.sirix.api.json.JsonResourceSession; import io.sirix.axis.DescendantAxis; import io.sirix.axis.IncludeSelf; import io.sirix.node.NodeKind; import io.sirix.service.InsertPosition; import io.sirix.service.ShredderCommit; import io.sirix.settings.Constants; import io.sirix.settings.Fixed; import it.unimi.dsi.fastutil.longs.LongArrayList; import java.io.IOException; import java.io.UncheckedIOException; import java.nio.file.Files; import java.util.concurrent.Callable; import static java.util.Objects.requireNonNull; /** * Copy a resource or a subtree into another * resoure. even copy all changes and revisions between a given revision/transaction. */ public final class JsonResourceCopy implements Callable { private final String INSERT = InsertOperations.INSERT.getName(); private final String UPDATE = InsertOperations.UPDATE.getName(); private final String DELETE = InsertOperations.DELETE.getName(); private final String REPLACE = InsertOperations.REPLACE.getName(); private final JsonResourceSession readResourceSession; /** * {@link JsonNodeTrx}. */ private final JsonNodeTrx wtx; /** * Determines if changes are going to be commit right after shredding. */ private final ShredderCommit commit; private final JsonNodeReadOnlyTrx rtx; private final long startNodeKey; /** * Insertion position. */ private final InsertPosition insert; /** * Determines if diffs between revisions should be copied. */ private final boolean copyAllRevisionsUpToMostRecent; /** * Builder to build an {@link JsonItemShredder} instance. */ public static class Builder { /** * {@link JsonNodeTrx} implementation. */ private final JsonNodeTrx wtx; /** * The transaction to read from. */ private final JsonNodeReadOnlyTrx rtx; /** * Insertion position. */ private final InsertPosition insert; /** * Determines if after shredding the transaction should be immediately committed. */ private ShredderCommit commit = ShredderCommit.NOCOMMIT; private boolean copyAllRevisionsUpToMostRecent; /** * Constructor. * * @param wtx the transaction to write to * @param rtx the transaction to read from * @param insert insertion position * @throws NullPointerException if one of the arguments is {@code null} */ public Builder(final JsonNodeTrx wtx, final JsonNodeReadOnlyTrx rtx, final InsertPosition insert) { this.wtx = requireNonNull(wtx); this.rtx = requireNonNull(rtx); this.insert = requireNonNull(insert); } /** * Commit afterwards. * * @return this builder instance */ public JsonResourceCopy.Builder commitAfterwards() { commit = ShredderCommit.COMMIT; return this; } /** * Determines if changes between the revisions should be copied up to the most recent revision. * * @return this builder instance */ public JsonResourceCopy.Builder copyAllRevisionsUpToMostRecent() { copyAllRevisionsUpToMostRecent = true; return this; } /** * Build an instance. * * @return {@link JsonItemShredder} instance */ public JsonResourceCopy build() { return new JsonResourceCopy(wtx, rtx, this); } } /** * Stack for reading end element. */ private final LongArrayList stack = new LongArrayList(); /** * Private constructor. * * @param wtx the transaction used to write * @param rtx the transaction used to read * @param builder builder of the JSON resource copy */ private JsonResourceCopy(final JsonNodeTrx wtx, final JsonNodeReadOnlyTrx rtx, final Builder builder) { this.wtx = wtx; this.rtx = rtx; this.readResourceSession = rtx.getResourceSession(); this.insert = builder.insert; this.commit = builder.commit; this.startNodeKey = rtx.getNodeKey(); this.copyAllRevisionsUpToMostRecent = builder.copyAllRevisionsUpToMostRecent; } public Void call() { rtx.moveTo(startNodeKey); // Setup primitives. boolean moveToParent = false; boolean first = true; long previousKey = Fixed.NULL_NODE_KEY.getStandardProperty(); insert(moveToParent, first, previousKey); if (copyAllRevisionsUpToMostRecent) { wtx.commit(); for (var revision = rtx.getRevisionNumber() + 1; revision <= rtx.getResourceSession().getMostRecentRevisionNumber(); revision++) { try (final var rtxOnRevision = readResourceSession.beginNodeReadOnlyTrx(revision)) { final var updateOperationsFile = readResourceSession.getResourceConfig() .getResource() .resolve(ResourceConfiguration.ResourcePaths.UPDATE_OPERATIONS.getPath()) .resolve( "diffFromRev" + (revision - 1) + "toRev" + revision + ".json"); final JsonElement jsonElement; try { jsonElement = JsonParser.parseString(Files.readString(updateOperationsFile)); } catch (IOException e) { throw new UncheckedIOException(e); } final var jsonObject = jsonElement.getAsJsonObject(); final var diffsArray = jsonObject.getAsJsonArray("diffs"); for (final var diffsElement : diffsArray) { final var diffsObject = diffsElement.getAsJsonObject(); if (diffsObject.has(INSERT)) { final JsonObject insertObject = diffsObject.getAsJsonObject(INSERT); executeInsert(insertObject, rtxOnRevision); } else if (diffsObject.has(REPLACE)) { final JsonObject replaceObject = diffsObject.getAsJsonObject(REPLACE); executeReplace(replaceObject, rtxOnRevision); } else if (diffsObject.has(UPDATE)) { final JsonObject updateObject = diffsObject.getAsJsonObject(UPDATE); executeUpdate(updateObject, rtxOnRevision); } else if (diffsObject.has(DELETE)) { final long nodeKey = diffsObject.getAsJsonPrimitive(DELETE).getAsLong(); executeDelete(nodeKey); } } wtx.commit(); } } } else { commit.commit(wtx); } return null; } private void executeDelete(final long nodeKey) { wtx.moveTo(nodeKey); wtx.remove(); } private void executeUpdate(JsonObject updateObject, JsonNodeReadOnlyTrx rtxOnRevision) { final var key = updateObject.get("nodeKey").getAsLong(); final var type = updateObject.get("type").getAsString(); rtxOnRevision.moveTo(key); wtx.moveTo(key); switch (type) { case "boolean" -> wtx.setBooleanValue(rtxOnRevision.getBooleanValue()); case "string" -> wtx.setStringValue(rtxOnRevision.getValue()); case "number" -> wtx.setNumberValue(rtxOnRevision.getNumberValue()); } } private void executeReplace(JsonObject replaceObject, JsonNodeReadOnlyTrx rtxOnRevision) { final var oldNodeKey = replaceObject.get("oldNodeKey").getAsLong(); final var newNodeKey = replaceObject.get("newNodeKey").getAsLong(); final var type = replaceObject.get("type").getAsString(); wtx.moveTo(oldNodeKey); rtxOnRevision.moveTo(newNodeKey); final String insertPosition; if (wtx.hasRightSibling()) { insertPosition = "insertAsLeftSibling"; } else if (wtx.hasLeftSibling()) { insertPosition = "insertAsRightSibling"; } else { insertPosition = "insertAsFirstChild"; } if (wtx.getParentKind() == NodeKind.OBJECT_KEY) { wtx.moveToParent(); switch (rtxOnRevision.getKind()) { case OBJECT -> wtx.replaceObjectRecordValue(new ObjectValue()); case ARRAY -> wtx.replaceObjectRecordValue(new ArrayValue()); case OBJECT_NUMBER_VALUE, NUMBER_VALUE -> wtx.replaceObjectRecordValue(new NumberValue(rtxOnRevision.getNumberValue())); case OBJECT_NULL_VALUE, NULL_VALUE -> wtx.replaceObjectRecordValue(new NullValue()); case OBJECT_STRING_VALUE, STRING_VALUE -> wtx.replaceObjectRecordValue(new StringValue(rtxOnRevision.getValue())); case OBJECT_BOOLEAN_VALUE, BOOLEAN_VALUE -> wtx.replaceObjectRecordValue(new BooleanValue(rtxOnRevision.getBooleanValue())); } } else { wtx.remove(); insert(type, rtxOnRevision, insertPosition); } } private void executeInsert(JsonObject insertObject, JsonNodeReadOnlyTrx rtxOnRevision) { final var key = insertObject.get("nodeKey").getAsLong(); final var insertPosition = insertObject.get("insertPosition").getAsString(); final var insertPositionNodeKey = insertObject.get("insertPositionNodeKey").getAsLong(); final var type = insertObject.get("type").getAsString(); wtx.moveTo(insertPositionNodeKey); rtxOnRevision.moveTo(key); insert(type, rtxOnRevision, insertPosition); } private void insert(String type, JsonNodeReadOnlyTrx rtxOnRevision, String insertPosition) { switch (type) { case "jsonFragment" -> insertFragment(rtxOnRevision, insertPosition); case "boolean" -> { if (insertPosition.equals("asFirstChild")) { wtx.insertBooleanValueAsFirstChild(rtxOnRevision.getBooleanValue()); } else { wtx.insertBooleanValueAsRightSibling(rtxOnRevision.getBooleanValue()); } } case "null" -> { if (insertPosition.equals("asFirstChild")) { wtx.insertNullValueAsFirstChild(); } else { wtx.insertNullValueAsRightSibling(); } } case "string" -> { if (insertPosition.equals("asFirstChild")) { wtx.insertStringValueAsFirstChild(rtxOnRevision.getValue()); } else { wtx.insertStringValueAsRightSibling(rtxOnRevision.getValue()); } } } } private void insertFragment(JsonNodeReadOnlyTrx rtxOnRevision, String insertPosition) { final var copyResource = new Builder(wtx, rtxOnRevision, InsertPosition.ofString(insertPosition)).build(); copyResource.call(); } private void insert(boolean moveToParent, boolean isFirst, long previousKey) { // Iterate over all nodes of the subtree including self. for (final var axis = new DescendantAxis(rtx, IncludeSelf.YES); axis.hasNext(); ) { final long key = axis.nextLong(); // Process all pending moves to parents. if (moveToParent) { while (!stack.isEmpty() && stack.peekLong(0) != rtx.getLeftSiblingKey()) { rtx.moveTo(stack.popLong()); rtx.moveTo(key); wtx.moveToParent(); } if (!stack.isEmpty()) { rtx.moveTo(stack.popLong()); wtx.moveToParent(); } rtx.moveTo(key); } // Process node. final long nodeKey = rtx.getNodeKey(); InsertPosition insertPosition; if (isFirst) { insertPosition = insert; } else { if (moveToParent) { insertPosition = InsertPosition.AS_RIGHT_SIBLING; } else if (rtx.hasLeftSibling() && previousKey == rtx.getLeftSiblingKey()) { insertPosition = InsertPosition.AS_RIGHT_SIBLING; } else { insertPosition = InsertPosition.AS_FIRST_CHILD; } } moveToParent = false; // Values of object keys have already been inserted. if (isFirst || rtx.getParentKind() != NodeKind.OBJECT_KEY) { processNode(rtx, insertPosition); } rtx.moveTo(nodeKey); isFirst = false; // Push end element to stack if we are a start element with children. boolean withChildren = false; if (!rtx.isDocumentRoot() && rtx.hasFirstChild()) { stack.push(rtx.getNodeKey()); withChildren = true; } // Remember to process all pending moves to parents from stack if required. if (!withChildren && !rtx.isDocumentRoot() && !rtx.hasRightSibling()) { moveToParent = true; } previousKey = key; } // Finally emit all pending moves to parents. while (!stack.isEmpty() && stack.peekLong(0) != Constants.NULL_ID_LONG) { rtx.moveTo(stack.popLong()); } } /** * Emit node. * * @param rtx Sirix {@link JsonNodeReadOnlyTrx} */ public void processNode(final JsonNodeReadOnlyTrx rtx, final InsertPosition insertPosition) { switch (rtx.getKind()) { case JSON_DOCUMENT: break; case OBJECT: if (insertPosition == InsertPosition.AS_FIRST_CHILD) { wtx.insertObjectAsFirstChild(); } else if (insertPosition == InsertPosition.AS_RIGHT_SIBLING) { wtx.insertObjectAsRightSibling(); } else if (insertPosition == InsertPosition.AS_LEFT_SIBLING) { wtx.insertObjectAsLeftSibling(); } else { throw new IllegalStateException("Insert location not known!"); } break; case ARRAY: if (insertPosition == InsertPosition.AS_FIRST_CHILD) { wtx.insertArrayAsFirstChild(); } else if (insertPosition == InsertPosition.AS_RIGHT_SIBLING) { wtx.insertArrayAsRightSibling(); } else if (insertPosition == InsertPosition.AS_LEFT_SIBLING) { wtx.insertArrayAsLeftSibling(); } else { throw new IllegalStateException("Insert location not known!"); } break; case OBJECT_KEY: if (insertPosition == InsertPosition.AS_FIRST_CHILD) { final var key = rtx.getName().getLocalName(); rtx.moveToFirstChild(); switch (rtx.getKind()) { case OBJECT -> wtx.insertObjectRecordAsFirstChild(key, new ObjectValue()); case ARRAY -> wtx.insertObjectRecordAsFirstChild(key, new ArrayValue()); case OBJECT_BOOLEAN_VALUE -> wtx.insertObjectRecordAsFirstChild(key, new BooleanValue(rtx.getBooleanValue())); case OBJECT_NULL_VALUE -> wtx.insertObjectRecordAsFirstChild(key, new NullValue()); case OBJECT_STRING_VALUE -> wtx.insertObjectRecordAsFirstChild(key, new io.sirix.access.trx.node.json.objectvalue.StringValue( rtx.getValue())); case OBJECT_NUMBER_VALUE -> wtx.insertObjectRecordAsFirstChild(key, new NumberValue(rtx.getNumberValue())); } rtx.moveToParent(); } else if (insertPosition == InsertPosition.AS_LEFT_SIBLING) { final var key = rtx.getName().getLocalName(); rtx.moveToFirstChild(); switch (rtx.getKind()) { case OBJECT -> wtx.insertObjectRecordAsLeftSibling(key, new ObjectValue()); case ARRAY -> wtx.insertObjectRecordAsLeftSibling(key, new ArrayValue()); case OBJECT_BOOLEAN_VALUE -> wtx.insertObjectRecordAsLeftSibling(key, new BooleanValue(rtx.getBooleanValue())); case OBJECT_NULL_VALUE -> wtx.insertObjectRecordAsLeftSibling(key, new NullValue()); case OBJECT_STRING_VALUE -> wtx.insertObjectRecordAsLeftSibling(key, new io.sirix.access.trx.node.json.objectvalue.StringValue( rtx.getValue())); case OBJECT_NUMBER_VALUE -> wtx.insertObjectRecordAsLeftSibling(key, new NumberValue(rtx.getNumberValue())); } rtx.moveToParent(); } else if (insertPosition == InsertPosition.AS_RIGHT_SIBLING) { final var key = rtx.getName().getLocalName(); rtx.moveToFirstChild(); switch (rtx.getKind()) { case OBJECT -> wtx.insertObjectRecordAsRightSibling(key, new ObjectValue()); case ARRAY -> wtx.insertObjectRecordAsRightSibling(key, new ArrayValue()); case OBJECT_BOOLEAN_VALUE -> wtx.insertObjectRecordAsRightSibling(key, new BooleanValue(rtx.getBooleanValue())); case OBJECT_NULL_VALUE -> wtx.insertObjectRecordAsRightSibling(key, new NullValue()); case OBJECT_STRING_VALUE -> wtx.insertObjectRecordAsRightSibling(key, new io.sirix.access.trx.node.json.objectvalue.StringValue( rtx.getValue())); case OBJECT_NUMBER_VALUE -> wtx.insertObjectRecordAsRightSibling(key, new NumberValue(rtx.getNumberValue())); } rtx.moveToParent(); } else { throw new IllegalStateException("Insert location not known!"); } break; case BOOLEAN_VALUE: if (insertPosition == InsertPosition.AS_FIRST_CHILD) { wtx.insertBooleanValueAsFirstChild(rtx.getBooleanValue()); } else if (insertPosition == InsertPosition.AS_RIGHT_SIBLING) { wtx.insertBooleanValueAsRightSibling(rtx.getBooleanValue()); } else if (insertPosition == InsertPosition.AS_LEFT_SIBLING) { wtx.insertBooleanValueAsLeftSibling(rtx.getBooleanValue()); } else { throw new IllegalStateException("Insert location not known!"); } break; case NULL_VALUE: if (insertPosition == InsertPosition.AS_FIRST_CHILD) { wtx.insertNullValueAsFirstChild(); } else if (insertPosition == InsertPosition.AS_RIGHT_SIBLING) { wtx.insertNullValueAsRightSibling(); } else if (insertPosition == InsertPosition.AS_LEFT_SIBLING) { wtx.insertNullValueAsLeftSibling(); } else { throw new IllegalStateException("Insert location not known!"); } break; case NUMBER_VALUE: if (insertPosition == InsertPosition.AS_FIRST_CHILD) { wtx.insertNumberValueAsFirstChild(rtx.getNumberValue()); } else if (insertPosition == InsertPosition.AS_RIGHT_SIBLING) { wtx.insertNumberValueAsRightSibling(rtx.getNumberValue()); } else if (insertPosition == InsertPosition.AS_LEFT_SIBLING) { wtx.insertNumberValueAsLeftSibling(rtx.getNumberValue()); } else { throw new IllegalStateException("Insert location not known!"); } break; case STRING_VALUE: if (insertPosition == InsertPosition.AS_FIRST_CHILD) { wtx.insertStringValueAsFirstChild(rtx.getValue()); } else if (insertPosition == InsertPosition.AS_RIGHT_SIBLING) { wtx.insertStringValueAsRightSibling(rtx.getValue()); } else if (insertPosition == InsertPosition.AS_LEFT_SIBLING) { wtx.insertStringValueAsLeftSibling(rtx.getValue()); } else { throw new IllegalStateException("Insert location not known!"); } break; // $CASES-OMITTED$ default: throw new IllegalStateException("Node kind not known!"); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy