语言(有id)
能力(有id)
CompetenceName(具有能力参考,语言参考和字符串)
Competence对使用Map实现的CompetenceName有一对多的引用,包含一个对象,每个语言都有一个Competence的名称.请注意,权限是动态创建的,因此它们的名称不能存在于资源包中.
在网页上列出Competences时,我希望它们以当前登录用户的语言显示,它存储在Session Scoped Managed Bean中.
如果没有破坏良好的MVC设计,有没有什么好方法可以做到这一点?我的第一个想法是通过FacesContext直接从Competence实体中的“getName”方法获取会话范围bean,并在CompetenceNames的地图中查找如下:
public class Competence { ... @MapKey(name="language") @OneToMany(mappedBy="competence",cascade=CascadeType.ALL,orphanRemoval=true) private Map<Language,CompetenceName> competenceNames; public String getName(String controller){ FacesContext context = FacesContext.getCurrentInstance(); ELResolver resolver = context.getApplication().getELResolver(); SessionController sc = (SessionController)resolver.getValue(context.getELContext(),null,"sessionController"); Language language = sc.getLoggedInUser().getLanguage(); if(competenceNames.get(language) != null) return competenceNames.get(language).getName(); else return "resource missing"; }
这个解决方案感觉非常粗糙,因为实体依赖于Controller层,并且每次我想要它的名字时都必须获取会话控制器.一个更符合MVC的解决方案是采用一个Language参数,但这意味着来自JSF的每一次调用都必须包含从会话作用域托管bean中获取的语言,这也不是一个好的解决方案.
有没有人对这个问题有任何想法或设计模式?
解决方法
在JSF中,< resource-bundle> faces-config.xml中的条目和< f:loadBundle>在XHTML中也可以指向一个完整的ResourceBundle类而不是.properties文件的basename.在Java SE 6中,有一个新的ResourceBundle.Control
API可以完全控制加载和填充包.
了解这些事实后,应该可以使用自定义ResourceBundle和Control从DB加载捆绑消息.这是一个启动示例:
public class CompetenceBundle extends ResourceBundle { protected static final String BASE_NAME = "Competence.messages"; // Can be name of @NamedQuery protected static final Control DB_CONTROL = new DBControl(); private Map<String,String> messages; public CompetenceBundle() { setParent(ResourceBundle.getBundle(BASE_NAME,FacesContext.getCurrentInstance().getViewRoot().getLocale(),DB_CONTROL)); } protected CompetenceBundle(Map<String,String> messages) { this.messages = messages; } @Override protected Object handleGetObject(String key) { return messages != null ? messages.get(key) : parent.getObject(key); } @Override public Enumeration<String> getKeys() { return messages != null ? Collections.enumeration(messages.keySet()) : parent.getKeys(); } protected static class DBControl extends Control { @Override public ResourceBundle newBundle (String baseName,Locale locale,String format,ClassLoader loader,boolean reload) throws IllegalAccessException,InstantiationException,IOException { String language = locale.getLanguage(); Map<String,String> messages = getItSomehow(baseName,language); // Do your JPA thing. The baseName can be used as @NamedQuery name. return new CompetenceBundle(messages); } } }
这样你就可以在faces-config.xml中声明如下:
<resource-bundle> <base-name>com.example.i18n.CompetenceBundle</base-name> <var>competenceBundle</var> </resource-bundle>
或者在Facelet中如下:
<f:loadBundle basename="com.example.i18n.CompetenceBundle" var="competenceBundle" />
无论哪种方式,您都可以通常的方式使用它:
<h:outputText value="#{competenceBundle.name}" />