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 the Java class ("bean" or POJO) using a web form (which immediately suggests using beaneditform ) - however, the editable Java class has a rather complicated structure. I am looking for the easiest way to do this in Tapestry 5.

First, let's define some utility classes, for example.

 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 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 ... eg (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 specify beaneditform in an instance of Project.class, you won’t get very far before you have to supply many custom coercers, translators, cost icons, etc. - and then you still encounter a problem, you can’t use generics when they “contribute” to the mentioned coercors, translators, denominators, etc.

Then I started writing my own components to get around these issues (e.g. ModelObjectDisplay and ModelObjectEdit ), but it would require that I understand a lot more Gobelin guts than I have time to find out ... it feels like I can to do what I want using standard components and liberal use of the "delegate", etc. Can anyone see a simple way for me to take this?

Thanks for reading this.

PS: if you're wondering why I did such things, this is because the model is related data from an RDF graphic database (also called a triple-store) - I need to remember the URI of each data bit and how it relates (links ) with other data bits (you can also suggest better ways to do this :-)

EDIT:

@uklance suggested using display and edit blocks - this is what I have 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 the java class to String that Tapestry will use, for example. Link.class → "link" and Literal.class → "literal" ... in the 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 editing blocks ... however, none of this seemed to work for me - I think because the original object was passed to the "delegate" and not the deleted object, which was either a value stored in a letter or an object, on which the link indicates (hmm ... should be [Ll] inkTarget in the above, and not in [Ll] inkValue). I also continued to encounter errors when the Tapestry could not find a suitable “translator”, “value coder” or “coercion” ... I am under some pressure of the time, so it is difficult to follow these winding passages to get out of the maze :-)

+4
source share
3 answers

I would suggest creating a thin shell around the objects that you would like to edit, although BeanEditForm and pass them to 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... } 

Thus, the tapestry will deal with all the types that he knows about leaving you free from the need to create your own interactions (which, by the way, is pretty simple).

+2
source

You can add blocks to display and edit the "link" and "literal" data types.

beaneditform , beaneditor and beandisplay supported by the BeanBlockSource service. BeanBlockSource is responsible for providing display and editing blocks for various data types.

If you download the tapestry source code and 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 the tapestry contributes to EditBlockContribution and DisplayBlockContribution to provide default blocks (for example, for the "date" data type).

If you contribute to BeanBlockSource , you can provide display and edit blocks for your custom data types. To do this, you will need link blocks by id on the page. The page can be hidden from your users by annotating it with @WhitelistAccessOnly .

+2
source

Here is an example of using an interface and proxy to hide implementation details from your model. Notice how the proxy server takes care of updating the changed flag and is able to map URIs 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)); } } 
0
source

All Articles