Creating a WhatsApp Clone - Part XIII
The next step is the services layer which implements the relatively simple business layer of this application
@Service
public class UserService {
@Autowired
private UserRepository users;
@Autowired
private ChatGroupRepository groups;
@Autowired
private ChatMessageRepository messages;
@Autowired
private APIKeys keys;
@Autowired
private PasswordEncoder encoder;
@Autowired
private MediaRepository medias;
e
UserService
We’ll start with the UserService class, it’s the main class in this package and handles pretty much the entire business logic of the app

It’s a service bean, this means it handles generic business logic for the application
@Service
public class UserService {
@Autowired
private UserRepository users;
@Autowired
private ChatGroupRepository groups;
@Autowired
private ChatMessageRepository messages;
@Autowired
private APIKeys keys;
@Autowired
private PasswordEncoder encoder;
@Autowired
private MediaRepository medias;
e
UserService
We need access to the repositories we just defined for users, groups and messages
@Service
public class UserService {
@Autowired
private UserRepository users;
@Autowired
private ChatGroupRepository groups;
@Autowired
private ChatMessageRepository messages;
@Autowired
private APIKeys keys;
@Autowired
private PasswordEncoder encoder;
@Autowired
private MediaRepository medias;
e
UserService
The API keys service is the exact one I used in facebook with the exception of a different properties file name. I’ll discuss that later, it generally abstracts API keys and
separates them from the source code
@Service
public class UserService {
@Autowired
private UserRepository users;
@Autowired
private ChatGroupRepository groups;
@Autowired
private ChatMessageRepository messages;
@Autowired
private APIKeys keys;
@Autowired
private PasswordEncoder encoder;
@Autowired
private MediaRepository medias;
e
UserService
The password encoder is used to encrypt and verify the token
private UserRepository users;
@Autowired
private ChatGroupRepository groups;
@Autowired
private ChatMessageRepository messages;
@Autowired
private APIKeys keys;
@Autowired
private PasswordEncoder encoder;
@Autowired
private MediaRepository medias;
@Autowired
private NotificationService notifications;
private void sendActivationSMS(String number, String text) {
Twilio.init(keys.get("twilio.sid"), keys.get("twilio.auth"));
Message message = Message
e
UserService
MediaRepository and NotificationService are identical to the stuff we had in the facebook clone app
private PasswordEncoder encoder;
@Autowired
private MediaRepository medias;
@Autowired
private NotificationService notifications;
private void sendActivationSMS(String number, String text) {
Twilio.init(keys.get("twilio.sid"), keys.get("twilio.auth"));
Message message = Message
.creator(new PhoneNumber(number),
new PhoneNumber(keys.get("twilio.phone")),
text)
.create();
message.getSid();
}
public UserDAO login(String phone, String auth)
throws LoginException {
List<User> userList;
userList = users.findByPhone(phone);
if (userList != null && userList.size() == 1) {
e
UserService
This method sends an SMS message via the twilio web service. If you recall we added the twilio SDK into the pom file in the first lesson. This SDK makes sending an
SMS message very easy as you can see from this code.
.creator(new PhoneNumber(number),
new PhoneNumber(keys.get("twilio.phone")),
text)
.create();
message.getSid();
}
public UserDAO login(String phone, String auth)
throws LoginException {
List<User> userList;
userList = users.findByPhone(phone);
if (userList != null && userList.size() == 1) {
User u = userList.get(0);
if (encoder.matches(auth, u.getAuthtoken())) {
return u.getLoginDAO();
}
throw new LoginException("Authentication error!");
}
throw new LoginException("User not found!");
}
private String createVerificationCode(int length) {
StringBuilder k = new StringBuilder();
Random r = new Random();
e
UserService
The login API lets us validate a user and get the current data the server has for that user. Since there is no username/password we need to use a token to authenticate
.creator(new PhoneNumber(number),
new PhoneNumber(keys.get("twilio.phone")),
text)
.create();
message.getSid();
}
public UserDAO login(String phone, String auth)
throws LoginException {
List<User> userList;
userList = users.findByPhone(phone);
if (userList != null && userList.size() == 1) {
User u = userList.get(0);
if (encoder.matches(auth, u.getAuthtoken())) {
return u.getLoginDAO();
}
throw new LoginException("Authentication error!");
}
throw new LoginException("User not found!");
}
private String createVerificationCode(int length) {
StringBuilder k = new StringBuilder();
Random r = new Random();
e
UserService
First we need to find the user with the given phone. Assuming the user isn’t there we’ll throw an exception
.creator(new PhoneNumber(number),
new PhoneNumber(keys.get("twilio.phone")),
text)
.create();
message.getSid();
}
public UserDAO login(String phone, String auth)
throws LoginException {
List<User> userList;
userList = users.findByPhone(phone);
if (userList != null && userList.size() == 1) {
User u = userList.get(0);
if (encoder.matches(auth, u.getAuthtoken())) {
return u.getLoginDAO();
}
throw new LoginException("Authentication error!");
}
throw new LoginException("User not found!");
}
private String createVerificationCode(int length) {
StringBuilder k = new StringBuilder();
Random r = new Random();
e
UserService
Since the auth is hashed as we discussed before, we need to test the incoming auth via the matches method in the encoder. It verifies the hash matches the auth token
if (encoder.matches(auth, u.getAuthtoken())) {
return u.getLoginDAO();
}
throw new LoginException("Authentication error!");
}
throw new LoginException("User not found!");
}
private String createVerificationCode(int length) {
StringBuilder k = new StringBuilder();
Random r = new Random();
for (int iter = 0; iter < length; iter++) {
k.append(r.nextInt(10));
}
return k.toString();
}
public UserDAO signup(UserDAO user) throws SignupException {
List<User> ul = users.findByPhone(user.getPhone());
if (ul != null && ul.size() > 0) {
throw new SignupException(
"The phone number is already registered!");
e
UserService
This method creates a string of the given length which includes a random number for verification
return k.toString();
}
public UserDAO signup(UserDAO user) throws SignupException {
List<User> ul = users.findByPhone(user.getPhone());
if (ul != null && ul.size() > 0) {
throw new SignupException(
"The phone number is already registered!");
}
User u = new User();
setProps(user, u);
u.setAuthtoken(UUID.randomUUID().toString());
u.setVerificationCode(createVerificationCode(4));
users.save(u);
sendActivationSMS(user.getPhone(), "Activation key: " + u.
getVerificationCode());
return u.getLoginDAO();
}
e
UserService
Signup creates an entry for a specific user but does’t activate the account until it’s verified
return k.toString();
}
public UserDAO signup(UserDAO user) throws SignupException {
List<User> ul = users.findByPhone(user.getPhone());
if (ul != null && ul.size() > 0) {
throw new SignupException(
"The phone number is already registered!");
}
User u = new User();
setProps(user, u);
u.setAuthtoken(UUID.randomUUID().toString());
u.setVerificationCode(createVerificationCode(4));
users.save(u);
sendActivationSMS(user.getPhone(), "Activation key: " + u.
getVerificationCode());
return u.getLoginDAO();
}
e
UserService
We first check if the phone number is already registered. If so we need to fail
return k.toString();
}
public UserDAO signup(UserDAO user) throws SignupException {
List<User> ul = users.findByPhone(user.getPhone());
if (ul != null && ul.size() > 0) {
throw new SignupException(
"The phone number is already registered!");
}
User u = new User();
setProps(user, u);
u.setAuthtoken(UUID.randomUUID().toString());
u.setVerificationCode(createVerificationCode(4));
users.save(u);
sendActivationSMS(user.getPhone(), "Activation key: " + u.
getVerificationCode());
return u.getLoginDAO();
}
e
UserService
Otherwise we create the new user and initialize the value of the data and the verification code
return k.toString();
}
public UserDAO signup(UserDAO user) throws SignupException {
List<User> ul = users.findByPhone(user.getPhone());
if (ul != null && ul.size() > 0) {
throw new SignupException(
"The phone number is already registered!");
}
User u = new User();
setProps(user, u);
u.setAuthtoken(UUID.randomUUID().toString());
u.setVerificationCode(createVerificationCode(4));
users.save(u);
sendActivationSMS(user.getPhone(), "Activation key: " + u.
getVerificationCode());
return u.getLoginDAO();
}
e
UserService
Finally we send the activation code and return the user entity
sendActivationSMS(user.getPhone(), "Activation key: " + u.
getVerificationCode());
return u.getLoginDAO();
}
public boolean verifyPhone(String userId, String code) {
User u = users.findById(userId).get();
if (u.getVerificationCode().equals(code)) {
u.setVerificationCode(null);
u.setVerified(true);
users.save(u);
return true;
}
return false;
}
private void setProps(UserDAO user, User u) {
u.setName(user.getName());
u.setTagline(user.getTagline());
}
public void update(String auth, UserDAO user) {
e
UserService
The verify method activates a user account, if the verification code is correct we mark the user as verified and return true.
sendActivationSMS(user.getPhone(), "Activation key: " + u.
getVerificationCode());
return u.getLoginDAO();
}
public boolean verifyPhone(String userId, String code) {
User u = users.findById(userId).get();
if (u.getVerificationCode().equals(code)) {
u.setVerificationCode(null);
u.setVerified(true);
users.save(u);
return true;
}
return false;
}
private void setProps(UserDAO user, User u) {
u.setName(user.getName());
u.setTagline(user.getTagline());
}
public void update(String auth, UserDAO user) {
e
UserService
We use setProps both from the signup and update methods. There isn’t much here but if we add additional meta-data this might become a bigger method like it is in the
facebook clone
}
private void setProps(UserDAO user, User u) {
u.setName(user.getName());
u.setTagline(user.getTagline());
}
public void update(String auth, UserDAO user) {
User u = users.findById(user.getId()).get();
if (encoder.matches(auth, u.getAuthtoken())) {
setProps(user, u);
users.save(u);
}
}
public byte[] getAvatar(String userId) {
User u = users.findById(userId).get();
if (u.getAvatar() != null) {
return u.getAvatar().getData();
}
return null;
}
public void setAvatar(String auth, String userId, String mediaId) {
e
UserService
Update verifies the users token then updates the properties. There isn’t much here
setProps(user, u);
users.save(u);
}
}
public byte[] getAvatar(String userId) {
User u = users.findById(userId).get();
if (u.getAvatar() != null) {
return u.getAvatar().getData();
}
return null;
}
public void setAvatar(String auth, String userId, String mediaId) {
Media m = medias.findById(mediaId).get();
User u = users.findById(userId).get();
if (encoder.matches(auth, u.getAuthtoken())) {
u.setAvatar(m);
users.save(u);
}
}
public void userTyping(String userId, String toUser, boolean t) {
Optional<User> to = users.findById(toUser);
e
UserService
These aren’t used at the moment but they are pretty much identical to what we have in the facebook clone and should be easy to integrate in a similar way
User u = users.findById(userId).get();
if (encoder.matches(auth, u.getAuthtoken())) {
u.setAvatar(m);
users.save(u);
}
}
public void userTyping(String userId, String toUser, boolean t) {
Optional<User> to = users.findById(toUser);
if(to.isPresent()) {
AppSocket.sendUserTyping(to.get().getAuthtoken(), userId, t);
} else {
ChatGroup g = groups.findById(toUser).get();
for(User u : g.getMembers()) {
AppSocket.sendUserTyping(u.getAuthtoken(), userId, t);
}
}
}
public MessageDAO sendMessage(MessageDAO m) {
ChatMessage cm = new ChatMessage();
cm.setAuthor(users.findById(m.getAuthorId()).get());
cm.setBody(m.getBody());
e
UserService
This is part of the work to integrate support for the “user typing" feature. Right now the client app doesn’t send or render this event but it should be relatively simple to
add. When a user starts typing to a conversation we can invoke this method
User u = users.findById(userId).get();
if (encoder.matches(auth, u.getAuthtoken())) {
u.setAvatar(m);
users.save(u);
}
}
public void userTyping(String userId, String toUser, boolean t) {
Optional<User> to = users.findById(toUser);
if(to.isPresent()) {
AppSocket.sendUserTyping(to.get().getAuthtoken(), userId, t);
} else {
ChatGroup g = groups.findById(toUser).get();
for(User u : g.getMembers()) {
AppSocket.sendUserTyping(u.getAuthtoken(), userId, t);
}
}
}
public MessageDAO sendMessage(MessageDAO m) {
ChatMessage cm = new ChatMessage();
cm.setAuthor(users.findById(m.getAuthorId()).get());
cm.setBody(m.getBody());
e
UserService
toUser can be a user or a group is the user is present it’s a user
User u = users.findById(userId).get();
if (encoder.matches(auth, u.getAuthtoken())) {
u.setAvatar(m);
users.save(u);
}
}
public void userTyping(String userId, String toUser, boolean t) {
Optional<User> to = users.findById(toUser);
if(to.isPresent()) {
AppSocket.sendUserTyping(to.get().getAuthtoken(), userId, t);
} else {
ChatGroup g = groups.findById(toUser).get();
for(User u : g.getMembers()) {
AppSocket.sendUserTyping(u.getAuthtoken(), userId, t);
}
}
}
public MessageDAO sendMessage(MessageDAO m) {
ChatMessage cm = new ChatMessage();
cm.setAuthor(users.findById(m.getAuthorId()).get());
cm.setBody(m.getBody());
e
UserService
I’ll discuss the event code in the sockets when we reach the app socket class
User u = users.findById(userId).get();
if (encoder.matches(auth, u.getAuthtoken())) {
u.setAvatar(m);
users.save(u);
}
}
public void userTyping(String userId, String toUser, boolean t) {
Optional<User> to = users.findById(toUser);
if(to.isPresent()) {
AppSocket.sendUserTyping(to.get().getAuthtoken(), userId, t);
} else {
ChatGroup g = groups.findById(toUser).get();
for(User u : g.getMembers()) {
AppSocket.sendUserTyping(u.getAuthtoken(), userId, t);
}
}
}
public MessageDAO sendMessage(MessageDAO m) {
ChatMessage cm = new ChatMessage();
cm.setAuthor(users.findById(m.getAuthorId()).get());
cm.setBody(m.getBody());
e
UserService
If this is a group we need to send the event to all the users within the group via the socket connection
}
}
}
public MessageDAO sendMessage(MessageDAO m) {
ChatMessage cm = new ChatMessage();
cm.setAuthor(users.findById(m.getAuthorId()).get());
cm.setBody(m.getBody());
cm.setMessageTime(new Date());
Optional<User> to = users.findById(m.getSentTo());
if(to.isPresent()) {
cm.setSentTo(to.get());
String json = createMessageImpl(m.getSentTo(), cm);
User usr = to.get();
sendMessageImpl(usr.getAuthtoken(), usr.getPushKey(), json,
m.getBody());
} else {
ChatGroup g = groups.findById(m.getSentTo()).get();
cm.setSentToGroup(g);
String json = createMessageImpl(m.getSentTo(), cm);
for(User u : g.getMembers()) {
sendMessageImpl(u.getAuthtoken(), u.getPushKey(), json,
m.getBody());
e
UserService
This method sends a message to its destination which can be a user or a group.
}
}
}
public MessageDAO sendMessage(MessageDAO m) {
ChatMessage cm = new ChatMessage();
cm.setAuthor(users.findById(m.getAuthorId()).get());
cm.setBody(m.getBody());
cm.setMessageTime(new Date());
Optional<User> to = users.findById(m.getSentTo());
if(to.isPresent()) {
cm.setSentTo(to.get());
String json = createMessageImpl(m.getSentTo(), cm);
User usr = to.get();
sendMessageImpl(usr.getAuthtoken(), usr.getPushKey(), json,
m.getBody());
} else {
ChatGroup g = groups.findById(m.getSentTo()).get();
cm.setSentToGroup(g);
String json = createMessageImpl(m.getSentTo(), cm);
for(User u : g.getMembers()) {
sendMessageImpl(u.getAuthtoken(), u.getPushKey(), json,
m.getBody());
e
UserService
In order to send a message we first need to create a ChatMessage entity so we can persist the message in case delivery failed.
}
}
}
public MessageDAO sendMessage(MessageDAO m) {
ChatMessage cm = new ChatMessage();
cm.setAuthor(users.findById(m.getAuthorId()).get());
cm.setBody(m.getBody());
cm.setMessageTime(new Date());
Optional<User> to = users.findById(m.getSentTo());
if(to.isPresent()) {
cm.setSentTo(to.get());
String json = createMessageImpl(m.getSentTo(), cm);
User usr = to.get();
sendMessageImpl(usr.getAuthtoken(), usr.getPushKey(), json,
m.getBody());
} else {
ChatGroup g = groups.findById(m.getSentTo()).get();
cm.setSentToGroup(g);
String json = createMessageImpl(m.getSentTo(), cm);
for(User u : g.getMembers()) {
sendMessageImpl(u.getAuthtoken(), u.getPushKey(), json,
m.getBody());
e
UserService
This is the same code we saw in the typing event. If the message is destined to a user the following block will occur
public MessageDAO sendMessage(MessageDAO m) {
ChatMessage cm = new ChatMessage();
cm.setAuthor(users.findById(m.getAuthorId()).get());
cm.setBody(m.getBody());
cm.setMessageTime(new Date());
Optional<User> to = users.findById(m.getSentTo());
if(to.isPresent()) {
cm.setSentTo(to.get());
String json = createMessageImpl(m.getSentTo(), cm);
User usr = to.get();
sendMessageImpl(usr.getAuthtoken(), usr.getPushKey(), json,
m.getBody());
} else {
ChatGroup g = groups.findById(m.getSentTo()).get();
cm.setSentToGroup(g);
String json = createMessageImpl(m.getSentTo(), cm);
for(User u : g.getMembers()) {
sendMessageImpl(u.getAuthtoken(), u.getPushKey(), json,
m.getBody());
}
}
return cm.getDAO();
}
e
UserService
Otherwise we’ll go to the else block where the exact same code will execute in a loop over all the members of the group
public MessageDAO sendMessage(MessageDAO m) {
ChatMessage cm = new ChatMessage();
cm.setAuthor(users.findById(m.getAuthorId()).get());
cm.setBody(m.getBody());
cm.setMessageTime(new Date());
Optional<User> to = users.findById(m.getSentTo());
if(to.isPresent()) {
cm.setSentTo(to.get());
String json = createMessageImpl(m.getSentTo(), cm);
User usr = to.get();
sendMessageImpl(usr.getAuthtoken(), usr.getPushKey(), json,
m.getBody());
} else {
ChatGroup g = groups.findById(m.getSentTo()).get();
cm.setSentToGroup(g);
String json = createMessageImpl(m.getSentTo(), cm);
for(User u : g.getMembers()) {
sendMessageImpl(u.getAuthtoken(), u.getPushKey(), json,
m.getBody());
}
}
return cm.getDAO();
}
e
UserService
We mark the destination of the message and convert it to a JSON string
public MessageDAO sendMessage(MessageDAO m) {
ChatMessage cm = new ChatMessage();
cm.setAuthor(users.findById(m.getAuthorId()).get());
cm.setBody(m.getBody());
cm.setMessageTime(new Date());
Optional<User> to = users.findById(m.getSentTo());
if(to.isPresent()) {
cm.setSentTo(to.get());
String json = createMessageImpl(m.getSentTo(), cm);
User usr = to.get();
sendMessageImpl(usr.getAuthtoken(), usr.getPushKey(), json,
m.getBody());
} else {
ChatGroup g = groups.findById(m.getSentTo()).get();
cm.setSentToGroup(g);
String json = createMessageImpl(m.getSentTo(), cm);
for(User u : g.getMembers()) {
sendMessageImpl(u.getAuthtoken(), u.getPushKey(), json,
m.getBody());
}
}
return cm.getDAO();
}
e
UserService
We the invoke the send message API
String json = createMessageImpl(m.getSentTo(), cm);
for(User u : g.getMembers()) {
sendMessageImpl(u.getAuthtoken(), u.getPushKey(), json,
m.getBody());
}
}
return cm.getDAO();
}
private void sendMessageImpl(String authToken, String pushKey,
String json, String message) {
if(!AppSocket.sendMessage(authToken, json)) {
notifications.sendPushNotification(pushKey, message, 1);
}
}
public void sendMessage(String toUser,
Map<String, Object> parsedJSON) {
ChatMessage cm = new ChatMessage();
cm.setAuthor(users.findById((String)parsedJSON.get("authorId")).
get());
cm.setBody((String)parsedJSON.get("body"));
cm.setMessageTime(new Date());
Optional<User> to = users.findById(toUser);
e
UserService
Send message uses the socket to send the message to the device.
String json = createMessageImpl(m.getSentTo(), cm);
for(User u : g.getMembers()) {
sendMessageImpl(u.getAuthtoken(), u.getPushKey(), json,
m.getBody());
}
}
return cm.getDAO();
}
private void sendMessageImpl(String authToken, String pushKey,
String json, String message) {
if(!AppSocket.sendMessage(authToken, json)) {
notifications.sendPushNotification(pushKey, message, 1);
}
}
public void sendMessage(String toUser,
Map<String, Object> parsedJSON) {
ChatMessage cm = new ChatMessage();
cm.setAuthor(users.findById((String)parsedJSON.get("authorId")).
get());
cm.setBody((String)parsedJSON.get("body"));
cm.setMessageTime(new Date());
Optional<User> to = users.findById(toUser);
e
UserService
If this failed and the device isn't reachable we should send this message as text using push notification
notifications.sendPushNotification(pushKey, message, 1);
}
}
public void sendMessage(String toUser,
Map<String, Object> parsedJSON) {
ChatMessage cm = new ChatMessage();
cm.setAuthor(users.findById((String)parsedJSON.get("authorId")).
get());
cm.setBody((String)parsedJSON.get("body"));
cm.setMessageTime(new Date());
Optional<User> to = users.findById(toUser);
if(to.isPresent()) {
cm.setSentTo(to.get());
String json = createMessageImpl(toUser, cm);
User usr = to.get();
sendMessageImpl(usr.getAuthtoken(), usr.getPushKey(),
json, cm.getBody());
} else {
ChatGroup g = groups.findById(toUser).get();
cm.setSentToGroup(g);
String json = createMessageImpl(toUser, cm);
for(User u : g.getMembers()) {
sendMessageImpl(u.getAuthtoken(), u.getPushKey(),
e
UserService
This method is identical to the other sendMessage method but it uses a JSON string which is more convenient when a message comes in through the websocket. The
previous version is the one used when this is invoked from the webservice (which is what we use) and this one works when a message is sent via the websocket
String json = createMessageImpl(toUser, cm);
for(User u : g.getMembers()) {
sendMessageImpl(u.getAuthtoken(), u.getPushKey(),
json, cm.getBody());
}
}
}
private String createMessageImpl(String toUser, ChatMessage cm) {
cm = messages.save(cm);
ObjectMapper objectMapper = new ObjectMapper();
try {
String dao = objectMapper.writeValueAsString(cm.getDAO());
return dao;
} catch(JsonProcessingException err) {
throw new RuntimeException(err);
}
}
public void sendJSONTo(String toUser, String json) {
Optional<User> to = users.findById(toUser);
if(to.isPresent()) {
AppSocket.sendMessage(to.get().getAuthtoken(), json);
e
UserService
This method converts a chat message entity to JSON so we can send it to the client.
String json = createMessageImpl(toUser, cm);
for(User u : g.getMembers()) {
sendMessageImpl(u.getAuthtoken(), u.getPushKey(),
json, cm.getBody());
}
}
}
private String createMessageImpl(String toUser, ChatMessage cm) {
cm = messages.save(cm);
ObjectMapper objectMapper = new ObjectMapper();
try {
String dao = objectMapper.writeValueAsString(cm.getDAO());
return dao;
} catch(JsonProcessingException err) {
throw new RuntimeException(err);
}
}
public void sendJSONTo(String toUser, String json) {
Optional<User> to = users.findById(toUser);
if(to.isPresent()) {
AppSocket.sendMessage(to.get().getAuthtoken(), json);
e
UserService
object mapper can convert a POJO object to the equivalent JSON string
String dao = objectMapper.writeValueAsString(cm.getDAO());
return dao;
} catch(JsonProcessingException err) {
throw new RuntimeException(err);
}
}
public void sendJSONTo(String toUser, String json) {
Optional<User> to = users.findById(toUser);
if(to.isPresent()) {
AppSocket.sendMessage(to.get().getAuthtoken(), json);
} else {
ChatGroup g = groups.findById(toUser).get();
for(User u : g.getMembers()) {
AppSocket.sendMessage(u.getAuthtoken(), json);
}
}
}
public UserDAO findRegisteredUser(String phone) {
return fromList(users.findByPhone(phone));
}
e
UserService
This method sends JSON via the socket to the group or a user. It allows us to propagate a message onward. It works pretty much like the other methods in this class that
send to group or user
for(User u : g.getMembers()) {
AppSocket.sendMessage(u.getAuthtoken(), json);
}
}
}
public UserDAO findRegisteredUser(String phone) {
return fromList(users.findByPhone(phone));
}
private UserDAO fromList(List<User> ul) {
if(ul.isEmpty()) {
return null;
}
User u = ul.get(0);
if(!u.isVerified()) {
return null;
}
return u.getDAO();
}
public UserDAO findRegisteredUserById(String id) {
return users.findById(id).get().getDAO();
}
e
UserService
This method finds the user matching the given phone number
for(User u : g.getMembers()) {
AppSocket.sendMessage(u.getAuthtoken(), json);
}
}
}
public UserDAO findRegisteredUser(String phone) {
return fromList(users.findByPhone(phone));
}
private UserDAO fromList(List<User> ul) {
if(ul.isEmpty()) {
return null;
}
User u = ul.get(0);
if(!u.isVerified()) {
return null;
}
return u.getDAO();
}
public UserDAO findRegisteredUserById(String id) {
return users.findById(id).get().getDAO();
}
e
UserService
This method is used by findRegisteredUser and findRegisteredUserById. It generalizes the translation of a user list to a single UserDAO value. It implicitly fails for
unverified users as well
public UserDAO findRegisteredUserById(String id) {
return users.findById(id).get().getDAO();
}
public void ackMessage(String id) {
ChatMessage m = messages.findById(id).get();
m.setAck(true);
messages.save(m);
}
public void sendUnAckedMessages(String toUser) {
List<ChatMessage> mess = messages.findByUnAcked(toUser);
for(ChatMessage m : mess) {
sendMessage(m.getDAO());
}
}
public void updatePushKey(String auth, String id, String key) {
User u = users.findById(auth).get();
if (encoder.matches(auth, u.getAuthtoken())) {
u.setPushKey(key);
users.save(u);
}
e
UserService
Ack allows us to acknowledge that a message was received, it just toggles the ack flag.
public UserDAO findRegisteredUserById(String id) {
return users.findById(id).get().getDAO();
}
public void ackMessage(String id) {
ChatMessage m = messages.findById(id).get();
m.setAck(true);
messages.save(m);
}
public void sendUnAckedMessages(String toUser) {
List<ChatMessage> mess = messages.findByUnAcked(toUser);
for(ChatMessage m : mess) {
sendMessage(m.getDAO());
}
}
public void updatePushKey(String auth, String id, String key) {
User u = users.findById(auth).get();
if (encoder.matches(auth, u.getAuthtoken())) {
u.setPushKey(key);
users.save(u);
}
e
UserService
When a user connects via websocket this method is invoked, it finds all the messages that weren't acked by the user and sends them to that user. That way if a device
lost connection it will get the content once it’s back online.
public void ackMessage(String id) {
ChatMessage m = messages.findById(id).get();
m.setAck(true);
messages.save(m);
}
public void sendUnAckedMessages(String toUser) {
List<ChatMessage> mess = messages.findByUnAcked(toUser);
for(ChatMessage m : mess) {
sendMessage(m.getDAO());
}
}
public void updatePushKey(String auth, String id, String key) {
User u = users.findById(auth).get();
if (encoder.matches(auth, u.getAuthtoken())) {
u.setPushKey(key);
users.save(u);
}
}
}
e
UserService
This method is invoked on launch to update the push key in the server so we can send push messages to the device. With that UserService is finished

Creating a Whatsapp Clone - Part XIII - Transcript.pdf

  • 1.
    Creating a WhatsAppClone - Part XIII The next step is the services layer which implements the relatively simple business layer of this application
  • 2.
    @Service public class UserService{ @Autowired private UserRepository users; @Autowired private ChatGroupRepository groups; @Autowired private ChatMessageRepository messages; @Autowired private APIKeys keys; @Autowired private PasswordEncoder encoder; @Autowired private MediaRepository medias; e UserService We’ll start with the UserService class, it’s the main class in this package and handles pretty much the entire business logic of the app It’s a service bean, this means it handles generic business logic for the application
  • 3.
    @Service public class UserService{ @Autowired private UserRepository users; @Autowired private ChatGroupRepository groups; @Autowired private ChatMessageRepository messages; @Autowired private APIKeys keys; @Autowired private PasswordEncoder encoder; @Autowired private MediaRepository medias; e UserService We need access to the repositories we just defined for users, groups and messages
  • 4.
    @Service public class UserService{ @Autowired private UserRepository users; @Autowired private ChatGroupRepository groups; @Autowired private ChatMessageRepository messages; @Autowired private APIKeys keys; @Autowired private PasswordEncoder encoder; @Autowired private MediaRepository medias; e UserService The API keys service is the exact one I used in facebook with the exception of a different properties file name. I’ll discuss that later, it generally abstracts API keys and separates them from the source code
  • 5.
    @Service public class UserService{ @Autowired private UserRepository users; @Autowired private ChatGroupRepository groups; @Autowired private ChatMessageRepository messages; @Autowired private APIKeys keys; @Autowired private PasswordEncoder encoder; @Autowired private MediaRepository medias; e UserService The password encoder is used to encrypt and verify the token
  • 6.
    private UserRepository users; @Autowired privateChatGroupRepository groups; @Autowired private ChatMessageRepository messages; @Autowired private APIKeys keys; @Autowired private PasswordEncoder encoder; @Autowired private MediaRepository medias; @Autowired private NotificationService notifications; private void sendActivationSMS(String number, String text) { Twilio.init(keys.get("twilio.sid"), keys.get("twilio.auth")); Message message = Message e UserService MediaRepository and NotificationService are identical to the stuff we had in the facebook clone app
  • 7.
    private PasswordEncoder encoder; @Autowired privateMediaRepository medias; @Autowired private NotificationService notifications; private void sendActivationSMS(String number, String text) { Twilio.init(keys.get("twilio.sid"), keys.get("twilio.auth")); Message message = Message .creator(new PhoneNumber(number), new PhoneNumber(keys.get("twilio.phone")), text) .create(); message.getSid(); } public UserDAO login(String phone, String auth) throws LoginException { List<User> userList; userList = users.findByPhone(phone); if (userList != null && userList.size() == 1) { e UserService This method sends an SMS message via the twilio web service. If you recall we added the twilio SDK into the pom file in the first lesson. This SDK makes sending an SMS message very easy as you can see from this code.
  • 8.
    .creator(new PhoneNumber(number), new PhoneNumber(keys.get("twilio.phone")), text) .create(); message.getSid(); } publicUserDAO login(String phone, String auth) throws LoginException { List<User> userList; userList = users.findByPhone(phone); if (userList != null && userList.size() == 1) { User u = userList.get(0); if (encoder.matches(auth, u.getAuthtoken())) { return u.getLoginDAO(); } throw new LoginException("Authentication error!"); } throw new LoginException("User not found!"); } private String createVerificationCode(int length) { StringBuilder k = new StringBuilder(); Random r = new Random(); e UserService The login API lets us validate a user and get the current data the server has for that user. Since there is no username/password we need to use a token to authenticate
  • 9.
    .creator(new PhoneNumber(number), new PhoneNumber(keys.get("twilio.phone")), text) .create(); message.getSid(); } publicUserDAO login(String phone, String auth) throws LoginException { List<User> userList; userList = users.findByPhone(phone); if (userList != null && userList.size() == 1) { User u = userList.get(0); if (encoder.matches(auth, u.getAuthtoken())) { return u.getLoginDAO(); } throw new LoginException("Authentication error!"); } throw new LoginException("User not found!"); } private String createVerificationCode(int length) { StringBuilder k = new StringBuilder(); Random r = new Random(); e UserService First we need to find the user with the given phone. Assuming the user isn’t there we’ll throw an exception
  • 10.
    .creator(new PhoneNumber(number), new PhoneNumber(keys.get("twilio.phone")), text) .create(); message.getSid(); } publicUserDAO login(String phone, String auth) throws LoginException { List<User> userList; userList = users.findByPhone(phone); if (userList != null && userList.size() == 1) { User u = userList.get(0); if (encoder.matches(auth, u.getAuthtoken())) { return u.getLoginDAO(); } throw new LoginException("Authentication error!"); } throw new LoginException("User not found!"); } private String createVerificationCode(int length) { StringBuilder k = new StringBuilder(); Random r = new Random(); e UserService Since the auth is hashed as we discussed before, we need to test the incoming auth via the matches method in the encoder. It verifies the hash matches the auth token
  • 11.
    if (encoder.matches(auth, u.getAuthtoken())){ return u.getLoginDAO(); } throw new LoginException("Authentication error!"); } throw new LoginException("User not found!"); } private String createVerificationCode(int length) { StringBuilder k = new StringBuilder(); Random r = new Random(); for (int iter = 0; iter < length; iter++) { k.append(r.nextInt(10)); } return k.toString(); } public UserDAO signup(UserDAO user) throws SignupException { List<User> ul = users.findByPhone(user.getPhone()); if (ul != null && ul.size() > 0) { throw new SignupException( "The phone number is already registered!"); e UserService This method creates a string of the given length which includes a random number for verification
  • 12.
    return k.toString(); } public UserDAOsignup(UserDAO user) throws SignupException { List<User> ul = users.findByPhone(user.getPhone()); if (ul != null && ul.size() > 0) { throw new SignupException( "The phone number is already registered!"); } User u = new User(); setProps(user, u); u.setAuthtoken(UUID.randomUUID().toString()); u.setVerificationCode(createVerificationCode(4)); users.save(u); sendActivationSMS(user.getPhone(), "Activation key: " + u. getVerificationCode()); return u.getLoginDAO(); } e UserService Signup creates an entry for a specific user but does’t activate the account until it’s verified
  • 13.
    return k.toString(); } public UserDAOsignup(UserDAO user) throws SignupException { List<User> ul = users.findByPhone(user.getPhone()); if (ul != null && ul.size() > 0) { throw new SignupException( "The phone number is already registered!"); } User u = new User(); setProps(user, u); u.setAuthtoken(UUID.randomUUID().toString()); u.setVerificationCode(createVerificationCode(4)); users.save(u); sendActivationSMS(user.getPhone(), "Activation key: " + u. getVerificationCode()); return u.getLoginDAO(); } e UserService We first check if the phone number is already registered. If so we need to fail
  • 14.
    return k.toString(); } public UserDAOsignup(UserDAO user) throws SignupException { List<User> ul = users.findByPhone(user.getPhone()); if (ul != null && ul.size() > 0) { throw new SignupException( "The phone number is already registered!"); } User u = new User(); setProps(user, u); u.setAuthtoken(UUID.randomUUID().toString()); u.setVerificationCode(createVerificationCode(4)); users.save(u); sendActivationSMS(user.getPhone(), "Activation key: " + u. getVerificationCode()); return u.getLoginDAO(); } e UserService Otherwise we create the new user and initialize the value of the data and the verification code
  • 15.
    return k.toString(); } public UserDAOsignup(UserDAO user) throws SignupException { List<User> ul = users.findByPhone(user.getPhone()); if (ul != null && ul.size() > 0) { throw new SignupException( "The phone number is already registered!"); } User u = new User(); setProps(user, u); u.setAuthtoken(UUID.randomUUID().toString()); u.setVerificationCode(createVerificationCode(4)); users.save(u); sendActivationSMS(user.getPhone(), "Activation key: " + u. getVerificationCode()); return u.getLoginDAO(); } e UserService Finally we send the activation code and return the user entity
  • 16.
    sendActivationSMS(user.getPhone(), "Activation key:" + u. getVerificationCode()); return u.getLoginDAO(); } public boolean verifyPhone(String userId, String code) { User u = users.findById(userId).get(); if (u.getVerificationCode().equals(code)) { u.setVerificationCode(null); u.setVerified(true); users.save(u); return true; } return false; } private void setProps(UserDAO user, User u) { u.setName(user.getName()); u.setTagline(user.getTagline()); } public void update(String auth, UserDAO user) { e UserService The verify method activates a user account, if the verification code is correct we mark the user as verified and return true.
  • 17.
    sendActivationSMS(user.getPhone(), "Activation key:" + u. getVerificationCode()); return u.getLoginDAO(); } public boolean verifyPhone(String userId, String code) { User u = users.findById(userId).get(); if (u.getVerificationCode().equals(code)) { u.setVerificationCode(null); u.setVerified(true); users.save(u); return true; } return false; } private void setProps(UserDAO user, User u) { u.setName(user.getName()); u.setTagline(user.getTagline()); } public void update(String auth, UserDAO user) { e UserService We use setProps both from the signup and update methods. There isn’t much here but if we add additional meta-data this might become a bigger method like it is in the facebook clone
  • 18.
    } private void setProps(UserDAOuser, User u) { u.setName(user.getName()); u.setTagline(user.getTagline()); } public void update(String auth, UserDAO user) { User u = users.findById(user.getId()).get(); if (encoder.matches(auth, u.getAuthtoken())) { setProps(user, u); users.save(u); } } public byte[] getAvatar(String userId) { User u = users.findById(userId).get(); if (u.getAvatar() != null) { return u.getAvatar().getData(); } return null; } public void setAvatar(String auth, String userId, String mediaId) { e UserService Update verifies the users token then updates the properties. There isn’t much here
  • 19.
    setProps(user, u); users.save(u); } } public byte[]getAvatar(String userId) { User u = users.findById(userId).get(); if (u.getAvatar() != null) { return u.getAvatar().getData(); } return null; } public void setAvatar(String auth, String userId, String mediaId) { Media m = medias.findById(mediaId).get(); User u = users.findById(userId).get(); if (encoder.matches(auth, u.getAuthtoken())) { u.setAvatar(m); users.save(u); } } public void userTyping(String userId, String toUser, boolean t) { Optional<User> to = users.findById(toUser); e UserService These aren’t used at the moment but they are pretty much identical to what we have in the facebook clone and should be easy to integrate in a similar way
  • 20.
    User u =users.findById(userId).get(); if (encoder.matches(auth, u.getAuthtoken())) { u.setAvatar(m); users.save(u); } } public void userTyping(String userId, String toUser, boolean t) { Optional<User> to = users.findById(toUser); if(to.isPresent()) { AppSocket.sendUserTyping(to.get().getAuthtoken(), userId, t); } else { ChatGroup g = groups.findById(toUser).get(); for(User u : g.getMembers()) { AppSocket.sendUserTyping(u.getAuthtoken(), userId, t); } } } public MessageDAO sendMessage(MessageDAO m) { ChatMessage cm = new ChatMessage(); cm.setAuthor(users.findById(m.getAuthorId()).get()); cm.setBody(m.getBody()); e UserService This is part of the work to integrate support for the “user typing" feature. Right now the client app doesn’t send or render this event but it should be relatively simple to add. When a user starts typing to a conversation we can invoke this method
  • 21.
    User u =users.findById(userId).get(); if (encoder.matches(auth, u.getAuthtoken())) { u.setAvatar(m); users.save(u); } } public void userTyping(String userId, String toUser, boolean t) { Optional<User> to = users.findById(toUser); if(to.isPresent()) { AppSocket.sendUserTyping(to.get().getAuthtoken(), userId, t); } else { ChatGroup g = groups.findById(toUser).get(); for(User u : g.getMembers()) { AppSocket.sendUserTyping(u.getAuthtoken(), userId, t); } } } public MessageDAO sendMessage(MessageDAO m) { ChatMessage cm = new ChatMessage(); cm.setAuthor(users.findById(m.getAuthorId()).get()); cm.setBody(m.getBody()); e UserService toUser can be a user or a group is the user is present it’s a user
  • 22.
    User u =users.findById(userId).get(); if (encoder.matches(auth, u.getAuthtoken())) { u.setAvatar(m); users.save(u); } } public void userTyping(String userId, String toUser, boolean t) { Optional<User> to = users.findById(toUser); if(to.isPresent()) { AppSocket.sendUserTyping(to.get().getAuthtoken(), userId, t); } else { ChatGroup g = groups.findById(toUser).get(); for(User u : g.getMembers()) { AppSocket.sendUserTyping(u.getAuthtoken(), userId, t); } } } public MessageDAO sendMessage(MessageDAO m) { ChatMessage cm = new ChatMessage(); cm.setAuthor(users.findById(m.getAuthorId()).get()); cm.setBody(m.getBody()); e UserService I’ll discuss the event code in the sockets when we reach the app socket class
  • 23.
    User u =users.findById(userId).get(); if (encoder.matches(auth, u.getAuthtoken())) { u.setAvatar(m); users.save(u); } } public void userTyping(String userId, String toUser, boolean t) { Optional<User> to = users.findById(toUser); if(to.isPresent()) { AppSocket.sendUserTyping(to.get().getAuthtoken(), userId, t); } else { ChatGroup g = groups.findById(toUser).get(); for(User u : g.getMembers()) { AppSocket.sendUserTyping(u.getAuthtoken(), userId, t); } } } public MessageDAO sendMessage(MessageDAO m) { ChatMessage cm = new ChatMessage(); cm.setAuthor(users.findById(m.getAuthorId()).get()); cm.setBody(m.getBody()); e UserService If this is a group we need to send the event to all the users within the group via the socket connection
  • 24.
    } } } public MessageDAO sendMessage(MessageDAOm) { ChatMessage cm = new ChatMessage(); cm.setAuthor(users.findById(m.getAuthorId()).get()); cm.setBody(m.getBody()); cm.setMessageTime(new Date()); Optional<User> to = users.findById(m.getSentTo()); if(to.isPresent()) { cm.setSentTo(to.get()); String json = createMessageImpl(m.getSentTo(), cm); User usr = to.get(); sendMessageImpl(usr.getAuthtoken(), usr.getPushKey(), json, m.getBody()); } else { ChatGroup g = groups.findById(m.getSentTo()).get(); cm.setSentToGroup(g); String json = createMessageImpl(m.getSentTo(), cm); for(User u : g.getMembers()) { sendMessageImpl(u.getAuthtoken(), u.getPushKey(), json, m.getBody()); e UserService This method sends a message to its destination which can be a user or a group.
  • 25.
    } } } public MessageDAO sendMessage(MessageDAOm) { ChatMessage cm = new ChatMessage(); cm.setAuthor(users.findById(m.getAuthorId()).get()); cm.setBody(m.getBody()); cm.setMessageTime(new Date()); Optional<User> to = users.findById(m.getSentTo()); if(to.isPresent()) { cm.setSentTo(to.get()); String json = createMessageImpl(m.getSentTo(), cm); User usr = to.get(); sendMessageImpl(usr.getAuthtoken(), usr.getPushKey(), json, m.getBody()); } else { ChatGroup g = groups.findById(m.getSentTo()).get(); cm.setSentToGroup(g); String json = createMessageImpl(m.getSentTo(), cm); for(User u : g.getMembers()) { sendMessageImpl(u.getAuthtoken(), u.getPushKey(), json, m.getBody()); e UserService In order to send a message we first need to create a ChatMessage entity so we can persist the message in case delivery failed.
  • 26.
    } } } public MessageDAO sendMessage(MessageDAOm) { ChatMessage cm = new ChatMessage(); cm.setAuthor(users.findById(m.getAuthorId()).get()); cm.setBody(m.getBody()); cm.setMessageTime(new Date()); Optional<User> to = users.findById(m.getSentTo()); if(to.isPresent()) { cm.setSentTo(to.get()); String json = createMessageImpl(m.getSentTo(), cm); User usr = to.get(); sendMessageImpl(usr.getAuthtoken(), usr.getPushKey(), json, m.getBody()); } else { ChatGroup g = groups.findById(m.getSentTo()).get(); cm.setSentToGroup(g); String json = createMessageImpl(m.getSentTo(), cm); for(User u : g.getMembers()) { sendMessageImpl(u.getAuthtoken(), u.getPushKey(), json, m.getBody()); e UserService This is the same code we saw in the typing event. If the message is destined to a user the following block will occur
  • 27.
    public MessageDAO sendMessage(MessageDAOm) { ChatMessage cm = new ChatMessage(); cm.setAuthor(users.findById(m.getAuthorId()).get()); cm.setBody(m.getBody()); cm.setMessageTime(new Date()); Optional<User> to = users.findById(m.getSentTo()); if(to.isPresent()) { cm.setSentTo(to.get()); String json = createMessageImpl(m.getSentTo(), cm); User usr = to.get(); sendMessageImpl(usr.getAuthtoken(), usr.getPushKey(), json, m.getBody()); } else { ChatGroup g = groups.findById(m.getSentTo()).get(); cm.setSentToGroup(g); String json = createMessageImpl(m.getSentTo(), cm); for(User u : g.getMembers()) { sendMessageImpl(u.getAuthtoken(), u.getPushKey(), json, m.getBody()); } } return cm.getDAO(); } e UserService Otherwise we’ll go to the else block where the exact same code will execute in a loop over all the members of the group
  • 28.
    public MessageDAO sendMessage(MessageDAOm) { ChatMessage cm = new ChatMessage(); cm.setAuthor(users.findById(m.getAuthorId()).get()); cm.setBody(m.getBody()); cm.setMessageTime(new Date()); Optional<User> to = users.findById(m.getSentTo()); if(to.isPresent()) { cm.setSentTo(to.get()); String json = createMessageImpl(m.getSentTo(), cm); User usr = to.get(); sendMessageImpl(usr.getAuthtoken(), usr.getPushKey(), json, m.getBody()); } else { ChatGroup g = groups.findById(m.getSentTo()).get(); cm.setSentToGroup(g); String json = createMessageImpl(m.getSentTo(), cm); for(User u : g.getMembers()) { sendMessageImpl(u.getAuthtoken(), u.getPushKey(), json, m.getBody()); } } return cm.getDAO(); } e UserService We mark the destination of the message and convert it to a JSON string
  • 29.
    public MessageDAO sendMessage(MessageDAOm) { ChatMessage cm = new ChatMessage(); cm.setAuthor(users.findById(m.getAuthorId()).get()); cm.setBody(m.getBody()); cm.setMessageTime(new Date()); Optional<User> to = users.findById(m.getSentTo()); if(to.isPresent()) { cm.setSentTo(to.get()); String json = createMessageImpl(m.getSentTo(), cm); User usr = to.get(); sendMessageImpl(usr.getAuthtoken(), usr.getPushKey(), json, m.getBody()); } else { ChatGroup g = groups.findById(m.getSentTo()).get(); cm.setSentToGroup(g); String json = createMessageImpl(m.getSentTo(), cm); for(User u : g.getMembers()) { sendMessageImpl(u.getAuthtoken(), u.getPushKey(), json, m.getBody()); } } return cm.getDAO(); } e UserService We the invoke the send message API
  • 30.
    String json =createMessageImpl(m.getSentTo(), cm); for(User u : g.getMembers()) { sendMessageImpl(u.getAuthtoken(), u.getPushKey(), json, m.getBody()); } } return cm.getDAO(); } private void sendMessageImpl(String authToken, String pushKey, String json, String message) { if(!AppSocket.sendMessage(authToken, json)) { notifications.sendPushNotification(pushKey, message, 1); } } public void sendMessage(String toUser, Map<String, Object> parsedJSON) { ChatMessage cm = new ChatMessage(); cm.setAuthor(users.findById((String)parsedJSON.get("authorId")). get()); cm.setBody((String)parsedJSON.get("body")); cm.setMessageTime(new Date()); Optional<User> to = users.findById(toUser); e UserService Send message uses the socket to send the message to the device.
  • 31.
    String json =createMessageImpl(m.getSentTo(), cm); for(User u : g.getMembers()) { sendMessageImpl(u.getAuthtoken(), u.getPushKey(), json, m.getBody()); } } return cm.getDAO(); } private void sendMessageImpl(String authToken, String pushKey, String json, String message) { if(!AppSocket.sendMessage(authToken, json)) { notifications.sendPushNotification(pushKey, message, 1); } } public void sendMessage(String toUser, Map<String, Object> parsedJSON) { ChatMessage cm = new ChatMessage(); cm.setAuthor(users.findById((String)parsedJSON.get("authorId")). get()); cm.setBody((String)parsedJSON.get("body")); cm.setMessageTime(new Date()); Optional<User> to = users.findById(toUser); e UserService If this failed and the device isn't reachable we should send this message as text using push notification
  • 32.
    notifications.sendPushNotification(pushKey, message, 1); } } publicvoid sendMessage(String toUser, Map<String, Object> parsedJSON) { ChatMessage cm = new ChatMessage(); cm.setAuthor(users.findById((String)parsedJSON.get("authorId")). get()); cm.setBody((String)parsedJSON.get("body")); cm.setMessageTime(new Date()); Optional<User> to = users.findById(toUser); if(to.isPresent()) { cm.setSentTo(to.get()); String json = createMessageImpl(toUser, cm); User usr = to.get(); sendMessageImpl(usr.getAuthtoken(), usr.getPushKey(), json, cm.getBody()); } else { ChatGroup g = groups.findById(toUser).get(); cm.setSentToGroup(g); String json = createMessageImpl(toUser, cm); for(User u : g.getMembers()) { sendMessageImpl(u.getAuthtoken(), u.getPushKey(), e UserService This method is identical to the other sendMessage method but it uses a JSON string which is more convenient when a message comes in through the websocket. The previous version is the one used when this is invoked from the webservice (which is what we use) and this one works when a message is sent via the websocket
  • 33.
    String json =createMessageImpl(toUser, cm); for(User u : g.getMembers()) { sendMessageImpl(u.getAuthtoken(), u.getPushKey(), json, cm.getBody()); } } } private String createMessageImpl(String toUser, ChatMessage cm) { cm = messages.save(cm); ObjectMapper objectMapper = new ObjectMapper(); try { String dao = objectMapper.writeValueAsString(cm.getDAO()); return dao; } catch(JsonProcessingException err) { throw new RuntimeException(err); } } public void sendJSONTo(String toUser, String json) { Optional<User> to = users.findById(toUser); if(to.isPresent()) { AppSocket.sendMessage(to.get().getAuthtoken(), json); e UserService This method converts a chat message entity to JSON so we can send it to the client.
  • 34.
    String json =createMessageImpl(toUser, cm); for(User u : g.getMembers()) { sendMessageImpl(u.getAuthtoken(), u.getPushKey(), json, cm.getBody()); } } } private String createMessageImpl(String toUser, ChatMessage cm) { cm = messages.save(cm); ObjectMapper objectMapper = new ObjectMapper(); try { String dao = objectMapper.writeValueAsString(cm.getDAO()); return dao; } catch(JsonProcessingException err) { throw new RuntimeException(err); } } public void sendJSONTo(String toUser, String json) { Optional<User> to = users.findById(toUser); if(to.isPresent()) { AppSocket.sendMessage(to.get().getAuthtoken(), json); e UserService object mapper can convert a POJO object to the equivalent JSON string
  • 35.
    String dao =objectMapper.writeValueAsString(cm.getDAO()); return dao; } catch(JsonProcessingException err) { throw new RuntimeException(err); } } public void sendJSONTo(String toUser, String json) { Optional<User> to = users.findById(toUser); if(to.isPresent()) { AppSocket.sendMessage(to.get().getAuthtoken(), json); } else { ChatGroup g = groups.findById(toUser).get(); for(User u : g.getMembers()) { AppSocket.sendMessage(u.getAuthtoken(), json); } } } public UserDAO findRegisteredUser(String phone) { return fromList(users.findByPhone(phone)); } e UserService This method sends JSON via the socket to the group or a user. It allows us to propagate a message onward. It works pretty much like the other methods in this class that send to group or user
  • 36.
    for(User u :g.getMembers()) { AppSocket.sendMessage(u.getAuthtoken(), json); } } } public UserDAO findRegisteredUser(String phone) { return fromList(users.findByPhone(phone)); } private UserDAO fromList(List<User> ul) { if(ul.isEmpty()) { return null; } User u = ul.get(0); if(!u.isVerified()) { return null; } return u.getDAO(); } public UserDAO findRegisteredUserById(String id) { return users.findById(id).get().getDAO(); } e UserService This method finds the user matching the given phone number
  • 37.
    for(User u :g.getMembers()) { AppSocket.sendMessage(u.getAuthtoken(), json); } } } public UserDAO findRegisteredUser(String phone) { return fromList(users.findByPhone(phone)); } private UserDAO fromList(List<User> ul) { if(ul.isEmpty()) { return null; } User u = ul.get(0); if(!u.isVerified()) { return null; } return u.getDAO(); } public UserDAO findRegisteredUserById(String id) { return users.findById(id).get().getDAO(); } e UserService This method is used by findRegisteredUser and findRegisteredUserById. It generalizes the translation of a user list to a single UserDAO value. It implicitly fails for unverified users as well
  • 38.
    public UserDAO findRegisteredUserById(Stringid) { return users.findById(id).get().getDAO(); } public void ackMessage(String id) { ChatMessage m = messages.findById(id).get(); m.setAck(true); messages.save(m); } public void sendUnAckedMessages(String toUser) { List<ChatMessage> mess = messages.findByUnAcked(toUser); for(ChatMessage m : mess) { sendMessage(m.getDAO()); } } public void updatePushKey(String auth, String id, String key) { User u = users.findById(auth).get(); if (encoder.matches(auth, u.getAuthtoken())) { u.setPushKey(key); users.save(u); } e UserService Ack allows us to acknowledge that a message was received, it just toggles the ack flag.
  • 39.
    public UserDAO findRegisteredUserById(Stringid) { return users.findById(id).get().getDAO(); } public void ackMessage(String id) { ChatMessage m = messages.findById(id).get(); m.setAck(true); messages.save(m); } public void sendUnAckedMessages(String toUser) { List<ChatMessage> mess = messages.findByUnAcked(toUser); for(ChatMessage m : mess) { sendMessage(m.getDAO()); } } public void updatePushKey(String auth, String id, String key) { User u = users.findById(auth).get(); if (encoder.matches(auth, u.getAuthtoken())) { u.setPushKey(key); users.save(u); } e UserService When a user connects via websocket this method is invoked, it finds all the messages that weren't acked by the user and sends them to that user. That way if a device lost connection it will get the content once it’s back online.
  • 40.
    public void ackMessage(Stringid) { ChatMessage m = messages.findById(id).get(); m.setAck(true); messages.save(m); } public void sendUnAckedMessages(String toUser) { List<ChatMessage> mess = messages.findByUnAcked(toUser); for(ChatMessage m : mess) { sendMessage(m.getDAO()); } } public void updatePushKey(String auth, String id, String key) { User u = users.findById(auth).get(); if (encoder.matches(auth, u.getAuthtoken())) { u.setPushKey(key); users.save(u); } } } e UserService This method is invoked on launch to update the push key in the server so we can send push messages to the device. With that UserService is finished