JAXB-ElipseLink: Marshaller not validating - validation

I would like my Eclipselink 2.3 Marshaller to perform validation upon marshalling.
I have made sure that the Schema is correctly created by a SchemaFactory, i am passing it to Marshaller.setSchema and i have registered a handler via Marshaller.setEventHandler().
The marshal result is clearly not valid acc. to its Schema (verified in Eclipse), nevertheless i can see that my breakpoint in handleEvent(ValidationEvent event) is never hit.
I am marshalling XML-Fragments using marshal(Object, XMLStreamWriter) and would expect the Marshaller to perform validation on these fragments according to the Schema i passed.
Anybody any idea why this is not happening?
EDIT:
The Validation error that should occur: 2 missing attributes on an element.
The element corresponds to a Java-Object that is contained in a List<>. I am marshalling the List using:
<xml-element java-attribute="listInstance" xml-path="ListWrapperElement/ListElement" type="foo.ElementType" container-type="java.util.ArrayList"/>
The mapping for the element itself:
<java-type name="foo.ElementType" xml-accessor-type="PROPERTY">
<java-attributes>
// just <xml-attribute> elements here
</java-attributes>
</java-type>
Therefore all attributes are marshalled to ListWrapperElement/ListElement/#attribute.
2 of these are missing and not detected by validation.

I have not been able to reproduce the issue that you are seeing. Below is what I have tried (adapted from the follow blog post):
http://blog.bdoughan.com/2010/12/jaxb-and-marshalunmarshal-schema.html
MarshalDemo (adapted from blog post)
import java.io.File;
import javax.xml.XMLConstants;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamWriter;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import org.eclipse.persistence.Version;
public class MarshalDemo {
public static void main(String[] args) throws Exception {
Customer customer = new Customer();
customer.setName("Jane Doe");
customer.getPhoneNumbers().add(new PhoneNumber());
customer.getPhoneNumbers().add(new PhoneNumber());
customer.getPhoneNumbers().add(new PhoneNumber());
SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
Schema schema = sf.newSchema(new File("src/blog/jaxb/validation/customer.xsd"));
JAXBContext jc = JAXBContext.newInstance(Customer.class);
System.out.println(jc.getClass());
System.out.println(Version.getVersion());
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.setSchema(schema);
marshaller.setEventHandler(new MyValidationEventHandler());
XMLStreamWriter xsw = XMLOutputFactory.newFactory().createXMLStreamWriter(System.out);
marshaller.marshal(customer, xsw);
}
}
Output
class org.eclipse.persistence.jaxb.JAXBContext
2.3.0
EVENT
SEVERITY: 1
MESSAGE: cvc-maxLength-valid: Value 'Jane Doe' with length = '8' is not facet-valid with respect to maxLength '5' for type 'stringWithMaxSize5'.
LINKED EXCEPTION: org.eclipse.persistence.oxm.record.ValidatingMarshalRecord$MarshalSAXParseException: cvc-maxLength-valid: Value 'Jane Doe' with length = '8' is not facet-valid with respect to maxLength '5' for type 'stringWithMaxSize5'.
LOCATOR
LINE NUMBER: -1
COLUMN NUMBER: -1
OFFSET: -1
OBJECT: forum8924293.Customer#ef2c60
NODE: null
URL: null
EVENT
SEVERITY: 1
MESSAGE: cvc-type.3.1.3: The value 'Jane Doe' of element 'name' is not valid.
LINKED EXCEPTION: org.eclipse.persistence.oxm.record.ValidatingMarshalRecord$MarshalSAXParseException: cvc-type.3.1.3: The value 'Jane Doe' of element 'name' is not valid.
LOCATOR
LINE NUMBER: -1
COLUMN NUMBER: -1
OFFSET: -1
OBJECT: forum8924293.Customer#ef2c60
NODE: null
URL: null
EVENT
SEVERITY: 1
MESSAGE: cvc-complex-type.2.4.d: Invalid content was found starting with element 'customer'. No child element '{phone-number}' is expected at this point.
LINKED EXCEPTION: org.eclipse.persistence.oxm.record.ValidatingMarshalRecord$MarshalSAXParseException: cvc-complex-type.2.4.d: Invalid content was found starting with element 'customer'. No child element '{phone-number}' is expected at this point.
LOCATOR
LINE NUMBER: -1
COLUMN NUMBER: -1
OFFSET: -1
OBJECT: forum8924293.Customer#ef2c60
NODE: null
URL: null
<?xml version="1.0"?><customer><name>Jane Doe</name><phone-number></phone-number><phone-number></phone-number><phone-number></phone-number></customer>

