Java generic type validation - java

I have a generic super class which I'd like to validate:
abstract class Generic<T> {
// ... other validated fields
private T value;
}
Then I have several concrete classes and for each of it I'd like to appy specific validations on a value field.
E.g. for the below class I'd like to ensure that the value field is not less than 0.
class ConcreteInt<Integer> {
// #Min(0)
}
Another example, for the below class I'd like to ensure that the value is not blank.
class ConcreteString<String> {
// #NotBlank
}
Where should I put my validation annotations on a concrete classes to make them work?

I would suggest to use the Template design pattern and apply the validations accordingly.
First we have the generic class that accepts any type as T with an abstract method that the children must implement.
public abstract class Generic<T> {
protected T value;
abstract T getValue();
}
then we have a child for integer implementation with annotations #NotNull and #PositiveOrZero.
import javax.validation.constraints.NotNull;
import javax.validation.constraints.PositiveOrZero;
public class ConcreteInteger extends Generic<Integer> {
public ConcreteInteger(Integer v) {
value = v;
}
#Override
#NotNull
#PositiveOrZero
public Integer getValue() {
return value;
}
}
and a child for string implementation with annotations #NotNull, and #NotBlank. Here we already see that we can have different validations.
package com.yieldlab.reporting.dto.analytics;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
public class ConcreteString extends Generic<String> {
public ConcreteString(String v) {
value = v;
}
#Override
#NotNull
#NotBlank
public String getValue() {
return value;
}
}
But it won't validate if we just use the annotations. We have to invoke the javax.validation.Validator API. Here is one example:
import javax.validation.Configuration;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
public class BeanFieldValidationExample {
private static final Validator validator;
static {
Configuration<?> config = Validation.byDefaultProvider().configure();
ValidatorFactory factory = config.buildValidatorFactory();
validator = factory.getValidator();
factory.close();
}
public static void main(String[] args) {
// it won't validate by just call the constructor
ConcreteInteger concreteInteger0 = new ConcreteInteger(0);
System.out.println(concreteInteger0.value);
ConcreteInteger concreteInteger1 = new ConcreteInteger(1);
System.out.println(concreteInteger1.value);
ConcreteInteger concreteIntegerMinus1 = new ConcreteInteger(-1);
System.out.println(concreteIntegerMinus1.value);
ConcreteInteger concreteIntegerNull = new ConcreteInteger(null);
System.out.println(concreteIntegerNull.value);
ConcreteString concreteString0 = new ConcreteString("some string");
System.out.println(concreteString0.value);
ConcreteString concreteString1 = new ConcreteString("");
System.out.println(concreteString1.value);
ConcreteString concreteStringNull = new ConcreteString(null);
System.out.println(concreteStringNull.value);
// invoking the validator API will in validate our beans validator.validate(concreteInteger0).stream().forEach(BeanFieldValidationExample::printErrorConcreteInteger);
validator.validate(concreteInteger1).stream().forEach(BeanFieldValidationExample::printErrorConcreteInteger);
validator.validate(concreteIntegerMinus1).stream().forEach(BeanFieldValidationExample::printErrorConcreteInteger);
validator.validate(concreteIntegerNull).stream().forEach(BeanFieldValidationExample::printErrorConcreteInteger);
validator.validate(concreteString0).stream().forEach(BeanFieldValidationExample::printErrorConcreteString);
validator.validate(concreteString1).stream().forEach(BeanFieldValidationExample::printErrorConcreteString);
validator.validate(concreteStringNull).stream().forEach(BeanFieldValidationExample::printErrorConcreteString);
}
private static void printErrorConcreteInteger(ConstraintViolation<ConcreteInteger> concreteIntegerConstraintViolation) {
System.out.println(concreteIntegerConstraintViolation.getPropertyPath() + " " + concreteIntegerConstraintViolation.getMessage());
}
private static void printErrorConcreteString(ConstraintViolation<ConcreteString> concreteStringConstraintViolation) {
System.out.println(concreteStringConstraintViolation.getPropertyPath() + " " + concreteStringConstraintViolation.getMessage());
}
}
then we can see the output:
0
1
-1
null
some string
null
value must be greater than or equal to 0
value must not be null
value must not be blank
value must not be null
value must not be blank

