Редактирование сложного объекта Java в веб-приложении Tapestry 5
Я использую Tapestry 5.3.6 для веб-приложения и хочу, чтобы пользователь редактировал экземпляр класса Java ("bean", или POJO), используя веб-форму (которая сразу же предлагает использовать beaneditform
) - однако класс Java для редактирования имеет довольно сложную структуру. Я ищу самый простой способ сделать это в Гобелене 5.
Во-первых, давайте определим некоторые служебные классы, например:
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 ...
}
Теперь вы можете создать несколько довольно сложных структур данных, например:
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 ...
}
}
Если вы указываете beaneditform
в экземпляре Project.class вы не доберетесь слишком далеко до того, как вам понадобится предоставить множество пользовательских коэрсеров, переводчиков, кодировщиков значений и т. д. - и тогда вы все равно столкнетесь с проблемой, что вы не можете использовать дженерики при "содействии" указанные коучеры, переводчики, ценники и т. д.
Затем я начал писать свои собственные компоненты, чтобы обойти эти проблемы (например, ModelObjectDisplay
а также ModelObjectEdit
) но это потребовало бы, чтобы я понял гораздо больше внутренностей Гобелена, чем у меня есть время, чтобы освоить... такое ощущение, что я мог бы делать то, что я хочу, используя стандартные компоненты и либеральное использование "делегата" и т. д. Может кто-нибудь увидеть простой путь для меня, чтобы взять с этим?
Спасибо за чтение этого далеко.
PS: если вам интересно, почему я сделал такие вещи, то это потому, что модель представляет связанные данные из базы данных RDF-графа (иначе говоря, триплетное хранилище) - мне нужно запомнить URI каждого бита данных и как это связано (ссылки) к другим частям данных (вы также можете предложить лучшие способы сделать это:-)
РЕДАКТИРОВАТЬ:
@uklance предложил использовать блоки отображения и редактирования - вот что я уже пробовал:
Во-первых, у меня было следующее в 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>
и в 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 - это карта из java-класса в строку, которая будет использоваться Tapestry, например Link.class -> "link" и Literal.class -> "literal" ... в AppModule у меня был следующий код...
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));
}
}
У меня был похожий код для блоков редактирования... но, похоже, ничего из этого не работало - я думаю, потому что исходный объект был передан "делегату", а не объекту, на который ссылаются, который был либо значением, хранящимся в литерале, либо объект, на который указывает ссылка (хмм... в приведенном выше примере должно быть [Ll]inkTarget, а не [Ll]inkValue). Я также продолжал сталкиваться с ошибками, когда Tapestry не мог найти подходящего "переводчика", "valueencoder" или "coercer" ... Я нахожусь под некоторым временным давлением, поэтому трудно пройти эти извилистые проходы, чтобы выбраться из лабиринт:-)
3 ответа
Я бы предложил создать тонкую оболочку вокруг объектов, которые вы хотели бы редактировать через BeanEditForm, и передать их в него. Так что-то вроде:
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...
}
Таким образом, гобелен будет иметь дело со всеми типами, которые он знает о том, что вам не нужно создавать собственные приведения (что, кстати, само по себе довольно просто).
Вы можете добавлять блоки для отображения и редактирования вашего "link"
а также "literal"
типы данных.
beaneditform
, beaneditor
а также beandisplay
поддерживаются BeanBlockSource
оказание услуг. BeanBlockSource
отвечает за предоставление блоков отображения и редактирования для различных типов данных.
Если вы скачали исходный код гобелена и посмотрите на следующие файлы:
- гобелен одножильных \ SRC \ главная \ Java \ орг \ Apache\tapestry5\corelib\ страницы \PropertyEditBlocks.java
- гобелен-ядро \ SRC \ главная \ ресурсы \ орг \ Apache\tapestry5\corelib\ страницы \PropertyEditBlocks.tml
- гобелен-ядро \ SRC \ главная \ Java \ орг \ Apache \ tapestry5 \ услуги \TapestryModule.java
Вы увидите, как гобелен способствует EditBlockContribution
а также DisplayBlockContribution
обеспечить блоки по умолчанию (например, для "date"
тип данных).
Если вы способствуете BeanBlockSource
, вы можете обеспечить отображение и редактирование блоков для ваших пользовательских типов данных. Это потребует от вас ссылки блоков по идентификатору на странице. Страница может быть скрыта от ваших пользователей, пометив ее @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
Вот пример использования интерфейса и прокси, чтобы скрыть детали реализации от вашей модели. Обратите внимание, как прокси-сервер заботится об обновлении измененного флага и может сопоставлять URI из массива Literal со свойствами в интерфейсе HumanBeing.
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));
}
}