Related

Assert multiple field error codes from Validation using MockMvc

I am trying to assert two errors due to two given constraints to my form. My form has two constraints on its single field:
#Data
#NoArgsConstructor
#NotExistingGroup(groups = SecondGroupValidation.class)
public class GroupForm {
#NotBlank(groups = FirstGroupValidation.class)
#Size(min = 2, max = 30, groups = FirstGroupValidation.class)
private String name;
}
With the following test, I want to trigger both the #NotBlank and #Size validation and assert both raised errors:
#Test
void givenGroupEmptyName_groupPost_assertErrors() throws Exception {
mvc.perform(post("/groups/add").param("name", ""))
.andDo(print())
.andExpect(status().isOk())
.andExpect(view().name("groups-add"))
.andExpect(model().hasErrors())
.andExpect(model().attributeErrorCount("groupForm", 2))
.andExpect(model().attributeHasFieldErrorCode("groupForm", "name", "NotBlank"))
.andExpect(model().attributeHasFieldErrorCode("groupForm", "name", "Size"));
}
The mvc doPrint() method shows both errors are given
ModelAndView:
View name = groups-add
View = null
Attribute = groupForm
value = GroupForm(name=)
errors = [Field error in object 'groupForm' on field 'name': rejected value []; codes [NotBlank.groupForm.name,NotBlank.name,NotBlank.java.lang.String,NotBlank]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [groupForm.name,name]; arguments []; default message [name]]; default message [must not be blank], Field error in object 'groupForm' on field 'name': rejected value []; codes [Size.groupForm.name,Size.name,Size.java.lang.String,Size]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [groupForm.name,name]; arguments []; default message [name],30,2]; default message [size must be between 2 and 30]]
However, the test breaks with the following error
java.lang.AssertionError: Field error code expected:<Size> but was:<NotBlank>

.net 5.0 signature change of System.Text.Json.JsonSerializer.Deserialize()

I am attempting the step from .NET Core 3.1 to .NET 5.0 and get a bunch of nullability warnings at the uses of Deserialize<TValue>(String, JsonSerializerOptions). A quick investigation shows that the signature has changed from
public static TValue Deserialize<TValue> (string json, System.Text.Json.JsonSerializerOptions options = default); (doc) in .NET Core 3.1 to
public static TValue? Deserialize<TValue> (string json, System.Text.Json.JsonSerializerOptions? options = default); (doc) in .NET 5.0.
It looks as a reasonable change, but I haven't been able to provoke a null to actually be returned, since all bad input/bad use will throw an exception in my experiments, and the documentation does not describe why the call would return a null as far as I can tell.
It seems a bit unnecessary to add null return checks to all our uses, if failed deserialization will throw rather than returning null.
What am I missing?
As shown in the original JSON proposal, the text null is perfectly well-formed JSON:
A value can be a string in double quotes, or a number, or true or false or null, or an object or an array. These structures can be nested.
This is further clarified in RFC 8259: The JavaScript Object Notation (JSON) Data Interchange Format which states that a well-formed JSON text need be nothing more than a single primitive value including null:
A JSON text is a sequence of tokens. The set of tokens includes six structural characters, strings, numbers, and three literal names [false, true and null].
A JSON text is a serialized value. Note that certain previous specifications of JSON constrained a JSON text to be an object or an array. Implementations that generate only objects or arrays where a JSON text is called for will be interoperable in the sense that all implementations will accept these as conforming JSON texts.
Since null is a well-formed JSON text according to this most recent JSON RFC, JsonSerializer will not throw when deserializing it to a reference type or nullable value type, and will instead just return a null value:
object? obj1 = JsonSerializer.Deserialize<object>("null"); // Does not throw; explicitly typed for clarity.
Assert.IsNull(obj1); // Passes
var array = JsonSerializer.Deserialize<int []>("null"); // Does not throw;
Assert.IsNull(array); // Passes
var nullable = JsonSerializer.Deserialize<int?>("null"); // Does not throw;
Assert.IsNull(nullable); // Passes
Conversely the following generates a compiler warning:
#nullable enable
object obj2 = JsonSerializer.Deserialize<object>("null"); // Compiler warning: Converting null literal or possible value to non-nullable type;
And the following throws, since an int is a non-nullable value type to which null cannot be assigned:
var i = JsonSerializer.Deserialize<int>("null"); // Throws, since int is a non-nullable value type.
If you want to an exception to be thrown when deserializing the JSON text null, you could add the following extension method:
public static class ObjectExtensions
{
public static T ThrowOnNull<T>(this T? value) where T : class => value ?? throw new ArgumentNullException();
}
And do:
var value = JsonSerializer.Deserialize<TValue>(json).ThrowOnNull();
Demo fiddle here.

