Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

A proposal for supporting the JSON-P types of the javax.json package. #220

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
implement the adaptation SPI using JSON-P types
ceharris committed Sep 13, 2018
commit e74ffa2dd8a9480ee9d0607c1bbddb8729c19704
6 changes: 6 additions & 0 deletions core/pom.xml
Original file line number Diff line number Diff line change
@@ -139,6 +139,12 @@
<artifactId>json</artifactId>
<version>20180130</version>
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.json</artifactId>
<version>1.1.2</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
package org.everit.json.schema.javax.json;

import org.everit.json.schema.spi.JsonAdaptation;
import org.everit.json.schema.spi.JsonAdapter;

import javax.json.*;
import java.math.BigDecimal;
import java.math.BigInteger;

/**
* A {@link JsonAdaptation} for the standard Java JSON types in the {@code javax.json}
* package.
* <p>
* The current specification for these types is part of the JSON-P specification
* (JSR-374), which is part of the Java EE 8 platform. An earlier version of the JSON-P
* specification (JSR-353) was used in the Java EE 7 platform.
* <p>
* Because the later specification provides some improvements to the API needed to support
* these types with JSON schema validation, we want to use the later version where possible.
* However, most Java EE 7 containers will have the older JSR-353 API.
* <p>
* Using the {@link #newInstance()} method, a user of this adaptation can get an instance
* that uses the latest version of the JSON-P that is available at runtime.
*/
public abstract class JavaxJsonAdaptation implements JsonAdaptation<JsonValue> {

private static final String JSR_374_ADAPTATION = "Jsr374Adaptation";
private static final String JSR_353_ADAPTATION = "Jsr353Adaptation";

@Override
public Class<?> arrayType() {
return JsonArray.class;
}

@Override
public Class<?> objectType() {
return JsonObject.class;
}

@Override
public Class<?>[] supportedTypes() {
return new Class<?>[] { JsonValue.class };
}

/**
* Creates a new instance of an adaptation for the standard JSON types using the
* latest version of the JSON-P implementation that is available via either the
* thread context class loader (if one is set on the calling thread) or from the
* same class loader that loaded this class.
*
* @return adaptation instance for JSON-P data types
* @throws RuntimeException if the adaptation class cannot be instantiated
*/
public static JavaxJsonAdaptation newInstance() {
if (Thread.currentThread().getContextClassLoader() != null) {
return newInstance(Thread.currentThread().getContextClassLoader());
}
return newInstance(JavaxJsonAdaptation.class.getClassLoader());
}

/**
* Creates a new instance of an adaptation for the standard JSON types using the
* latest version of the JSON-P implementation that is available on the specified
* class loader.
* <p>
* This method may be use in dynamic modular runtime environments such as those
* provided by OSGi.
*
* @param classLoader the class loader to use to find the JSON-P API and
* implementation classes
* @return adaptation instance for JSON-P data types
* @throws RuntimeException if the adaptation class cannot be instantiated
*/
public static JavaxJsonAdaptation newInstance(ClassLoader classLoader) {
try {
Json.class.getMethod("createValue", String.class);
return newInstance(classLoader, JSR_374_ADAPTATION);
} catch (NoSuchMethodException ex) {
return newInstance(classLoader, JSR_353_ADAPTATION);
}
}

/**
* Creates a new adaptation instance using the given class loader to load a
* specific provider name.
* @param classLoader source class loader for the JSON-P types
* @param providerName name of the {@link JavaxJsonAdaptation} to load
* @return adaptation instance
* @throws RuntimeException an instance of the specified type cannot be instantiated
*/
private static JavaxJsonAdaptation newInstance(ClassLoader classLoader,
String providerName) {
try {
return (JavaxJsonAdaptation) classLoader.loadClass(
providerClassName(providerName)).newInstance();
}
catch (ClassNotFoundException | InstantiationException | IllegalAccessException ex) {
throw new RuntimeException(ex);
}
}

/**
* Constructs the fully-qualified class name for a given named provider class.
* <p>
* It is assumed that all providers are subtypes of this abstract base type and
* are located in the same package.
*
* @param providerName provider class name
* @return fully qualified class name
*/
private static String providerClassName(String providerName) {
return JavaxJsonAdaptation.class.getPackage().getName() + "." + providerName;
}

@Override
public boolean isSupportedType(Class<?> type) {
return JsonValue.class.isAssignableFrom(type);
}

@Override
public boolean isNull(Object value) {
return value == null || value == JsonValue.NULL;
}

@Override
public Object adapt(Object value) {
if (value == JsonValue.TRUE) {
return true;
} else if (value == JsonValue.FALSE) {
return false;
} else if (value instanceof JsonString) {
return ((JsonString) value).getString();
} else if (value instanceof JsonNumber) {
return ((JsonNumber) value).bigDecimalValue();
} else if (value instanceof JsonArray) {
return new JavaxJsonArrayAdapter((JsonArray) value);
} else if (value instanceof JsonObject) {
return new JavaxJsonObjectAdapter((JsonObject) value);
} else {
return value;
}
}

@Override
public JsonValue invert(Object value) {
if (value == null) {
return JsonValue.NULL;
}
if (value instanceof JsonAdapter) {
return (JsonValue) ((JsonAdapter) value).unwrap();
} else if (value instanceof String) {
return createValue((String) value);
} else if (value instanceof Boolean) {
return ((boolean) value) ? JsonValue.TRUE : JsonValue.FALSE;
} else if (value instanceof Integer || value instanceof Byte || value instanceof Short) {
return createValue((int) value);
} else if (value instanceof Long) {
return createValue((long) value);
} else if (value instanceof Double || value instanceof Float) {
return createValue((double) value);
} else if (value instanceof BigInteger) {
return createValue((BigInteger) value);
} else if (value instanceof BigDecimal) {
return createValue((BigDecimal) value);
} else {
throw new AssertionError("unrecognized intrinsic type");
}
}

/**
* Creates a {@link JsonValue} representing the given string using a provider-specific
* mechanism.
* @param value the subject string value
* @return JSON value instance
*/
abstract JsonValue createValue(String value);

/**
* Creates a {@link JsonValue} representing the given integer using a provider-specific
* mechanism.
* @param value the subject integer value
* @return JSON value instance
*/
abstract JsonValue createValue(int value);

/**
* Creates a {@link JsonValue} representing the given long using a provider-specific
* mechanism.
* @param value the subject long value
* @return JSON value instance
*/
abstract JsonValue createValue(long value);

/**
* Creates a {@link JsonValue} representing the given double using a provider-specific
* mechanism.
* @param value the subject double value
* @return JSON value instance
*/
abstract JsonValue createValue(double value);

/**
* Creates a {@link JsonValue} representing the given big integer using a
* provider-specific mechanism.
* @param value the subject big integer value
* @return JSON value instance
*/
abstract JsonValue createValue(BigInteger value);

/**
* Creates a {@link JsonValue} representing the given big decimal using a
* provider-specific mechanism.
* @param value the subject big decimal value
* @return JSON value instance
*/
abstract JsonValue createValue(BigDecimal value);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package org.everit.json.schema.javax.json;

import org.everit.json.schema.spi.JsonArrayAdapter;

import javax.json.JsonArray;
import javax.json.JsonValue;
import java.util.Collections;
import java.util.List;

/**
* A {@link JsonArrayAdapter} that delegates to a JSON-P {@link JsonArray}.
*/
class JavaxJsonArrayAdapter implements JsonArrayAdapter<JsonValue> {

private final JsonArray delegate;

JavaxJsonArrayAdapter(JsonArray delegate) {
this.delegate = delegate;
}

@Override
public int length() {
return delegate.size();
}

@Override
public JsonValue get(int index) {
return delegate.get(index);
}

@Override
public void put(int index, JsonValue value) {
delegate.set(index, value);
}

@Override
public List<JsonValue> toList() {
return Collections.unmodifiableList(delegate);
}

@Override
public JsonValue unwrap() {
return delegate;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package org.everit.json.schema.javax.json;

import org.everit.json.schema.spi.JsonObjectAdapter;

import javax.json.JsonObject;
import javax.json.JsonValue;
import java.util.Collections;
import java.util.Map;

/**
* A {@link JsonObjectAdapter} that delegates to a JSON-P {@link JsonObject}.
*/
class JavaxJsonObjectAdapter implements JsonObjectAdapter<JsonValue> {

private final JsonObject delegate;

JavaxJsonObjectAdapter(JsonObject delegate) {
this.delegate = delegate;
}

@Override
public int length() {
return delegate.size();
}

@Override
public String[] keys() {
return delegate.keySet().toArray(new String[0]);
}

@Override
public boolean has(String key) {
return delegate.containsKey(key);
}

@Override
public JsonValue get(String key) {
return delegate.get(key);
}

@Override
public void put(String key, JsonValue value) {
delegate.put(key, value);
}

@Override
public Map<String, JsonValue> toMap() {
return Collections.unmodifiableMap(delegate);
}

@Override
public JsonValue unwrap() {
return delegate;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package org.everit.json.schema.javax.json;

import javax.json.Json;
import javax.json.JsonValue;
import java.math.BigDecimal;
import java.math.BigInteger;

/**
* An adaptation for the JSON types of JSR-353 for support of Java EE 7.
* <p>
* In JSR-353, none of the {@link Json} methods to create JSON scalar
* values are available. However there are methods to create array
* and object builders. To create a single scalar value, in this
* implementation we use an array builder; adding an intrinsic scalar
* value and then extracting it from the completed array.
* <p>
* While this approach has some additional overhead, it at least provides
* a means to support Java EE 7 which includes the JSR-353 specification
* of the standard JSON types.
*/
public class Jsr353Adaptation extends JavaxJsonAdaptation {

JsonValue createValue(String value) {
return Json.createArrayBuilder().add(value).build().get(0);
}

JsonValue createValue(int value) {
return Json.createArrayBuilder().add(value).build().get(0);
}

JsonValue createValue(double value) {
return Json.createArrayBuilder().add(value).build().get(0);
}

JsonValue createValue(long value) {
return Json.createArrayBuilder().add(value).build().get(0);
}

JsonValue createValue(BigInteger value) {
return Json.createArrayBuilder().add(value).build().get(0);
}

JsonValue createValue(BigDecimal value) {
return Json.createArrayBuilder().add(value).build().get(0);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package org.everit.json.schema.javax.json;

import javax.json.Json;
import javax.json.JsonValue;
import java.math.BigDecimal;
import java.math.BigInteger;

/**
* An adaptation for the JSON types of JSR-374 for support of Java EE 8.
*/
public class Jsr374Adaptation extends JavaxJsonAdaptation {

@Override
JsonValue createValue(String value) {
return Json.createValue(value);
}

@Override
JsonValue createValue(int value) {
return Json.createValue(value);
}

@Override
JsonValue createValue(double value) {
return Json.createValue(value);
}

@Override
JsonValue createValue(long value) {
return Json.createValue(value);
}

@Override
JsonValue createValue(BigInteger value) {
return Json.createValue(value);
}

@Override
JsonValue createValue(BigDecimal value) {
return Json.createValue(value);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/**
* Adaptation for the JSON-P types of {@code javax.json}
*/
package org.everit.json.schema.javax.json;
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
/*
* File created on Sep 12, 2018
*
* Copyright (c) 2018 Carl Harris, Jr
* and others as noted
*
* 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.everit.json.schema;

import junitparams.JUnitParamsRunner;
import junitparams.Parameters;
import org.everit.json.schema.javax.json.Jsr353Adaptation;
import org.everit.json.schema.javax.json.Jsr374Adaptation;
import org.everit.json.schema.spi.JsonAdaptation;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

import javax.json.Json;
import javax.json.JsonObject;
import javax.json.JsonValue;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.*;

@RunWith(JUnitParamsRunner.class)
public class JavaxJsonValidatingVisitorTest {

private static final Jsr374Adaptation JSR_374_ADAPTATION = new Jsr374Adaptation();
private static final Jsr353Adaptation JSR_353_ADAPTATION = new Jsr353Adaptation();

private ValidationFailureReporter reporter;

@Before
public void before() {
reporter = mock(ValidationFailureReporter.class);
}

public Object[] javaxJsonAdaptations() {
return new Object[] { JSR_374_ADAPTATION, JSR_353_ADAPTATION };
}

@Test
@Parameters(method = "javaxJsonAdaptations")
public void passesTypeCheck_otherType_noRequires(JsonAdaptation<?> jsonAdaptation) {
ValidatingVisitor subject = new ValidatingVisitor("string", reporter, null, jsonAdaptation);
assertFalse(subject.passesTypeCheck(jsonAdaptation.objectType(), false, null));
verifyZeroInteractions(reporter);
}

@Test
@Parameters(method = "javaxJsonAdaptations")
public void passesTypeCheck_otherType_requires(JsonAdaptation<?> jsonAdaptation) {
ValidatingVisitor subject = new ValidatingVisitor("string", reporter, null, jsonAdaptation);
assertFalse(subject.passesTypeCheck(jsonAdaptation.objectType(), true, null));
verify(reporter).failure(JsonObject.class, "string");
}

@Test
@Parameters(method = "javaxJsonAdaptations")
public void passesTypeCheck_otherType_nullPermitted_nullObject(JsonAdaptation<?> jsonAdaptation) {
ValidatingVisitor subject = new ValidatingVisitor(JsonValue.NULL, reporter, null, jsonAdaptation);
assertFalse(subject.passesTypeCheck(jsonAdaptation.objectType(), true, Boolean.TRUE));
verifyZeroInteractions(reporter);
}

@Test
@Parameters(method = "javaxJsonAdaptations")
public void passesTypeCheck_otherType_nullPermitted_nullReference(JsonAdaptation<?> jsonAdaptation) {
ValidatingVisitor subject = new ValidatingVisitor(null, reporter, null, jsonAdaptation);
assertFalse(subject.passesTypeCheck(jsonAdaptation.objectType(), true, Boolean.TRUE));
verifyZeroInteractions(reporter);
}

@Test
@Parameters(method = "javaxJsonAdaptations")
public void passesTypeCheck_nullPermitted_nonNullValue(JsonAdaptation<?> jsonAdaptation) {
ValidatingVisitor subject = new ValidatingVisitor("string", reporter, null, jsonAdaptation);
assertFalse(subject.passesTypeCheck(jsonAdaptation.objectType(), true, Boolean.TRUE));
verify(reporter).failure(jsonAdaptation.objectType(), "string");
}

@Test
@Parameters(method = "javaxJsonAdaptations")
public void passesTypeCheck_requiresType_nullableIsNull(JsonAdaptation<?> jsonAdaptation) {
ValidatingVisitor subject = new ValidatingVisitor(null, reporter, null, jsonAdaptation);
assertFalse(subject.passesTypeCheck(jsonAdaptation.objectType(), true, null));
verify(reporter).failure(jsonAdaptation.objectType(), null);
}

@Test
@Parameters(method = "javaxJsonAdaptations")
public void passesTypeCheck_sameType(JsonAdaptation<?> jsonAdaptation) {
ValidatingVisitor subject = new ValidatingVisitor("string", reporter, null, jsonAdaptation);
assertTrue(subject.passesTypeCheck(String.class, true, Boolean.TRUE));
verifyZeroInteractions(reporter);
}

public Object[] permittedTypes() {
return new Object[] {
new Object[] { "str" },
new Object[] { 1 },
new Object[] { 1L },
new Object[] { 1.0 },
new Object[] { 1.0f },
new Object[] { new BigInteger("42") },
new Object[] { new BigDecimal("42.3") },
new Object[] { true },
new Object[] { null },
new Object[] { JsonValue.NULL },
new Object[] { JsonValue.FALSE },
new Object[] { JsonValue.TRUE },
new Object[] { Json.createValue("str") },
new Object[] { Json.createValue(1) },
new Object[] { Json.createObjectBuilder().build() },
new Object[] { Json.createArrayBuilder().build() },
};
}

public Object[] notPermittedTypes() {
return new Object[] {
new Object[] { new ArrayList<String>() },
new Object[] { new RuntimeException() }
};
}

@Test
@Parameters(method = "permittedTypes")
public void permittedTypeSuccess(Object subject) {
new ValidatingVisitor(subject, reporter, ReadWriteValidator.NONE, JSR_374_ADAPTATION);
}

@Test(expected = IllegalArgumentException.class)
@Parameters(method = "notPermittedTypes")
public void notPermittedTypeFailure(Object subject) {
new ValidatingVisitor(subject, reporter, ReadWriteValidator.NONE, JSR_374_ADAPTATION);
}

}