JAXB validation using annotations
If I have a simple class such as:-
@XmlRootE开发者_C百科lement
public class MyClass
{
@XmlAttribute(required=true)
private String myattribute
}
Is it possible to validate a corresponding xml document WITHOUT an xml schema i.e. using only the annotations?
Good question. As far as I know, the required
attribute is generated by XJC when it finds a non-optional schema type, and I think it's also used by the schema generator. At runtime, though, it's not used for anything, serving no other purpose than a documentary annotation.
One thing you could consider is the JAXB runtime's callback options. In this case, you could just define a afterUnmarshal()
method on MyClass
which programmatically validates the state of the object, throwing an exception if it doesn't like it. See the above link for other options, including registering separate validator classes.
Having said that, validation against a schema really is the best way. If you don't have one, you should considering writing one. The schemagen tool can generate a schema from your object model, which you can then modify to add whatever constraints you like. Hopefully, schemagen
will generate mandatory schema elements from your required=true
class fields.
Great question, especially considering popularity of object-first, schema-never development. I too would like to validate objects by leveraging the existing annotations prior to marshaling.
Whilst either we wait for JAXB-430, or become accepted contributors to Java, what follows is an extremely limited home-grown attempt regarding only XmlElement(required=true}
. Note this will not work with non-default security policy because of Field.setAccessible()
.
Use Case Test
import javax.xml.bind.annotation.XmlElement;
import JaxbValidator.ValidationException;
import org.testng.annotations.Test;
public class JaxbValidatorTest {
static class Llama {
@XmlElement(required = false)
private final String no;
@XmlElement(required = true)
private final String yes;
public Llama(String no, String yes) {
super();
this.no = no;
this.yes = yes;
}
}
@Test
public void validateRequired() {
try {
Llama o = new Llama("a", "b");
// THE MAIN EVENT - see if 'required' is honored
JaxbValidator.validateRequired(o, Llama.class);
} catch (ValidationException e) {
assert false : "Should not have thrown validation exception.";
}
try {
Llama o = new Llama(null, null);
// Again - see if 'required' is honored
JaxbValidator.validateRequired(o, Llama.class);
assert false : "Should have thrown validation exception for 'yes'";
} catch (ValidationException e) {
assert e.getMessage() != null: "Expected validation message, got null." ;
}
}
}
Implementation
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import javax.xml.bind.annotation.XmlElement;
import org.apache.log4j.Logger;
/**
* oh so minimal consideration of JAXB annotation
*/
public class JaxbValidator {
private static final Logger LOG = Logger.getLogger(JaxbValidator.class);
public static class ValidationException extends Exception {
public ValidationException(String message, Throwable cause) {
super(message, cause);
}
public ValidationException(String message) {
super(message);
}
}
/**
* Enforce 'required' attibute.
*
* Requires either no security manager is used or the default security manager is employed.
* @see {@link Field#setAccessible(boolean)}.
*/
public static <T> void validateRequired(T target, Class<T> targetClass)
throws ValidationException {
StringBuilder errors = new StringBuilder();
Field[] fields = targetClass.getDeclaredFields();
for (Field field : fields) {
XmlElement annotation = field.getAnnotation(XmlElement.class);
if (annotation != null && annotation.required()) {
try {
field.setAccessible(true);
if (field.get(target) == null) {
if (errors.length() != 0) {
errors.append(" ");
}
String message = String.format("%s: required field '%s' is null.",
targetClass.getSimpleName(),
field.getName());
LOG.error(message);
errors.append(message);
}
} catch (IllegalArgumentException e) {
LOG.error(field.getName(), e);
} catch (IllegalAccessException e) {
LOG.error(field.getName(), e);
}
}
}
if (errors.length() != 0) {
throw new ValidationException(errors.toString());
}
}
And yes ... it doesn't do deep inspection. I wasn't sure if JAXB handles cyclic graphs, so I didn't attempt recursion without knowing if that had to be dealt with. I shall save that for a dear reader or next edit.
精彩评论