I have a table in my database containing email
and email_state
amongst other values.
Email is the email of the contact, and email_state
is the existence of the email. It can contains 3 values: null
(not checked), EXISTS
or NOTEXISTS
.
When the application (a PlayFramework Java application) starts, I call the start()
method from the code below. Everytime I add a new contact (either a one time, or from a CSV import), I also call start
.
The start
method checks if there is no checked emails in the database, and if so, add them in the queue list.
Then it checks if the queue is empty, if yes stop right here.
Then it checks if there is already a processus working, if yes, it stops right here.
The code is right below, and I was wondering if it was correct or if I missed anything. The aim is to check for all the emails in the database, in the most efficient way, and manage app shutdown/restart, concurrent creations (two users uploading massive CSV), etc.
package in.twolead.asyncs;
import in.twolead.enums.EmailState;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.List;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.TimeUnit;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import play.libs.Akka;
import scala.concurrent.duration.Duration;
import com.avaje.ebean.Ebean;
import com.avaje.ebean.SqlRow;
import com.avaje.ebean.SqlUpdate;
public class EmailVerifier implements Runnable {
private static EmailVerifier instance = null;
public static EmailVerifier getInstance() {
if (instance == null) {
instance = new EmailVerifier();
}
return instance;
}
private boolean running = false;
private ConcurrentLinkedQueue<String> emails;
private SqlUpdate update;
private EmailVerifier() {
this.emails = new ConcurrentLinkedQueue<String>();
this.update = Ebean.createSqlUpdate("UPDATE `contacts` SET `email_state` = :state WHERE `email` = :email");
}
public boolean isRunning() {
return running;
}
public boolean isEmpty() {
return this.emails.isEmpty();
}
public void start() {
List<SqlRow> rows = Ebean.createSqlQuery("SELECT `email` FROM `contacts` WHERE `email_state` IS NULL GROUP BY `email`").findList();
for (SqlRow row : rows) {
this.emails.add(row.getString("email"));
}
if (isEmpty()) return;
if (isRunning()) return;
Akka.system().scheduler().scheduleOnce(
Duration.create(10, TimeUnit.MILLISECONDS),
this,
Akka.system().dispatcher()
);
}
@Override
public void run() {
running = true;
while (this.emails.peek() != null) {
String email = this.emails.poll();
// Request the server
this.update.setParameter("email", email);
this.update.setParameter("state", (isEmailExists(email) ? EmailState.EXISTS : EmailState.NOTEXISTS));
this.update.execute();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
running = false;
return;
}
}
running = false;
}
private boolean isEmailExists(String email) {
CloseableHttpClient httpclient = HttpClients.createSystem();
try {
HttpGet get = new HttpGet("http://api.voilanorbert.com/v2/verify/" + URLEncoder.encode(email, "UTF-8"));
get.addHeader("TOKEN", "secure_token");
HttpResponse response = httpclient.execute(get);
return (response.getStatusLine().getStatusCode() == 204);
}
catch (UnsupportedEncodingException e1) {}
catch (IOException e) {}
return false;
}
}
(note: VoilaNorbert is a service that I run. The currently used API is not yet released to the public)