Mojarra 2.1
I need to write a validator for the h:inputText which performs some logic only if the value for that input is changed. I.e.
public class MyValidator implements Validator{
public void validate(FacesContext context,
UIComponent component,
Object value) throws ValidatorException;
if(valueChanged(UIComponent component)){ //The method checks if the value's changed
//do some piece of logic
}
return;
}
}
I dug into the queuing events of the UIInput and found this:
validateValue(context, newValue);
// If our value is valid, store the new value, erase the
// "submitted" value, and emit a ValueChangeEvent if appropriate
if (isValid()) {
Object previous = getValue();
setValue(newValue);
setSubmittedValue(null);
if (compareValues(previous, newValue)) {
queueEvent(new ValueChangeEvent(this, previous, newValue));
}
}
This piece of code is from the method, executed by the Validation phase callback. The first thought that popped into my head was queriyng all events fired during handling the request. The method queueEvent(FacesEvent) is implemented as follows:
public void queueEvent(FacesEvent event) {
if (event == null) {
throw new NullPointerException();
}
UIComponent parent = getParent();
if (parent == null) {
throw new IllegalStateException();
} else {
parent.queueEvent(event);
}
}
Therefore every such invokation will end up in UIViewRoot.queueEvent(FacesEvent) which is implemented as:
public void queueEvent(FacesEvent event) {
if (event == null) {
throw new NullPointerException();
}
// We are a UIViewRoot, so no need to check for the ISE
if (events == null) {
int len = PhaseId.VALUES.size();
List<List<FacesEvent>> events = new ArrayList<List<FacesEvent>>(len);
for (int i = 0; i < len; i++) {
events.add(new ArrayList<FacesEvent>(5));
}
this.events = events;
}
events.get(event.getPhaseId().getOrdinal()).add(event);
}
Which means, all events is actually stored as a List<List<FacesEvent>> for each phase. But the List<List<FacesEvent>> events is a private field, so it's impossible to get direct acces to it.
Another thing is that the actual validation is being perfromed before the quingEvent, so implemting valueChangeListener doesn't seem useful as well.
Question: Is it possible to implements such validator in JSF in a natural way?
Just do the value comparison yourself. In the validator, the old value is just readily available via UIComponent argument.
#Override
public void validate(FacesContext context, UIComponent component, Object submittedValue) {
if (component instanceof EditableValueHolder) {
Object newValue = submittedValue;
Object oldValue = ((EditableValueHolder) component).getValue();
if (newValue == null ? oldValue == null : newValue.equals(oldValue)) {
return; // Not changed, so skip validation.
}
}
// Do actual validation here.
}
If you happen to use JSF utility library OmniFaces, it has a ValueChangeValidator which does exactly this.
Related
I'm writing a Python script in IronPython that uses a C# DLL implementing the observer/observable pattern, the C# library has the observable class and I want to implement the observer in Python.
I'm registering my Python function as the observer method, but when it gets called, I get an "System.MissingMemberException: 'PeripheralDiscoveredEventArgs' object has no attribute 'ChangeType' exception thrown.
I can't find any documentation on how to use custom arguments with C# events when using IronPython, looked here, here, here and here.
My Python code is:
central = None
def MyCallback(sender, event_args):
print event_args.ChangeType, event_args.Name
def main():
global central
central = objFactory.GetCentral();
d = System.EventHandler[CustomEventArgs](MyCallback)
central.myHandler += d
(do something)
if __name__ == "__main__":
main()
I also tried to do just this:
central.myHandler += MyCallback
But I got the same exception.
The central variable as a Central instantance and the central.myHandler property in the is defined in the Central class as:
public event EventHandler<CustomEventArgs> myHandler = delegate { };
The CustomEventArgs class is defined as:
public class CustomEventArgs: EventArgs
{
public CustomEventArgs(Class1 obj1, int i, Class2 obj2, bool tf);
public Class1 Obj1 { get; }
public int I{ get; }
public Class2 Obj2t { get; }
public bool Tf{ get; }
}
The observable class method that calls my callback is:
internal abstract class EventHelper
{
internal static void TriggerEvent<T>(EventHandler<T> eventHandler, object source, T args)
{
// event handle no used ?
if (eventHandler == null) return;
try
{
// call event handler
eventHandler(source, args);
}
catch (Exception ex)
{
var target = eventHandler.Target != null ? eventHandler.Target : "unknown";
var methodName = (eventHandler.Method != null && eventHandler.Method.Name != null) ? eventHandler.Method.Name : "unknown";
Log.Error(string.Format("Exception in event handler '{0}' in '{1}'", methodName, target), ex);
}
}
The whole exception is:
"System.MissingMemberException: 'CustomEventArgs' object has no attribute 'ChangeType'
at IronPython.Runtime.Binding.PythonGetMemberBinder.FastErrorGet`1.GetError(CallSite site, TSelfType target, CodeContext context)
at System.Dynamic.UpdateDelegates.UpdateAndExecute2[T0,T1,TRet](CallSite site, T0 arg0, T1 arg1)
at __main__$1.MyCallback$74(PythonFunction $function, Object sender, Object event_args) in C:\\code\\Launcher.py:line 51
at System.Dynamic.UpdateDelegates.UpdateAndExecute3[T0,T1,T2,TRet](CallSite site, T0 arg0, T1 arg1, T2 arg2)
at _Scripting_(Object[] , Object , CustomEventArgs )
at System.EventHandler`1.Invoke(Object sender, TEventArgs e)
at EventHelper.TriggerEvent[T](EventHandler`1 eventHandler, Object source, T args) in C:\\code\\Utility\\EventHelper.cs:line 27"
Any pointers on how to use custom event arguments in C# with IronPython? Thanks!!
Environment:
Windows 7 Enterprise 64 bits
IronPython 2.7.8 (2.7.8.0) on .NET 4.0.30319.42000 (32-bit)
.NET Framework 4.5.2
Nevermind, it was my mistake (as usual). Trying to access a non-existent member of a class usually leads to errors/exceptions...
Just changing the observer code to :
def centralOnPeripheralDiscovered_callback(sender, event_args):
print sender
print event_args
Solves the problem. Thanks for the help!
I have figured out how to log when a request is an ajax request and which page it is from, in a filter.
What I would really like to do is log what the ajax request is actually for. Such as the name of the method being called by the ajax (eg "findAddress" in this call:<p:ajax process="contactDetails" update="#form" listener="#{aboutYouController.findAddress}" .... )
How can I do this? My app has many ajax requests and I want to log which are being triggered.
public class TrackingFilter implements Filter {
private static Logger LOG = Logger.getLogger(TrackingFilter.class);
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
HttpServletRequest req = (HttpServletRequest) request;
String pageHit = req.getRequestURI().substring(req.getContextPath().length()+1).replace(".xhtml", "");
if(!pageHit.contains("javax.faces.resource")){ // if is a url we want to log
if ("partial/ajax".equals(req.getHeader("Faces-Request"))) {
LOG.trace("ajax on URI: " + req.getRequestURI());
}
What I would really like to do is log what the ajax request is actually for. Such as the name of the method being called by the ajax (eg "findAddress" in this call:<p:ajax process="contactDetails" update="#form" listener="#{aboutYouController.findAddress}" ....)
This information is only available in the JSF component tree. The JSF component tree is only available after view build time. A view is only built when the request has been served by FacesServlet. Thus, a servlet filter is way too early as it runs before any servlet.
You'd better run the code after the restore view phase of a postback. The JSF component tree is guaranteed to be available during that moment. You can use FacesContext#isPostback() to check if the current request is a postback. You can use PartialViewContext#isAjaxRequest() to check if the current request is an ajax request. You can use the predefined javax.faces.source request parameter to obtain the client ID of the source component of the ajax request. You can use the predefined javax.faces.behavior.event request parameter to obtain the ajax event name (e.g. change, click, action, etc).
Obtaining the associated behavior listeners is in turn a story apart. This is easy on ActionSource2 components (e.g. <h|p:commandButton action="#{...}">) as the MethodExpression is just available by ActionSource2#getActionExpression(). However, this isn't easy on BehaviorBase taghandlers (e.g. <f|p:ajax listener="#{...}">) as this API doesn't have any method like getBehaviorListeners(). There are only methods to add and remove them, but not to obtain a list of them. So some nasty reflection trickery is necessary to access the private field with those listeners whose name is JSF implementation specific. In Mojarra it's listeners and in MyFaces it's _behaviorListeners. Both are fortunately assignable from List and it's the only field of that type, so we could just check for that. Once having hand of the BehaviorListener instance, then you still need to do another reflection trickery to obtain the MethodExpression field of that instance. Yuck.
All in all, here's how the trickery look like in flavor of a PhaseListener listening on afterPhase of RESTORE_VIEW:
public class AjaxActionLoggerPhaseListener implements PhaseListener {
#Override
public PhaseId getPhaseId() {
return PhaseId.RESTORE_VIEW;
}
#Override
public void beforePhase(PhaseEvent event) {
// NOOP.
}
#Override
public void afterPhase(PhaseEvent event) {
FacesContext context = event.getFacesContext();
if (!(context.isPostback() && context.getPartialViewContext().isAjaxRequest())) {
return; // Not an ajax postback.
}
Map<String, String> params = context.getExternalContext().getRequestParameterMap();
String sourceClientId = params.get("javax.faces.source");
String behaviorEvent = params.get("javax.faces.behavior.event");
UIComponent source = context.getViewRoot().findComponent(sourceClientId);
List<String> methodExpressions = new ArrayList<>();
if (source instanceof ClientBehaviorHolder && behaviorEvent != null) {
for (ClientBehavior behavior : ((ClientBehaviorHolder) source).getClientBehaviors().get(behaviorEvent)) {
List<BehaviorListener> listeners = getField(BehaviorBase.class, List.class, behavior);
if (listeners != null) {
for (BehaviorListener listener : listeners) {
MethodExpression methodExpression = getField(listener.getClass(), MethodExpression.class, listener);
if (methodExpression != null) {
methodExpressions.add(methodExpression.getExpressionString());
}
}
}
}
}
if (source instanceof ActionSource2) {
MethodExpression methodExpression = ((ActionSource2) source).getActionExpression();
if (methodExpression != null) {
methodExpressions.add(methodExpression.getExpressionString());
}
}
System.out.println(methodExpressions); // Do your thing with it.
}
private static <C, F> F getField(Class<? extends C> classType, Class<F> fieldType, C instance) {
try {
for (Field field : classType.getDeclaredFields()) {
if (field.getType().isAssignableFrom(fieldType)) {
field.setAccessible(true);
return (F) field.get(instance);
}
}
} catch (Exception e) {
// Handle?
}
return null;
}
}
In order to get it to run, register as below in faces-config.xml:
<lifecycle>
<phase-listener>com.example.AjaxActionLoggerPhaseListener</phase-listener>
</lifecycle>
Above is tested and compatible with Mojarra and PrimeFaces and theoretically also compatible with MyFaces.
Update: in case you're using JSF utility library OmniFaces, or are open to, since version 2.4 you can use the new Components#getCurrentActionSource() utility method to find out the current action source component and Components#getActionExpressionsAndListeners() to get a list of all action methods and listeners registered on a given component. This is also useable on regular (non-ajax) requests. With that, the above PhaseListener example can be reduced as below:
public class FacesActionLoggerPhaseListener implements PhaseListener {
#Override
public PhaseId getPhaseId() {
return PhaseId.PROCESS_VALIDATIONS;
}
#Override
public void beforePhase(PhaseEvent event) {
// NOOP.
}
#Override
public void afterPhase(PhaseEvent event) {
if (!event.getFacesContext().isPostback())) {
return;
}
UIComponent source = Components.getCurrentActionSource();
List<String> methodExpressions = Components.getActionExpressionsAndListeners(source);
System.out.println(methodExpressions); // Do your thing with it.
}
}
I'm trying to write some validation annotations for a Spring project.
I have several fields which are nullable, unless the value of a bool (in the same object) is true.
Is there some easy way to do something like:
#NotNull(if xyz=true)
? Thanks
I think it's not possible to do with annotations (too complex), but this can be done fairly easy by implementing org.springframework.validation.Validator interface:
public class MyClassValidator implements Validator {
#Override
public boolean supports(Class c) {
return MyClass.class.equals(c);
}
#Override
public void validate(Object object, Errors errors) {
MyClass myClass = (MyClass) object;
if (myClass.getA() == null) {
errors.rejectValue("avalue", "avalue.empty", "'A' value cannot be empty");
}
else if (myClass.getA() == true && (myClass.getB() == null || myClass.getB() < 0)) {
errors.rejectValue("bvalue", "bvalue.notvalid", "'B' value is not valid");
}
}
}
I am using JSF 2.0 and want to attach my custom RangeValidator to a HtmlInputText. In my backing bean, there is a snippet which creates the HTMLInputText. If I add a default validator, my code works like expected. If I add my custom RangeValidator in the same manner (by using FacesContext...) there is an error coming up, more precisely an 'unknown id' error. If I create an instance by using the new keyword, it seems that I am not able to use my accessor-methods, more precisely it seems that my HtmlInputText will use a freshly created RangeValidator-instance at runtime, so property values are minimum = 0 and maximum = 0, my validator is useless. How can I get my custom validator working? Thanks in advance.
// Snippet which creates HTML-Components
if ((integerField.getMaximalValue() != null) && (integerField.getMinimalValue() != null)) { // serverside validation required
// Default JSF Validator - works!
// final LongRangeValidator validator = (LongRangeValidator) FacesContext.getCurrentInstance().getApplication().createValidator(LongRangeValidator.VALIDATOR_ID);
// CustomValidator - ERROR: Unknown validator id;
final RangeValidator validator = (RangeValidator) FacesContext.getCurrentInstance().getApplication().createValidator("mypackage.validators.RangeValidator");
// construct custom validator instance - not working, seems to be a different instance!
// RangeValidator validator = new RangeValidator();
validator.setMaximum(integerField.getMaximalValue());
validator.setMinimum(integerField.getMinimalValue());
inputInteger.addValidator(validator);
}
// Validator
#FacesValidator("RangeValidator")
public class RangeValidator implements Validator {
private int minimum;
private int maximum;
#Override
public void validate(FacesContext context, UIComponent uiComponent, Object value) throws ValidatorException {
//System.out.println("RangeValidator.validate: " + value);
int val = Integer.parseInt((String) value);
if ((val <= this.getMaximum()) && (val >= this.getMinimum())){
// ok
} else {
FacesMessage fm = new FacesMessage();
String message = "Das Feld " + uiComponent.getClientId() + " liefert Validierungsfehler!";
fm.setSeverity(FacesMessage.SEVERITY_ERROR);
fm.setSummary(message);
fm.setDetail(message);
throw new ValidatorException(fm);
}
}
// Accessors
...
}
I found a solution for the problem: RangeValidator needs to implement the Serializable-interface as well and all will work like expected;
My current setup is JBoss Seam 2.2 on JBoss 4.2.3.GA.
I have two Beans like so:
#Name("mailingManager")
#Scope(ScopeType.PAGE)
public class MailingMgr {
private Mailing selectedMailing;
#Observer("mailing.letter.success")
public void recordSuccess(final Object arg) {
if (null != selectedMailing) { // store arg }
}
public void send() {
selectedMailing = new Mailing();
if ('EMAIL' == determineType()) {
EmailSender mailer = (EmailSender) Component.getInstance(EmailSender.class);
mailer.send(getAddresses());
}
// ... more options
}
}
#Name("emailSender")
#Scope(ScopeType.PAGE)
public class EmailSender {
public void send(final Set<String> addresses) {
for (String addr : addresses) {
// ... create a mail
Events.instance().raiseEvent("mailing.letter.success", getGeneratedMail());
}
}
}
The problem is that when recordSuccess() is called selectedMailing is always null.
As a workaround I'm setting selectedMailing in the conversation context manually before calling any code that could potentially trigger my events, and then annotate my field with #In(required=false) to inject it again before recordSuccess is called. But is there a more elegant solution (keeping the decoupling intact)? And why isn't the calling bean reused to handle the event?