在Tapestry 5 web应用程序中编辑复杂的Java对象



我使用Tapestry 5.3.6用于web应用程序,我希望用户使用web表单(立即建议使用beaneditform)编辑Java类("bean"或POJO)的实例-然而要编辑的Java类具有相当复杂的结构。我正在寻找在Tapestry 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的一个实例,在你必须提供大量自定义的覆盖器、翻译器、值编码器等之前,你将不会走得太远——然后你仍然会遇到在"贡献"覆盖器、翻译器、值编码器等时不能使用泛型的问题。

然后我开始编写自己的组件来解决这些问题(例如ModelObjectDisplayModelObjectEdit),但这将要求我了解更多Tapestry的核心,而不是我有时间去学习……感觉我可以使用标准组件和自由使用"委托"等来做我想做的事情。有人能给我一条简单的路吗?

感谢你读到这里。

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"的错误……我在一些时间压力下,所以很难跟随这些曲折的通道,以走出迷宫:-)

我建议在您想要通过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...
}

这样,tapestry将处理它所知道的所有类型,从而使您不必创建自己的转换(顺便说一下,这本身非常简单)。

您可以贡献块来显示和编辑您的"link""literal"数据类型。

beaneditformbeaneditorbeandisplayBeanBlockSource服务支持。BeanBlockSource负责为各种数据类型提供显示和编辑块。

如果您下载了tapestry源代码并查看了以下文件:

  • 主要tapestry-core src java org apache tapestry5 corelib PropertyEditBlocks.java页面
  • 主要tapestry-core src 资源 org apache tapestry5 corelib PropertyEditBlocks.tml页面
  • 主要tapestry-core src java org apache tapestry5 services TapestryModule.java

您将看到tapestry如何贡献EditBlockContributionDisplayBlockContribution来提供默认块(例如对于"date"数据类型)。

如果您对BeanBlockSource做出贡献,您可以为您的自定义数据类型提供显示和编辑块。这将要求您在页面中按id引用块。该页面可以通过使用@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));
    }
}

相关内容

  • 没有找到相关文章

最新更新