SPeL - set a value of an object which should be in an empty list

I have the following SPel expression:
custData.address[0].postcode
The custData is an existing object but the address is an empty list. It is an existing object but it is empty. When I try to set a the post code on this path then I got the
org.springframework.expression.spel.SpelEvaluationException: EL1007E: Property or field 'postcode' cannot be found on null
What I'd need that a new address object will be put to the list and set its postcode attribute.
Is it something that can be done in the SPel expression?
Thanks,
V.
So this basically is a NullPointerException. You need to make sure the object from which you are trying to get a field value exists. SPeL has the special operator '?' to check if the object has value, though I'm not sure if it works for an array, but definitely worth a try. In general expression where some object might be null looks like this:
object?.anotherObject?.field
This makes sure that "object" is not null and if it has value gets "anotherObject" and check if it's not null either, and then gets "field". So try something like this:
custData.address[0]?.postcode
Eventually I ended up using a custom function in spel expression.
#addIfNecessary(custData.address, 0, "uk.co.acme.AddressType").postcode
The user defined function is
import org.springframework.util.ReflectionUtils;
import java.util.List;
public class CustomFunc {
public static Object addIfNecessary(List<Object> list, Integer index, String className) throws IllegalAccessException, InstantiationException, ClassNotFoundException {
Object o = null;
if (list != null) {
if (list.size() <= index || list.get(index) == null) {
list.set(index, Class.forName(className).newInstance());
}
o = list.get(index);
}
return o;
}
}
It is neither nice nor elegant but it works.
Please let me know if you have a more elegant one!

Kotlin not nullable value can be null?

I have backend that return me some json.
I parse it to my class:
class SomeData(
#SerializedName("user_name") val name: String,
#SerializedName("user_city") val city: String,
var notNullableValue: String
)
Use gson converter factory:
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(ENDPOINT)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create(gson))
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
And in my interface:
interface MyAPI {
#GET("get_data")
Observable<List<SomeData>> getSomeData();
}
Then I retrieve data from the server (with rxJava) without any error. But I expected an error because I thought I should do something like this (to prevent GSON converter error, because notNullableValue is not present in my JSON response):
class SomeData #JvmOverloads constructor(
#SerializedName("user_name") val name: String,
#SerializedName("user_city") val city: String,
var notNullableValue: String = ""
)
After the data is received from backend and parsed to my SomeData class with constructor without def value, the value of the notNullableValue == null.
As I understand not nullable value can be null in Kotlin?
Yes, that is because you're giving it a default value. Ofcourse it will never be null. That's the whole point of a default value.
Remove ="" from constructor and you will get an error.
Edit: Found the issue. GSON uses the magic sun.misc.Unsafe class which has an allocateInstance method which is obviously considered very unsafe because what it does is skip initialization (constructors/field initializers and the like) and security checks. So there is your answer why a Kotlin non-nullable field can be null. Offending code is in com/google/gson/internal/ConstructorConstructor.java:223
Some interesting details about the Unsafe class: http://mishadoff.com/blog/java-magic-part-4-sun-dot-misc-dot-unsafe/
Try to override constructor like this:
class SomeData(
#SerializedName("user_name") val name: String,
#SerializedName("user_city") val city: String,
var notNullableValue: String = "") {
constructor() : this("","","")
}
Now after server response you can check the notNullableValue is not null - its empty

