@Test public void testCustomizedBsonConverter() throws Exception { BsonValueConverterRepertory.registerCustomizedBsonConverter(String.class, new AbstractBsonConverter<String, BsonString>() { @Override public String decode(BsonReader bsonReader) { return "replace string"; } @Override public void encode(BsonWriter bsonWriter, String value) { } @Override public String decode(BsonValue bsonValue) { return "replace string"; } @Override public BsonString encode(String object) { return new BsonString("replace string"); } }); readFrom(); }
static List<String> getPath(BsonValue path) { List<String> result = new ArrayList<String>(); StringBuilder builder = new StringBuilder(); String cleanPath = path.asString().getValue().replaceAll("\"", ""); for (int index = 0; index < cleanPath.length(); index++) { char c = cleanPath.charAt(index); if (c == '/') { result.add(decodePath(builder.toString())); builder.delete(0, builder.length()); } else { builder.append(c); } } result.add(decodePath(builder.toString())); return result; }
@Override public void add(List<String> path, BsonValue value) { if (path.isEmpty()) { error(Operation.ADD, "path is empty , path : "); } else { BsonValue parentNode = getParentNode(path, Operation.ADD); String fieldToReplace = path.get(path.size() - 1).replaceAll("\"", ""); if (fieldToReplace.equals("") && path.size() == 1) target = value; else if (!parentNode.isDocument() && !parentNode.isArray()) error(Operation.ADD, "parent is not a container in source, path provided : " + PathUtils.getPathRepresentation(path) + " | node : " + parentNode); else if (parentNode.isArray()) addToArray(path, value, parentNode); else addToObject(path, parentNode, value); } }
@Override public void replace(List<String> path, BsonValue value) { if (path.isEmpty()) { error(Operation.REPLACE, "path is empty"); } else { BsonValue parentNode = getParentNode(path, Operation.REPLACE); String fieldToReplace = path.get(path.size() - 1).replaceAll("\"", ""); if (isNullOrEmpty(fieldToReplace) && path.size() == 1) target = value; else if (parentNode.isDocument()) parentNode.asDocument().put(fieldToReplace, value); else if (parentNode.isArray()) parentNode.asArray().set(arrayIndex(fieldToReplace, parentNode.asArray().size() - 1, false), value); else error(Operation.REPLACE, "noSuchPath in source, path provided : " + PathUtils.getPathRepresentation(path)); } }
@Override public void remove(List<String> path) { if (path.isEmpty()) { error(Operation.REMOVE, "path is empty"); } else { BsonValue parentNode = getParentNode(path, Operation.REMOVE); String fieldToRemove = path.get(path.size() - 1).replaceAll("\"", ""); if (parentNode.isDocument()) parentNode.asDocument().remove(fieldToRemove); else if (parentNode.isArray()) { // If path specifies a non-existent array element and the REMOVE_NONE_EXISTING_ARRAY_ELEMENT flag is not set, then // arrayIndex will throw an error. int i = arrayIndex(fieldToRemove, parentNode.asArray().size() - 1, flags.contains(CompatibilityFlags.REMOVE_NONE_EXISTING_ARRAY_ELEMENT)); // However, BsonArray.remove(int) is not very forgiving, so we need to avoid making the call if the index is past the end // otherwise, we'll get an IndexArrayOutOfBounds error if (i < parentNode.asArray().size()) { parentNode.asArray().remove(i); } } else error(Operation.REMOVE, "noSuchPath in source, path provided : " + PathUtils.getPathRepresentation(path)); } }
public static BsonArray asBson(final BsonValue source, final BsonValue target, EnumSet<DiffFlags> flags) { final List<Diff> diffs = new ArrayList<Diff>(); List<Object> path = new ArrayList<Object>(0); // generating diffs in the order of their occurrence generateDiffs(diffs, path, source, target); if (!flags.contains(DiffFlags.OMIT_MOVE_OPERATION)) { // Merging remove & add to move operation compactDiffs(diffs); } if (!flags.contains(DiffFlags.OMIT_COPY_OPERATION)) { // Introduce copy operation introduceCopyOperation(source, target, diffs); } return getBsonNodes(diffs, flags); }
private static void computeUnchangedValues(Map<BsonValue, List<Object>> unchangedValues, List<Object> path, BsonValue source, BsonValue target) { if (source.equals(target)) { if (!unchangedValues.containsKey(target)) { unchangedValues.put(target, path); } return; } if (source.getBsonType().equals(target.getBsonType())) { switch (source.getBsonType()) { case DOCUMENT: computeDocument(unchangedValues, path, source, target); break; case ARRAY: computeArray(unchangedValues, path, source, target); default: /* nothing */ } } }
@Test public void testPatchAppliedCleanly() throws Exception { for (int i = 0; i < jsonNode.size(); i++) { BsonDocument node = jsonNode.get(i).asDocument(); BsonValue first = node.get("first"); BsonValue second = node.get("second"); BsonArray patch = node.getArray("patch"); String message = node.containsKey("message") ? node.getString("message").getValue() : ""; // System.out.println("Test # " + i); // System.out.println(first); // System.out.println(second); // System.out.println(patch); BsonValue secondPrime = BsonPatch.apply(patch, first); // System.out.println(secondPrime); Assert.assertThat(message, secondPrime, equalTo(second)); } }
@Test public void testSampleJsonDiff() throws Exception { for (int i = 0; i < jsonNode.size(); i++) { BsonValue first = jsonNode.get(i).asDocument().get("first"); BsonValue second = jsonNode.get(i).asDocument().get("second"); // System.out.println("Test # " + i); // System.out.println(first); // System.out.println(second); BsonArray actualPatch = BsonDiff.asBson(first, second); // System.out.println(actualPatch); BsonValue secondPrime = BsonPatch.apply(actualPatch, first); // System.out.println(secondPrime); Assert.assertTrue(second.equals(secondPrime)); } }
@Test public void testRenderedOperationsExceptMoveAndCopy() throws Exception { BsonDocument source = new BsonDocument(); source.put("age", new BsonInt32(10)); BsonDocument target = new BsonDocument(); target.put("height", new BsonInt32(10)); EnumSet<DiffFlags> flags = DiffFlags.dontNormalizeOpIntoMoveAndCopy().clone(); //only have ADD, REMOVE, REPLACE, Don't normalize operations into MOVE & COPY BsonArray diff = BsonDiff.asBson(source, target, flags); // System.out.println(source); // System.out.println(target); // System.out.println(diff); for (BsonValue d : diff) { Assert.assertNotEquals(Operation.MOVE.rfcName(), d.asDocument().getString("op").getValue()); Assert.assertNotEquals(Operation.COPY.rfcName(), d.asDocument().getString("op").getValue()); } BsonValue targetPrime = BsonPatch.apply(diff, source); // System.out.println(targetPrime); Assert.assertTrue(target.equals(targetPrime)); }
/** * Converts a binary uuid to a standard uuid * * @param json The json string (should contain "$binary" and "$type" or something like that) * @return The uuid */ private UUID convertBinaryUUID(String key, String json) { BsonDocument document = BsonDocument.parse(json); BsonValue value = document.get(key); if(!(value instanceof BsonBinary)) { return null; } byte[] bytes = ((BsonBinary) value).getData(); ByteBuffer bb = ByteBuffer.wrap(bytes); bb.order(ByteOrder.LITTLE_ENDIAN); long l1 = bb.getLong(); long l2 = bb.getLong(); return new UUID(l1, l2); }
private ArrayList<BsonValue> getBsonValueList(Field field, Collection values, BsonMapperConfig bsonMapperConfig, Class<?> componentType) { ArrayList<BsonValue> arrayList = new ArrayList<BsonValue>(); for (Object o : values) { if (o == null) { continue; } Class<?> oClazz = o.getClass(); if (Utils.isArrayType(oClazz)) { BsonArray innerBsonArray = new BsonArray(); encode(innerBsonArray, field, o, bsonMapperConfig); arrayList.add(innerBsonArray); } if (componentType.isInstance(o)) { if (BsonValueConverterRepertory.isCanConverterValueType(componentType)) { arrayList.add(BsonValueConverterRepertory.getValueConverterByClazz(componentType).encode(o)); } else { BsonDocument arrayEle = new BsonDocument(); BsonValueConverterRepertory.getBsonDocumentConverter().encode(arrayEle, o, bsonMapperConfig); arrayList.add(arrayEle); } } else { throw new BsonMapperConverterException(String.format("array field has element which has different type with declaring componentType.field name: %s", field.getName())); } } return arrayList; }
private Object handleArrayForBsonArray(BsonArray bsonArray, Field field, BsonMapperConfig bsonMapperConfig) { ArrayList<Object> arrayList = new ArrayList<Object>(); Class<?> fieldClazz = field.getType(); for (BsonValue bsonValue : bsonArray) { if (bsonValue == null) { continue; } if (bsonValue.isArray()) { arrayList.add(decode(bsonValue.asArray(), field, bsonMapperConfig)); } else { Object javaValue; if (bsonValue.isDocument()) { javaValue = BsonValueConverterRepertory.getBsonDocumentConverter().decode(bsonValue.asDocument(), fieldClazz.getComponentType(), bsonMapperConfig); } else { javaValue = BsonValueConverterRepertory.getValueConverterByBsonType(bsonValue.getBsonType()).decode(bsonValue); } arrayList.add(javaValue); } } return arrayList.toArray((Object[]) Array.newInstance(fieldClazz.getComponentType(), 0)); }
BsonValue toBson() { @SuppressWarnings("unchecked") Encoder<T> encoder = BsonEncoding.encoderFor((Class<T>) value.getClass(), adapter); BsonDocument bson = new BsonDocument(); org.bson.BsonWriter writer = new BsonDocumentWriter(bson); // Bson doesn't allow to write directly scalars / primitives, they have to be embedded in a document. writer.writeStartDocument(); writer.writeName("$"); encoder.encode(writer, value, EncoderContext.builder().build()); writer.writeEndDocument(); writer.flush(); return bson.get("$"); }
@Override public void move(List<String> fromPath, List<String> toPath) { BsonValue parentNode = getParentNode(fromPath, Operation.MOVE); String field = fromPath.get(fromPath.size() - 1).replaceAll("\"", ""); BsonValue valueNode = parentNode.isArray() ? parentNode.asArray().get(Integer.parseInt(field)) : parentNode.asDocument().get(field); remove(fromPath); add(toPath, valueNode); }
@Override public void copy(List<String> fromPath, List<String> toPath) { BsonValue parentNode = getParentNode(fromPath, Operation.COPY); String field = fromPath.get(fromPath.size() - 1).replaceAll("\"", ""); BsonValue valueNode = parentNode.isArray() ? parentNode.asArray().get(Integer.parseInt(field)) : parentNode.asDocument().get(field); add(toPath, valueNode); }
private BsonValue getParentNode(List<String> fromPath, Operation forOp) { List<String> pathToParent = fromPath.subList(0, fromPath.size() - 1); // would never by out of bound, lets see BsonValue node = getNode(target, pathToParent, 1); if (node == null) error(forOp, "noSuchPath in source, path provided: " + PathUtils.getPathRepresentation(fromPath)); return node; }
static List<BsonValue> toList(BsonArray input) { int size = input.size(); List<BsonValue> toReturn = new ArrayList<BsonValue>(size); for (int i = 0; i < size; i++) { toReturn.add(input.get(i)); } return toReturn; }
private static BsonValue getPatchAttrWithDefault(BsonValue bsonNode, String attr, BsonValue defaultValue) { BsonValue child = bsonNode.asDocument().get(attr); if (child == null) return defaultValue; else return child; }
@Override public Object get(Object key) { BsonValue bsonValue = innerBsonDocument.get(key); if (bsonValue == null) { return null; } return BsonValueConverterRepertory.getValueConverterByClazz(bsonValue.getClass()).decode(bsonValue); }
private static void introduceCopyOperation(BsonValue source, BsonValue target, List<Diff> diffs) { Map<BsonValue, List<Object>> unchangedValues = getUnchangedPart(source, target); for (int i = 0; i < diffs.size(); i++) { Diff diff = diffs.get(i); if (Operation.ADD == diff.getOperation()) { List<Object> matchingValuePath = getMatchingValuePath(unchangedValues, diff.getValue()); if (matchingValuePath != null && isAllowed(matchingValuePath, diff.getPath())) { diffs.set(i, new Diff(Operation.COPY, matchingValuePath, diff.getPath())); } } } }
private static void computeArray(Map<BsonValue, List<Object>> unchangedValues, List<Object> path, BsonValue source, BsonValue target) { final int size = Math.min(source.asArray().size(), target.asArray().size()); for (int i = 0; i < size; i++) { List<Object> currPath = getPath(path, i); computeUnchangedValues(unchangedValues, currPath, source.asArray().get(i), target.asArray().get(i)); } }
private static void computeDocument(Map<BsonValue, List<Object>> unchangedValues, List<Object> path, BsonValue source, BsonValue target) { final Iterator<String> firstFields = source.asDocument().keySet().iterator(); while (firstFields.hasNext()) { String name = firstFields.next(); if (target.asDocument().containsKey(name)) { List<Object> currPath = getPath(path, name); computeUnchangedValues(unchangedValues, currPath, source.asDocument().get(name), target.asDocument().get(name)); } } }
private static void generateDiffs(List<Diff> diffs, List<Object> path, BsonValue source, BsonValue target) { if (!source.equals(target)) { if (source.isArray() && target.isArray()) { //both are arrays compareArray(diffs, path, source, target); } else if (source.isDocument() && target.isDocument()) { //both are json compareDocuments(diffs, path, source, target); } else { //can be replaced diffs.add(Diff.generateDiff(Operation.REPLACE, path, source, target)); } } }
private static Integer removeRemaining(List<Diff> diffs, List<Object> path, int pos, int srcIdx, int srcSize, BsonValue source) { while (srcIdx < srcSize) { List<Object> currPath = getPath(path, pos); diffs.add(Diff.generateDiff(Operation.REMOVE, currPath, source.asArray().get(srcIdx))); srcIdx++; } return pos; }
private void testOperation() throws Exception { BsonDocument node = p.getNode(); BsonValue doc = node.get("node"); BsonValue expected = node.get("expected"); BsonArray patch = node.getArray("op"); String message = node.containsKey("message") ? node.getString("message").getValue() : ""; BsonValue result = BsonPatch.apply(patch, doc); String failMessage = "The following test failed: \n" + "message: " + message + '\n' + "at: " + p.getSourceFile(); assertEquals(failMessage, expected, result); }
private void testError() throws ClassNotFoundException { BsonDocument node = p.getNode(); BsonValue first = node.get("node"); BsonArray patch = node.getArray("op"); String message = node.containsKey("message") ? node.getString("message").getValue() : ""; Class<?> type = node.containsKey("type") ? exceptionType(node.getString("type").getValue()) : BsonPatchApplicationException.class; try { BsonPatch.apply(patch, first); fail(errorMessage("Failure expected: " + message)); } catch (Exception e) { if (matchOnErrors()) { StringWriter fullError = new StringWriter(); e.printStackTrace(new PrintWriter(fullError)); assertThat( errorMessage("Operation failed but with wrong exception type", e), e, instanceOf(type)); if (message != null) { assertThat( errorMessage("Operation failed but with wrong message", e), e.getMessage(), containsString(message)); // equalTo would be better, but fail existing tests } } } }
@Test public void testMoveArrayGeneratedHasNoValue() throws IOException { BsonValue jsonNode1 = BsonDocument.parse("{ \"foo\": [ \"all\", \"grass\", \"cows\", \"eat\" ] }"); BsonValue jsonNode2 = BsonDocument.parse("{ \"foo\": [ \"all\", \"cows\", \"eat\", \"grass\" ] }"); BsonArray patch = BsonArray.parse("[{\"op\":\"move\",\"from\":\"/foo/1\",\"path\":\"/foo/3\"}]"); BsonArray diff = BsonDiff.asBson(jsonNode1, jsonNode2); assertThat(diff, equalTo(patch)); }
@Override public Collection<Object> values() { Collection<Object> result = new ArrayList<Object>(innerBsonDocument.size()); for (BsonValue bsonValue : innerBsonDocument.values()) { result.add(BsonValueConverterRepertory.getValueConverterByBsonType(bsonValue.getBsonType()).decode(bsonValue)); } return result; }
@Override public <P> P get(Object key, Class<P> clazz) { BsonValue bsonValue = innerBsonDocument.get(key); if (bsonValue != null && BsonValueConverterRepertory.isCanConverterValueType(clazz)) { return (P) BsonValueConverterRepertory.getValueConverterByClazz(clazz).decode(bsonValue); } else { return null; } }
@Override public BsonValue getDocumentId(T document) { PolymorphicCodec<T> codecForValue = getCodecForValue(document); if (codecForValue != null) { return codecForValue.getDocumentId(document); } return null; }
private Object getJavaValueFromBsonValue(BsonValue bsonValue, Field field, BsonMapperConfig bsonMapperConfig) { if (bsonValue.isArray()) { return BsonValueConverterRepertory.getBsonArrayConverter().decode(bsonValue.asArray(), field, bsonMapperConfig); } if (bsonValue.isDocument()) { return BsonValueConverterRepertory.getBsonDocumentConverter().decode(bsonValue.asDocument(), field.getType(), bsonMapperConfig); } if (bsonValue.isObjectId() && Utils.fieldIsObjectId(field)) { ObjectId objectId = (ObjectId) BsonValueConverterRepertory.getValueConverterByBsonType(bsonValue.getBsonType()).decode(bsonValue); return getObjectIdByRealType(field.getType(), objectId); } return BsonValueConverterRepertory.getValueConverterByBsonType(bsonValue.getBsonType()).decode(bsonValue); }
@Override public Document writeToMongoDocument(Object obj) { if (obj == null) { return null; } BsonDocument bsonDocument = new BsonDocument(); BsonValueConverterRepertory.getBsonDocumentConverter().encode(bsonDocument, obj, bsonMapperConfig); Document document = new Document(); for (Entry<String, BsonValue> entry : bsonDocument.entrySet()) { document.put(entry.getKey(), entry.getValue()); } return document; }
InPlaceApplyProcessor(BsonValue target) { this(target, CompatibilityFlags.defaults()); }
InPlaceApplyProcessor(BsonValue target, EnumSet<CompatibilityFlags> flags) { this.target = target; this.flags = flags; }
public BsonValue result() { return target; }
private void addToObject(List<String> path, BsonValue node, BsonValue value) { final BsonDocument target = node.asDocument(); String key = path.get(path.size() - 1).replaceAll("\"", ""); target.put(key, value); }
@Override public String decode(BsonValue bsonValue) { return bsonValue.asString().getValue(); }
@Override public Code decode(BsonValue bsonValue) { return new Code(bsonValue.asJavaScript().getCode()); }
Diff(Operation operation, List<Object> path, BsonValue value) { this.operation = operation; this.path = path; this.value = value; this.srcValue = null; }