我构建的应用程序的要求要求用户角色是动态的,它们将存储在数据库中,并且它们也将映射到应用程序的功能(表单),也存储在数据库。
限制角色访问特定页面并不困难,但要求还规定表单输入必须根据角色进行自定义,这意味着输入可以是强制的,也可以是不可见的,可见或不可读,读取 - 仅仅基于角色。
我控制这些限制的方法是基于为每个角色创建一个属性文件,它将所有表单的所有输入存储在应用程序中,作为键,以及一个长字符串作为值,我们在其中定义状态输入,如下所示:
user-inputs.properties
# form.input=mandatory:visibility
searchBooks.bookName=true:true
searchBooks.bookCategory=false:true
searchBooks.authorName=false:false
admin-inputs.properties
searchBooks.bookName=true:true
searchBooks.bookCategory=false:true
searchBooks.authorName=false:true
然后执行一些神奇的Java代码,无论何时访问表单,从特定用户角色的文件中读取其输入属性,并解析值,以便为rendered=""
和{required=""
提供正确的值{1}}的{1}}属性。
这可能是一个解决方案,但应用程序的输入不仅仅是一个书名和类别,意味着我将添加许多必需和呈现的属性,这将使JSF页面看起来很丑,其中包含大量的变量托管bean。
我的问题是否有更好的方法/框架/解决方案?
答案 0 :(得分:1)
我认为你是以正确的方式,我将继续使用你的方法,包括创建多个属性文件,每个用户一个,除了我们不会使用任何"巨额变量 在托管bean" 。
因此,第一步是使用单个资源包前缀(<var></var>
中的<resource-bundle>
)管理多个资源属性,在第二步中,我们将看到如何在这些文件之间切换,以及在最后一步中,我们将使用JSTL从属性文件中读取。
我们首先在faces-config
文件中定义我们的ResourceBundle:
<application>
<resource-bundle>
<base-name>UserMessages</base-name>
<var>msgs</var>
</resource-bundle>
</application>
UserMessages
是ResourceBundle
我们将实现允许我们在属性文件之间切换的逻辑(假设yourpackage.user-inputs是user-inputs.properties的完全限定名称) ):
import java.util.Enumeration;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import javax.faces.context.FacesContext;
public class UserMessages extends ResourceBundle {
public UserMessages() {
// we are loading user-inputs.properties as the default properties file
setParent(getBundle("yourpackage.user-inputs", FacesContext.getCurrentInstance()
.getViewRoot().getLocale()));
}
@Override
protected Object handleGetObject(String key) {
// we could just return parent.getObject(key) but we want to respect JSF recommandations
try {
return parent.getObject(key);
} catch (MissingResourceException e) {
return "???" + key + "???";
}
}
@Override
public Enumeration<String> getKeys() {
return parent.getKeys();
}
// this is the method that will allow us to switch between our .properties
public void setResourceBundle(String basename) {
setParent(getBundle(basename, FacesContext.getCurrentInstance()
.getViewRoot().getLocale()));
}
}
为了从属性文件切换到另一个属性文件,我们需要使用我们刚刚在上面的类中声明的方法setResourceBundle(String basename)
,所以在您声明业务逻辑的托管bean中以及您在哪里打算根据用户的角色切换文件,您需要注入包,如:
//don't forget adding getters and setters or you end with NullPointerException
@ManagedProperty("#{msgs}")
private UserMessages userMesssages;
然后,要切换到另一个文件(admin-inputs.properties
),只需使用它:
//yourpackage.admin-inputs is the fully qualified name
userMesssages.setResourceBundle("yourpackage.admin-inputs");
NB:您只能在请求范围的bean中以这种方式(上面)注入包,要在更广泛的范围内使用它,请参阅:Read i18n variables from properties file in a Bean
现在,我们可以轻松地从user-inputs
切换到admin-inputs
,最后一步是最简单的步骤。
坏消息是,在使用此方法时,您需要为您愿意管理的每个输入添加rendered=""
和required=""
属性(但不要忘记好的输入是你不需要管理托管bean中的变量;))。
首先,您需要在xhtml文件的顶部添加JSTL名称空间声明:
xmlns:fn="http://java.sun.com/jsp/jstl/functions"
您可以在javadocs中找到有关函数substringAfter
的JSTL函数的更多信息:
返回特定子字符串后面的字符串的子集。
示例:
P.O. Box: ${fn:substringAfter(zip, "-")}
函数substringBefore
:
返回特定子字符串之前的字符串子集。
示例:
Zip (without P.O. Box): ${fn:substringBefore(zip, "-")}
其次,由于String
的第一部分代表required
属性:
//Returns the substring of msgs['searchBooks.authorName'] before the first occurrence of the separator ':'
required="${fn:substringBefore(msgs['searchBooks.authorName'], ':')}"
和第二部分:
//Returns the substring of msgs['searchBooks.authorName'] after the first occurrence of the separator ':'.
rendered="${fn:substringAfter(msgs['searchBooks.authorName'], ':')}"