/* * Copyright 2015-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.data.mongodb.test.util; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; import org.bson.Document; import org.bson.conversions.Bson; import org.hamcrest.Description; import org.hamcrest.TypeSafeMatcher; import org.hamcrest.core.IsEqual; import org.springframework.data.mongodb.core.query.SerializationUtils; import org.springframework.util.ClassUtils; /** * @author Christoph Strobl * @param <T> */ public class IsBsonObject<T extends Bson> extends TypeSafeMatcher<T> { private List<ExpectedBsonContent> expectations = new ArrayList<>(); private Integer expectedSize; public static <T extends Bson> IsBsonObject<T> isBsonObject() { return new IsBsonObject<T>(); } @Override protected void describeMismatchSafely(T item, Description mismatchDescription) { mismatchDescription.appendText("was ").appendValue(SerializationUtils.serializeToJsonSafely(item)); } @Override public void describeTo(Description description) { if (expectedSize != null) { description.appendText(String.format("Expected to contain %s fields. ", expectedSize)); } for (ExpectedBsonContent expectation : expectations) { if (expectation.not) { description.appendText(String.format("Path %s should not be present. ", expectation.path)); } else if (expectation.value == null) { description.appendText(String.format("Expected to find path %s. ", expectation.path)); } else { description.appendText(String.format("Expected to find %s for path %s. ", expectation.value, expectation.path)); } } } @Override protected boolean matchesSafely(T item) { if (expectedSize != null && item instanceof Document) { Document document = (Document) item; if (expectedSize != document.keySet().size()) { return false; } } if (expectations.isEmpty()) { return true; } for (ExpectedBsonContent expectation : expectations) { Object o = getValue(item, expectation.path); if (o == null && expectation.not) { return true; } if (o == null) { return false; } if (expectation.type != null) { if (ClassUtils.isAssignable(List.class, expectation.type) && ClassUtils.isAssignable(List.class, o.getClass())) { return true; } return ClassUtils.isAssignable(expectation.type, o.getClass()); } if (expectation.value != null && !new IsEqual<Object>(expectation.value).matches(o)) { return false; } if (o != null && expectation.not) { return false; } } return true; } public IsBsonObject<T> containing(String key) { ExpectedBsonContent expected = new ExpectedBsonContent(); expected.path = key; this.expectations.add(expected); return this; } public IsBsonObject<T> containing(String key, Class<?> type) { ExpectedBsonContent expected = new ExpectedBsonContent(); expected.path = key; expected.type = type; this.expectations.add(expected); return this; } public IsBsonObject<T> containing(String key, Object value) { if (value == null) { return notContaining(key); } ExpectedBsonContent expected = new ExpectedBsonContent(); expected.path = key; expected.type = ClassUtils.getUserClass(value); expected.value = value; this.expectations.add(expected); return this; } public IsBsonObject<T> notContaining(String key) { ExpectedBsonContent expected = new ExpectedBsonContent(); expected.path = key; expected.not = true; this.expectations.add(expected); return this; } public IsBsonObject<T> withSize(int size) { this.expectedSize = Integer.valueOf(size); return this; } static class ExpectedBsonContent { String path; Class<?> type; Object value; boolean not = false; } Object getValue(Bson source, String path) { String[] fragments = path.split("(?<!\\\\)\\."); if (fragments.length == 1) { return ((Document) source).get(path.replace("\\.", ".")); } Iterator<String> it = Arrays.asList(fragments).iterator(); Object current = source; while (it.hasNext()) { String key = it.next().replace("\\.", "."); if (!(current instanceof Bson) && !key.startsWith("[")) { return null; } if (key.startsWith("[")) { String indexNumber = key.substring(1, key.indexOf("]")); if (current instanceof List) { current = ((List) current).get(Integer.valueOf(indexNumber)); } if (!it.hasNext()) { return current; } } else { if (current instanceof Document) { current = ((Document) current).get(key); } if (!it.hasNext()) { return current; } } } throw new NoSuchElementException(String.format("Unable to find '%s' in %s.", path, source)); } }