I have found a solution. It's enough to override the getter of the class and place the annotations there:
class ConcreteString<String> {
#Override
#NotBlank
public String getValue(){
return super.getValue();
}
}

Related

How to set & fetch fields of inner class in another class

package com.pr.trio;
import java.util.List;
public class lalala {
private List<SegmentationFieldValue> segmentationFieldValues;
public static class SegmentationFieldValue {
private Integer segmentationFieldId;
private Integer segmentationFieldGroupId;
private String value;
public Integer getSegmentationFieldId() {
return segmentationFieldId;
}
public void setSegmentationFieldId(Integer segmentationFieldId) {
this.segmentationFieldId = segmentationFieldId;
}
public Integer getSegmentationFieldGroupId() {
return segmentationFieldGroupId;
}
public void setSegmentationFieldGroupId(Integer segmentationFieldGroupId) {
this.segmentationFieldGroupId = segmentationFieldGroupId;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
public List<SegmentationFieldValue> getSegmentationFieldValues() {
return segmentationFieldValues;
}
public void setSegmentationFieldValues(List<SegmentationFieldValue> segmentationFieldValues) {
this.segmentationFieldValues = segmentationFieldValues;
}
}
package com.pr.trio;
import java.util.Arrays;
public class kk {
public static void main(String[] args) {
lalala l1 = new lalala();
//currently passed as an empty array, want to set SegmentationFieldId & value here from inner class
l1.setSegmentationFieldValues(Arrays.asList());
//lalala.SegmentationFieldValue.this.setSegmentationFieldId(15);
System.out.println(l1.getSegmentationFieldValues());
}
}
So here, I'm not able to pass values for the segmentation field instead of the empty array, gives an error. So how can I set the values from the inner class fields & pass it to my list?
Seeing as your SegmentationFieldValue class is public, it's trivial to use it inside another class, there are basically two ways to go about this:
The first is to import the inner class:
import com.pr.trio.lalala.SegmentationFieldValue;
The second is to qualify the classname whenever you use it:
lalala.SegmentationFieldValue a = new lalala.SegmentationFieldValue();
You can then call the setters on this class, and use the objects in your call to setSegmentationFieldValues:
lalala.SegmentationFieldValue a = new lalala.SegmentationFieldValue();
a.setSegmentationFieldId(1);
a.setSegmentationFieldGroupId(1);
a.setValue("a");
lalala.SegmentationFieldValue b = new lalala.SegmentationFieldValue();
b.setSegmentationFieldId(2);
b.setSegmentationFieldGroupId(1);
b.setValue("b");
l1.setSegmentationFieldValues(Arrays.asList(a, b));
Judging from your comment code, you also seem to be looking for a shorthand way to add an element to your list. A simple implementation could look like this (in class lalala):
public void addSegmentationFieldValue(Integer id, Integer groupId, String value)
{
if (segmentationFieldValues == null)
{
segmentationFieldValues = new ArrayList<>();
}
SegmentationFieldValue result = new SegmentationFieldValue();
result.setSegmentationFieldId(id);
result.setSegmentationFieldGroupId(groupId);
result.setValue(value);
segmentationFieldValues.add(result);
}
After which you can do the following in the main method of k1:
l1.addSegmentationFieldValue(1, 1, "a");

How to implement JSR-303 for Enums

I'm building a Rest WS and to validate the request elements I'm using a JSR-303 BeanValidation, but there's a field type Enum.
EmploymentType.java
public enum EmploymentType {
EMPTY, FULL, PARTTIME, CONTRACT, CASUAL;
public static EmploymentType getDefaultEnum() {
return EMPTY;
}
}
and the class I'm using use to implement this:
Employment.java
public class Employment implements Serializable{
private static final long serialVersionUID = 1L;
#NotNull(message="employmentType does not accept null values")
private EmploymentType employmentType;
#Valid
#NotNull(message="orgData does not accept null values")
private OrgData orgData;
public Employment() {
employmentType = EmploymentType.getDefaultEnum();
orgData = new OrgData();
}
public EmploymentType getEmploymentType() {
return employmentType;
}
public void setEmploymentType(EmploymentType employmentType) {
this.employmentType = employmentType;
}
public OrgData getOrgData() {
return orgData;
}
public void setOrgData(OrgData orgData) {
this.orgData = orgData;
}
}
the implementation I developed only prevents the enum being a null object, is there a way to validate that the value of the enum is only within the range of declared values? (EMPTY, FULL, PARTTIME, CONTRACT, CASUAL)
I believe you have to do the validation of the valueOf OR the name of the enum
Here is the excerpt what will make the validation happen
public class Employment implements Serializable {
#NotNull(message = "employmentType does not accept null values")
#Valid
private EmploymentType employmentType;
public EmploymentType getEmploymentType() {
getEmploymentTypeOfEnum();
return employmentType;
}
#Pattern(regexp = "EMPTY|FULL")
private String getEmploymentTypeOfEnum(){ // you don't need it to be public
return employmentType.name();
}
}

Get generic parameters of a generic type

For some background, I'm working on some framework stuff for a programming language I'm developing (JVM language, that is), and was testing some of the framework with Java classes, hence all the weird wrappers below.
So, my questions is, how do I get the type variables of a type parameter's bounds? Currently I have the following:
public static TemplateGenerics of(Class clazz) {
TemplateGenerics generics = new TemplateGenerics(); //TemplateGenerics is a wrapper class for generics that appear in the class header
Stream.of(clazz.getTypeParameters()).forEach(typeVariable -> {
java.lang.reflect.Type b = typeVariable.getBounds()[0];
try {
Class c = Primitives.resolveClass(b.getTypeName().split("<", 2)[0]); //Is there a better way to do this?
TemplateGenerics sub = TemplateGenerics.of(c); //Recursivley get the generics - it fails here
generics.getConditionals().add(new Conditional(new Type.Hierarchical(sub, c.getName()), Conditional.Condition.EXTENDS, typeVariable.getName())); //Conditional is another wrapper class that handles bounds of the generic,
//Type.Hierachical is yet another wrapper class that wraps types that appear in class headers
} catch (ClassNotFoundException e) {
throw new RuntimeException(e); //For testing purposes
}
});
return generics;
}
But this fails with a StackOverflowException when it encounters something like this:
public class A<T extends A<T>> ...
Since it just continues trying to get the type parameters of A over and over. I've been unable to find a method of getting the type variable's type variables... I've tried messing around with getGenericDeclaration, but it does not seem to return what I need. Any help is greatly appreciated.
#csharpfolk was right to suggest keeping a tally of what has already been parsed and making leverage of that. Below is a compile & runnable example which demos what this looks like in practice for your problem.
package so.answers;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;
public class TemplateGenerics {
private final List<Conditional> conditionals = new ArrayList<>();
public List<Conditional> getConditionals(){
return conditionals;
}
public String toString(){
return getConditionals().toString();
}
public static TemplateGenerics of(Class<?> clazz) {
return TemplateGenerics.of(clazz, new HashMap<>());
}
private static TemplateGenerics of(Class<?> clazz, Map<Class<?>, TemplateGenerics> existingGenericsForClasses) {
if(existingGenericsForClasses.containsKey(clazz)){
return existingGenericsForClasses.get(clazz);
}
final TemplateGenerics generics = new TemplateGenerics();
existingGenericsForClasses.put(clazz, generics);
Stream.of(clazz.getTypeParameters()).forEach(typeVariable -> {
java.lang.reflect.Type b = typeVariable.getBounds()[0];
try {
Class<?> c = Primitives.resolveClass(b.getTypeName().split("<", 2)[0]); //Is there a better way to do this?
TemplateGenerics sub = TemplateGenerics.of(c, existingGenericsForClasses); //Recursivley get the generics - it fails here
generics.getConditionals().add(new Conditional(new Type.Hierarchical(sub, c.getName()), Conditional.Condition.EXTENDS, typeVariable.getName())); //Conditional is another wrapper class that handles bounds of the generic,
//Type.Hierachical is yet another wrapper class that wraps types that appear in class headers
} catch (ClassNotFoundException e) {
throw new RuntimeException(e); //For testing purposes
}
});
return generics;
}
public static class Conditional{
public static enum Condition{
EXTENDS,
SUPER
}
private final Type.Hierarchical hierarchical;
private final Condition condition;
private final String typeName;
public Conditional(Type.Hierarchical hierarchical, Condition condition, String typeName){
this.hierarchical = hierarchical;
this.condition = condition;
this.typeName = typeName;
}
public String toString(){
return "Conditional$typeName="+typeName+" "
+"Conditional$condition="+condition+" "
+"Conditional$hierarchical={"+hierarchical+"} ";
}
}
public static class Primitives{
public static Class<?> resolveClass(String name) throws ClassNotFoundException{
String trimmedName = name.replaceFirst(TemplateGenerics.class.getCanonicalName()+".", "");
//not sure why this nonsense with the trimmed name
//is necessary, but you seem to already have a better
//version of this method anyway
if(trimmedName.contains(TemplateGenerics.class.getCanonicalName())){
name = trimmedName;
}
return Primitives.class.getClassLoader().loadClass(name);
}
}
public static class Type{
public static class Hierarchical{
private TemplateGenerics generics;
private String name;
public Hierarchical(TemplateGenerics generics, String name){
this.generics = generics;
this.name = name;
}
private boolean printing;
public String toString(){
try{
if(!printing){
printing = true;
return "Hierarchical$name="+name+ " Hierarchical$generics=("+generics+")";
} else {
return "Hierarchical$name="+name;
}
} finally {
printing = false;
}
}
}
}
public static class B{
}
public static class C<T extends B>{
}
public static class A<T extends A<T>>{
}
public static class X<T extends Y>{
}
public static class Y<T extends X>{
}
public static void main(String[] args){
System.out.println("For A:"+TemplateGenerics.of(A.class));
System.out.println("For C:"+TemplateGenerics.of(C.class));
System.out.println("For X:"+TemplateGenerics.of(X.class));
}
}
Output:
For A:[Conditional$typeName=T Conditional$condition=EXTENDS Conditional$hierarchical={Hierarchical$name=so.answers.TemplateGenerics$A Hierarchical$generics=([Conditional$typeName=T Conditional$condition=EXTENDS Conditional$hierarchical={Hierarchical$name=so.answers.TemplateGenerics$A} ])} ]
For C:[Conditional$typeName=T Conditional$condition=EXTENDS Conditional$hierarchical={Hierarchical$name=so.answers.TemplateGenerics$B Hierarchical$generics=([])} ]
For X:[Conditional$typeName=T Conditional$condition=EXTENDS Conditional$hierarchical={Hierarchical$name=so.answers.TemplateGenerics$Y Hierarchical$generics=([Conditional$typeName=T Conditional$condition=EXTENDS Conditional$hierarchical={Hierarchical$name=so.answers.TemplateGenerics$X Hierarchical$generics=([Conditional$typeName=T Conditional$condition=EXTENDS Conditional$hierarchical={Hierarchical$name=so.answers.TemplateGenerics$Y} ])} ])} ]
You could clean up the printing to look a little less redundant by printing the type rather than the generics directly. But this shows all the salient features of the solution.

get inside setter information about instance variable name

Is it possible to get the variable name of the current instance in the object's setter?
Something like this
public class Class {
private DataType dataType;
}
public class DataType {
public void setValue(String value) {
if (variableName is 'dataType') {
this.value = value;
} else {
this.value = null;
}
}
}
If it's not possible using standard utilities then it's possible to create some kind of annotation to store there variable name and then use it in setter?
When I try to do like this - the annotation is null.
I create annotation
#Retention(RetentionPolicy.CLASS)
#Target(ElementType.FIELD)
public #interface FieldName {
String fieldName();
}
Then I add it to field
public class Class {
#FieldName(fieldName = "dataType")
private DataType dataType;
}
And when I try to get it in the getter of DataType - the annotation FieldName is null.
private String wrapGetter(String requiredFieldName, String dataTypeField) {
FieldName fieldName = this.getClass().getAnnotation(FieldName.class);
if (fieldName.fieldName().equals(requiredFieldName)) {
return dataTypeField;
} else {
return dataTypeField;
}
}
There are a few problems with what you are trying to do:
Your RetentionPolicy is set it to CLASS which means that class loader will discard it and it will not be available at runtime. You should use RetentionPolicy.RUNTIME instead.
this.getClass().getAnnotation(FieldName.class) will give you the class annotation.
In the below example the annotation is not null and you can get "example" string in setValue method:
#FieldName(fieldName = "example")
public class DataType {
public void setValue(String value) {
System.out.println(this.getClass().getAnnotation(FieldName.class));
}
}
public static void main(String[] args) {
new DataType().setValue("ignored");
}
This also requires to change the target of your annotation to #Target(ElementType.TYPE).
Variable or field name is just a reference, it points to some object in the memory. You can have multiple references to the same object. Although you can annotate fields in different classes, it will be a problem with local variables and parameters - it is difficult to say as I don't know what you are trying to achieve.
Problematic example:
public class ClassOne {
#FieldName(fieldName = "dataType")
private DataType a;
}
public class ClassTwo {
#FieldName(fieldName = "dataType")
private DataType b;
}
public class ClassThree {
public void doSomething() {
DataType c = new DataType();
}
}
public class ClassFour {
public void doSomething(DataType d) {
// ...
}
}
Generally, it all comes down to the problem that the instance of the class has no information about how it is being referred to. However, for the field, enclosing class has this information. Consider moving your method into that class. And you can handle this without any annotations:
public class DataType {
public void setValue(String value) {
// ...
}
}
public class ClassOne {
private DataType dataType;
public void setDataTypeValue(String value) {
dataType.setValue(value);
}
}
public class ClassTwo {
private DataType anyOtherFieldName;
public void setDataTypeValue(String value) {
anyOtherFieldName.setValue(null);
}
}
The setter that sets null and ignores the parameter is very misleading, your IDE should be giving you a warning about unused parameter, it's not without a reason. I think you should consider a redesign, but I cannot advice you further without knowing more details.
Instead of solving the problem, try to solve the cause of that problem.
Use #Retention(RetentionPolicy.RUNTIME)
Replace this
FieldName fieldNameAnnotation = field.getAnnotation(FieldName.class);
With this
Field field = this.getClass().getField("dataType");
FieldName fieldName = field.getAnnotation(FieldName.class);

Editing a complex Java object in a Tapestry 5 web application

I am using Tapestry 5.3.6 for a web application and I want the user to edit an instance of a Java class (a "bean", or POJO) using a web form (which immediately suggests the use of beaneditform) - however the Java class to be edited has a fairly complex structure. I am looking for the simplest way of doing this in Tapestry 5.
Firstly, lets define some utility classes e.g.
public class ModelObject {
private URI uri;
private boolean modified;
// the usual constructors, getters and setters ...
}
public class Literal<T> extends ModelObject {
private Class<?> valueClass;
private T value;
public Literal(Class<?> valueClass) {
this.valueClass = valueClass;
}
public Literal(Class<?> valueClass, T value) {
this.valueClass = valueClass;
this.value = value;
}
// the usual getters and setters ...
}
public class Link<T extends ModelObject> extends ModelObject {
private Class<?> targetClass;
private T target;
public Link(Class<?> targetClass) {
this.targetClass = targetClass;
}
public Link(Class<?> targetClass, T target) {
this.targetClass = targetClass;
this.target = target;
}
// the usual getters and setters ...
}
Now you can create some fairly complex data structures, for example:
public class HumanBeing extends ModelObject {
private Literal<String> name;
// ... other stuff
public HumanBeing() {
name = new Literal<String>(String.class);
}
// the usual getters and setters ...
}
public class Project extends ModelObject {
private Literal<String> projectName;
private Literal<Date> startDate;
private Literal<Date> endDate;
private Literal<Integer> someCounter;
private Link<HumanBeing> projectLeader;
private Link<HumanBeing> projectManager;
// ... other stuff, including lists of things, that may be Literals or
// Links ... e.g. (ModelObjectList is an enhanced ArrayList that remembers
// the type(s) of the objects it contains - to get around type erasure ...
private ModelObjectList<Link<HumanBeing>> projectMembers;
private ModelObjectList<Link<Project>> relatedProjects;
private ModelObjectList<Literal<String>> projectAliases;
// the usual constructors, getters and setters for all of the above ...
public Project() {
projectName = new Literal<String>(String.class);
startDate = new Literal<Date>(Date.class);
endDate = new Literal<Date>(Date.class);
someCounter = new Literal<Integer>(Integer.class);
projectLeader = new Link<HumanBeing>(HumanBeing.class);
projectManager = new Link<HumanBeing>(HumanBeing.class);
projectMembers = new ModelObjectList<Link<HumanBeing>>(Link.class, HumanBeing.class);
// ... more ...
}
}
If you point beaneditform at an instance of Project.class, you will not get very far before you have to supply a lot of custom coercers, translators, valueencoders, etc - and then you still run into the problem that you can't use generics when "contributing" said coercers, translators, valueencoders, etc.
I then started writing my own components to get around these problems (e.g. ModelObjectDisplay and ModelObjectEdit) but this would require me to understand a lot more of the guts of Tapestry than I have time to learn ... it feels like I might be able to do what I want using the standard components and liberal use of "delegate" etc. Can anyone see a simple path for me to take with this?
Thanks for reading this far.
PS: if you are wondering why I have done things like this, it is because the model represents linked data from an RDF graph database (aka triple-store) - I need to remember the URI of every bit of data and how it relates (links) to other bits of data (you are welcome to suggest better ways of doing this too :-)
EDIT:
#uklance suggested using display and edit blocks - here is what I had already tried:
Firstly, I had the following in AppPropertyDisplayBlocks.tml ...
<t:block id="literal">
<t:delegate to="literalType" t:value="literalValue" />
</t:block>
<t:block id="link">
<t:delegate to="linkType" t:value="linkValue" />
</t:block>
and in AppPropertyDisplayBlocks.java ...
public Block getLiteralType() {
Literal<?> literal = (Literal<?>) context.getPropertyValue();
Class<?> valueClass = literal.getValueClass();
if (!AppModule.modelTypes.containsKey(valueClass))
return null;
String blockId = AppModule.modelTypes.get(valueClass);
return resources.getBlock(blockId);
}
public Object getLiteralValue() {
Literal<?> literal = (Literal<?>) context.getPropertyValue();
return literal.getValue();
}
public Block getLinkType() {
Link<?> link = (Link<?>) context.getPropertyValue();
Class<?> targetClass = link.getTargetClass();
if (!AppModule.modelTypes.containsKey(targetClass))
return null;
String blockId = AppModule.modelTypes.get(targetClass);
return resources.getBlock(blockId);
}
public Object getLinkValue() {
Link<?> link = (Link<?>) context.getPropertyValue();
return link.getTarget();
}
AppModule.modelTypes is a map from java class to a String to be used by Tapestry e.g. Link.class -> "link" and Literal.class -> "literal" ... in AppModule I had the following code ...
public static void contributeDefaultDataTypeAnalyzer(
MappedConfiguration<Class<?>, String> configuration) {
for (Class<?> type : modelTypes.keySet()) {
String name = modelTypes.get(type);
configuration.add(type, name);
}
}
public static void contributeBeanBlockSource(
Configuration<BeanBlockContribution> configuration) {
// using HashSet removes duplicates ...
for (String name : new HashSet<String>(modelTypes.values())) {
configuration.add(new DisplayBlockContribution(name,
"blocks/AppPropertyDisplayBlocks", name));
configuration.add(new EditBlockContribution(name,
"blocks/AppPropertyEditBlocks", name));
}
}
I had similar code for the edit blocks ... however none of this seemed to work - I think because the original object was passed to the "delegate" rather than the de-referenced object which was either the value stored in the literal or the object the link pointed to (hmm... should be [Ll]inkTarget in the above, not [Ll]inkValue). I also kept running into errors where Tapestry couldn't find a suitable "translator", "valueencoder" or "coercer" ... I am under some time pressure so it is difficult to follow these twisty passages through in order to get out of the maze :-)
I would suggest to build a thin wrapper around the Objects you would like to edit though the BeanEditForm and pass those into it. So something like:
public class TapestryProject {
private Project project;
public TapestryProject(Project proj){
this.project = proj;
}
public String getName(){
this.project.getProjectName().getValue();
}
public void setName(String name){
this.project.getProjectName().setValue(name);
}
etc...
}
This way tapestry will deal with all the types it knows about leaving you free of having to create your own coersions (which is quite simple in itself by the way).
You can contribute blocks to display and edit your "link" and "literal" datatypes.
The beaneditform, beaneditor and beandisplay are backed by the BeanBlockSource service. BeanBlockSource is responsible for providing display and edit blocks for various datatypes.
If you download the tapestry source code and have a look at the following files:
tapestry-core\src\main\java\org\apache\tapestry5\corelib\pages\PropertyEditBlocks.java
tapestry-core\src\main\resources\org\apache\tapestry5\corelib\pages\PropertyEditBlocks.tml
tapestry-core\src\main\java\org\apache\tapestry5\services\TapestryModule.java
You will see how tapestry contributes EditBlockContribution and DisplayBlockContribution to provide default blocks (eg for a "date" datatype).
If you contribute to BeanBlockSource, you could provide display and edit blocks for your custom datatypes. This will require you reference blocks by id in a page. The page can be hidden from your users by annotating it with #WhitelistAccessOnly.
http://tapestry.apache.org/current/apidocs/org/apache/tapestry5/services/BeanBlockSource.html
http://tapestry.apache.org/current/apidocs/org/apache/tapestry5/services/DisplayBlockContribution.html
http://tapestry.apache.org/current/apidocs/org/apache/tapestry5/services/EditBlockContribution.html
http://tapestry.apache.org/current/apidocs/org/apache/tapestry5/annotations/WhitelistAccessOnly.html
Here's an example of using an interface and a proxy to hide the implementation details from your model. Note how the proxy takes care of updating the modified flag and is able to map URI's from the Literal array to properties in the HumanBeing interface.
package com.github.uklance.triplestore;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import org.junit.Test;
public class TripleStoreOrmTest {
public static class Literal<T> {
public String uri;
public boolean modified;
public Class<T> type;
public T value;
public Literal(String uri, Class<T> type, T value) {
super();
this.uri = uri;
this.type = type;
this.value = value;
}
#Override
public String toString() {
return "Literal [uri=" + uri + ", type=" + type + ", value=" + value + ", modified=" + modified + "]";
}
}
public interface HumanBeing {
public String getName();
public void setName(String name);
public int getAge();
public void setAge();
}
public interface TripleStoreProxy {
public Map<String, Literal<?>> getLiteralMap();
}
#Test
public void testMockTripleStore() {
Literal<?>[] literals = {
new Literal<String>("http://humanBeing/1/Name", String.class, "Henry"),
new Literal<Integer>("http://humanBeing/1/Age", Integer.class, 21)
};
System.out.println("Before " + Arrays.asList(literals));
HumanBeing humanBeingProxy = createProxy(literals, HumanBeing.class);
System.out.println("Before Name: " + humanBeingProxy.getName());
System.out.println("Before Age: " + humanBeingProxy.getAge());
humanBeingProxy.setName("Adam");
System.out.println("After Name: " + humanBeingProxy.getName());
System.out.println("After Age: " + humanBeingProxy.getAge());
Map<String, Literal<?>> literalMap = ((TripleStoreProxy) humanBeingProxy).getLiteralMap();
System.out.println("After " + literalMap);
}
protected <T> T createProxy(Literal<?>[] literals, Class<T> type) {
Class<?>[] proxyInterfaces = { type, TripleStoreProxy.class };
final Map<String, Literal> literalMap = new HashMap<String, Literal>();
for (Literal<?> literal : literals) {
String name = literal.uri.substring(literal.uri.lastIndexOf("/") + 1);
literalMap.put(name, literal);
}
InvocationHandler handler = new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getDeclaringClass().equals(TripleStoreProxy.class)) {
return literalMap;
}
if (method.getName().startsWith("get")) {
String name = method.getName().substring(3);
return literalMap.get(name).value;
} else if (method.getName().startsWith("set")) {
String name = method.getName().substring(3);
Literal<Object> literal = literalMap.get(name);
literal.value = args[0];
literal.modified = true;
}
return null;
}
};
return type.cast(Proxy.newProxyInstance(getClass().getClassLoader(), proxyInterfaces, handler));
}
}

Categories

Resources