Using <resource-bundle> files I'm able to have i18n text in my JSF pages.
But is it possible to access these same properties in my managed bean so I can set faces messages with i18n values?
Assuming that you've configured it as follows:
<resource-bundle>
<base-name>com.example.i18n.text</base-name>
<var>text</var>
</resource-bundle>
If your bean is request scoped, you can just inject the <resource-bundle> as #ManagedProperty by its <var>:
#ManagedProperty("#{text}")
private ResourceBundle text;
public void someAction() {
String someKey = text.getString("some.key");
// ...
}
Or if you just need some specific key:
#ManagedProperty("#{text['some.key']}")
private String someKey;
public void someAction() {
// ...
}
If your bean is however in a broader scope, then evaluate #{text} programmatically in method local scope:
public void someAction() {
FacesContext context = FacesContext.getCurrentInstance();
ResourceBundle text = context.getApplication().evaluateExpressionGet(context, "#{text}", ResourceBundle.class);
String someKey = text.getString("some.key");
// ...
}
Or if you only need some specific key:
public void someAction() {
FacesContext context = FacesContext.getCurrentInstance();
String someKey = context.getApplication().evaluateExpressionGet(context, "#{text['some.key']}", String.class);
// ...
}
You can even just get it by the standard ResourceBundle API the same way as JSF itself is already doing under the covers, you'd only need to repeat the base name in code:
public void someAction() {
FacesContext context = FacesContext.getCurrentInstance();
ResourceBundle text = ResourceBundle.getBundle("com.example.i18n.text", context.getViewRoot().getLocale());
String someKey = text.getString("some.key");
// ...
}
Or if you're managing beans by CDI instead of JSF, then you can create a #Producer for that:
public class BundleProducer {
#Produces
public PropertyResourceBundle getBundle() {
FacesContext context = FacesContext.getCurrentInstance();
return context.getApplication().evaluateExpressionGet(context, "#{text}", PropertyResourceBundle.class);
}
}
And inject it as below:
#Inject
private PropertyResourceBundle text;
Alternatively, if you're using the Messages class of the JSF utility library OmniFaces, then you can just set its resolver once to let all Message methods utilize the bundle.
Messages.setResolver(new Messages.Resolver() {
public String getMessage(String message, Object... params) {
ResourceBundle bundle = ResourceBundle.getBundle("com.example.i18n.text", Faces.getLocale());
if (bundle.containsKey(message)) {
message = bundle.getString(message);
}
return MessageFormat.format(message, params);
}
});
See also the example in the javadoc and the showcase page.
Another possibility:
faces-config.xml
<?xml version='1.0' encoding='UTF-8'?>
<faces-config ...>
<application>
<locale-config>
<default-locale>de</default-locale>
</locale-config>
<resource-bundle>
<base-name>de.fhb.resources.text.backend</base-name>
<var>backendText</var>
</resource-bundle>
</application>
</faces-config>
YourBean.java
FacesContext context = FacesContext.getCurrentInstance();
Application app = context.getApplication();
ResourceBundle backendText = app.getResourceBundle(context, "backendText");
backendText.getString("your.property.key");
Here is a solution I'm using, not as simple but at least working :
First class :
package com.spectotechnologies.website.util;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator;
/**
*
* #author Alexandre Lavoie
*/
public class MessageInterpolator extends ResourceBundleMessageInterpolator
{
public static final String LANGUAGE_TAG_PATTERN = "\\{[^}]*\\}";
#Override
public String interpolate(String p_sMessage, Context p_oContext)
{
return super.interpolate(replaceMessages(p_sMessage),p_oContext);
}
#Override
public String interpolate(String p_sMessage, Context p_oContext, Locale p_oLocale)
{
StringManager.setLocale(p_oLocale);
return super.interpolate(replaceMessages(p_sMessage),p_oContext,p_oLocale);
}
private String replaceMessages(String p_sMessage)
{
Matcher oMatcher;
String sKey;
String sReplacement;
StringBuffer sbTemp = new StringBuffer();
oMatcher = Pattern.compile(LANGUAGE_TAG_PATTERN).matcher(p_sMessage);
while(oMatcher.find())
{
sKey = oMatcher.group().substring(1,oMatcher.group().length() - 1);
sReplacement = StringManager.getString(sKey);
if(!sReplacement.startsWith("???"))
{
oMatcher.appendReplacement(sbTemp,sReplacement);
}
}
oMatcher.appendTail(sbTemp);
return sbTemp.toString();
}
}
Second class :
package com.spectotechnologies.website.util;
import com.spectotechnologies.util.BundleManager;
import com.spectotechnologies.util.FacesSessionManager;
import java.util.Locale;
/**
* set-up and interface a BundleManager
* save the bundleManager into the session
* #author Charles Montigny
*/
public final class StringManager
{
/** the session name of this class bundle manager */
public static final String BUNDLE_MANAGER_SESSION_NAME = "StringManager_BundleManager";
/** List of ResourceBundle names to load.
* Will be load in order.
* The last ones values may overrite the first ones values. */
private final static String[] BUNDLE_LIST = {
"com.spectotechnologies.hibernate.validation.resources.ValidationMessages",
"com.spectotechnologies.website.general.resources.ValidationMessages",
"com.spectotechnologies.website.general.resources.General"};
/** bundle manager */
private static BundleManager m_oBundleManager = null;
private static BundleManager getBundleManager()
{
if(m_oBundleManager == null)
{
// get the bundle into the session
m_oBundleManager = (BundleManager)FacesSessionManager.getObject(BUNDLE_MANAGER_SESSION_NAME);
if(m_oBundleManager == null)
{
// session was empty, load a new one
m_oBundleManager = new BundleManager();
for(int iIndex = 0; iIndex < BUNDLE_LIST.length; iIndex++)
{
m_oBundleManager.addBundle(BUNDLE_LIST[iIndex]);
}
// add the bundle to the session
FacesSessionManager.setObject(BUNDLE_MANAGER_SESSION_NAME, m_oBundleManager);
}
}
return m_oBundleManager;
}
/**
* get a string value in the bundle manager by a string key
* #param p_sKey the string key
* #return the value of the string key
*/
public static String getString(String p_sKey)
{
return getBundleManager().getStringValue(p_sKey);
}
/**
* set the locale
* #param p_oLocale the locale to set
*/
public static void setLocale(Locale p_oLocale)
{
getBundleManager().setLocale(p_oLocale);
// update the session
FacesSessionManager.setObject(BUNDLE_MANAGER_SESSION_NAME,
getBundleManager());
}
}
After you need to override BUNDLE_LIST with your properties files. Once done, use it like that :
sMessage = StringManager.getString("website.validation.modifySystem.modified");
If you have questions, do not hesitate!
EDIT :
You also need to declare the MessageInterpolator
META-INF/validation.xml
<validation-config
xmlns="http://jboss.org/xml/ns/javax/validation/configuration"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://jboss.org/xml/ns/javax/validation/configuration">
<message-interpolator>com.spectotechnologies.website.util.MessageInterpolator</message-interpolator>
</validation-config>
Related
I have the following problem!
On one of my sites i have a button:
<h:commandButton value="IDA Analyzer Results" action="#{SelectionBean.monitoringLog()}"/>
The method it calls with some part of the bean:
#ManagedBean(name = "SelectionBean")
#SessionScoped
public class TableSelectionBean {
private List<String> analyzerLog = new ArrayList<String>();
public String monitoringLog() throws FileNotFoundException, IOException{
String fileName = "/opt/IDA2/Linux/bin/"+"filtered_"+selectionMonitoringData.get(0).getMonitoringName()+"_result.txt";
if(selectionMonitoringData.get(0).getIsExecuted())
{
BufferedReader br = new BufferedReader(new FileReader(fileName));
try {
String line;
while ((line=br.readLine()) != null) {
getAnalyzerLog().add(line);
}
} finally {
br.close();
System.out.println(getAnalyzerLog());
}
}
return "analyzerresult.xhtml";
}
After i click this button as you can see it navigates me to an other page:
<h:body>
<h:form>
<h:commandButton value="hi" action="#{AnalyzerBean.myMethod()}"></h:commandButton>
</h:form>
</h:body>
Here is the Bean:
#ManagedBean(name = "AnalyzerBean")
#SessionScoped
public class AnalyzerResultBean {
#ManagedProperty(value="#{SelectionBean.analyzerLog}")
private List<String> analyzerLog;
public void myMethod(){
System.out.print(analyzerLog);
}
/**
* #return the analyzerLog
*/
public List<String> getAnalyzerLog() {
return analyzerLog;
}
/**
* #param analyzerLog the analyzerLog to set
*/
public void setAnalyzerLog(List<String> analyzerLog) {
this.analyzerLog = analyzerLog;
}
So when I'm trying to use this Managed property it says:
The scope of the object referenced by expression #{SelectionBean.analyzerLog}, view, is shorter than the referring managed beans (AnalyzerBean) scope of session but as you can see both of the is Session Scoped. What could be the problem?
If you use JSF 2.x and you want to navigate analyzerresult.xhtml page return analyzerresult
public String monitoringLog() throws FileNotFoundException, IOException{
return "analyzerresult";
}
.xhtml extension is not needed.
This question already has answers here:
Validation Error: Value is not valid
(3 answers)
Closed 7 years ago.
I know this has been discussed a lot, and I also tried most of resolution, but I still got this error:
sourceId=comboNewTaskParent[severity=(ERROR 2), summary=(comboNewTaskParent: Validation Error: Value is not valid), detail=(comboNewTaskParent: Validation Error: Value is not valid)]
Here is the code for HTML:
<h:outputLabel value="Parent task" for="comboNewTaskParent" />
<div class="formRight">
<h:selectOneMenu id="comboNewTaskParent" value="#{taskController.parentTask}" converter="#{taskConverter}"
<f:selectItems value="#{comboTaskByProject}" var="task" itemValue="#{task}" itemLabel="#{task.taskName}" />
</h:selectOneMenu>
</div>
Here is the code of my entity bean:
package com.projectportal.entity;
import java.io.Serializable;
import javax.persistence.*;
import java.util.Date;
import java.util.List;
/**
* The persistent class for the Task database table.
*
*/
#Entity
#Table(name="Task")
public class Task implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(unique=true, nullable=false, length=36)
private String taskId;
#Column(length=1000)
private String taskDesc;
#Column(nullable=false)
private int taskDurationHour;
#Temporal(TemporalType.TIMESTAMP)
#Column(nullable=false)
private Date taskEstimated;
#Column(nullable=false, length=200)
private String taskName;
#Column(nullable=false)
private float taskPercentComplete;
#Temporal(TemporalType.TIMESTAMP)
#Column(nullable=false)
private Date taskStartDate;
//bi-directional many-to-one association to Priority
#ManyToOne
#JoinColumn(name="priorityId", nullable=false)
private Priority priority;
//bi-directional many-to-one association to Project
#ManyToOne
#JoinColumn(name="projectId")
private Project project;
//bi-directional many-to-one association to Status
#ManyToOne
#JoinColumn(name="statusId", nullable=false)
private Status status;
//bi-directional many-to-one association to Task
#ManyToOne
#JoinColumn(name="parentTaskId")
private Task parentTask;
//bi-directional many-to-one association to Task
#OneToMany(mappedBy="parentTask")
private List<Task> childTasks;
//bi-directional many-to-one association to Task
#ManyToOne
#JoinColumn(name="preTaskId")
private Task preTask;
//bi-directional many-to-one association to Task
#OneToMany(mappedBy="preTask")
private List<Task> dependentTasks;
//bi-directional many-to-one association to UserXTask
#OneToMany(mappedBy="task")
private List<UserXTask> userXtasks;
public Task() {
}
public String getTaskId() {
return this.taskId;
}
public void setTaskId(String taskId) {
this.taskId = taskId;
}
public String getTaskDesc() {
return this.taskDesc;
}
public void setTaskDesc(String taskDesc) {
this.taskDesc = taskDesc;
}
public int getTaskDurationHour() {
return this.taskDurationHour;
}
public void setTaskDurationHour(int taskDurationHour) {
this.taskDurationHour = taskDurationHour;
}
public Date getTaskEstimated() {
return this.taskEstimated;
}
public void setTaskEstimated(Date taskEstimated) {
this.taskEstimated = taskEstimated;
}
public String getTaskName() {
return this.taskName;
}
public void setTaskName(String taskName) {
this.taskName = taskName;
}
public float getTaskPercentComplete() {
return this.taskPercentComplete;
}
public void setTaskPercentComplete(float taskPercentComplete) {
this.taskPercentComplete = taskPercentComplete;
}
public Date getTaskStartDate() {
return this.taskStartDate;
}
public void setTaskStartDate(Date taskStartDate) {
this.taskStartDate = taskStartDate;
}
public Priority getPriority() {
return this.priority;
}
public void setPriority(Priority priority) {
this.priority = priority;
}
public Project getProject() {
return this.project;
}
public void setProject(Project project) {
this.project = project;
}
public Status getStatus() {
return this.status;
}
public void setStatus(Status status) {
this.status = status;
}
public Task getParentTask() {
return this.parentTask;
}
public void setParentTask(Task parentTask) {
this.parentTask = parentTask;
}
public List<Task> getChildTasks() {
return this.childTasks;
}
public void setChildTasks(List<Task> childTasks) {
this.childTasks = childTasks;
}
public Task getPreTask() {
return this.preTask;
}
public void setPreTask(Task preTask) {
this.preTask = preTask;
}
public List<Task> getDependentTasks() {
return this.dependentTasks;
}
public void setDependentTasks(List<Task> dependentTasks) {
this.dependentTasks = dependentTasks;
}
public List<UserXTask> getUserXtasks() {
return this.userXtasks;
}
public void setUserXtasks(List<UserXTask> userXtasks) {
this.userXtasks = userXtasks;
}
}
The controller:
public #Model class TaskController {
#Inject private EntityManager em;
#Inject Identity identity;
#Inject Logger log;
#Inject Event<Task> taskEventSrc;
#Named
#Produces
private List<Task> requestTaskList;
private Task parentTask;
private Task newTask;
#Produces
#Named
public Task getNewTask(){
return this.newTask;
}
/**
*
*/
public TaskController() {
// TODO Auto-generated constructor stub
}
#PostConstruct
public void loadSelfTasks(){
// Init
newTask = new Task();
// Get user from DB.
User user = em.find(User.class, identity.getUser().getId());
requestTaskList = new ArrayList<Task>();
// Loop user's tasks.
for(UserXTask userTask : user.getUserXtasks()){
requestTaskList.add(userTask.getTask());
}
log.info("Tasks for user: " + user.getFirstname() + " loaded.");
}
/**
* Create task.
* #throws Exception
*/
public void createTask() throws Exception{
log.info("Persistencing task: " + newTask.getParentTask().getTaskId());
em.persist(newTask);
taskEventSrc.fire(newTask);
newTask = new Task();
}
/**
* #return the parentTask
*/
public Task getParentTask() {
return parentTask;
}
/**
* #param parentTask the parentTask to set
*/
public void setParentTask(Task parentTask) {
this.parentTask = parentTask;
}
}
And of course the converter:
#Named
/**
* #author lastcow
*
*/
public class TaskConverter implements Converter {
#Inject EntityManager em;
#Inject Logger log;
/* (non-Javadoc)
* #see javax.faces.convert.Converter#getAsObject(javax.faces.context.FacesContext, javax.faces.component.UIComponent, java.lang.String)
*/
#Override
public Object getAsObject(FacesContext context, UIComponent component, String value) {
log.info("=========== Convert to Object " + value);
if(value.equals("0")){
return null;
}
Task t = em.find(Task.class, value);
log.info("======== Got : " + t.getTaskName());
return t;
}
/* (non-Javadoc)
* #see javax.faces.convert.Converter#getAsString(javax.faces.context.FacesContext, javax.faces.component.UIComponent, java.lang.Object)
*/
#Override
public String getAsString(FacesContext context, UIComponent component, Object value) {
log.info("=========== Convert to String " + value);
return ((Task)value).getTaskId();
}
}
from what logged, the convert are working as it, but when I try to submit the form, always throw 'Validation Error: Value is not valid' ERROR, I have struck here for almost 2 days.
Anyone please give some suggestions.
BTW, I tried put equals and hashCode in Task.java, doesn't working either.
Thanks in advance.
Validation Error: Value is not valid
This error will be thrown when the equals() method of the selected item hasn't returned true for any of the available items in <f:selectItem(s)>. Thus, this can technically have only 2 causes:
The equals() method of your Task class is missing or broken.
The <f:selectItems value="#{comboTaskByProject}"> has incompatibly changed during the postback request of the form submit as compared to during the initial request of the form display.
To fix cause #1, make sure that you understand how to implement equals() properly. You can find kickoff examples here: Right way to implement equals contract
To fix cause #2, make sure that the #{comboTaskByProject} never canges during postback. Best is to put it in the view scope or broader, or to make sure that request based conditions for populating that list are preserved in the postback request by e.g. using <f:viewParam>.
See also:
Our selectOneMenu wiki page
Validation Error: Value is not valid
I am not sure which version of JSF you are using. As far as I know, the converter in HTML should be used like this converter="javax.faces.DateTime". Where this part javax.faces.DateTime is converter name defined in faces-config.xml or in converter class with #FacesConverter.
in my preRender code for a page i add faces message then make navigation to another page as follows:
if(error){
addMessageToComponent(null,"AN ERROR HAS OCCURRED");
FacesContext.getCurrentInstance().getExternalContext().getFlash()
.setKeepMessages(true);
navigateActionListener("myoutcome");
}
and the util methods for adding message and navigation are:
public static String getClientId(String componentId)
{
FacesContext context = FacesContext.getCurrentInstance();
UIViewRoot root = context.getViewRoot();
UIComponent c = findComponent(root, componentId);
return c.getClientId(context);
}
public static UIComponent findComponent(UIComponent c, String id)
{
if (id.equals(c.getId())) { return c; }
Iterator<UIComponent> kids = c.getFacetsAndChildren();
while (kids.hasNext())
{
UIComponent found = findComponent(kids.next(), id);
if (found != null) { return found; }
}
return null;
}
/**
* #param componentId
* : the id for the jsf/primefaces component without formId:
* prefix. <br>
* if you use null then the message will be added to the
* h:messages component.
**/
public static void addMessageToComponent(String componentId, String message)
{
if (componentId != null)
componentId = GeneralUtils.getClientId(componentId);
FacesContext.getCurrentInstance().addMessage(componentId,
new FacesMessage(message));
}
public static void navigateActionListener(String outcome)
{
FacesContext context = FacesContext.getCurrentInstance();
NavigationHandler navigator = context.getApplication()
.getNavigationHandler();
navigator.handleNavigation(context, null, outcome);
}
but messages are not saved and so it doesn't appear after redirect.
please advise how to fix that.
The preRenderView event runs in the very beginning of the RENDER_RESPONSE phase. It's too late to instruct the Flash scope to keep the messages. You can do this at the latest during the INVOKE_APPLICATION phase.
Since there's no standard JSF component system event for this, you'd need to homebrew one:
#NamedEvent(shortName="postInvokeAction")
public class PostInvokeActionEvent extends ComponentSystemEvent {
public PostInvokeActionEvent(UIComponent component) {
super(component);
}
}
To publish this, you need a PhaseListener:
public class PostInvokeActionListener implements PhaseListener {
#Override
public PhaseId getPhaseId() {
return PhaseId.INVOKE_APPLICATION;
}
#Override
public void beforePhase(PhaseEvent event) {
// NOOP.
}
#Override
public void afterPhase(PhaseEvent event) {
FacesContext context = FacesContext.getCurrentInstance();
context.getApplication().publishEvent(context, PostInvokeActionEvent.class, context.getViewRoot());
}
}
After registering it as follows in faces-config.xml:
<lifecycle>
<phase-listener>com.example.PostInvokeActionListener</phase-listener>
</lifecycle>
You'll be able to use the new event as follows:
<f:event type="postInvokeAction" listener="#{bean.init}" />
You only need to make sure that you've at least a <f:viewParam>, otherwise JSF won't enter the invoked phase at all.
The JSF utility library OmniFaces already supports this event and the preInvokeAction event out the box. See also the showcase page which also demonstrates setting a facesmessage for redirect.
I have the following commandButton action method handler:
public String reject()
{
//Do something
addMessage(null, "rejectAmountInvalid", FacesMessage.SEVERITY_ERROR);
redirectToPortlet("/xxx/inbox?source=pendingActions#pendingApproval");
}
public static void addMessage(String clientId, String key, Severity level, Object... objArr)
{
FacesContext context = FacesContext.getCurrentInstance();
FacesMessage message = null;
String msg = getTextFromResourceBundle(key);
if (objArr != null && objArr.length > 0)
msg = MessageFormat.format(msg, objArr);
message = new FacesMessage(msg);
message.setSeverity(level);
context.addMessage(clientId, message);
}
public static void redirectToPortlet(String urlToRedirect)
{
FacesContext context = FacesContext.getCurrentInstance();
ExternalContext externalContext = context.getExternalContext();
try
{
PortletRequest portletRequest = (PortletRequest) externalContext.getRequest();
ThemeDisplay themeDisplay = (ThemeDisplay) portletRequest.getAttribute("THEME_DISPLAY");
String portalURL = themeDisplay.getPortalURL();
String redirect = portalURL + urlToRedirect;
externalContext.redirect(redirect);
}
catch (Throwable e)
{
logger.log("Exception in redirectToPortlet to the URL: " + urlToRedirect, VLevel.ERROR, e);
}
}
When the page is redirected to "/xxx/inbox?source=pendingActions#pendingApproval", the error message I added is lost. Is there a way to preserve the error message in JSF 2.1?
Thanks
Sri
You can use a PhaseListener to save the messages that weren't displayed for the next request.
I've been using for a while one from Lincoln Baxter's blog post Persist and pass FacesMessages over multiple page redirects, you just copy the class to some package and register on your faces-config.xml.
It's not mentioned explicitly in the blog post, but I'm assuming the code is public domain, so I am posting here for a more self-contained answer:
package com.yoursite.jsf;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;
import javax.faces.event.PhaseEvent;
import javax.faces.event.PhaseId;
import javax.faces.event.PhaseListener;
/**
* Enables messages to be rendered on different pages from which they were set.
*
* After each phase where messages may be added, this moves the messages
* from the page-scoped FacesContext to the session-scoped session map.
*
* Before messages are rendered, this moves the messages from the
* session-scoped session map back to the page-scoped FacesContext.
*
* Only global messages, not associated with a particular component, are
* moved. Component messages cannot be rendered on pages other than the one on
* which they were added.
*
* To enable multi-page messages support, add a <code>lifecycle</code> block to your
* faces-config.xml file. That block should contain a single
* <code>phase-listener</code> block containing the fully-qualified classname
* of this file.
*
* #author Jesse Wilson jesse[AT]odel.on.ca
* #secondaryAuthor Lincoln Baxter III lincoln[AT]ocpsoft.com
*/
public class MultiPageMessagesSupport implements PhaseListener
{
private static final long serialVersionUID = 1250469273857785274L;
private static final String sessionToken = "MULTI_PAGE_MESSAGES_SUPPORT";
public PhaseId getPhaseId()
{
return PhaseId.ANY_PHASE;
}
/*
* Check to see if we are "naturally" in the RENDER_RESPONSE phase. If we
* have arrived here and the response is already complete, then the page is
* not going to show up: don't display messages yet.
*/
// TODO: Blog this (MultiPageMessagesSupport)
public void beforePhase(final PhaseEvent event)
{
FacesContext facesContext = event.getFacesContext();
this.saveMessages(facesContext);
if (PhaseId.RENDER_RESPONSE.equals(event.getPhaseId()))
{
if (!facesContext.getResponseComplete())
{
this.restoreMessages(facesContext);
}
}
}
/*
* Save messages into the session after every phase.
*/
public void afterPhase(final PhaseEvent event)
{
if (!PhaseId.RENDER_RESPONSE.equals(event.getPhaseId()))
{
FacesContext facesContext = event.getFacesContext();
this.saveMessages(facesContext);
}
}
#SuppressWarnings("unchecked")
private int saveMessages(final FacesContext facesContext)
{
List<FacesMessage> messages = new ArrayList<FacesMessage>();
for (Iterator<FacesMessage> iter = facesContext.getMessages(null); iter.hasNext();)
{
messages.add(iter.next());
iter.remove();
}
if (messages.size() == 0)
{
return 0;
}
Map<String, Object> sessionMap = facesContext.getExternalContext().getSessionMap();
List<FacesMessage> existingMessages = (List<FacesMessage>) sessionMap.get(sessionToken);
if (existingMessages != null)
{
existingMessages.addAll(messages);
}
else
{
sessionMap.put(sessionToken, messages);
}
return messages.size();
}
#SuppressWarnings("unchecked")
private int restoreMessages(final FacesContext facesContext)
{
Map<String, Object> sessionMap = facesContext.getExternalContext().getSessionMap();
List<FacesMessage> messages = (List<FacesMessage>) sessionMap.remove(sessionToken);
if (messages == null)
{
return 0;
}
int restoredCount = messages.size();
for (Object element : messages)
{
facesContext.addMessage(null, (FacesMessage) element);
}
return restoredCount;
}
}
And then, on your faces-config.xml:
<phase-listener>com.yoursite.jsf.MultiPageMessagesSupport</phase-listener>
If the redirect is to the same path, you could just use Flash#setKeepMessages().
context.getExternalContext().getFlash().setKeepMessages(true);
This way the messages are persisted in the flash scope which lives effectively as long as a single subsequent GET request (as occurs during a redirect).
I'm trying to add a very simple file input to my webapp which I'm doing using JSF2.0 and RichFaces 3.3.3, the thing is I really dislike the richfaces fileInput component and I'm looking for something simpler (in terms of use and looks), so far I've found:
tomahawk's fileInput - but tomahawk only supports JSF1.2
trinidad fileInput - but trinidad for JSF2.0 is in alpha stage
primefaces - but of course they won't work with RichFaces 3.3.3 (only 4.0 which are in beta)
Are there any other options? I need something really simple like a normal html file input component.
You basically need to do two things:
Create a Filter which puts the multipart/form-data items in a custom map and replace the original request parameter map with it so that the normal request.getParameter() process keeps working.
Create a JSF 2.0 custom component which renders a input type="file" and which is aware of this custom map and can obtain the uploaded files from it.
#taher has already given a link where you could find insights and code snippets. The JSF 2.0 snippets should be reuseable. You yet have to modify the MultipartMap to use the good 'ol Apache Commons FileUpload API instead of the Servlet 3.0 API.
If I have time, I will by end of day rewrite it and post it here.
Update: I almost forgot you, I did a quick update to replace Servlet 3.0 API by Commons FileUpload API, it should work:
package net.balusc.http.multipart;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
public class MultipartMap extends HashMap<String, Object> {
// Constants ----------------------------------------------------------------------------------
private static final String ATTRIBUTE_NAME = "parts";
private static final String DEFAULT_ENCODING = "UTF-8";
private static final int DEFAULT_BUFFER_SIZE = 10240; // 10KB.
// Vars ---------------------------------------------------------------------------------------
private String encoding;
private String location;
// Constructors -------------------------------------------------------------------------------
/**
* Construct multipart map based on the given multipart request and file upload location. When
* the encoding is not specified in the given request, then it will default to <tt>UTF-8</tt>.
* #param multipartRequest The multipart request to construct the multipart map for.
* #param location The location to save uploaded files in.
* #throws ServletException If something fails at Servlet level.
* #throws IOException If something fails at I/O level.
*/
#SuppressWarnings("unchecked") // ServletFileUpload#parseRequest() isn't parameterized.
public MultipartMap(HttpServletRequest multipartRequest, String location)
throws ServletException, IOException
{
multipartRequest.setAttribute(ATTRIBUTE_NAME, this);
this.encoding = multipartRequest.getCharacterEncoding();
if (this.encoding == null) {
multipartRequest.setCharacterEncoding(this.encoding = DEFAULT_ENCODING);
}
this.location = location;
try {
List<FileItem> parts = new ServletFileUpload(new DiskFileItemFactory()).parseRequest(multipartRequest);
for (FileItem part : parts) {
if (part.isFormField()) {
processFormField(part);
} else if (!part.getName().isEmpty()) {
processFileField(part);
}
}
} catch (FileUploadException e) {
throw new ServletException("Parsing multipart/form-data request failed.", e);
}
}
// Actions ------------------------------------------------------------------------------------
#Override
public Object get(Object key) {
Object value = super.get(key);
if (value instanceof String[]) {
String[] values = (String[]) value;
return values.length == 1 ? values[0] : Arrays.asList(values);
} else {
return value; // Can be File or null.
}
}
/**
* #see ServletRequest#getParameter(String)
* #throws IllegalArgumentException If this field is actually a File field.
*/
public String getParameter(String name) {
Object value = super.get(name);
if (value instanceof File) {
throw new IllegalArgumentException("This is a File field. Use #getFile() instead.");
}
String[] values = (String[]) value;
return values != null ? values[0] : null;
}
/**
* #see ServletRequest#getParameterValues(String)
* #throws IllegalArgumentException If this field is actually a File field.
*/
public String[] getParameterValues(String name) {
Object value = super.get(name);
if (value instanceof File) {
throw new IllegalArgumentException("This is a File field. Use #getFile() instead.");
}
return (String[]) value;
}
/**
* #see ServletRequest#getParameterNames()
*/
public Enumeration<String> getParameterNames() {
return Collections.enumeration(keySet());
}
/**
* #see ServletRequest#getParameterMap()
*/
public Map<String, String[]> getParameterMap() {
Map<String, String[]> map = new HashMap<String, String[]>();
for (Entry<String, Object> entry : entrySet()) {
Object value = entry.getValue();
if (value instanceof String[]) {
map.put(entry.getKey(), (String[]) value);
} else {
map.put(entry.getKey(), new String[] { ((File) value).getName() });
}
}
return map;
}
/**
* Returns uploaded file associated with given request parameter name.
* #param name Request parameter name to return the associated uploaded file for.
* #return Uploaded file associated with given request parameter name.
* #throws IllegalArgumentException If this field is actually a Text field.
*/
public File getFile(String name) {
Object value = super.get(name);
if (value instanceof String[]) {
throw new IllegalArgumentException("This is a Text field. Use #getParameter() instead.");
}
return (File) value;
}
// Helpers ------------------------------------------------------------------------------------
/**
* Process given part as Text part.
*/
private void processFormField(FileItem part) {
String name = part.getFieldName();
String[] values = (String[]) super.get(name);
if (values == null) {
// Not in parameter map yet, so add as new value.
put(name, new String[] { part.getString() });
} else {
// Multiple field values, so add new value to existing array.
int length = values.length;
String[] newValues = new String[length + 1];
System.arraycopy(values, 0, newValues, 0, length);
newValues[length] = part.getString();
put(name, newValues);
}
}
/**
* Process given part as File part which is to be saved in temp dir with the given filename.
*/
private void processFileField(FileItem part) throws IOException {
// Get filename prefix (actual name) and suffix (extension).
String filename = FilenameUtils.getName(part.getName());
String prefix = filename;
String suffix = "";
if (filename.contains(".")) {
prefix = filename.substring(0, filename.lastIndexOf('.'));
suffix = filename.substring(filename.lastIndexOf('.'));
}
// Write uploaded file.
File file = File.createTempFile(prefix + "_", suffix, new File(location));
InputStream input = null;
OutputStream output = null;
try {
input = new BufferedInputStream(part.getInputStream(), DEFAULT_BUFFER_SIZE);
output = new BufferedOutputStream(new FileOutputStream(file), DEFAULT_BUFFER_SIZE);
IOUtils.copy(input, output);
} finally {
IOUtils.closeQuietly(output);
IOUtils.closeQuietly(input);
}
put(part.getFieldName(), file);
part.delete(); // Cleanup temporary storage.
}
}
You still need both the MultipartFilter and MultipartRequest classes as described in this article. You only need to remove the #WebFilter annotation and map the filter on an url-pattern of /* along with an <init-param> of location wherein you specify the absolute path where the uploaded files are to be stored. You can use the JSF 2.0 custom file upload component as described in this article unchanged.
Dear either you have to use rich:uploadFile or make custom component in JSF by following http://balusc.blogspot.com/2009/12/uploading-files-with-jsf-20-and-servlet.html
After I also tried tomahawk, I mentioned that it does not work with AJAX. So I decided to hack rich:fileUpload and perform the click on add button over a a4j:commandButton. Here's the code:
<a4j:form id="myForm">
<a4j:commandButton id="myButton" value="Upload" title="Upload" styleClass="myButtonClass"
onclick="document.getElementById('myForm:myFileUpload:file').click()/>
<rich:fileUpload id="myFileUpload" maxFilesQuantity="1" autoclear="true"
immediateUpload="true" styleClass="invisibleClass"
fileUploadListener="#{uploadBean.uploadListener}"/>
</a4j:form>
myForm:myFileUpload:file is the input-Element (type="file") for the Add-Button. invisibleClass should only contain display:none;. With style="display:none;" it won't work.
You can used rich faces 3.3.3 file upload.
Step 1 : fileUpload.xhtml
<rich:fileUpload id="fileupload" addControlLabel="Browse"
required="true"
fileUploadListener="#{testForm.listener}"
acceptedTypes="xml"
ontyperejected="alert('Only xml files are accepted');"
maxFilesQuantity="1" listHeight="57px" listWidth="100%"
disabled="#{testForm..disabled}" >
<a4j:support event="onclear"
action="#{testForm..clearUploadData}"
reRender="fileupload" />
</rich:fileUpload>
Step 2: FileUpload.java
public class FileUpload implements Serializable{
/**
*
*/
private static final long serialVersionUID = 1L;
private String Name;
private String mime;
private long length;
private byte [] file;
private String absolutePath;
public String getName() {
return Name;
}
/**
* #return the file
*/
public byte[] getFile() {
return file;
}
/**
* #param file the file to set
*/
public void setFile(byte[] file) {
this.file = file;
}
/**
* #param mime the mime to set
*/
public void setMime(String mime) {
this.mime = mime;
}
public void setName(String name) {
Name = name;
int extDot = name.lastIndexOf('.');
if(extDot > 0){
String extension = name.substring(extDot +1);
if("txt".equals(extension)){
mime="txt";
} else if("xml".equals(extension)){
mime="xml";
} else {
mime = "unknown file";
}
}
}
public long getLength() {
return length;
}
public void setLength(long length) {
this.length = length;
}
public String getMime(){
return mime;
}
/**
* #return the absolutePath
*/
public String getAbsolutePath() {
return absolutePath;
}
/**
* #param absolutePath the absolutePath to set
*/
public void setAbsolutePath(String absolutePath) {
this.absolutePath = absolutePath;
}
}
Step 3 :TestForm // calling listner
/**
*
* #param event
* #throws Exception
*/
public void listener(UploadEvent event) throws Exception{
UploadItem item = event.getUploadItem();
FileUpload file = new FileUpload();
file.setLength(item.getData().length);
file.setFile(item.getData());
file.setName(item.getFileName());
files.add(file);
}