i have a junit test method that calls a backing bean method as follows:
myBackingBean.signup();
, in the backing bean method there's a call to Faces.getLocale() and it gives null pointer exception in the line
UIViewRoot viewRoot = context.getViewRoot();
please advise how to be able to set locale in test method and fix this error.
solution was as follows:
1- add the following class to project:
public abstract class FacesContextMocker extends FacesContext {
private FacesContextMocker() {
}
private static final Release RELEASE = new Release();
private static class Release implements Answer<Void> {
#Override
public Void answer(InvocationOnMock invocation) throws Throwable {
setCurrentInstance(null);
return null;
}
}
public static FacesContext mockFacesContext() {
FacesContext context = Mockito.mock(FacesContext.class);
setCurrentInstance(context);
Mockito.doAnswer(RELEASE).when(context).release();
return context;
}
}
2- In #Before for the test use the following code:
FacesContext facesContext = FacesContextMocker.mockFacesContext();
UIViewRoot uiViewRoot = Mockito.mock(UIViewRoot.class);
Mockito.when(facesContext.getCurrentInstance().getViewRoot())
.thenReturn(uiViewRoot);
Mockito.when(
facesContext.getCurrentInstance().getViewRoot().getLocale())
.thenReturn(new Locale("en"));
Related
I have an application scoped bean
#ManagedBean(name = "myController")
#ApplicationScoped
public class MyController implements Serializable{
...
public void allOn(){...}
And i want to call the allOn() method from a quartz-job
import org.quartz.Job;
public class CronJobAllOn implements Job{
#Override
public void execute(..){
//call allOn();}
}
I tried to pass the FacesContext to the Job-Class via the JobDataMap
JobDataMap jobDataMap = new JobDataMap();
jobDataMap.put("facesContext", FacesContext.getCurrentInstance());
JobDetail job = newJob(CronJobAllOn.class)
.usingJobData(jobDataMap)
.withIdentity("job1", "group1")
.build();
But it only throws an IllegalStateException when i try to call it in the CronJobAllOn Class
public void execute(JobExecutionContext context) throws JobExecutionException {
FacesContext fc= (FacesContext) context.getMergedJobDataMap().get("facesContext");
MyController test = (MyController)fc.getExternalContext().getApplicationMap().get("MyController");
test.allOn();}
How can i call the allOn() method in MyController from a quartz-job?
I got the solution for my Problem, the short comment from BalusC put me on the right path.
I switched to TomEE, to get CDI.
To use the CDI-Bean injection in my jobs, i had to create my own JobFactory Class:
public class CdiJobFactory implements JobFactory {
#Inject
#Any
private Instance<Job> jobs;
#Override
public Job newJob(TriggerFiredBundle triggerFiredBundle, Scheduler scheduler) throws SchedulerException {
final JobDetail jobDetail = triggerFiredBundle.getJobDetail();
final Class<? extends Job> jobClass = jobDetail.getJobClass();
for (Job job : jobs) {
if (job.getClass().isAssignableFrom(jobClass)) {
return job;
}
}
throw new RuntimeException("Cannot create a Job of type " + jobClass);
}
create the Factory
Scheduler scheduler = new StdSchedulerFactory().getScheduler();
scheduler.setJobFactory(cdiJobFactory);
after that i was able to inject myController:
public class CronJobAllOn implements Job{
#Inject
private MyController mc;
#Override
public void execute(JobExecutionContext context) throws JobExecutionException {
mc.allOn();
}
This is how I'm rendering my composite component inside a loop, it works, but when I switch to edit mode and sumbmit new values I can't retrieve them from the InputText.
#FacesComponent("customComponent")
public class CustomComponent extends UIInput implements NamingContainer, Serializable {
private static final long serialVersionUID = 1L;
#Override
public String getFamily() {
return UINamingContainer.COMPONENT_FAMILY;
}
private UIComponent component;
private HtmlInputText inputTextValue;
#Override
public void encodeBegin(FacesContext context) throws IOException {
AttributeObject attrObject = (AttributeObject) getAttributes().get("value");
Boolean enableInput = (Boolean) getAttributes().get("enableInput");
if (attrObject.getAttributeValue() != null) {
if (attrObject.getAttributeDescriptor().getDataType() == DataTypeConstants.TEXT && enableInput) {
InputText inputText = new InputText();
inputText.setRequired(true);
inputText.setValueExpression("binding",
createValueExpression("#{searchController.myComponent}", UIComponent.class));
inputText.setId("editableTextId");
inputText.encodeAll(context);
inputText.setParent(this);
component = inputText;
} else if (attrObject.getAttributeDescriptor().getDataType() == DataTypeConstants.TEXT
&& enableInput == false) {
OutputLabel outputLabel = new OutputLabel();
outputLabel.setValue(attrObject.getAttributeValue());
outputLabel.encodeAll(context);
outputLabel.setId("nonEditatbleId");
component = outputLabel;
}
}
}
private ValueExpression createValueExpression(String valueExpression, Class<?> valueType) {
FacesContext facesContext = FacesContext.getCurrentInstance();
return facesContext.getApplication().getExpressionFactory()
.createValueExpression(facesContext.getELContext(), valueExpression, valueType);
}
Ok I think I found what caused all that mad performance problems. I did some logic inside a getter and because that getter was getting called multiple times that caused performance issues.
I created a ApplicationScoped bean that have a PostConstruct method named start.
Whenever i want to get instance of FacesContext in the start method and it returns null:
#ManagedBean
#ApplicationScoped
public class RemoveOldFilesScheduler implements Serializable {
#PostConstruct
private void start() {
final FacesContext facesContext = FacesContext.getCurrentInstance();
if(facesContext != null) {
String realDownloadDirName = facesContext.getExternalContext().getRealPath("/") + DOWNLOAD_DIRECTORY;
File downloadDir = new File(realDownloadDirName);
if (downloadDir.exists()) {
removeOldFiles(downloadDir.listFiles());
}
}
}
How can i access to facesContext in this situation?
I want to get real path of my download directory in the start method and i don't know how to get path of my directory without using FaceContext.
Is there another way to do it?
I implementing my class as Listener and it worked and i can access to ServletContext in contextInitialized method :
public class RemoveOldFilesListener implements ServletContextListener {
public ServletContext servletContext;
#Override
public void contextInitialized(ServletContextEvent sce) {
servletContext = sce.getServletContext();
String realDownloadDirName = servletContext.getRealPath("/") + DOWNLOAD_DIRECTORY;
File downloadDir = new File(realDownloadDirName);
if (downloadDir.exists()) {
removeOldFiles(downloadDir.listFiles());
}
}
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 wrote myself a custom NavigationHandler very similar to following one but just using a stack to save the history:
http://jsfatwork.irian.at/book_de/custom_component.html#!idx:/custom_component.html:fig:backnavigationhandler-code
public class HistoryNavigationHandler extends NavigationHandler
{
private final NavigationHandler navigationHandler;
private final Stack<String> outcomes;
public HistoryNavigationHandler(final NavigationHandler navigationHandler)
{
this.navigationHandler = navigationHandler;
this.outcomes = new Stack<String>();
}
#Override
public void handleNavigation(final FacesContext context, final String fromAction, final String outcome)
{
if (outcome != null)
{
if (outcome.equals("back"))
{
final String lastViewId = this.outcomes.pop();
final ViewHandler viewHandler = context.getApplication().getViewHandler();
final UIViewRoot viewRoot = viewHandler.createView(context, lastViewId);
context.setViewRoot(viewRoot);
context.renderResponse();
return;
}
else
{
this.outcomes.push(context.getViewRoot().getViewId());
}
}
this.navigationHandler.handleNavigation(context, fromAction, outcome);
}
}
Registering this one in the faces-config.xml:
<navigation-handler>
package.HistoryNavigationHandler
</navigation-handler>
Results in following log warning and a message where a previously working link was present:
Warning: jsf.outcome.target.invalid.navigationhandler.type
Something like: this link is disabled because a navigation case could not be matched
What is the problem?
Since JSF 2, the NavigationHandler has been replaced by ConfigurableNavigationHandler. All JSF 2 specific tags/components like <h:link> and so on are relying on it. The NavigationHandler is kept for backwards compatibility.
Here's a kickoff example how to properly extend ConfigurableNavigationHandler:
public class HistoryNavigationHandler extends ConfigurableNavigationHandler {
private NavigationHandler wrapped;
public HistoryNavigationHandler(NavigationHandler wrapped) {
this.wrapped = wrapped;
}
#Override
public void handleNavigation(FacesContext context, String from, String outcome) {
// TODO: Do your job here.
wrapped.handleNavigation(context, from, outcome);
}
#Override
public NavigationCase getNavigationCase(FacesContext context, String fromAction, String outcome) {
return (wrapped instanceof ConfigurableNavigationHandler)
? ((ConfigurableNavigationHandler) wrapped).getNavigationCase(context, fromAction, outcome)
: null;
}
#Override
public Map<String, Set<NavigationCase>> getNavigationCases() {
return (wrapped instanceof ConfigurableNavigationHandler)
? ((ConfigurableNavigationHandler) wrapped).getNavigationCases()
: null;
}
}