How to get equals and hashcode to work with Java RMI? - java

I'm trying to learn Java RMI and I need to compare some objects. Specifically, I want to know if a set already contains an element, but it doesn't detect that an object is already present in the set. I don't have any other communication problems with RMI in general.
I've tried overriding hashcode() and equals(Object obj) in my remote object implementation, which is what I have to do from my understanding.
Here's some code:
public class ChatClientImpl extends UnicastRemoteObject implements ChatClient {
private static final long serialVersionUID = 3056117484716596895L;
private String id = null;
// some other stuff...
#Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (obj == null || !(obj instanceof ChatClientImpl)) {
return false;
}
ChatClientImpl other = (ChatClientImpl) obj;
return this.id.equals(other.id);
}
#Override
public int hashCode() {
return this.id.hashCode();
}
}
Set#contains and List#contains just don't work. Is there some other trick to this?
Some more complete code:
public void processMessage(MessageImpl inMessage) throws java.rmi.RemoteException {
// check the message to see if we have processed it before - discard if so
if( this.processedMessages.contains(inMessage.id) ) {
System.err.println("Ignoring duplicate message: " + inMessage.id);
return;
}
else {
System.out.println("Processing message: " + inMessage.id);
this.processedMessages.add(inMessage.id);
System.out.println(this.processedMessages.size() + " messages have been processed");
}
// update the GUI display with the new message
this.gui.updateDisplay(inMessage);
// add this client to the set of clients that have seen the message
inMessage.addRecipient(this);
// pass the message on to one of the clients that haven't seen it yet
sendMessage(inMessage);
}
private void sendMessage(MessageImpl msg) {
Iterator<ChatClient> clientIterator;
try {
// list of all known clients fetched from a server via RMI call
clientIterator = server.getClients().iterator();
} catch (RemoteException e) {
// log the error...
return;
}
// clients that have already seen the message
Set<ChatClient> alreadyPassedThru = msg.getRecipients();
boolean messageSent = false;
while ( ! messageSent && clientIterator.hasNext() ){
ChatClient knownClient = clientIterator.next();
try {
// clients that are in alreadyPassedThru are NOT detected...
if ( alreadyPassedThru.contains(knownClient) ){
System.out.println("Skipping client that has already seen the message: " + knownClient.getName());
} else {
knownClient.processMessage(msg);
System.out.println("Message has been sent to " + knownClient.getName());
messageSent = true;
}
} catch (RemoteException e) {
// client couldn't be contacted
clientIterator.remove();
}
}
}
It's worth mentioning that if I replace the code if ( alreadyPassedThru.contains(knownClient) ) (which is where the problem is) with some code that iterates over the set alreadyPassedThru and manually checks if any of the elements match knownClient by comparing their respective clientName variables, then everything works fine.
The MessageImpl code:
public class MessageImpl implements java.io.Serializable, Message {
private static final long serialVersionUID = 8914588083609635659L;
/**
* The globally unique identifier for this message
*/
public final GUID id;
/**
* All remote clients that have seen this message so far
*/
public final Set<ChatClient> passedThrough = new HashSet<ChatClient>();
/**
* The content of the message.
*/
private String messageContent = null;
/**
* The client who created the object
*/
private ChatClient author = null;
/**
* Create a new Message instance.
*/
public MessageImpl(ChatClient auth, String msg) {
this.id = new GUID(auth);
this.author = auth;
this.messageContent = msg;
addRecipient(auth); // the author has seen the message
}
#Override
public void addRecipient(ChatClient client) {
this.passedThrough.add(client);
}
#Override
public Set<ChatClient> getRecipients() {
return this.passedThrough;
}
#Override
public String getContent() {
return this.messageContent;
}
public String getSource() {
try {
return this.author.getName();
} catch (Exception e) {
return "[Unknown User]";
}
}
}
Just to clarify the difference between when the application works and when it doesn't work: if I replace this:
if ( alreadyPassedThru.contains(knownClient) ){...
where alreadyPassedThru is a HashSet<ChatClient> and knownClient is a ChatClient with this code:
// RMI call on ChatClient - simply returns clientName field
String knownClientName = knownClient.getName();
for (ChatClient client : alreadyPassedThru) {
if ( client.getName().equals(knownClientName) ){
return true;
}
}
...then it works.

You should try using instanceof instead of the getClass() method.
if (obj == null || !(obj instanceof MyRemote)) {
...
}
This is, at the very least, more efficient and may be the cause of your issue.
You might also want to consider simplifying your logic a bit. Right now, you have 3 return points in your code and the intent is not immediately clear. Try this:
public boolean equals(Object obj) {
if (obj instanceof MyRemoteImpl) {
MyRemoteImpl other = (MyRemoteImpl) obj;
return this.id.equals(other.id);
}
return false;
}

They already work with Java RMI.
Remote objects and their stubs have the same hashcodes and equals() behaviour by default if the remote objects extend UnicastRemoteObject.
However you've broken that by overriding hashCode() and equals().
Just remove those overrides.

Try doing as follows:
public class ChatClientImpl extends UnicastRemoteObject implements ChatClient {
private static final long serialVersionUID = 3056117484716596895L;
private String id = null;
// some other stuff...
#Override
public boolean equals(Object obj) {
if (super.equals(obj)) {
//Should also return true for stubs pointing to this
return true;
}
if (obj == null || !(obj instanceof ChatClient)) { //Check against the interface
return false;
}
//Only use the interface. obj might be a stub, not only an impl.
ChatClient other = (ChatClient) obj;
return getName().equals(obj.getName());
}
#Override
public int hashCode() {
return getName().hashCode();
}
}
I am definitely not sure this will work, but the specification for RemoteObject.equals makes me hope so.
EDIT
This can't work, because equals and hashCode are computed directly on the stub, regardless of what you implemented on your server.
This leaves you with two options:
(which I suggest) rely on the default equals. The client ID should be unique, and there should be no two clients with the same ID. That's clearly not the case in your implementation, because you're using an additional id field, implying the identity of an instance is not enough. I think it should be.
Wrap your ChatClients in a CustomHashChatClient and use a Set<CustomHashChatClient>, with CustomHashChatClient computing a custom hash, based on the object name, id, or whatever you want (of course, you need to swallow the exceptions or fail on them, which makes this a not-so-good idea).

Related

Why an ejb that has another ejb as a field doesn´t update the values of this ejb that is acting as a field?

I am making my first approach to java-ee. What I am trying to achieve is next:
I have written an application that collects bets prices from differents sources, I manage the collected data into a custom List class(ListEventsArquitectura) that manages the events data stored in EventoArquitectura(custom class). This EventoArquitectura class has also as fields other custom objects like BetArquitectura.
What I am testing is that this application works as a client of an Ejb sending the collected data to have it available later as a Rest webservice of the Ejb.
Now I have an ejb remote interface to modify fields of EventoArquitectura instances contained into ListEventsArquitectura, and it works but as EventoArquitectura instances have as a field instances of BetArquitectura I also created another Ejb remote interface to modify the fields of BetArquitectura and here is where I have the problem because the updates of the field BetArquitectura contained into EventoArquitectura doesn't produce any change.
I leave my code of the classes created for the tests and for the remote client.
For clarification, I am not using #Inject because it produces errors deploying to glassfish so I change the annotation to #Ejb.
#Stateful
public class ListEventsArquitectura implements ListEventsArquitecturaService{
List<EventoArquitectura> listaDeEventos;
#Ejb
private EventoArquitectura eventoService;
public ListEventsArquitectura() {
this.listaDeEventos = new ArrayList<>();
List<BetArquitectura> betsList = new ArrayList<>();
//betsList.add(new BetArquitectura("betDelConstructor", "0"));
this.listaDeEventos.add(new EventoArquitectura("evento del contructor id", "betIdDelConstructor"));
}
public List<EventoArquitectura> getListaDeEventos() {
return listaDeEventos;
}
#Override
public void updateListEvent(EventoArquitectura eventoActualizado){
for(EventoArquitectura evento : this.listaDeEventos){
if(evento.equals(eventoActualizado)){
this.eventoService = evento;
eventoService.updateEvent(eventoActualizado);
return;
}
}
}
#Override
public EventoArquitectura getEventFromList(int index) {
return this.listaDeEventos.get(index);
}
#Override
public void addEvent(EventoArquitectura evento) {
this.listaDeEventos.add(evento);
}
}
public interface ListEventsArquitecturaService {
public void updateListEvent(EventoArquitectura updatedEvent);
public EventoArquitectura getEventFromList(int index);
public void addEvent(EventoArquitectura evento);
}
#Stateful
public class EventoArquitectura implements Serializable,EventoArquitecturaService {
String eventId;
// List<BetArquitectura> betsList;
String betId;
public EventoArquitectura() {
}
public EventoArquitectura(String eventId, String betId) {
this.eventId = eventId;
//this.betsList = betsList;
}
public String getEventId() {
return eventId;
}
public void setEventId(String eventId) {
this.eventId = eventId;
}
/* public List<BetArquitectura> getBetsList() {
return betsList;
}
public void setBetsList(List<BetArquitectura> betsList) {
this.betsList = betsList;
}
*/
public String getBetId() {
return betId;
}
public void setBetId(String betId) {
this.betId = betId;
}
#Override
public void updateEvent(EventoArquitectura updatedEvent){
if(!(updatedEvent.equals(this))){
this.eventId = updatedEvent.eventId;
}
}
#Override
public boolean equals(Object obj) {
if(!(obj instanceof EventoArquitectura)){
return false;
}
EventoArquitectura evento = (EventoArquitectura)obj;
return evento.eventId.equals(this.eventId);
}
#Override
public int hashCode() {
int hash = 7;
hash = 59 * hash + Objects.hashCode(this.eventId);
return hash;
}
}
#Remote
public interface EventoArquitecturaService {
public void updateEvent(EventoArquitectura updatedEvent);
public String getBetId();
public void setBetId(String betId);
}
public class BetArquitectura implements Serializable{
String marketId;
String valorBet;
public BetArquitectura(String marketId, String valorBet) {
this.marketId = marketId;
this.valorBet = valorBet;
}
public BetArquitectura() {
}
public String getMarketId() {
return marketId;
}
public void setMarketId(String marketId) {
this.marketId = marketId;
}
public String getValorBet() {
return valorBet;
}
public void setValorBet(String valorBet) {
this.valorBet = valorBet;
}
private void updateValor(String valorBet){
this.valorBet = valorBet;
}
public void updateBet(BetArquitectura betActualizada){
if(this.equals(betActualizada)){
this.updateValor(betActualizada.getValorBet());
}
}
#Override
public boolean equals(Object obj) {
if(!(obj instanceof BetArquitectura)){
return false;
}
BetArquitectura bet = (BetArquitectura)obj;
return bet.marketId.equals(this.marketId);
}
#Override
public int hashCode() {
int hash = 7;
hash = 89 * hash + Objects.hashCode(this.marketId);
return hash;
}
}
And here I leave my remote client, It can change values of fields of the instances of EventoArquitectura contained in the ListEventsArquitectura, but if It doesn´t make changes into BetArquitectura object contained into each EventoArquitectura instance.
public class ClienteArquitecturaTest {
public static void main(String[] args){
try {
Properties props = new Properties();
props.setProperty("java.naming.factory.initial", "com.sun.enterprise.naming.SerialInitContextFactory");
props.setProperty("java.naming.factory.url.pkgs", "com.sun.enterprise.naming");
props.setProperty("java.naming.factory.state", "com.sun.corba.ee.impl.presentation.rmi.JNDIStateFactoryImpl");
// optional. Default localhost. Aquise cambia la IP del servidor donde esta Glassfishprops.setProperty("org.omg.CORBA.ORBInitialHost", "127.0.0.1");
// optional. Puerto por Default 3700. Solo se necesita cambiar si el puerto no es 3700.
//props.setProperty("org.omg.CORBA.ORBInitialPort", "3700");
Context jndi;
jndi = new InitialContext(props);
ListEventsArquitecturaService listEventsService = (ListEventsArquitecturaService) jndi.lookup("java:global/ArquitecturaEJBTest/ListEventsArquitectura!com.mycompany.ejb.interfaces.ListEventsArquitecturaService");
System.out.println("Id of the event added into constructor: " + listEventsService.getEventFromList(0).getEventId());
EventoArquitecturaService eventoParaModificar = listEventsService.getEventFromList(0);
eventoParaModificar.setBetId("betIdModified");
listEventsService.addEvent(new EventoArquitectura("newEventId", "newBetId"));
System.out.println("Modified Bet Id: " + listEventsService.getEventFromList(0).getBetId());
System.out.println("Added EventoArquitectura id: " + listEventsService.getEventFromList(1).getEventId());
System.out.println("Added Bet Id: " + listEventsService.getEventFromList(1).getBetId());
} catch (NamingException ex) {
Logger.getLogger(ClienteArquitecturaTest.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
And the output I get with this client, shows that I didn´t achieve to modify BetArquitectura objects, they are always null:
Id of the event added into constructor: evento del contructor id
Modified Bet Id: null
Added EventoArquitectura id: newEventId
Added Bet Id: null
I think your problem is that you modifiy an property of an instance which does only exists on the client side. When you call a method via a EJB client proxy, which returns an object, then you get an own instance (serialized on server side and deserialized at client side). These are two different objects, one exists on the client side and the other on the server side. Thats a common misunderstanding when working with remote ejbs.
Try to implement a method on your EJB like this, which modifies the intended object property on ther server side.
setIdAt(Integer idx, String id)
Then implement on the client side
// will pass parameters to server side EJB, which modifies the object property
service.setIdAt(0, "betIdModified");
// Get server side modified instance and its id
service.getElementFromList(0).getBetId();
With the example you pass parameters to the server side EJB and and the object on the server side gets modfied, which you retrieve after the server side EJB has modified it. Again, you get your own instance, which is not a proxy, as I assume you expect it to be. Only the service is proxied not the objects its methods return.

Overriding generic equals method

To preface this, I've looked for numerous examples prior to asking and can't find any solution in regards to my problem.
I'm trying to implement a generic queue in a program I'm making, but stuck at a certain point. The program I've made is supposed to simulate a printer, queued with print jobs. There is a Queue class, PrintQueue class, and job class. (It is important to note the Job class consists of a job ID and String of who ordered it). I've included a function (in the printQueue class) where if the first job matches the job ID you put in, it will be deleted.
Unfortunately however, the queue is generic. This means I can't traverse the array with just an integer to check equality because it is a queue of job objects. To fix this I create a job with a blank name, and regular ID. The Job class has an equals method, which determines if either ID or Owner match, then it is true. But when I execute the code, this class is not called. The generic equals class is called instead, which will of course be false. After looking at many examples on this site, I tried all the recommended solutions, which did not work for me as my case (and problem) are different. What can I do to override the generic equals method? My code below is as simple as I could make it to reproduce this problem while keep context.
JOB CLASS
public class Job{
private String owner;
private int jobId;
public Job(String o, int j){
owner = o;
jobId = j;
}
public String getOwner(){
return owner;
}
public int getJobId(){
return jobId;
}
public String toString() {
return owner + " " + jobId + ". ";
}
public boolean equals(Job a) {
if(this.jobId == a.getJobId() || this.owner.equals(a.getOwner())) {
return true;
}
else
System.out.println("nomatch");
return false;
}
}
GENERIC QUEUE CLASS
import java.util.ArrayList;
public class Queue<T>{
private ArrayList<T> queue;
public Queue() {
queue = new ArrayList<T>();
}
public void enQueue(T obj1) {
queue.add(obj1);
}
public T deQueue() {
if(queue.size() != 0) {
T temp = queue.get(queue.size() - 1);
queue.remove(queue.size() -1);
return temp;
}
else
return null;
}
public int size() {
return queue.size();
}
public boolean isEmpty() {
if (size() == 0) {
return true;
}
else
return false;
}
public int positionOf(T a) {
for(int x = 0; x < queue.size(); x++) {
if(a.equals(queue.get(x))) {
System.out.println("Positionmatch");
return x;
}
}
return -1;
}
}
PRINTQUEUE CLASS
public class PrintQueue {
Queue<Job> prqueue = new Queue<Job>();
public PrintQueue() {}
public void lprm(int jobID) { //Removes the active job at the front of the queue if jobId matches, error message otherwise
//I can't JUST use jobID to check the position because the queue is a collection of JOBS not JobId's
if (prqueue.positionOf(new Job("",jobID))==0) {
prqueue.deQueue();
}
else if (prqueue.positionOf(new Job("",jobID))== -1) {
System.out.println("Job does not occupy first row.");
}
}
}
I know this is an extensive question, so if you do take the time to read it thank you very much. I wouldn't ask this if I could find the answer anywhere else.
Solution is simple: you are not overriding equals in your class, common mistake. Always annotate your methods with #Override so you can avoid this mistake.
Real equals method is taking an Object parameter, and yours has a Job as parameter, change that to Object and then cast it accordingly.
If you are using IDE I suggest right click -> source -> generate equals and you will see a good example how to do it.
You have to override your methods like this
#Override
public boolean equals(Object a) {
if(!(a instanceof Job))
throw new IllegalArgumentException();
Job job =(Job)a;
if(this.jobId == job.getJobId() || this.owner.equals(job.getOwner())) {
return true;
}
else
System.out.println("nomatch");
return false;
}
See also Why do I need to override the equals and hashCode methods in Java?

Java: message system needs to be able to pass various objects

I'm writing a messaging system to queue actions for my program to execute. I need to be able to pass various objects by the messages. I currently have a Msg object that accepts (Action enum, Data<?>...object). The Data object is intended to be a wrapper for any object I might pass.
Currently the Data object uses this code, with generics:
public class Data<T> {
private T data;
public Data(T data){
this.data = data;
}
public T getData(){
return data;
}
}
The Msg object takes Data<?>... type, so Msg has a Data<?>[] field.
If getData() is called on a Data<?> object, it returns the Object type. Obviously not ideal.
I need to be able to pass, say, Image objects as well as String objects. I'm certain there's a better way of passing arbitrary data.
The reason you're having trouble is that you're trying to get the static typing system of Java to do something that it can't. Once you convert from a Data<T> to a Data<?>, whatever T was is effectively lost. There's no clean way to get it back.
The quickest way to get it to work (from what you have right now) is to start throwing casts everywhere, like this:
Data<?> d = new Data("Hello");
String contents = (String)d.getData();
This is kind of a terrible idea, so let's go back to the drawing board.
If (ideally), you have all of the types you could ever need ahead of time (i.e. every Data is either a String or an Image or an Integer), then you can pretty easily (though it's a bit tedious) define a Sum type (aka a union if you're coming from C) of the different types of data you'll have to handle. As a class invariant, we assume that exactly one of the fields is non-null, and the rest are null. For this example I'll assume it can be either a String, an Image, or an Integer, but it's fairly simple to add or remove types from Data as necessary.
public class Data {
private Image imgData;
private String stringData;
private Integer intData;
public Data(Image img) {
this.imgData = img;
}
public Data(String stringData) {
this.stringData = stringData;
}
public Data(Integer intData) {
this.intData = intData;
}
public boolean isImage() {
return imageData != null;
}
public boolean isInteger() {
return intData != null;
}
public boolean isString() {
return stringData != null;
}
public Image asImage() {
if(! isImage()) throw new RuntimeException();
return imgData;
}
public Image asString() {
if(! isString()) throw new RuntimeException();
return stringData;
}
public Image asInt() {
if(! isInt()) throw new RuntimeException();
return intData;
}
}
One necessary side effect is that we cannot wrap null without causing exceptional behavior. Is this is desired, it isn't too difficult to modify the class to allow for it.
With this Data class, it's pretty easy to do if-else logic to parse it.
Data d = ....... //Get a data from somewhere
if(d.isImage()) {
Image img = d.asImage();
//...
} else if (d.isString()) {
String string = d.asString();
//...
} else if (d.isInteger()) {
Integer i = d.asInt();
//...
} else {
throw new RuntimeException("Illegal data " + d + " received");
}
If you call getData().getClass() you will get the class or type that was passed, which doesn't seem to me to be the same as an Object. You might not know what you are getting, but you can either find out or define a common interface for everything you might pass. You could for example, call toString() or getClass() on anything passed. Your question is that you are passing any conceivable object, so my question is what are you going to do with it? If you are going to serialize it into a database you don't need know anything about what type it is, otherwise you can test it or call a common interface.
public class PlayData {
class Msg {
private List<Data<?>> message = new ArrayList<Data<?>>();
public void addData(Data<?> datum) { message.add(datum); }
public void printTypes() { for ( Data<?> datum: message ) { System.out.println(datum.getData().getClass()); } }
}
class Data<T> {
private T value;
public Data(T value) { this.value = value; }
public T getData() { return value; }
}
class Listener {
public void receive(Msg msg) { msg.printTypes(); }
}
class Sender {
private Listener listener;
public Sender(Listener listener) { this.listener = listener; }
public void send(Msg msg) { listener.receive(msg); }
}
class MyPacket {
int i;
public MyPacket(int i) { this.i = i; }
}
public static void main(String[] args) throws Exception { new PlayData().run(); }
public void run() throws Exception {
Sender sender = new Sender(new Listener());
Msg msg = new Msg();
msg.addData(new Data<String>("testing") );
msg.addData(new Data<MyPacket>(new MyPacket(42)) );
sender.send(msg);
}
}

Casting Proxies - Getting ClassCastException

I'm getting some weirdness when I'm casting a Dynamic Proxy Class to the object I want it to be. At runtime, under certain conditions, I receive a ClassCastException.
In order to explain this better, here are the definitions for the classes/interfaces I want to use. Brackets have been put around any extended interfaces that (should be) irrelevant.
public interface CommandSender (extends Permissible)
public interface ConsoleCommandSender extends CommandSender, (Conversable)
public interface Player extends (HumanEntity, Conversable), CommandSender, (OfflinePlayer, PluginMessageRecipient)
Full Javadocs can be found here: http://jd.bukkit.org/apidocs/org/bukkit/command/CommandSender.html
Now, here is the code for my proxy class:
public class CommandSignsMessagingProxy implements InvocationHandler {
private Object sender;
private Object receiver;
private boolean silent;
public static Object newInstance(Object proxy) {
return newInstance(proxy, proxy, false);
}
public static Object newInstance(Object proxy, boolean silent) {
return newInstance(proxy, proxy, silent);
}
public static Object newInstance(Object sender, Object receiver) {
return newInstance(sender, receiver, false);
}
public static Object newInstance(Object sender, Object receiver, boolean silent) {
return Proxy.newProxyInstance(
sender.getClass().getClassLoader(),
sender.getClass().getInterfaces(),
new CommandSignsMessagingProxy(sender, receiver, silent));
}
private CommandSignsMessagingProxy(Object sender, Object receiver, boolean silent) {
this.sender = sender;
this.receiver = receiver;
this.silent = silent;
}
// Is called whenever a method is invoked
public Object invoke(Object p, Method m, Object[] args) throws Throwable {
Object result = null;
try {
String name = m.getName();
// If the receiver is being sent a message, only do so if the silent flag is not set
if (name == "sendMessage" || name == "sendRawMessage") {
if (!silent && receiver != null)
result = m.invoke(receiver, args);
} else {
result = m.invoke(sender, args);
}
} catch (InvocationTargetException e) {
throw e.getTargetException();
} catch (Exception e) {
throw new RuntimeException("Unexpected invocation exception: " + e.getMessage());
}
return result;
}
}
And here is a fully working instance of the class:
Player proxy = (Player)CommandSignsMessagingProxy.newInstance(player, false);
proxy.sendMessage("Hi! Silent is turned off, so you can see this!");
proxy.setOp(true);
proxy.other_stuff();
Yet, this one doesn't work:
ConsoleCommandSender ccs = plugin.getServer().getConsoleSender();
CommandSender cs = (CommandSender)CommandSignsMessagingProxy.newInstance(ccs, false);
At run time, this example would produce the following:
java.lang.ClassCastException: $Proxy18 cannot be cast to org.bukkit.command.CommandSender
The created proxy class need to pass the interfaces it suppose to implement,
return Proxy.newProxyInstance(
sender.getClass().getClassLoader(),
sender.getClass().getInterfaces(),
new CommandSignsMessagingProxy(sender, receiver, silent));
failure seems to happen because CommandSender interface may not be returned from the call sender.getClass().getInterfaces() method. So try to see if it properly passes by debugging. If not try sending the interface manually to the method and see if it works.

Is it possible to set multiple messages using oval AbstractAnnotationCheck?

I am using the Oval validation framework to validate fields that HTML fields cannot hold malicious javascript code. For the malicious code detection, I am using an external framework that returns me a list of errors that I would like to use as error messages on the field. The problem I am running into is that I can only setMessage in the check implementation, while I would rather do something like setMessages(List). So while I am currently just joining the errors with a comma, I would rather pass them back up as a list.
Annotation
#Target({ ElementType.METHOD, ElementType.FIELD})
#Retention( RetentionPolicy.RUNTIME)
#Constraint(checkWith = HtmlFieldValidator.class)
public #interface HtmlField {
String message() default "HTML could not be validated";
}
Check
public class HtmlFieldValidator extends AbstractAnnotationCheck<HtmlDefaultValue> {
public boolean isSatisfied( Object o, Object o1, OValContext oValContext, Validator validator ) throws OValException {
if (o1 == null) {
return true;
} else {
CleanResults cleanResults = UIowaAntiSamy.cleanHtml((String) o1);
if (cleanResults.getErrorMessages().size() > 0) {
String errors = StringUtils.join(cleanResults.getErrorMessages(), ", ");
this.setMessage(errors);
return false;
} else {
return true;
}
}
}
}
Model class
class Foo {
#HtmlField
public String bar;
}
Controller code
Validator validator = new Validator(); // use the OVal validator
Foo foo = new Foo();
foo.bar = "<script>hack()</script>";
List<ConstraintViolation> violations = validator.validate(bo);
if (violations.size() > 0) {
// inform the user that I cannot accept the string because
// it contains invalid html, using error messages from OVal
}
If setMessage(String message) is a method created by a superclass, you can override it and once it receives the data, simply split the string into a list and call a second function in which you would actually place your code. On a side note, I would also recommend changing the separating string to something more unique as the error message itself could include a comma.
Your question doesn't really make much sense though. If you are "passing them back up" to a method implemented in a superclass, then this voids the entire point of your question as the superclass will be handling the data.
I am going to assume the setError methods is a simple setter that sets a String variable to store an error message that you plan to access after checking the data. Since you want to have the data in your preferred type, just create a new array of strings in your class and ignore the superclass. You can even use both if you so desire.
public class HtmlFieldValidator extends AbstractAnnotationCheck<HtmlDefaultValue> {
public String[] errorMessages = null;
public void setErrorMessages(String[] s) {
this.errorMessages = s;
}
public boolean isSatisfied( Object o, Object o1, OValContext oValContext, Validator validator ) throws OValException {
if (o1 == null) {
return true;
} else {
CleanResults cleanResults = UIowaAntiSamy.cleanHtml((String) o1);
if (cleanResults.getErrorMessages().size() > 0) {
//String errors = StringUtils.join(cleanResults.getErrorMessages(), ", ");
//this.setMessage(errors);
this.setErrorMessages(cleanResults.getErrorMessages());
return false;
} else {
return true;
}
}
}
}
Elsewhere:
HtmlFieldValidator<DefaultValue> hfv = new HtmlFieldValidator<DefaultValue>();
boolean satisfied = hfv.isSatisfied(params);
if (!satisfied) {
String[] errorMessages = hfv.errorMessages;
//instead of using their error message
satisfy(errorMessages);//or whatever you want to do
}
EDIT:
After you updated your code I see what you mean. While I think this is sort of overdoing it and it would be much easier to just convert the string into an array later, you might be able to do it by creating a new class that extends Validator its setMessage method. In the method, you would call super.setMethod as well as splitting and storing the string as an array in its class.
class ValidatorWithArray extends Validator {
public String[] errors;
public final static String SPLIT_REGEX = ";&spLit;";// Something unique so you wont accidentally have it in the error
public void setMessage(String error) {
super.setMessage(error);
this.errors = String.split(error, SPLIT_REGEX);
}
}
In HtmlFieldValidator:
public boolean isSatisfied( Object o, Object o1, OValContext oValContext, Validator validator ) throws OValException {
if (o1 == null) {
return true;
} else {
CleanResults cleanResults = UIowaAntiSamy.cleanHtml((String) o1);
if (cleanResults.getErrorMessages().size() > 0) {
String errors = StringUtils.join(cleanResults.getErrorMessages(), ValidatorWithArray.SPLIT_REGEX);
this.setMessage(errors);
return false;
} else {
return true;
}
}
}
And now just use ValidatorWithArray instead of Validator
The situation in which I want to achieve this was different from yours, however what I found was best in my case was to create an annotation for each error (rather than having one that would return multiple errors). I guess it depends on how many errors you are likely to be producing in my case it was only two or three.
This method makes also makes your code really easy to reuse as you can just add the annotations wherenever you need them and combine them at will.

Categories

Resources