Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in
Toggle navigation
Menu
Open sidebar
Eclipse Projects
The Eclipse Integrated Computational Environment
ice
Commits
be0bf43f
Commit
be0bf43f
authored
May 12, 2020
by
Daniel Bluhm
Browse files
Initial tests for @DataElement
Signed-off-by:
Daniel Bluhm
<
bluhmdj@ornl.gov
>
parent
79729ffe
Changes
6
Hide whitespace changes
Inline
Side-by-side
org.eclipse.ice.dev.annotations/pom.xml
View file @
be0bf43f
...
...
@@ -59,6 +59,18 @@
<version>
1.18.12
</version>
<scope>
compile
</scope>
</dependency>
<dependency>
<groupId>
com.fasterxml.jackson.core
</groupId>
<artifactId>
jackson-core
</artifactId>
<version>
2.10.2
</version>
<scope>
provided
</scope>
</dependency>
<dependency>
<groupId>
com.fasterxml.jackson.core
</groupId>
<artifactId>
jackson-databind
</artifactId>
<version>
2.10.2
</version>
<scope>
provided
</scope>
</dependency>
</dependencies>
</project>
\ No newline at end of file
org.eclipse.ice.dev.annotations/src/main/java/org/eclipse/ice/dev/annotations/IDataElement.java
0 → 100644
View file @
be0bf43f
package
org.eclipse.ice.dev.annotations
;
public
interface
IDataElement
{
}
org.eclipse.ice.dev.annotations/src/main/java/org/eclipse/ice/dev/annotations/JavascriptValidator.java
0 → 100644
View file @
be0bf43f
/*******************************************************************************
* Copyright (c) 2020- UT-Battelle, LLC.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Initial API and implementation and/or initial documentation -
* Jay Jay Billings
*******************************************************************************/
package
org.eclipse.ice.dev.annotations
;
import
java.io.Serializable
;
import
javax.script.Invocable
;
import
javax.script.ScriptEngine
;
import
javax.script.ScriptEngineManager
;
import
javax.script.ScriptException
;
import
org.slf4j.Logger
;
import
org.slf4j.LoggerFactory
;
import
com.fasterxml.jackson.annotation.JsonIgnore
;
/**
* This class provides a simple utility for checking that the data in objects is
* valid and meets some basic expectations. For example, it can be used to check
* that numbers lie within certain bounds or that strings are spelled correctly.
*
* All validation in this class is performed using a Javascript function that is
* injected with the function accessors. All clients are expected to configure a
* validation function by passing a Javascript function in the form of a string
* to the setFunction() operation. The function signature is of the form "var
* checkData = function (data) {return data == 'Solar Fields';}" and this class
* expects to be able to call the checkData() function by name.
*
* Clients should provide functions that, in general, perform both verification
* and validation. That is, functions should verify that data exists within
* expected parameters and insure that the values provided are accurate in a
* larger context and conform to business rules.
*
* Future ideas: 1) Can we take Javascript function objects instead of strings?
* 2) Do we need to create a script engine for *every* validator? Most likely
* not! 3) Can we get feedback from Javascript functions to identify what the
* error was? 4) Can we read functions from files too? 5) Can we inject a
* function name to call instead of defaulting to checkData()?
*
* @author Jay Jay Billings
*
*/
public
class
JavascriptValidator
<
T
>
implements
Serializable
{
/**
* Logging tool
*/
private
static
final
Logger
logger
=
LoggerFactory
.
getLogger
(
JavascriptValidator
.
class
);
/**
* An id for the Serializable interface implementation.
*/
private
static
final
long
serialVersionUID
=
4748960154143122573L
;
/**
* A valid Javascript function, stored as a string, that can be called when the
* validate() operation is executed.
*/
private
String
function
;
/**
* The script engine manager for executing Javascript scripts
*/
@JsonIgnore
ScriptEngineManager
scriptEngineManager
;
/**
* The Nashorn Javascript engine
*/
@JsonIgnore
ScriptEngine
engine
;
/**
* Constructor
*/
public
JavascriptValidator
()
{
setFunction
(
new
String
());
setupScriptEngine
();
}
/**
* Copy constructor
*/
public
JavascriptValidator
(
JavascriptValidator
<
T
>
otherValidator
)
{
if
(
otherValidator
!=
null
)
{
function
=
otherValidator
.
function
;
}
// Still need to setup the scripting engine if we copy it.
setupScriptEngine
();
}
/**
* This function sets up the Nashorn scripting engine.
*/
private
void
setupScriptEngine
()
{
scriptEngineManager
=
new
ScriptEngineManager
();
engine
=
scriptEngineManager
.
getEngineByName
(
"JavaScript"
);
}
/**
* This function returns the Javascript function that will be executed as a
* string.
*
* @return the Javascript function
*/
public
String
getFunction
()
{
return
function
;
}
/**
* This operation sets the validation function from a Javascript function stored
* as a string.
*
* @param function a Javascript function stored as a string that can be called
* by the validate() operation.
*/
public
void
setFunction
(
String
function
)
{
this
.
function
=
function
;
}
/**
* See {@link java.lang.Object#equals(Object)}.
*/
@Override
public
boolean
equals
(
Object
otherObject
)
{
boolean
retValue
=
false
;
// Check shallow identify and type first
if
(
this
==
otherObject
)
{
retValue
=
true
;
}
else
if
(
otherObject
instanceof
JavascriptValidator
<?>)
{
JavascriptValidator
<
T
>
otherValidator
=
(
JavascriptValidator
<
T
>)
otherObject
;
retValue
=
this
.
function
.
equals
(
otherValidator
.
function
);
}
return
retValue
;
}
/**
* This operation checks the data for validity.
*
* @param data the data to check
* @return true if the data is in a valid state, false otherwise
* @throws NoSuchMethodException This exception is thrown if the Javascript
* validation function cannot be found.
*/
public
boolean
validate
(
final
T
data
)
throws
NoSuchMethodException
{
boolean
retValue
=
false
;
Object
result
=
null
;
try
{
engine
.
eval
(
function
);
Invocable
invocableEngine
=
(
Invocable
)
engine
;
result
=
invocableEngine
.
invokeFunction
(
"checkData"
,
data
);
retValue
=
(
boolean
)
result
;
}
catch
(
ScriptException
e
)
{
logger
.
error
(
"Error running validation function!"
,
e
);
}
return
retValue
;
}
/**
* See {@link java.lang.Object#hashCode()}.
*/
@Override
public
int
hashCode
()
{
// Using a somewhat generic and common technique for computing the hash code
// here. It matches the version in the old ICE 2.x product line, but I
// incremented the initial hash seed to 31 from 11 since this is for version 3.
int
hash
=
31
;
// The 31 below is just coincidental and part of the original source where I
// read
// about hash codes.
hash
=
31
*
hash
+
function
.
hashCode
();
return
hash
;
}
/**
* This operation clones the object. Note that it differs from the base class
* implementation in that it will return null if it cannot create the clone to
* promote fast failure. See {@link java.lang.Object#clone()};
*/
@Override
public
Object
clone
()
{
try
{
// Call the copy constructor to create the clone.
return
new
JavascriptValidator
<
T
>(
this
);
}
catch
(
Exception
e
)
{
logger
.
error
(
"Unable to clone DataElement!"
,
e
);
return
null
;
}
}
}
org.eclipse.ice.dev.annotations/src/main/resources/templates/DataElement.vm
View file @
be0bf43f
...
...
@@ -4,21 +4,28 @@ package $package;
import java.io.Serializable;
import java.util.UUID;
import lombok.Data;
import org.eclipse.ice.renderer.JavascriptValidator;
import org.eclipse.ice.dev.annotations.IDataElement;
import org.eclipse.ice.dev.annotations.JavascriptValidator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* This is an implementation of
$
interface
that satisfies the dependencies of
* the @DataElement Annotation and was auto-generated by the ICE Framework.
*/
@Data public class
${
class
}
implements
${
interface
}
, Serializable
{
@Data
@NoArgsConstructor
public class
${
class
}
implements
${
interface
}
, Serializable, IDataElement
{
/**
* Logging tool
...
...
@@ -28,7 +35,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
/**
* A unique private id that identifies the data element
*/
private UUID privateId;
private UUID privateId
= UUID.randomUUID()
;
/**
* A simple name for the data
...
...
@@ -43,7 +50,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
/**
* A unique identifier
*/
private
Stri
ng id =
"0"
;
private
Lo
ng id =
0L
;
/**
* A comment that annotates the data in meaningful way
...
...
@@ -74,9 +81,24 @@ import com.fasterxml.jackson.databind.ObjectMapper;
/**
* Generated from DataField annotations
*/
#
foreach
($
field
in
$
fields
)
#
foreach
($
field
in
$
fields
)
protected
${
field
.
ClassName
}
${
field
.
Name
}
;
#
end
#
end
public
$
class
($
class
other
)
throws Exception
{
if (other == null)
{
throw (new Exception("
$
class
to copy cannot be null."));
}
if (!(other instanceof
$
class
))
{
throw (new Exception("
$
class
can copy only from other instances of
$
class
."));
}
#
foreach
($
prop
in
[
"name"
,
"description"
,
"comment"
,
"id"
,
"context"
,
"privateId"
,
"validator"
,
"secret"
,
"required"
])
this.
$
prop
= other.
$
prop
;
#
end
#
foreach
($
field
in
$
fields
)
this.
${
field
.
Name
}
= other.
${
field
.
Name
}
;
#
end
}
/**
* This operation serializes the data element to a string in verified JSON.
...
...
@@ -115,12 +137,17 @@ import com.fasterxml.jackson.databind.ObjectMapper;
JsonNode rootNode = mapper.readTree(jsonDataElement);
// Static Fields
#
foreach
($
prop
in
[
"name"
,
"description"
,
"id"
,
"comment"
,
"context"
])
#
foreach
($
prop
in
[
"name"
,
"description"
,
"comment"
,
"context"
])
//
$
prop
JsonNode
${
prop
}
Node = rootNode.get("
$
prop
");
$
prop
= mapper.treeToValue(
${
prop
}
Node, String.class);
#
end
// id
JsonNode idNode = rootNode.get("id");
id = mapper.treeToValue(idNode, Long.class);
// Required and secret booleans
JsonNode requiredNode = rootNode.get("required");
required = mapper.treeToValue(requiredNode, Boolean.class);
...
...
@@ -133,7 +160,11 @@ import com.fasterxml.jackson.databind.ObjectMapper;
// Validators
JsonNode validatorNode = rootNode.get("validator");
validator = mapper.treeToValue(validatorNode, validator.getClass());
if (rootNode.hasNonNull("validator"))
{
validator = mapper.treeToValue(validatorNode, validator.getClass());
} else
{
validator = null;
}
// Dynamic Fields
#
foreach
($
field
in
$
fields
)
...
...
org.eclipse.ice.renderer/src/test/java/org/eclipse/ice/tests/renderer/GeneratedDataElement.java
0 → 100644
View file @
be0bf43f
package
org.eclipse.ice.tests.renderer
;
import
org.eclipse.ice.dev.annotations.*
;
@DataElement
@DataField
(
fieldName
=
"testField"
,
fieldType
=
String
.
class
)
public
interface
GeneratedDataElement
{
}
org.eclipse.ice.renderer/src/test/java/org/eclipse/ice/tests/renderer/GeneratedDataElementTest.java
0 → 100644
View file @
be0bf43f
/*******************************************************************************
* Copyright (c) 2020- UT-Battelle, LLC.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Initial API and implementation and/or initial documentation -
* Jay Jay Billings
*******************************************************************************/
package
org.eclipse.ice.tests.renderer
;
import
static
org
.
junit
.
jupiter
.
api
.
Assertions
.*;
import
org.eclipse.ice.dev.annotations.JavascriptValidator
;
import
org.junit.jupiter.api.Test
;
/**
* This class tests the DataElement class. With the exception of testing for
* serialization to a string, it is sufficient to test this class with T=String.
* For testing serialization, multiple types need to be tested.
*
* @author Jay Jay Billings
*
*/
class
GeneratedDataElementTest
{
/**
* This is a helper function for creating GeneratedDataElementImplementation values that are
* used in most of the tests.
*
* @param data the value that should be stored in the element.
* @return the data
*/
private
GeneratedDataElementImplementation
getStringElement
(
final
String
data
)
{
GeneratedDataElementImplementation
element
=
new
GeneratedDataElementImplementation
();
element
.
setTestField
(
data
);
return
element
;
}
/**
* Test method for {@link org.eclipse.ice.renderer.DataElement#getProperties()}.
*/
@Test
void
testProperties
()
{
// Use a basic string element for this test since it is looking at content on
// the base class.
GeneratedDataElementImplementation
element
=
getStringElement
(
"Phutureprimitive"
);
// Now check the getters
assertEquals
(
element
.
getName
(),
"name"
);
assertEquals
(
element
.
getDescription
(),
"description"
);
assertEquals
(
element
.
getId
(),
0
);
assertEquals
(
element
.
getComment
(),
"no comment"
);
assertEquals
(
element
.
getContext
(),
"default"
);
// Check the boolean property default values
assertEquals
(
element
.
isRequired
(),
false
);
assertEquals
(
element
.
isSecret
(),
false
);
// Setup new values for checking setters
String
name
=
"rock"
;
String
description
=
"round garden rock"
;
long
id
=
1L
;
String
comment
=
"Brown with a pumpkin next to it"
;
String
context
=
"garden"
;
boolean
required
=
true
;
boolean
secret
=
true
;
// Set all the properties
try
{
element
.
setComment
(
comment
);
element
.
setContext
(
context
);
element
.
setId
(
id
);
element
.
setName
(
name
);
element
.
setDescription
(
description
);
element
.
setRequired
(
required
);
element
.
setSecret
(
secret
);
}
catch
(
Exception
e
)
{
// Complain
e
.
printStackTrace
();
fail
();
}
// Check the getters
assertEquals
(
element
.
getName
(),
name
);
assertEquals
(
element
.
getDescription
(),
description
);
assertEquals
(
element
.
getId
(),
id
);
assertEquals
(
element
.
getComment
(),
comment
);
assertEquals
(
element
.
getContext
(),
context
);
// Check the boolean property default values
assertEquals
(
element
.
isRequired
(),
required
);
assertEquals
(
element
.
isSecret
(),
secret
);
// Make sure that adding validators works superficially
JavascriptValidator
<
GeneratedDataElementImplementation
>
validator
=
new
JavascriptValidator
<
GeneratedDataElementImplementation
>();
element
.
setValidator
(
validator
);
assertEquals
(
element
.
getValidator
(),
validator
);
// Make sure that the UUID is not null
assertNotNull
(
element
.
getPrivateId
());
return
;
}
/**
* Test method for {@link org.eclipse.ice.renderer.DataElement#getData()} and
* {@link org.eclipse.ice.renderer.DataElement#setData(java.lang.Object)}.
*/
@Test
void
testDataAccessors
()
{
// Basic intrinsic class is good for this test
GeneratedDataElementImplementation
element
=
getStringElement
(
"Phutureprimitive"
);
// No properties are configured here. Just want to make sure that the data
// behaves as expected.
// Do the straight check
assertEquals
(
"Phutureprimitive"
,
element
.
getTestField
());
// Make sure that changing the value works round-trip
String
data
=
element
.
getTestField
();
data
=
"The Glitch Mob"
;
element
.
setTestField
(
data
);
assertEquals
(
"The Glitch Mob"
,
element
.
getTestField
());
return
;
}
// /**
// * Test method for {@link org.eclipse.ice.renderer.DataElement#getData()} and
// * {@link org.eclipse.ice.renderer.DataElement#setData(java.lang.Object)} when
// * non-intrinsic POJOs are used.
// */
// @Test
// void testDataAccessorsForPOJOs() {
//
// // Use a test POJO for this that has members
// DataElement<TestPOJO> element = new DataElement<TestPOJO>();
// element.setData(new TestPOJO());
//
// // No properties are configured here. Just want to make sure that the data
// // behaves as expected.
//
// // Do the straight check
// TestPOJO pojo = element.getData();
// assertEquals("foo", pojo.getValue());
// assertEquals(2118.0, pojo.getDoubleValue(), 1.0e-15);
//
// // Make sure that changing the value works round-trip
// pojo.setDoubleValue(1234.0);
// pojo.setValue("bar");
// assertEquals("bar", pojo.getValue());
// assertEquals(1234.0, pojo.getDoubleValue(), 1.0e-15);
//
// return;
// }
//
/**
* Test method for {@link org.eclipse.ice.renderer.DataElement#toString()} and
* {@link org.eclipse.ice.renderer.DataElement#toString()} for intrinsic
* classes.
*/
@Test
void
testStringSerialization
()
{
// Basic intrinsic class is good for this test
GeneratedDataElementImplementation
element
=
getStringElement
(
"Major Lazer & La Roux"
);
element
.
setSecret
(
true
);
element
.
setRequired
(
true
);
element
.
setValidator
(
new
JavascriptValidator
<
GeneratedDataElementImplementation
>());
// Because of the private id changing and being unique, this cannot be checked
// against a reference but can only be checked by inversion.
String
output
=
element
.
toJSON
();
// Change some values then read back in the original to make sure fromString()
// correctly overwrites them.
element
.
setTestField
(
"Eastern Sun"
);
System
.
out
.
println
(
output
);
GeneratedDataElementImplementation
element2
=
getStringElement
(
"Emancipator"
);
element2
.
setValidator
(
new
JavascriptValidator
<
GeneratedDataElementImplementation
>());
element2
.
fromJSON
(
output
);
element
.
fromJSON
(
output
);
assertEquals
(
element
,
element2
);
return
;
}
//
// /**
// * Test method for {@link org.eclipse.ice.renderer.DataElement#toString()} and
// * {@link org.eclipse.ice.renderer.DataElement#toString()} for POJOs.
// */
// @Test
// void testPOJOSerialization() {
//
// // Use a test POJO for this that has members
// DataElement<TestPOJO> element = new DataElement<TestPOJO>();
// element.setData(new TestPOJO());
// element.setSecret(true);
// element.setRequired(true);
// element.setValidator(new JavascriptValidator<TestPOJO>());
//
// // Add a custom property to make sure they are included in serialization
// try {
// element.setProperty("sail", "awolnation");
// } catch (Exception e) {
// // Complain
// e.printStackTrace();
// fail();
// }
//
// // Because of the private id changing and being unique, this cannot be checked
// // against a reference but can only be checked by inversion.
// String output = element.toString();
//
// // Change some values then read back in the original to make sure fromString()
// // correctly overwrites them.
// DataElement<TestPOJO> element2 = new DataElement<TestPOJO>();
// TestPOJO pojo2 = new TestPOJO();
// pojo2.setDoubleValue(1.072);
// element2.setValidator(new JavascriptValidator<TestPOJO>());
// element2.setData(pojo2);
// element2.fromString(output);
//
// assertEquals(element,element2);
//
// return;