Grails command object validation gives exception instead of errors

I have command object like this:
#Validateable
class TaCustomerBoardActionCommand {
TaCustomerBoardAction action
static constraints = {
action casecade: true
}
}
and classes in command object below:
class TaCustomerBoardAction {
TaCustomerBoard taCustomerBoard
TaapAction taapAction
Date dateCreated // updated by grails
Date lastUpdated // updated by grails
User createdBy
OrgUnit orgUnit
Client client
static belongsTo = [Client]
static constraints = {
}
}
and
TaapAction {
int id
User createdUser
User responsibleUser
Brand brand
BusinessType businessType
Topic topic
Topic subTopic
String subject
String description
Date targetDate
int progress
String responsible
Client client
static belongsTo = [Client]
OrgUnit orgUnit
Date dateCreated // updated by grails
Date lastUpdated // updated by grails
TaapActionState taapActionState
static constraints = {
subject nullable: false, size: 1..64
description nullable: false, size: 1..4000
responsible nullable: false, size: 1..512
progress nullable: false
responsibleUser nullable:false
brand nullable:false
businessType nullable:false
topic nullable:false
subTopic nullable:false
targetDate nullable:false
}
TaCustomerBoard has similar constraints as above class.
but it gives exception instead of error codes.
Below is controller Post method:
def saveTaCustomerBoardAction(TaCustomerBoardActionCommand cmd){
if(cmd.validate()){
taActionPlanningService.saveAction(cmd.action.taapAction)
cmd.action.save(flush: true, failOnError: true)
}
[cmd:cmd]
}
Stack trace:
grails.validation.ValidationException: Validation Error(s) occurred
during save():
- Field error in object 'de.idare.move.taap.TaapAction' on field 'progress': rejected value [null]; codes
[de.idare.move.taap.TaapAction.progress.typeMismatch.error,de.idare.move.taap.TaapAction.progress.typeMismatch,taapAction.progress.typeMismatch.error,taapAction.progress.typeMismatch,typeMismatch.de.idare.move.taap.TaapAction.progress,typeMismatch.progress,typeMismatch.int,typeMismatch];
arguments [progress]; default message [Data Binding Failed]
- Field error in object 'de.idare.move.taap.TaapAction' on field 'description': rejected value [null]; codes
[de.idare.move.taap.TaapAction.description.nullable.error.de.idare.move.taap.TaapAction.description,de.idare.move.taap.TaapAction.description.nullable.error.description,de.idare.move.taap.TaapAction.description.nullable.error.java.lang.String,de.idare.move.taap.TaapAction.description.nullable.error,taapAction.description.nullable.error.de.idare.move.taap.TaapAction.description,taapAction.description.nullable.error.description,taapAction.description.nullable.error.java.lang.String,taapAction.description.nullable.error,de.idare.move.taap.TaapAction.description.nullable.de.idare.move.taap.TaapAction.description,de.idare.move.taap.TaapAction.description.nullable.description,de.idare.move.taap.TaapAction.description.nullable.java.lang.String,de.idare.move.taap.TaapAction.description.nullable,taapAction.description.nullable.de.idare.move.taap.TaapAction.description,taapAction.description.nullable.description,taapAction.description.nullable.java.lang.String,taapAction.description.nullable,nullable.de.idare.move.taap.TaapAction.description,nullable.description,nullable.java.lang.String,nullable];
arguments [description,class de.idare.move.taap.TaapAction]; default
message [Property [{0}] of class [{1}] can not be null]
Kindly help me I am stuck with this problem.
Your problem is rather straight forward. Well it would seem, you have provided how things work but not actually provided what is sent. My suggestion is to do a println params in the controller action using validation method to see what it is sent / and validated.
You have declared progress as int and not Integer. This means it can't be nullable. Always use Boolean Integer or whatever the case maybe if something is meant to be nullable. Secondly you have also declared description and progress as nullable false meaning they have to be provided. The error message suggests command sent does not have a progress or description sent to it as part of the validation. This is something you need to investigate further by simple debugging such as println at your end to figure out why that is the case.
int progress
...
static constraints = {
progress nullable: false
description nullable: false, size: 1..4000
}
Just remove the failOnError: true. You'll be able to process error objects instead of catching exception.
Documentation

Resources