org.glassfish.admin.rest.testing.DataVerifier Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of payara-micro Show documentation
Show all versions of payara-micro Show documentation
Micro Distribution of the Payara Project
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2013 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*
* Portions Copyright [2017-2021] Payara Foundation and/or affiliates
*/
package org.glassfish.admin.rest.testing;
import java.util.Map;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import jakarta.json.JsonArray;
import jakarta.json.JsonException;
import jakarta.json.JsonObject;
public class DataVerifier {
private Environment env;
private ObjectValue objectWant;
private JsonObject objectHave;
private IndentingStringBuffer sb = new IndentingStringBuffer();
public DataVerifier(Environment env, ObjectValue objectWant, JsonObject objectHave) {
this.env = env;
this.objectWant = objectWant;
this.objectHave = objectHave;
}
private void trace(String msg) {
this.sb.println(msg);
}
private void indent() {
this.sb.indent();
}
private void undent() {
this.sb.undent();
}
private Environment getEnvironment() {
return this.env;
}
public static void verify(Environment env, ObjectValue objectWant, JsonObject objectHave) throws Exception {
IndentingStringBuffer sb = new IndentingStringBuffer();
objectWant.print(sb);
env.debug("Body want : " + sb.toString());
env.debug("Body have : " + objectHave.toString());
(new DataVerifier(env, objectWant, objectHave)).verify();
}
private void verify() throws Exception {
try {
if (!sameProperties(this.objectWant.getProperties(), this.objectHave, this.objectWant.isIgnoreExtra())) {
trace("Response object want=");
this.objectWant.print(sb);
trace("Response object have=");
indent();
try {
trace(this.objectHave.toString());
} finally {
undent();
}
throw new IllegalArgumentException("Response object does not match expected value");
} else {
trace("same response bodies");
getEnvironment().debug(this.sb.toString());
}
} catch (Exception e) {
throw new Exception("Exception verifying resource response object\n" + this.sb.toString(), e);
}
}
private boolean sameProperties(Map want, JsonObject have, boolean ignoreExtra) throws Exception {
trace("comparing properties, ignoreExtra=" + ignoreExtra);
indent();
try {
if (want.size() > have.size()) {
trace("different since object has too few properties");
return false;
}
if (!(ignoreExtra) && want.size() < have.size()) {
trace("different since object has too many properties want=" + want.size() + " have=" + have.size());
return false;
}
for (Map.Entry p : want.entrySet()) {
if (!sameProperty(p.getKey(), p.getValue(), have)) {
trace("different since object didn't have matching " + p.getKey() + " property");
return false;
}
}
trace("same properties");
return true;
} finally {
undent();
}
}
private boolean sameArray(ArrayValue want, JsonArray have) throws Exception {
trace("comparing arrays, ignoreExtra=" + want.isIgnoreExtra() + ", ordered=" + want.isOrdered());
indent();
try {
boolean ignoreExtra = want.isIgnoreExtra();
boolean ordered = want.isOrdered();
List wantVals = want.getValues();
if (ordered && ignoreExtra) {
throw new AssertionError("ignore-extra must be false if ordered is true");
}
if (wantVals.size() > have.size()) {
trace("different since array has too few elements");
return false;
}
if (!ignoreExtra && wantVals.size() < have.size()) {
trace("diffent since array has too many elements");
return false;
}
if (ordered) {
for (int i = 0; i < wantVals.size(); i++) {
if (!sameValue(wantVals.get(i), have, i)) {
trace("different since array element " + i + " didn't match");
return false;
}
}
trace("same ordered elements");
return true;
} else {
if ((new UnorderedArrayMatcher(want, have)).matches()) {
trace("same unordered elements");
return true;
} else {
trace("different unorder elements");
return false;
}
}
} finally {
undent();
}
}
private boolean sameProperty(String nameWant, Value valueWant, JsonObject have) throws Exception {
trace("comparing property " + nameWant);
indent();
try {
if (!have.containsKey(nameWant)) {
trace("missing property " + nameWant);
return false;
}
if (!sameValue(valueWant, have, nameWant)) {
trace("different value for property " + nameWant);
return false;
}
trace("same property " + nameWant);
return true;
} finally {
undent();
}
}
private boolean sameValue(Value want, JsonObject parent, String name) throws Exception {
if (want instanceof ObjectValue) {
return sameObject((ObjectValue) want, parent, name);
}
if (want instanceof ArrayValue) {
return sameArray((ArrayValue) want, parent, name);
}
if (want instanceof ScalarValue) {
return sameScalar((ScalarValue) want, parent, name);
}
if (want instanceof NilValue) {
return sameNil(parent, name);
}
throw new AssertionError("Unknown value " + want);
}
private boolean sameValue(Value want, JsonArray parent, int index) throws Exception {
if (want instanceof ObjectValue) {
return sameObject((ObjectValue) want, parent, index);
}
if (want instanceof ArrayValue) {
return sameArray((ArrayValue) want, parent, index);
}
if (want instanceof ScalarValue) {
return sameScalar((ScalarValue) want, parent, index);
}
if (want instanceof NilValue) {
return sameNil(parent, index);
}
throw new AssertionError("Unknown value " + want);
}
private boolean sameObject(ObjectValue want, JsonObject parent, String name) throws Exception {
trace("comparing object object property " + name);
indent();
try {
try {
JsonObject have = parent.getJsonObject(name);
if (sameProperties(want.getProperties(), have, want.isIgnoreExtra())) {
trace("same object");
return true;
} else {
trace("different object");
return false;
}
} catch (JsonException e) {
trace("different since property was not an object");
return false;
}
} finally {
undent();
}
}
private boolean sameObject(ObjectValue want, JsonArray parent, int index) throws Exception {
trace("comparing array object element " + index);
indent();
try {
try {
JsonObject have = parent.getJsonObject(index);
if (sameProperties(want.getProperties(), have, want.isIgnoreExtra())) {
trace("same object");
return true;
} else {
trace("different object");
return false;
}
} catch (JsonException e) {
trace("different since property was not an object");
return false;
}
} finally {
undent();
}
}
private boolean sameArray(ArrayValue want, JsonObject parent, String name) throws Exception {
trace("comparing object array property " + name);
indent();
try {
try {
if (sameArray(want, parent.getJsonArray(name))) {
trace("same array");
return true;
} else {
trace("different array");
return false;
}
} catch (JsonException e) {
trace("different since property was not an array");
return false;
}
} finally {
undent();
}
}
private boolean sameArray(ArrayValue want, JsonArray parent, int index) throws Exception {
trace("comparing array array element " + index);
indent();
try {
try {
if (sameArray(want, parent.getJsonArray(index))) {
trace("same array");
return true;
} else {
trace("different array");
return false;
}
} catch (JsonException e) {
trace("different since property was not an array");
return false;
}
} finally {
undent();
}
}
private boolean sameScalar(ScalarValue want, JsonObject parent, String name) throws Exception {
trace("comparing object scalar property " + name);
String regexp = want.getRegexp();
if (want instanceof StringValue) {
return sameString(((StringValue) want).getValue(), regexp, parent.getString(name));
}
if (want instanceof LongValue) {
return sameString(longToString(((LongValue) want).getValue()), regexp, longToString(parent.getJsonNumber(name).longValue()));
}
if (want instanceof IntValue) {
return sameString(intToString(((IntValue) want).getValue()), regexp, intToString(parent.getInt(name)));
}
if (want instanceof DoubleValue) {
return sameString(doubleToString(((DoubleValue) want).getValue()), regexp, doubleToString(parent.getJsonNumber(name).doubleValue()));
}
if (want instanceof BooleanValue) {
return sameString(booleanToString(((BooleanValue) want).getValue()), regexp, booleanToString(parent.getBoolean(name)));
}
throw new AssertionError(want + " is not a valid scalar type. Valid types are string, long, int, double, boolean");
}
private boolean sameScalar(ScalarValue want, JsonArray parent, int index) throws Exception {
trace("comparing array scalar element " + index);
String regexp = want.getRegexp();
if (want instanceof StringValue) {
return sameString(((StringValue) want).getValue(), regexp, parent.getString(index));
}
if (want instanceof LongValue) {
return sameString(longToString(((LongValue) want).getValue()), regexp, longToString(parent.getJsonNumber(index).longValue()));
}
if (want instanceof IntValue) {
return sameString(intToString(((IntValue) want).getValue()), regexp, intToString(parent.getInt(index)));
}
if (want instanceof DoubleValue) {
return sameString(doubleToString(((DoubleValue) want).getValue()), regexp, doubleToString(parent.getJsonNumber(index).doubleValue()));
}
if (want instanceof BooleanValue) {
return sameString(booleanToString(((BooleanValue) want).getValue()), regexp, booleanToString(parent.getBoolean(index)));
}
throw new AssertionError(want + " is not a valid scalar type. Valid types are string, long, int, double, boolean");
}
private String longToString(long val) {
return Long.toString(val);
}
private String intToString(int val) {
return Integer.toString(val);
}
private String doubleToString(double val) {
return Double.toHexString(val);
}
private String booleanToString(boolean val) {
return Boolean.toString(val);
}
private boolean sameString(String want, String regexp, String have) throws Exception {
if (Common.haveValue(regexp)) {
return sameRegexpString(regexp, have);
} else {
return sameLiteralString(want, have);
}
}
private boolean sameRegexpString(String regexp, String have) throws Exception {
trace("comparing string against regular expression regexp='" + regexp + "', have='" + have + "'");
indent();
try {
Pattern pattern = Pattern.compile(regexp);
Matcher matcher = pattern.matcher(have);
if (matcher.matches()) {
trace("same since matches regular expression");
return true;
} else {
trace("different since does not match regular expression");
return false;
}
} finally {
undent();
}
}
private boolean sameLiteralString(String want, String have) throws Exception {
trace("comparing strings want='" + want + "', have='" + have + "'");
indent();
try {
if (Common.haveValue(want) != Common.haveValue(have)) {
trace("different strings - one is null and the other is not");
return false;
}
if (!Common.haveValue(want)) {
trace("same empty strings");
return true;
}
if (want.equals(have)) {
trace("same literal value");
return true;
} else {
trace("different literal value");
return false;
}
} finally {
undent();
}
}
private boolean sameNil(JsonObject parent, String name) {
trace("comparing object nil property " + name);
indent();
try {
if (parent.isNull(name)) {
trace("same nil");
return true;
} else {
trace("different nil");
return false;
}
} finally {
undent();
}
}
private boolean sameNil(JsonArray parent, int index) {
trace("comparing array nil element " + index);
indent();
try {
if (parent.isNull(index)) {
trace("same nil");
return true;
} else {
trace("different nil");
return false;
}
} finally {
undent();
}
}
// tbd - speed this up by finding out if the value is exact or flexible (ie. regexp or ignoreExtra) - if exact, then grab it as soon as we have a match
private class UnorderedArrayMatcher {
private int matchCount = 0;
private ArrayValue want;
private List wantValues;
private JsonArray have;
private int[] wantMatches; // an entry for each want element. -1 means it hasn't been matched yet. otherwise, the index of the have element we're matched with
private int[] haveMatches; // an entry for each have element. -1 means it hasn't been matched yet. otherwise, the index of the want element we're matched with
private boolean[][] potentialMatches; // wantPotentialMatches[wantIndex][haveIndex] indicates if want matches have
private UnorderedArrayMatcher(ArrayValue want, JsonArray have) {
this.want = want;
this.wantValues = this.want.getValues();
this.have = have;
this.wantMatches = new int[this.wantValues.size()];
this.haveMatches = new int[this.have.size()];
for (int i = 0; i < wantCount(); i++) {
setWantMatch(i, -1);
}
for (int i = 0; i < haveCount(); i++) {
setHaveMatch(i, -1);
}
this.potentialMatches = new boolean[wantCount()][];
for (int i = 0; i < this.wantMatches.length; i++) {
this.potentialMatches[i] = new boolean[haveCount()];
for (int j = 0; j < haveCount(); j++) {
setPotentialMatch(i, j, false);
}
}
}
private boolean matches() throws Exception {
findExactMatches();
if (!done()) {
findPotentialMatches();
while (!done()) {
findMatches();
if (!done()) {
selectAnyMatch();
}
}
}
return result();
}
private void findExactMatches() throws Exception {
for (int i = 0; i < wantCount(); i++) {
Value v = getWantValue(i);
if (requiresExactMatch(v)) {
for (int j = 0; j < haveCount(); j++) {
if (!haveHaveMatch(j) && sameValue(v, this.have, j)) {
matched(i, j);
break;
}
}
if (!haveWantMatch(i)) {
notMatched(i);
return;
}
}
}
}
private void findPotentialMatches() throws Exception {
for (int i = 0; i < wantCount(); i++) {
if (!haveWantMatch(i)) {
for (int j = 0; j < haveCount(); j++) {
if (!haveHaveMatch(j) && sameValue(getWantValue(i), this.have, j)) {
setPotentialMatch(i, j, true);
}
}
int c = potentialMatchCount(i);
if (c == 0) {
notMatched(i);
return;
}
if (c == 1) {
for (int j = 0; j < haveCount(); j++) {
if (isPotentialMatch(i, j)) {
matched(i, j);
}
}
}
}
}
}
// keep going until we run out of matches
private void findMatches() throws Exception {
boolean modified = true;
while (modified) {
modified = false;
for (int i = 0; i < wantCount(); i++) {
if (!haveWantMatch(i)) {
int c = potentialMatchCount(i);
// we're all done if I don't match anyone
if (c == 0) {
notMatched(i);
return;
}
// if I only have one match, grab it
if (c == 1) {
for (int j = 0; j < haveCount(); j++) {
if (isPotentialMatch(i, j)) {
matched(i, j);
modified = true;
}
}
}
// if I have a potential match that no one else has, grab it
if (c > 1) {
for (int j = 0; j < haveCount(); j++) {
if (isPotentialMatch(i, j) && !matchedBySomeoneElse(i, j)) {
matched(i, j); // this empties out all my other potential matches, which will terminate the 'have' loop
modified = true;
}
}
}
}
}
}
}
// we've found all the unambiguous matches we can
// select a match to break the deadlock.
private void selectAnyMatch() throws Exception {
for (int i = 0; i < wantCount(); i++) {
if (!haveWantMatch(i)) {
for (int j = 0; j < haveCount(); j++) {
if (!haveHaveMatch(j)) {
matched(i, j);
return;
}
}
}
}
}
private boolean matchedBySomeoneElse(int wantIndex, int haveIndex) {
for (int i = 0; i < wantCount(); i++) {
if (i != wantIndex) { // ignore me
if (isPotentialMatch(i, haveIndex)) {
return true;
}
}
}
return false;
}
private void notMatched(int wantIndex) {
this.matchCount = -1; // there is no match
trace("Different since no match for array element " + wantIndex);
}
private void matched(int wantIndex, int haveIndex) {
trace("matched array element want=" + wantIndex + " have=" + haveIndex);
// record the match
this.matchCount++;
setWantMatch(wantIndex, haveIndex);
setHaveMatch(haveIndex, wantIndex);
setPotentialMatch(wantIndex, haveIndex, true);
// i can't match anyone else
for (int i = 0; i < haveCount(); i++) {
if (i != haveIndex) {
setPotentialMatch(wantIndex, i, false);
}
}
// no one else can have this match
for (int i = 0; i < wantCount(); i++) {
if (i != wantIndex) {
setPotentialMatch(i, haveIndex, false);
}
}
}
private boolean done() {
if (this.matchCount == -1) {
return true;
}
if (this.matchCount == this.wantMatches.length) {
return true;
}
return false;
}
private boolean result() {
if (!done()) {
throw new AssertionError("Asking for result before we're done finding matches.");
}
return (this.matchCount == -1) ? false : true;
}
private boolean isPotentialMatch(int wantIndex, int haveIndex) {
return this.potentialMatches[wantIndex][haveIndex];
}
private void setPotentialMatch(int wantIndex, int haveIndex, boolean matches) {
this.potentialMatches[wantIndex][haveIndex] = matches;
}
private int wantCount() {
return this.wantMatches.length;
}
private int haveCount() {
return this.haveMatches.length;
}
private boolean haveWantMatch(int wantIndex) {
return (getWantMatch(wantIndex) != -1);
}
private boolean haveHaveMatch(int haveIndex) {
return (getHaveMatch(haveIndex) != -1);
}
private int getWantMatch(int wantIndex) {
return this.wantMatches[wantIndex];
}
private int getHaveMatch(int haveIndex) {
return this.haveMatches[haveIndex];
}
private void setWantMatch(int wantIndex, int haveIndex) {
this.wantMatches[wantIndex] = haveIndex;
}
private void setHaveMatch(int haveIndex, int wantIndex) {
this.haveMatches[haveIndex] = wantIndex;
}
private int potentialMatchCount(int wantIndex) {
int count = 0;
for (int i = 0; i < haveCount(); i++) {
if (isPotentialMatch(wantIndex, i)) {
count++;
}
}
return count;
}
private Value getWantValue(int index) {
return this.wantValues.get(index);
}
private boolean requiresExactMatch(Value val) {
if (val instanceof ObjectValue) {
ObjectValue v = (ObjectValue) val;
if (v.isIgnoreExtra()) {
return false;
}
for (Map.Entry p : v.getProperties().entrySet()) {
if (!requiresExactMatch(p.getValue())) {
return false;
}
}
return true;
}
if (val instanceof ArrayValue) {
ArrayValue v = (ArrayValue) val;
if (v.isIgnoreExtra() || !(v.isOrdered())) {
return false;
}
for (Value e : v.getValues()) {
if (!requiresExactMatch(e)) {
return false;
}
}
return true;
}
if (val instanceof ScalarValue) {
ScalarValue v = (ScalarValue) val;
if (Common.haveValue(v.getRegexp())) {
return false;
}
return true;
}
if (val instanceof NilValue) {
return true;
}
throw new AssertionError("Unknown value " + want);
}
}
}