SlideShare a Scribd company logo
1 of 26
Download to read offline
Creating a Facebook Clone - Part XLV
The changes to the NotificationService are significant and in effect that's the class that implements push notification calls from the server.
@Service
public class NotificationService {
private static final String PUSHURL =
"https://push.codenameone.com/push/push";
private final static Logger logger = LoggerFactory.
getLogger(NotificationService.class);
@Autowired
private NotificationRepository notifications;
@Autowired
private APIKeys keys;
@Autowired
private UserRepository users;
public void updatePushKey(String auth, String key) {
User u = users.findByAuthtoken(auth).get(0);
u.setPushKey(key);
users.save(u);
}
NotificationService
Before we start we need to add a few fields to the class, these will be used by the code we introduce soon. These are besides the keys field I mentioned before.

This is the URL of the Codename One push server, we'll use that to send out push messages
@Service
public class NotificationService {
private static final String PUSHURL =
"https://push.codenameone.com/push/push";
private final static Logger logger = LoggerFactory.
getLogger(NotificationService.class);
@Autowired
private NotificationRepository notifications;
@Autowired
private APIKeys keys;
@Autowired
private UserRepository users;
public void updatePushKey(String auth, String key) {
User u = users.findByAuthtoken(auth).get(0);
u.setPushKey(key);
users.save(u);
}
NotificationService
Logging is used for errors in the body of the class
@Service
public class NotificationService {
private static final String PUSHURL =
"https://push.codenameone.com/push/push";
private final static Logger logger = LoggerFactory.
getLogger(NotificationService.class);
@Autowired
private NotificationRepository notifications;
@Autowired
private APIKeys keys;
@Autowired
private UserRepository users;
public void updatePushKey(String auth, String key) {
User u = users.findByAuthtoken(auth).get(0);
u.setPushKey(key);
users.save(u);
}
NotificationService
We need to inject the users repository now as we need to find the user there
@Autowired
private NotificationRepository notifications;
@Autowired
private APIKeys keys;
@Autowired
private UserRepository users;
public void updatePushKey(String auth, String key) {
User u = users.findByAuthtoken(auth).get(0);
u.setPushKey(key);
users.save(u);
}
public void removePushKey(String key) {
List<User> userList = users.findByPushKey(key);
if(!userList.isEmpty()) {
User u = userList.get(0);
u.setPushKey(null);
users.save(u);
}
}
public void sendNotification(User u, NotificationDAO nd) {
NotificationService
The first step is trivial, it's about mapping the push key to the service class which is exactly what we do here
@Autowired
private NotificationRepository notifications;
@Autowired
private APIKeys keys;
@Autowired
private UserRepository users;
public void updatePushKey(String auth, String key) {
User u = users.findByAuthtoken(auth).get(0);
u.setPushKey(key);
users.save(u);
}
public void removePushKey(String key) {
List<User> userList = users.findByPushKey(key);
if(!userList.isEmpty()) {
User u = userList.get(0);
u.setPushKey(null);
users.save(u);
}
}
public void sendNotification(User u, NotificationDAO nd) {
NotificationService
On push registration we invoke this method to update the push key in the user object
@Autowired
private NotificationRepository notifications;
@Autowired
private APIKeys keys;
@Autowired
private UserRepository users;
public void updatePushKey(String auth, String key) {
User u = users.findByAuthtoken(auth).get(0);
u.setPushKey(key);
users.save(u);
}
public void removePushKey(String key) {
List<User> userList = users.findByPushKey(key);
if(!userList.isEmpty()) {
User u = userList.get(0);
u.setPushKey(null);
users.save(u);
}
}
public void sendNotification(User u, NotificationDAO nd) {
NotificationService
If the push server indicates that a push key is invalid or expired we delete that key. A fresh key will be created the next time the user runs the app.

The second method is internal to the server and isn't exposed as a WebService but the former method is.
users.save(u);
}
}
public void sendNotification(User u, NotificationDAO nd) {
Notification n = new Notification();
n.setDate(System.currentTimeMillis());
n.setReaction(nd.getReaction());
n.setReactionColor(nd.getReactionColor());
n.setText(nd.getText());
n.setUser(u);
n.setWasRead(nd.isWasRead());
notifications.save(n);
if(u.getPushKey() != null) {
sendPushNotification(u.getPushKey(), nd.getText(), 1);
}
}
public List<NotificationDAO> listNotifications(String authtoken,
int page, int amount) {
Page<Notification> notificationsPage = notifications.
findNotificationsForUser(
authtoken, PageRequest.of(page, amount,
Sort.by(Sort.Direction.DESC, "date")));
List<NotificationDAO> resp = new ArrayList<>();
NotificationService
The next stage is the push notification itself, we add these lines to the sendNotification method. Notice that a push key can be null since it can expire or might not be
registered yet… This leads us to the sendPushNotification method but we’ll get there via a detour
resp.add(c.getDAO());
}
return resp;
}
private String sendPushImpl(String deviceId,
String messageBody, int type) throws IOException {
HttpURLConnection connection = (HttpURLConnection)
new URL(PUSHURL).openConnection();
connection.setDoOutput(true);
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type",
"application/x-www-form-urlencoded;charset=UTF-8");
String cert;
String pass;
boolean production = keys.get("push.itunes_production",
"false").equals("true");
if(production) {
cert = keys.get("push.itunes_prodcert");
pass = keys.get("push.itunes_prodpass");
} else {
cert = keys.get("push.itunes_devcert");
pass = keys.get("push.itunes_devpass");
}
String query = "token=" + keys.get("push.token") +
"&device=" + URLEncoder.encode(deviceId, "UTF-8") +
NotificationService
This method is based on the server push code from the developer guide. Notice that this is the impl method not the method we invoked before. We'll go through this first
and then reach that code
resp.add(c.getDAO());
}
return resp;
}
private String sendPushImpl(String deviceId,
String messageBody, int type) throws IOException {
HttpURLConnection connection = (HttpURLConnection)
new URL(PUSHURL).openConnection();
connection.setDoOutput(true);
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type",
"application/x-www-form-urlencoded;charset=UTF-8");
String cert;
String pass;
boolean production = keys.get("push.itunes_production",
"false").equals("true");
if(production) {
cert = keys.get("push.itunes_prodcert");
pass = keys.get("push.itunes_prodpass");
} else {
cert = keys.get("push.itunes_devcert");
pass = keys.get("push.itunes_devpass");
}
String query = "token=" + keys.get("push.token") +
"&device=" + URLEncoder.encode(deviceId, "UTF-8") +
NotificationService
We connect to the push server URL to send the push message
resp.add(c.getDAO());
}
return resp;
}
private String sendPushImpl(String deviceId,
String messageBody, int type) throws IOException {
HttpURLConnection connection = (HttpURLConnection)
new URL(PUSHURL).openConnection();
connection.setDoOutput(true);
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type",
"application/x-www-form-urlencoded;charset=UTF-8");
String cert;
String pass;
boolean production = keys.get("push.itunes_production",
"false").equals("true");
if(production) {
cert = keys.get("push.itunes_prodcert");
pass = keys.get("push.itunes_prodpass");
} else {
cert = keys.get("push.itunes_devcert");
pass = keys.get("push.itunes_devpass");
}
String query = "token=" + keys.get("push.token") +
"&device=" + URLEncoder.encode(deviceId, "UTF-8") +
NotificationService
A standard form POST submission request is used
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type",
"application/x-www-form-urlencoded;charset=UTF-8");
String cert;
String pass;
boolean production = keys.get("push.itunes_production",
"false").equals("true");
if(production) {
cert = keys.get("push.itunes_prodcert");
pass = keys.get("push.itunes_prodpass");
} else {
cert = keys.get("push.itunes_devcert");
pass = keys.get("push.itunes_devpass");
}
String query = "token=" + keys.get("push.token") +
"&device=" + URLEncoder.encode(deviceId, "UTF-8") +
"&type=" + type + "&auth=" +
URLEncoder.encode(keys.get("push.gcm_key"), "UTF-8") +
"&certPassword=" + URLEncoder.encode(pass, "UTF-8") +
"&cert=" + URLEncoder.encode(cert, "UTF-8") +
"&body=" + URLEncoder.encode(messageBody, "UTF-8") +
"&production=" + production;
try (OutputStream output = connection.getOutputStream()) {
output.write(query.getBytes("UTF-8"));
}
NotificationService
We fetch the values for the fields that can differ specifically the certificates & passwords which vary between production and development
if(production) {
cert = keys.get("push.itunes_prodcert");
pass = keys.get("push.itunes_prodpass");
} else {
cert = keys.get("push.itunes_devcert");
pass = keys.get("push.itunes_devpass");
}
String query = "token=" + keys.get("push.token") +
"&device=" + URLEncoder.encode(deviceId, "UTF-8") +
"&type=" + type + "&auth=" +
URLEncoder.encode(keys.get("push.gcm_key"), "UTF-8") +
"&certPassword=" + URLEncoder.encode(pass, "UTF-8") +
"&cert=" + URLEncoder.encode(cert, "UTF-8") +
"&body=" + URLEncoder.encode(messageBody, "UTF-8") +
"&production=" + production;
try (OutputStream output = connection.getOutputStream()) {
output.write(query.getBytes("UTF-8"));
}
int c = connection.getResponseCode();
if(c == 200) {
try (InputStream i = connection.getInputStream()) {
return new String(readInputStream(i));
}
}
logger.error("Error response code from push server: " + c);
NotificationService
We send all the details as a POST request to the Codename One push server which then issues a push to the given device type
}
String query = "token=" + keys.get("push.token") +
"&device=" + URLEncoder.encode(deviceId, "UTF-8") +
"&type=" + type + "&auth=" +
URLEncoder.encode(keys.get("push.gcm_key"), "UTF-8") +
"&certPassword=" + URLEncoder.encode(pass, "UTF-8") +
"&cert=" + URLEncoder.encode(cert, "UTF-8") +
"&body=" + URLEncoder.encode(messageBody, "UTF-8") +
"&production=" + production;
try (OutputStream output = connection.getOutputStream()) {
output.write(query.getBytes("UTF-8"));
}
int c = connection.getResponseCode();
if(c == 200) {
try (InputStream i = connection.getInputStream()) {
return new String(readInputStream(i));
}
}
logger.error("Error response code from push server: " + c);
return null;
}
@Async
public void sendPushNotification(String deviceId,
String messageBody, int type) {
NotificationService
The server can return 200 in case of an error but if the response isn't 200 then it's surely an error
}
String query = "token=" + keys.get("push.token") +
"&device=" + URLEncoder.encode(deviceId, "UTF-8") +
"&type=" + type + "&auth=" +
URLEncoder.encode(keys.get("push.gcm_key"), "UTF-8") +
"&certPassword=" + URLEncoder.encode(pass, "UTF-8") +
"&cert=" + URLEncoder.encode(cert, "UTF-8") +
"&body=" + URLEncoder.encode(messageBody, "UTF-8") +
"&production=" + production;
try (OutputStream output = connection.getOutputStream()) {
output.write(query.getBytes("UTF-8"));
}
int c = connection.getResponseCode();
if(c == 200) {
try (InputStream i = connection.getInputStream()) {
return new String(readInputStream(i));
}
}
logger.error("Error response code from push server: " + c);
return null;
}
@Async
public void sendPushNotification(String deviceId,
String messageBody, int type) {
NotificationService
The server response is transformed to a String for parsing on next method, I'll cover the readInputStream method soon
logger.error("Error response code from push server: " + c);
return null;
}
@Async
public void sendPushNotification(String deviceId,
String messageBody, int type) {
try {
String json = sendPushImpl(deviceId, messageBody, type);
if(json != null) {
JsonParser parser = JsonParserFactory.getJsonParser();
if(json.startsWith("[")) {
List<Object> lst = parser.parseList(json);
for(Object o : lst) {
if(o instanceof Map) {
Map entry = (Map)o;
String status = (String)entry.get("status");
if(status != null) {
if(status.equals("error") ||
status.equals("inactive")) {
removePushKey((String)
entry.get("id"));
}
}
}
NotificationService
We need to go over the responses from the push server. These responses would include information such as push key expiration and we would need to purge that key
from our database.

That's all done in the actual method for sending push messages
logger.error("Error response code from push server: " + c);
return null;
}
@Async
public void sendPushNotification(String deviceId,
String messageBody, int type) {
try {
String json = sendPushImpl(deviceId, messageBody, type);
if(json != null) {
JsonParser parser = JsonParserFactory.getJsonParser();
if(json.startsWith("[")) {
List<Object> lst = parser.parseList(json);
for(Object o : lst) {
if(o instanceof Map) {
Map entry = (Map)o;
String status = (String)entry.get("status");
if(status != null) {
if(status.equals("error") ||
status.equals("inactive")) {
removePushKey((String)
entry.get("id"));
}
}
}
NotificationService
The @Async annotation indicates this method should execute on a separate thread, this is handled by Spring. We'll discuss that soon
logger.error("Error response code from push server: " + c);
return null;
}
@Async
public void sendPushNotification(String deviceId,
String messageBody, int type) {
try {
String json = sendPushImpl(deviceId, messageBody, type);
if(json != null) {
JsonParser parser = JsonParserFactory.getJsonParser();
if(json.startsWith("[")) {
List<Object> lst = parser.parseList(json);
for(Object o : lst) {
if(o instanceof Map) {
Map entry = (Map)o;
String status = (String)entry.get("status");
if(status != null) {
if(status.equals("error") ||
status.equals("inactive")) {
removePushKey((String)
entry.get("id"));
}
}
}
NotificationService
sendPushImpl return JSON with messages which we need to process
logger.error("Error response code from push server: " + c);
return null;
}
@Async
public void sendPushNotification(String deviceId,
String messageBody, int type) {
try {
String json = sendPushImpl(deviceId, messageBody, type);
if(json != null) {
JsonParser parser = JsonParserFactory.getJsonParser();
if(json.startsWith("[")) {
List<Object> lst = parser.parseList(json);
for(Object o : lst) {
if(o instanceof Map) {
Map entry = (Map)o;
String status = (String)entry.get("status");
if(status != null) {
if(status.equals("error") ||
status.equals("inactive")) {
removePushKey((String)
entry.get("id"));
}
}
}
NotificationService
The Spring JSON parsing API has different forms for Map/List but we can get both in the response from the server so we need to check
@Async
public void sendPushNotification(String deviceId,
String messageBody, int type) {
try {
String json = sendPushImpl(deviceId, messageBody, type);
if(json != null) {
JsonParser parser = JsonParserFactory.getJsonParser();
if(json.startsWith("[")) {
List<Object> lst = parser.parseList(json);
for(Object o : lst) {
if(o instanceof Map) {
Map entry = (Map)o;
String status = (String)entry.get("status");
if(status != null) {
if(status.equals("error") ||
status.equals("inactive")) {
removePushKey((String)
entry.get("id"));
}
}
}
}
} else {
Map<String, Object> m = parser.parseMap(json);
NotificationService
If it's a list then it's a device list with either acknowledgement of sending (Android only) or error messages
@Async
public void sendPushNotification(String deviceId,
String messageBody, int type) {
try {
String json = sendPushImpl(deviceId, messageBody, type);
if(json != null) {
JsonParser parser = JsonParserFactory.getJsonParser();
if(json.startsWith("[")) {
List<Object> lst = parser.parseList(json);
for(Object o : lst) {
if(o instanceof Map) {
Map entry = (Map)o;
String status = (String)entry.get("status");
if(status != null) {
if(status.equals("error") ||
status.equals("inactive")) {
removePushKey((String)
entry.get("id"));
}
}
}
}
} else {
Map<String, Object> m = parser.parseMap(json);
NotificationService
If we have an error message for a specific device key we need to remove it to prevent future problems with the push servers
if(o instanceof Map) {
Map entry = (Map)o;
String status = (String)entry.get("status");
if(status != null) {
if(status.equals("error") ||
status.equals("inactive")) {
removePushKey((String)
entry.get("id"));
}
}
}
}
} else {
Map<String, Object> m = parser.parseMap(json);
if(m.containsKey("error")) {
logger.error("Error message from server: " +
m.get("error"));
}
}
}
} catch(IOException err) {
logger.error("Error during connection to push server", err);
}
}
private static byte[] readInputStream(InputStream i)
NotificationService
If we got a Map in response it could indicate an error which we currently don't really handle other than through logging.

If push doesn't work the app would still work fine you'll just need to refresh manually. A better implementation would also use a fallback for cases of push failure e.g.
WebSocket. But it's not essential at least not at first.
}
}
} catch(IOException err) {
logger.error("Error during connection to push server", err);
}
}
private static byte[] readInputStream(InputStream i)
throws IOException {
try(ByteArrayOutputStream b = new ByteArrayOutputStream()) {
copy(i, b);
return b.toByteArray();
}
}
private static void copy(InputStream i, OutputStream o)
throws IOException {
byte[] buffer = new byte[8192];
int size = i.read(buffer);
while(size > -1) {
o.write(buffer, 0, size);
size = i.read(buffer);
}
}
}
NotificationService
We referenced readInputStream in the previous code blocks it's defined in the code as such. I could have written more modern code using nio but I was running out of
time and I had this code handy. It does the job well…
@RequestMapping(method=RequestMethod.GET, value="/notifications")
public List<NotificationDAO> listNotifications(
@RequestHeader(name="auth", required=true) String auth,
@RequestParam(name="page") int page,
@RequestParam(name="size") int size) {
return notifications.listNotifications(auth, page, size);
}
@RequestMapping(method=RequestMethod.POST, value="/contacts")
public String uploadContacts(
@RequestHeader(name="auth", required=true) String auth,
@RequestBody List<ShadowUserDAO> contacts) {
users.uploadContacts(auth, contacts);
return "OK";
}
@RequestMapping(method=RequestMethod.GET, value="/updatePush")
public String updatePushKey(
@RequestHeader(name="auth", required=true) String auth,
String key) {
notifications.updatePushKey(auth, key);
return "OK";
}
}
UserWebService
Next, we expose the push registration method as a restful webservice.

This is a direct mapping to the update method so there isn't much to say about that…
@SpringBootApplication
@EnableAsync
public class FacebookCloneServerApplication {
public static void main(String[] args) {
SpringApplication.run(FacebookCloneServerApplication.class, args);
}
@Bean
public Executor asyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(2);
executor.setMaxPoolSize(2);
executor.setQueueCapacity(5000);
executor.setThreadNamePrefix("PushThread-");
executor.initialize();
return executor;
}
}
FacebookCloneServerApplication
The last piece of the server code is the changes we need to make to the FacebookCloneServerApplication class. We didn't touch that class at all when we started off but
now we need some changes to support the asynchronous sendPushNotification method.

First we need the @EnableAsync annotation so @Async will work in the app
@SpringBootApplication
@EnableAsync
public class FacebookCloneServerApplication {
public static void main(String[] args) {
SpringApplication.run(FacebookCloneServerApplication.class, args);
}
@Bean
public Executor asyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(2);
executor.setMaxPoolSize(2);
executor.setQueueCapacity(5000);
executor.setThreadNamePrefix("PushThread-");
executor.initialize();
return executor;
}
}
FacebookCloneServerApplication
The @Bean for the asyncExecutor creates the thread pool used when we invoke an @Async method. We define reasonable capacities, we don't want an infinite queue as
we can run out of RAM & it can be used in a Denial of Service attack.

We also don't want too many threads in the pool as our server might overcrowd the push servers and receive rate limits. This level should work fine.

With that the server side portion of push support is done.

More Related Content

Similar to Creating a Facebook Clone - Part XLV - Transcript.pdf

AuthN deep.dive—ASP.NET Authentication Internals.pdf
AuthN deep.dive—ASP.NET Authentication Internals.pdfAuthN deep.dive—ASP.NET Authentication Internals.pdf
AuthN deep.dive—ASP.NET Authentication Internals.pdfondrejl1
 
Lesson_07_Spring_Security_Register_NEW.pdf
Lesson_07_Spring_Security_Register_NEW.pdfLesson_07_Spring_Security_Register_NEW.pdf
Lesson_07_Spring_Security_Register_NEW.pdfScott Anderson
 
Working effectively with legacy code
Working effectively with legacy codeWorking effectively with legacy code
Working effectively with legacy codeShriKant Vashishtha
 
Building @Anywhere (for TXJS)
Building @Anywhere (for TXJS)Building @Anywhere (for TXJS)
Building @Anywhere (for TXJS)danwrong
 
Creating a Facebook Clone - Part XXV - Transcript.pdf
Creating a Facebook Clone - Part XXV - Transcript.pdfCreating a Facebook Clone - Part XXV - Transcript.pdf
Creating a Facebook Clone - Part XXV - Transcript.pdfShaiAlmog1
 
Creating a Facebook Clone - Part XXVII - Transcript.pdf
Creating a Facebook Clone - Part XXVII - Transcript.pdfCreating a Facebook Clone - Part XXVII - Transcript.pdf
Creating a Facebook Clone - Part XXVII - Transcript.pdfShaiAlmog1
 
Creating a Whatsapp Clone - Part II.pdf
Creating a Whatsapp Clone - Part II.pdfCreating a Whatsapp Clone - Part II.pdf
Creating a Whatsapp Clone - Part II.pdfShaiAlmog1
 
GWT Training - Session 3/3
GWT Training - Session 3/3GWT Training - Session 3/3
GWT Training - Session 3/3Faiz Bashir
 
Creating a Facebook Clone - Part XLVI.pdf
Creating a Facebook Clone - Part XLVI.pdfCreating a Facebook Clone - Part XLVI.pdf
Creating a Facebook Clone - Part XLVI.pdfShaiAlmog1
 
Creating a Whatsapp Clone - Part II - Transcript.pdf
Creating a Whatsapp Clone - Part II - Transcript.pdfCreating a Whatsapp Clone - Part II - Transcript.pdf
Creating a Whatsapp Clone - Part II - Transcript.pdfShaiAlmog1
 
- the modification will be done in Main class- first, asks the use.pdf
- the modification will be done in Main class- first, asks the use.pdf- the modification will be done in Main class- first, asks the use.pdf
- the modification will be done in Main class- first, asks the use.pdfhanumanparsadhsr
 
Creating a Facebook Clone - Part XL - Transcript.pdf
Creating a Facebook Clone - Part XL - Transcript.pdfCreating a Facebook Clone - Part XL - Transcript.pdf
Creating a Facebook Clone - Part XL - Transcript.pdfShaiAlmog1
 
Lesson_07_Spring_Security_Login_NEW.pdf
Lesson_07_Spring_Security_Login_NEW.pdfLesson_07_Spring_Security_Login_NEW.pdf
Lesson_07_Spring_Security_Login_NEW.pdfScott Anderson
 
Mashing up JavaScript – Advanced Techniques for modern Web Apps
Mashing up JavaScript – Advanced Techniques for modern Web AppsMashing up JavaScript – Advanced Techniques for modern Web Apps
Mashing up JavaScript – Advanced Techniques for modern Web AppsBastian Hofmann
 
Lesson07-UsernamePasswordAuthenticationFilter.pdf
Lesson07-UsernamePasswordAuthenticationFilter.pdfLesson07-UsernamePasswordAuthenticationFilter.pdf
Lesson07-UsernamePasswordAuthenticationFilter.pdfScott Anderson
 
having trouble with my code I'm using VScode to code a javascript code.docx
having trouble with my code I'm using VScode to code a javascript code.docxhaving trouble with my code I'm using VScode to code a javascript code.docx
having trouble with my code I'm using VScode to code a javascript code.docxIsaac9LjWelchq
 
VPN Access Runbook
VPN Access RunbookVPN Access Runbook
VPN Access RunbookTaha Shakeel
 
Trip itparsing
Trip itparsingTrip itparsing
Trip itparsingCapIpad
 

Similar to Creating a Facebook Clone - Part XLV - Transcript.pdf (20)

AuthN deep.dive—ASP.NET Authentication Internals.pdf
AuthN deep.dive—ASP.NET Authentication Internals.pdfAuthN deep.dive—ASP.NET Authentication Internals.pdf
AuthN deep.dive—ASP.NET Authentication Internals.pdf
 
Lesson_07_Spring_Security_Register_NEW.pdf
Lesson_07_Spring_Security_Register_NEW.pdfLesson_07_Spring_Security_Register_NEW.pdf
Lesson_07_Spring_Security_Register_NEW.pdf
 
Working effectively with legacy code
Working effectively with legacy codeWorking effectively with legacy code
Working effectively with legacy code
 
Building @Anywhere (for TXJS)
Building @Anywhere (for TXJS)Building @Anywhere (for TXJS)
Building @Anywhere (for TXJS)
 
Creating a Facebook Clone - Part XXV - Transcript.pdf
Creating a Facebook Clone - Part XXV - Transcript.pdfCreating a Facebook Clone - Part XXV - Transcript.pdf
Creating a Facebook Clone - Part XXV - Transcript.pdf
 
Creating a Facebook Clone - Part XXVII - Transcript.pdf
Creating a Facebook Clone - Part XXVII - Transcript.pdfCreating a Facebook Clone - Part XXVII - Transcript.pdf
Creating a Facebook Clone - Part XXVII - Transcript.pdf
 
Creating a Whatsapp Clone - Part II.pdf
Creating a Whatsapp Clone - Part II.pdfCreating a Whatsapp Clone - Part II.pdf
Creating a Whatsapp Clone - Part II.pdf
 
Day 5
Day 5Day 5
Day 5
 
GWT Training - Session 3/3
GWT Training - Session 3/3GWT Training - Session 3/3
GWT Training - Session 3/3
 
Creating a Facebook Clone - Part XLVI.pdf
Creating a Facebook Clone - Part XLVI.pdfCreating a Facebook Clone - Part XLVI.pdf
Creating a Facebook Clone - Part XLVI.pdf
 
Creating a Whatsapp Clone - Part II - Transcript.pdf
Creating a Whatsapp Clone - Part II - Transcript.pdfCreating a Whatsapp Clone - Part II - Transcript.pdf
Creating a Whatsapp Clone - Part II - Transcript.pdf
 
Mashing up JavaScript
Mashing up JavaScriptMashing up JavaScript
Mashing up JavaScript
 
- the modification will be done in Main class- first, asks the use.pdf
- the modification will be done in Main class- first, asks the use.pdf- the modification will be done in Main class- first, asks the use.pdf
- the modification will be done in Main class- first, asks the use.pdf
 
Creating a Facebook Clone - Part XL - Transcript.pdf
Creating a Facebook Clone - Part XL - Transcript.pdfCreating a Facebook Clone - Part XL - Transcript.pdf
Creating a Facebook Clone - Part XL - Transcript.pdf
 
Lesson_07_Spring_Security_Login_NEW.pdf
Lesson_07_Spring_Security_Login_NEW.pdfLesson_07_Spring_Security_Login_NEW.pdf
Lesson_07_Spring_Security_Login_NEW.pdf
 
Mashing up JavaScript – Advanced Techniques for modern Web Apps
Mashing up JavaScript – Advanced Techniques for modern Web AppsMashing up JavaScript – Advanced Techniques for modern Web Apps
Mashing up JavaScript – Advanced Techniques for modern Web Apps
 
Lesson07-UsernamePasswordAuthenticationFilter.pdf
Lesson07-UsernamePasswordAuthenticationFilter.pdfLesson07-UsernamePasswordAuthenticationFilter.pdf
Lesson07-UsernamePasswordAuthenticationFilter.pdf
 
having trouble with my code I'm using VScode to code a javascript code.docx
having trouble with my code I'm using VScode to code a javascript code.docxhaving trouble with my code I'm using VScode to code a javascript code.docx
having trouble with my code I'm using VScode to code a javascript code.docx
 
VPN Access Runbook
VPN Access RunbookVPN Access Runbook
VPN Access Runbook
 
Trip itparsing
Trip itparsingTrip itparsing
Trip itparsing
 

More from ShaiAlmog1

The Duck Teaches Learn to debug from the masters. Local to production- kill ...
The Duck Teaches  Learn to debug from the masters. Local to production- kill ...The Duck Teaches  Learn to debug from the masters. Local to production- kill ...
The Duck Teaches Learn to debug from the masters. Local to production- kill ...ShaiAlmog1
 
create-netflix-clone-06-client-ui.pdf
create-netflix-clone-06-client-ui.pdfcreate-netflix-clone-06-client-ui.pdf
create-netflix-clone-06-client-ui.pdfShaiAlmog1
 
create-netflix-clone-01-introduction_transcript.pdf
create-netflix-clone-01-introduction_transcript.pdfcreate-netflix-clone-01-introduction_transcript.pdf
create-netflix-clone-01-introduction_transcript.pdfShaiAlmog1
 
create-netflix-clone-02-server_transcript.pdf
create-netflix-clone-02-server_transcript.pdfcreate-netflix-clone-02-server_transcript.pdf
create-netflix-clone-02-server_transcript.pdfShaiAlmog1
 
create-netflix-clone-04-server-continued_transcript.pdf
create-netflix-clone-04-server-continued_transcript.pdfcreate-netflix-clone-04-server-continued_transcript.pdf
create-netflix-clone-04-server-continued_transcript.pdfShaiAlmog1
 
create-netflix-clone-01-introduction.pdf
create-netflix-clone-01-introduction.pdfcreate-netflix-clone-01-introduction.pdf
create-netflix-clone-01-introduction.pdfShaiAlmog1
 
create-netflix-clone-06-client-ui_transcript.pdf
create-netflix-clone-06-client-ui_transcript.pdfcreate-netflix-clone-06-client-ui_transcript.pdf
create-netflix-clone-06-client-ui_transcript.pdfShaiAlmog1
 
create-netflix-clone-03-server.pdf
create-netflix-clone-03-server.pdfcreate-netflix-clone-03-server.pdf
create-netflix-clone-03-server.pdfShaiAlmog1
 
create-netflix-clone-04-server-continued.pdf
create-netflix-clone-04-server-continued.pdfcreate-netflix-clone-04-server-continued.pdf
create-netflix-clone-04-server-continued.pdfShaiAlmog1
 
create-netflix-clone-05-client-model_transcript.pdf
create-netflix-clone-05-client-model_transcript.pdfcreate-netflix-clone-05-client-model_transcript.pdf
create-netflix-clone-05-client-model_transcript.pdfShaiAlmog1
 
create-netflix-clone-03-server_transcript.pdf
create-netflix-clone-03-server_transcript.pdfcreate-netflix-clone-03-server_transcript.pdf
create-netflix-clone-03-server_transcript.pdfShaiAlmog1
 
create-netflix-clone-02-server.pdf
create-netflix-clone-02-server.pdfcreate-netflix-clone-02-server.pdf
create-netflix-clone-02-server.pdfShaiAlmog1
 
create-netflix-clone-05-client-model.pdf
create-netflix-clone-05-client-model.pdfcreate-netflix-clone-05-client-model.pdf
create-netflix-clone-05-client-model.pdfShaiAlmog1
 
Creating a Whatsapp Clone - Part IX - Transcript.pdf
Creating a Whatsapp Clone - Part IX - Transcript.pdfCreating a Whatsapp Clone - Part IX - Transcript.pdf
Creating a Whatsapp Clone - Part IX - Transcript.pdfShaiAlmog1
 
Creating a Whatsapp Clone - Part V - Transcript.pdf
Creating a Whatsapp Clone - Part V - Transcript.pdfCreating a Whatsapp Clone - Part V - Transcript.pdf
Creating a Whatsapp Clone - Part V - Transcript.pdfShaiAlmog1
 
Creating a Whatsapp Clone - Part IV - Transcript.pdf
Creating a Whatsapp Clone - Part IV - Transcript.pdfCreating a Whatsapp Clone - Part IV - Transcript.pdf
Creating a Whatsapp Clone - Part IV - Transcript.pdfShaiAlmog1
 
Creating a Whatsapp Clone - Part IV.pdf
Creating a Whatsapp Clone - Part IV.pdfCreating a Whatsapp Clone - Part IV.pdf
Creating a Whatsapp Clone - Part IV.pdfShaiAlmog1
 
Creating a Whatsapp Clone - Part I - Transcript.pdf
Creating a Whatsapp Clone - Part I - Transcript.pdfCreating a Whatsapp Clone - Part I - Transcript.pdf
Creating a Whatsapp Clone - Part I - Transcript.pdfShaiAlmog1
 
Creating a Whatsapp Clone - Part IX.pdf
Creating a Whatsapp Clone - Part IX.pdfCreating a Whatsapp Clone - Part IX.pdf
Creating a Whatsapp Clone - Part IX.pdfShaiAlmog1
 
Creating a Whatsapp Clone - Part VI.pdf
Creating a Whatsapp Clone - Part VI.pdfCreating a Whatsapp Clone - Part VI.pdf
Creating a Whatsapp Clone - Part VI.pdfShaiAlmog1
 

More from ShaiAlmog1 (20)

The Duck Teaches Learn to debug from the masters. Local to production- kill ...
The Duck Teaches  Learn to debug from the masters. Local to production- kill ...The Duck Teaches  Learn to debug from the masters. Local to production- kill ...
The Duck Teaches Learn to debug from the masters. Local to production- kill ...
 
create-netflix-clone-06-client-ui.pdf
create-netflix-clone-06-client-ui.pdfcreate-netflix-clone-06-client-ui.pdf
create-netflix-clone-06-client-ui.pdf
 
create-netflix-clone-01-introduction_transcript.pdf
create-netflix-clone-01-introduction_transcript.pdfcreate-netflix-clone-01-introduction_transcript.pdf
create-netflix-clone-01-introduction_transcript.pdf
 
create-netflix-clone-02-server_transcript.pdf
create-netflix-clone-02-server_transcript.pdfcreate-netflix-clone-02-server_transcript.pdf
create-netflix-clone-02-server_transcript.pdf
 
create-netflix-clone-04-server-continued_transcript.pdf
create-netflix-clone-04-server-continued_transcript.pdfcreate-netflix-clone-04-server-continued_transcript.pdf
create-netflix-clone-04-server-continued_transcript.pdf
 
create-netflix-clone-01-introduction.pdf
create-netflix-clone-01-introduction.pdfcreate-netflix-clone-01-introduction.pdf
create-netflix-clone-01-introduction.pdf
 
create-netflix-clone-06-client-ui_transcript.pdf
create-netflix-clone-06-client-ui_transcript.pdfcreate-netflix-clone-06-client-ui_transcript.pdf
create-netflix-clone-06-client-ui_transcript.pdf
 
create-netflix-clone-03-server.pdf
create-netflix-clone-03-server.pdfcreate-netflix-clone-03-server.pdf
create-netflix-clone-03-server.pdf
 
create-netflix-clone-04-server-continued.pdf
create-netflix-clone-04-server-continued.pdfcreate-netflix-clone-04-server-continued.pdf
create-netflix-clone-04-server-continued.pdf
 
create-netflix-clone-05-client-model_transcript.pdf
create-netflix-clone-05-client-model_transcript.pdfcreate-netflix-clone-05-client-model_transcript.pdf
create-netflix-clone-05-client-model_transcript.pdf
 
create-netflix-clone-03-server_transcript.pdf
create-netflix-clone-03-server_transcript.pdfcreate-netflix-clone-03-server_transcript.pdf
create-netflix-clone-03-server_transcript.pdf
 
create-netflix-clone-02-server.pdf
create-netflix-clone-02-server.pdfcreate-netflix-clone-02-server.pdf
create-netflix-clone-02-server.pdf
 
create-netflix-clone-05-client-model.pdf
create-netflix-clone-05-client-model.pdfcreate-netflix-clone-05-client-model.pdf
create-netflix-clone-05-client-model.pdf
 
Creating a Whatsapp Clone - Part IX - Transcript.pdf
Creating a Whatsapp Clone - Part IX - Transcript.pdfCreating a Whatsapp Clone - Part IX - Transcript.pdf
Creating a Whatsapp Clone - Part IX - Transcript.pdf
 
Creating a Whatsapp Clone - Part V - Transcript.pdf
Creating a Whatsapp Clone - Part V - Transcript.pdfCreating a Whatsapp Clone - Part V - Transcript.pdf
Creating a Whatsapp Clone - Part V - Transcript.pdf
 
Creating a Whatsapp Clone - Part IV - Transcript.pdf
Creating a Whatsapp Clone - Part IV - Transcript.pdfCreating a Whatsapp Clone - Part IV - Transcript.pdf
Creating a Whatsapp Clone - Part IV - Transcript.pdf
 
Creating a Whatsapp Clone - Part IV.pdf
Creating a Whatsapp Clone - Part IV.pdfCreating a Whatsapp Clone - Part IV.pdf
Creating a Whatsapp Clone - Part IV.pdf
 
Creating a Whatsapp Clone - Part I - Transcript.pdf
Creating a Whatsapp Clone - Part I - Transcript.pdfCreating a Whatsapp Clone - Part I - Transcript.pdf
Creating a Whatsapp Clone - Part I - Transcript.pdf
 
Creating a Whatsapp Clone - Part IX.pdf
Creating a Whatsapp Clone - Part IX.pdfCreating a Whatsapp Clone - Part IX.pdf
Creating a Whatsapp Clone - Part IX.pdf
 
Creating a Whatsapp Clone - Part VI.pdf
Creating a Whatsapp Clone - Part VI.pdfCreating a Whatsapp Clone - Part VI.pdf
Creating a Whatsapp Clone - Part VI.pdf
 

Recently uploaded

ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemkeProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemkeProduct Anonymous
 
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024Victor Rentea
 
Apidays New York 2024 - Passkeys: Developing APIs to enable passwordless auth...
Apidays New York 2024 - Passkeys: Developing APIs to enable passwordless auth...Apidays New York 2024 - Passkeys: Developing APIs to enable passwordless auth...
Apidays New York 2024 - Passkeys: Developing APIs to enable passwordless auth...apidays
 
MS Copilot expands with MS Graph connectors
MS Copilot expands with MS Graph connectorsMS Copilot expands with MS Graph connectors
MS Copilot expands with MS Graph connectorsNanddeep Nachan
 
Artificial Intelligence Chap.5 : Uncertainty
Artificial Intelligence Chap.5 : UncertaintyArtificial Intelligence Chap.5 : Uncertainty
Artificial Intelligence Chap.5 : UncertaintyKhushali Kathiriya
 
Why Teams call analytics are critical to your entire business
Why Teams call analytics are critical to your entire businessWhy Teams call analytics are critical to your entire business
Why Teams call analytics are critical to your entire businesspanagenda
 
Web Form Automation for Bonterra Impact Management (fka Social Solutions Apri...
Web Form Automation for Bonterra Impact Management (fka Social Solutions Apri...Web Form Automation for Bonterra Impact Management (fka Social Solutions Apri...
Web Form Automation for Bonterra Impact Management (fka Social Solutions Apri...Jeffrey Haguewood
 
Biography Of Angeliki Cooney | Senior Vice President Life Sciences | Albany, ...
Biography Of Angeliki Cooney | Senior Vice President Life Sciences | Albany, ...Biography Of Angeliki Cooney | Senior Vice President Life Sciences | Albany, ...
Biography Of Angeliki Cooney | Senior Vice President Life Sciences | Albany, ...Angeliki Cooney
 
TrustArc Webinar - Unlock the Power of AI-Driven Data Discovery
TrustArc Webinar - Unlock the Power of AI-Driven Data DiscoveryTrustArc Webinar - Unlock the Power of AI-Driven Data Discovery
TrustArc Webinar - Unlock the Power of AI-Driven Data DiscoveryTrustArc
 
Cyberprint. Dark Pink Apt Group [EN].pdf
Cyberprint. Dark Pink Apt Group [EN].pdfCyberprint. Dark Pink Apt Group [EN].pdf
Cyberprint. Dark Pink Apt Group [EN].pdfOverkill Security
 
Strategize a Smooth Tenant-to-tenant Migration and Copilot Takeoff
Strategize a Smooth Tenant-to-tenant Migration and Copilot TakeoffStrategize a Smooth Tenant-to-tenant Migration and Copilot Takeoff
Strategize a Smooth Tenant-to-tenant Migration and Copilot Takeoffsammart93
 
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...apidays
 
ICT role in 21st century education and its challenges
ICT role in 21st century education and its challengesICT role in 21st century education and its challenges
ICT role in 21st century education and its challengesrafiqahmad00786416
 
Architecting Cloud Native Applications
Architecting Cloud Native ApplicationsArchitecting Cloud Native Applications
Architecting Cloud Native ApplicationsWSO2
 
Spring Boot vs Quarkus the ultimate battle - DevoxxUK
Spring Boot vs Quarkus the ultimate battle - DevoxxUKSpring Boot vs Quarkus the ultimate battle - DevoxxUK
Spring Boot vs Quarkus the ultimate battle - DevoxxUKJago de Vreede
 
2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...Martijn de Jong
 
Manulife - Insurer Transformation Award 2024
Manulife - Insurer Transformation Award 2024Manulife - Insurer Transformation Award 2024
Manulife - Insurer Transformation Award 2024The Digital Insurer
 
MINDCTI Revenue Release Quarter One 2024
MINDCTI Revenue Release Quarter One 2024MINDCTI Revenue Release Quarter One 2024
MINDCTI Revenue Release Quarter One 2024MIND CTI
 
"I see eyes in my soup": How Delivery Hero implemented the safety system for ...
"I see eyes in my soup": How Delivery Hero implemented the safety system for ..."I see eyes in my soup": How Delivery Hero implemented the safety system for ...
"I see eyes in my soup": How Delivery Hero implemented the safety system for ...Zilliz
 

Recently uploaded (20)

ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemkeProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
 
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
 
Apidays New York 2024 - Passkeys: Developing APIs to enable passwordless auth...
Apidays New York 2024 - Passkeys: Developing APIs to enable passwordless auth...Apidays New York 2024 - Passkeys: Developing APIs to enable passwordless auth...
Apidays New York 2024 - Passkeys: Developing APIs to enable passwordless auth...
 
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
 
MS Copilot expands with MS Graph connectors
MS Copilot expands with MS Graph connectorsMS Copilot expands with MS Graph connectors
MS Copilot expands with MS Graph connectors
 
Artificial Intelligence Chap.5 : Uncertainty
Artificial Intelligence Chap.5 : UncertaintyArtificial Intelligence Chap.5 : Uncertainty
Artificial Intelligence Chap.5 : Uncertainty
 
Why Teams call analytics are critical to your entire business
Why Teams call analytics are critical to your entire businessWhy Teams call analytics are critical to your entire business
Why Teams call analytics are critical to your entire business
 
Web Form Automation for Bonterra Impact Management (fka Social Solutions Apri...
Web Form Automation for Bonterra Impact Management (fka Social Solutions Apri...Web Form Automation for Bonterra Impact Management (fka Social Solutions Apri...
Web Form Automation for Bonterra Impact Management (fka Social Solutions Apri...
 
Biography Of Angeliki Cooney | Senior Vice President Life Sciences | Albany, ...
Biography Of Angeliki Cooney | Senior Vice President Life Sciences | Albany, ...Biography Of Angeliki Cooney | Senior Vice President Life Sciences | Albany, ...
Biography Of Angeliki Cooney | Senior Vice President Life Sciences | Albany, ...
 
TrustArc Webinar - Unlock the Power of AI-Driven Data Discovery
TrustArc Webinar - Unlock the Power of AI-Driven Data DiscoveryTrustArc Webinar - Unlock the Power of AI-Driven Data Discovery
TrustArc Webinar - Unlock the Power of AI-Driven Data Discovery
 
Cyberprint. Dark Pink Apt Group [EN].pdf
Cyberprint. Dark Pink Apt Group [EN].pdfCyberprint. Dark Pink Apt Group [EN].pdf
Cyberprint. Dark Pink Apt Group [EN].pdf
 
Strategize a Smooth Tenant-to-tenant Migration and Copilot Takeoff
Strategize a Smooth Tenant-to-tenant Migration and Copilot TakeoffStrategize a Smooth Tenant-to-tenant Migration and Copilot Takeoff
Strategize a Smooth Tenant-to-tenant Migration and Copilot Takeoff
 
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...
 
ICT role in 21st century education and its challenges
ICT role in 21st century education and its challengesICT role in 21st century education and its challenges
ICT role in 21st century education and its challenges
 
Architecting Cloud Native Applications
Architecting Cloud Native ApplicationsArchitecting Cloud Native Applications
Architecting Cloud Native Applications
 
Spring Boot vs Quarkus the ultimate battle - DevoxxUK
Spring Boot vs Quarkus the ultimate battle - DevoxxUKSpring Boot vs Quarkus the ultimate battle - DevoxxUK
Spring Boot vs Quarkus the ultimate battle - DevoxxUK
 
2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...
 
Manulife - Insurer Transformation Award 2024
Manulife - Insurer Transformation Award 2024Manulife - Insurer Transformation Award 2024
Manulife - Insurer Transformation Award 2024
 
MINDCTI Revenue Release Quarter One 2024
MINDCTI Revenue Release Quarter One 2024MINDCTI Revenue Release Quarter One 2024
MINDCTI Revenue Release Quarter One 2024
 
"I see eyes in my soup": How Delivery Hero implemented the safety system for ...
"I see eyes in my soup": How Delivery Hero implemented the safety system for ..."I see eyes in my soup": How Delivery Hero implemented the safety system for ...
"I see eyes in my soup": How Delivery Hero implemented the safety system for ...
 

Creating a Facebook Clone - Part XLV - Transcript.pdf

  • 1. Creating a Facebook Clone - Part XLV The changes to the NotificationService are significant and in effect that's the class that implements push notification calls from the server.
  • 2. @Service public class NotificationService { private static final String PUSHURL = "https://push.codenameone.com/push/push"; private final static Logger logger = LoggerFactory. getLogger(NotificationService.class); @Autowired private NotificationRepository notifications; @Autowired private APIKeys keys; @Autowired private UserRepository users; public void updatePushKey(String auth, String key) { User u = users.findByAuthtoken(auth).get(0); u.setPushKey(key); users.save(u); } NotificationService Before we start we need to add a few fields to the class, these will be used by the code we introduce soon. These are besides the keys field I mentioned before. This is the URL of the Codename One push server, we'll use that to send out push messages
  • 3. @Service public class NotificationService { private static final String PUSHURL = "https://push.codenameone.com/push/push"; private final static Logger logger = LoggerFactory. getLogger(NotificationService.class); @Autowired private NotificationRepository notifications; @Autowired private APIKeys keys; @Autowired private UserRepository users; public void updatePushKey(String auth, String key) { User u = users.findByAuthtoken(auth).get(0); u.setPushKey(key); users.save(u); } NotificationService Logging is used for errors in the body of the class
  • 4. @Service public class NotificationService { private static final String PUSHURL = "https://push.codenameone.com/push/push"; private final static Logger logger = LoggerFactory. getLogger(NotificationService.class); @Autowired private NotificationRepository notifications; @Autowired private APIKeys keys; @Autowired private UserRepository users; public void updatePushKey(String auth, String key) { User u = users.findByAuthtoken(auth).get(0); u.setPushKey(key); users.save(u); } NotificationService We need to inject the users repository now as we need to find the user there
  • 5. @Autowired private NotificationRepository notifications; @Autowired private APIKeys keys; @Autowired private UserRepository users; public void updatePushKey(String auth, String key) { User u = users.findByAuthtoken(auth).get(0); u.setPushKey(key); users.save(u); } public void removePushKey(String key) { List<User> userList = users.findByPushKey(key); if(!userList.isEmpty()) { User u = userList.get(0); u.setPushKey(null); users.save(u); } } public void sendNotification(User u, NotificationDAO nd) { NotificationService The first step is trivial, it's about mapping the push key to the service class which is exactly what we do here
  • 6. @Autowired private NotificationRepository notifications; @Autowired private APIKeys keys; @Autowired private UserRepository users; public void updatePushKey(String auth, String key) { User u = users.findByAuthtoken(auth).get(0); u.setPushKey(key); users.save(u); } public void removePushKey(String key) { List<User> userList = users.findByPushKey(key); if(!userList.isEmpty()) { User u = userList.get(0); u.setPushKey(null); users.save(u); } } public void sendNotification(User u, NotificationDAO nd) { NotificationService On push registration we invoke this method to update the push key in the user object
  • 7. @Autowired private NotificationRepository notifications; @Autowired private APIKeys keys; @Autowired private UserRepository users; public void updatePushKey(String auth, String key) { User u = users.findByAuthtoken(auth).get(0); u.setPushKey(key); users.save(u); } public void removePushKey(String key) { List<User> userList = users.findByPushKey(key); if(!userList.isEmpty()) { User u = userList.get(0); u.setPushKey(null); users.save(u); } } public void sendNotification(User u, NotificationDAO nd) { NotificationService If the push server indicates that a push key is invalid or expired we delete that key. A fresh key will be created the next time the user runs the app. The second method is internal to the server and isn't exposed as a WebService but the former method is.
  • 8. users.save(u); } } public void sendNotification(User u, NotificationDAO nd) { Notification n = new Notification(); n.setDate(System.currentTimeMillis()); n.setReaction(nd.getReaction()); n.setReactionColor(nd.getReactionColor()); n.setText(nd.getText()); n.setUser(u); n.setWasRead(nd.isWasRead()); notifications.save(n); if(u.getPushKey() != null) { sendPushNotification(u.getPushKey(), nd.getText(), 1); } } public List<NotificationDAO> listNotifications(String authtoken, int page, int amount) { Page<Notification> notificationsPage = notifications. findNotificationsForUser( authtoken, PageRequest.of(page, amount, Sort.by(Sort.Direction.DESC, "date"))); List<NotificationDAO> resp = new ArrayList<>(); NotificationService The next stage is the push notification itself, we add these lines to the sendNotification method. Notice that a push key can be null since it can expire or might not be registered yet… This leads us to the sendPushNotification method but we’ll get there via a detour
  • 9. resp.add(c.getDAO()); } return resp; } private String sendPushImpl(String deviceId, String messageBody, int type) throws IOException { HttpURLConnection connection = (HttpURLConnection) new URL(PUSHURL).openConnection(); connection.setDoOutput(true); connection.setRequestMethod("POST"); connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8"); String cert; String pass; boolean production = keys.get("push.itunes_production", "false").equals("true"); if(production) { cert = keys.get("push.itunes_prodcert"); pass = keys.get("push.itunes_prodpass"); } else { cert = keys.get("push.itunes_devcert"); pass = keys.get("push.itunes_devpass"); } String query = "token=" + keys.get("push.token") + "&device=" + URLEncoder.encode(deviceId, "UTF-8") + NotificationService This method is based on the server push code from the developer guide. Notice that this is the impl method not the method we invoked before. We'll go through this first and then reach that code
  • 10. resp.add(c.getDAO()); } return resp; } private String sendPushImpl(String deviceId, String messageBody, int type) throws IOException { HttpURLConnection connection = (HttpURLConnection) new URL(PUSHURL).openConnection(); connection.setDoOutput(true); connection.setRequestMethod("POST"); connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8"); String cert; String pass; boolean production = keys.get("push.itunes_production", "false").equals("true"); if(production) { cert = keys.get("push.itunes_prodcert"); pass = keys.get("push.itunes_prodpass"); } else { cert = keys.get("push.itunes_devcert"); pass = keys.get("push.itunes_devpass"); } String query = "token=" + keys.get("push.token") + "&device=" + URLEncoder.encode(deviceId, "UTF-8") + NotificationService We connect to the push server URL to send the push message
  • 11. resp.add(c.getDAO()); } return resp; } private String sendPushImpl(String deviceId, String messageBody, int type) throws IOException { HttpURLConnection connection = (HttpURLConnection) new URL(PUSHURL).openConnection(); connection.setDoOutput(true); connection.setRequestMethod("POST"); connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8"); String cert; String pass; boolean production = keys.get("push.itunes_production", "false").equals("true"); if(production) { cert = keys.get("push.itunes_prodcert"); pass = keys.get("push.itunes_prodpass"); } else { cert = keys.get("push.itunes_devcert"); pass = keys.get("push.itunes_devpass"); } String query = "token=" + keys.get("push.token") + "&device=" + URLEncoder.encode(deviceId, "UTF-8") + NotificationService A standard form POST submission request is used
  • 12. connection.setRequestMethod("POST"); connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8"); String cert; String pass; boolean production = keys.get("push.itunes_production", "false").equals("true"); if(production) { cert = keys.get("push.itunes_prodcert"); pass = keys.get("push.itunes_prodpass"); } else { cert = keys.get("push.itunes_devcert"); pass = keys.get("push.itunes_devpass"); } String query = "token=" + keys.get("push.token") + "&device=" + URLEncoder.encode(deviceId, "UTF-8") + "&type=" + type + "&auth=" + URLEncoder.encode(keys.get("push.gcm_key"), "UTF-8") + "&certPassword=" + URLEncoder.encode(pass, "UTF-8") + "&cert=" + URLEncoder.encode(cert, "UTF-8") + "&body=" + URLEncoder.encode(messageBody, "UTF-8") + "&production=" + production; try (OutputStream output = connection.getOutputStream()) { output.write(query.getBytes("UTF-8")); } NotificationService We fetch the values for the fields that can differ specifically the certificates & passwords which vary between production and development
  • 13. if(production) { cert = keys.get("push.itunes_prodcert"); pass = keys.get("push.itunes_prodpass"); } else { cert = keys.get("push.itunes_devcert"); pass = keys.get("push.itunes_devpass"); } String query = "token=" + keys.get("push.token") + "&device=" + URLEncoder.encode(deviceId, "UTF-8") + "&type=" + type + "&auth=" + URLEncoder.encode(keys.get("push.gcm_key"), "UTF-8") + "&certPassword=" + URLEncoder.encode(pass, "UTF-8") + "&cert=" + URLEncoder.encode(cert, "UTF-8") + "&body=" + URLEncoder.encode(messageBody, "UTF-8") + "&production=" + production; try (OutputStream output = connection.getOutputStream()) { output.write(query.getBytes("UTF-8")); } int c = connection.getResponseCode(); if(c == 200) { try (InputStream i = connection.getInputStream()) { return new String(readInputStream(i)); } } logger.error("Error response code from push server: " + c); NotificationService We send all the details as a POST request to the Codename One push server which then issues a push to the given device type
  • 14. } String query = "token=" + keys.get("push.token") + "&device=" + URLEncoder.encode(deviceId, "UTF-8") + "&type=" + type + "&auth=" + URLEncoder.encode(keys.get("push.gcm_key"), "UTF-8") + "&certPassword=" + URLEncoder.encode(pass, "UTF-8") + "&cert=" + URLEncoder.encode(cert, "UTF-8") + "&body=" + URLEncoder.encode(messageBody, "UTF-8") + "&production=" + production; try (OutputStream output = connection.getOutputStream()) { output.write(query.getBytes("UTF-8")); } int c = connection.getResponseCode(); if(c == 200) { try (InputStream i = connection.getInputStream()) { return new String(readInputStream(i)); } } logger.error("Error response code from push server: " + c); return null; } @Async public void sendPushNotification(String deviceId, String messageBody, int type) { NotificationService The server can return 200 in case of an error but if the response isn't 200 then it's surely an error
  • 15. } String query = "token=" + keys.get("push.token") + "&device=" + URLEncoder.encode(deviceId, "UTF-8") + "&type=" + type + "&auth=" + URLEncoder.encode(keys.get("push.gcm_key"), "UTF-8") + "&certPassword=" + URLEncoder.encode(pass, "UTF-8") + "&cert=" + URLEncoder.encode(cert, "UTF-8") + "&body=" + URLEncoder.encode(messageBody, "UTF-8") + "&production=" + production; try (OutputStream output = connection.getOutputStream()) { output.write(query.getBytes("UTF-8")); } int c = connection.getResponseCode(); if(c == 200) { try (InputStream i = connection.getInputStream()) { return new String(readInputStream(i)); } } logger.error("Error response code from push server: " + c); return null; } @Async public void sendPushNotification(String deviceId, String messageBody, int type) { NotificationService The server response is transformed to a String for parsing on next method, I'll cover the readInputStream method soon
  • 16. logger.error("Error response code from push server: " + c); return null; } @Async public void sendPushNotification(String deviceId, String messageBody, int type) { try { String json = sendPushImpl(deviceId, messageBody, type); if(json != null) { JsonParser parser = JsonParserFactory.getJsonParser(); if(json.startsWith("[")) { List<Object> lst = parser.parseList(json); for(Object o : lst) { if(o instanceof Map) { Map entry = (Map)o; String status = (String)entry.get("status"); if(status != null) { if(status.equals("error") || status.equals("inactive")) { removePushKey((String) entry.get("id")); } } } NotificationService We need to go over the responses from the push server. These responses would include information such as push key expiration and we would need to purge that key from our database. That's all done in the actual method for sending push messages
  • 17. logger.error("Error response code from push server: " + c); return null; } @Async public void sendPushNotification(String deviceId, String messageBody, int type) { try { String json = sendPushImpl(deviceId, messageBody, type); if(json != null) { JsonParser parser = JsonParserFactory.getJsonParser(); if(json.startsWith("[")) { List<Object> lst = parser.parseList(json); for(Object o : lst) { if(o instanceof Map) { Map entry = (Map)o; String status = (String)entry.get("status"); if(status != null) { if(status.equals("error") || status.equals("inactive")) { removePushKey((String) entry.get("id")); } } } NotificationService The @Async annotation indicates this method should execute on a separate thread, this is handled by Spring. We'll discuss that soon
  • 18. logger.error("Error response code from push server: " + c); return null; } @Async public void sendPushNotification(String deviceId, String messageBody, int type) { try { String json = sendPushImpl(deviceId, messageBody, type); if(json != null) { JsonParser parser = JsonParserFactory.getJsonParser(); if(json.startsWith("[")) { List<Object> lst = parser.parseList(json); for(Object o : lst) { if(o instanceof Map) { Map entry = (Map)o; String status = (String)entry.get("status"); if(status != null) { if(status.equals("error") || status.equals("inactive")) { removePushKey((String) entry.get("id")); } } } NotificationService sendPushImpl return JSON with messages which we need to process
  • 19. logger.error("Error response code from push server: " + c); return null; } @Async public void sendPushNotification(String deviceId, String messageBody, int type) { try { String json = sendPushImpl(deviceId, messageBody, type); if(json != null) { JsonParser parser = JsonParserFactory.getJsonParser(); if(json.startsWith("[")) { List<Object> lst = parser.parseList(json); for(Object o : lst) { if(o instanceof Map) { Map entry = (Map)o; String status = (String)entry.get("status"); if(status != null) { if(status.equals("error") || status.equals("inactive")) { removePushKey((String) entry.get("id")); } } } NotificationService The Spring JSON parsing API has different forms for Map/List but we can get both in the response from the server so we need to check
  • 20. @Async public void sendPushNotification(String deviceId, String messageBody, int type) { try { String json = sendPushImpl(deviceId, messageBody, type); if(json != null) { JsonParser parser = JsonParserFactory.getJsonParser(); if(json.startsWith("[")) { List<Object> lst = parser.parseList(json); for(Object o : lst) { if(o instanceof Map) { Map entry = (Map)o; String status = (String)entry.get("status"); if(status != null) { if(status.equals("error") || status.equals("inactive")) { removePushKey((String) entry.get("id")); } } } } } else { Map<String, Object> m = parser.parseMap(json); NotificationService If it's a list then it's a device list with either acknowledgement of sending (Android only) or error messages
  • 21. @Async public void sendPushNotification(String deviceId, String messageBody, int type) { try { String json = sendPushImpl(deviceId, messageBody, type); if(json != null) { JsonParser parser = JsonParserFactory.getJsonParser(); if(json.startsWith("[")) { List<Object> lst = parser.parseList(json); for(Object o : lst) { if(o instanceof Map) { Map entry = (Map)o; String status = (String)entry.get("status"); if(status != null) { if(status.equals("error") || status.equals("inactive")) { removePushKey((String) entry.get("id")); } } } } } else { Map<String, Object> m = parser.parseMap(json); NotificationService If we have an error message for a specific device key we need to remove it to prevent future problems with the push servers
  • 22. if(o instanceof Map) { Map entry = (Map)o; String status = (String)entry.get("status"); if(status != null) { if(status.equals("error") || status.equals("inactive")) { removePushKey((String) entry.get("id")); } } } } } else { Map<String, Object> m = parser.parseMap(json); if(m.containsKey("error")) { logger.error("Error message from server: " + m.get("error")); } } } } catch(IOException err) { logger.error("Error during connection to push server", err); } } private static byte[] readInputStream(InputStream i) NotificationService If we got a Map in response it could indicate an error which we currently don't really handle other than through logging. If push doesn't work the app would still work fine you'll just need to refresh manually. A better implementation would also use a fallback for cases of push failure e.g. WebSocket. But it's not essential at least not at first.
  • 23. } } } catch(IOException err) { logger.error("Error during connection to push server", err); } } private static byte[] readInputStream(InputStream i) throws IOException { try(ByteArrayOutputStream b = new ByteArrayOutputStream()) { copy(i, b); return b.toByteArray(); } } private static void copy(InputStream i, OutputStream o) throws IOException { byte[] buffer = new byte[8192]; int size = i.read(buffer); while(size > -1) { o.write(buffer, 0, size); size = i.read(buffer); } } } NotificationService We referenced readInputStream in the previous code blocks it's defined in the code as such. I could have written more modern code using nio but I was running out of time and I had this code handy. It does the job well…
  • 24. @RequestMapping(method=RequestMethod.GET, value="/notifications") public List<NotificationDAO> listNotifications( @RequestHeader(name="auth", required=true) String auth, @RequestParam(name="page") int page, @RequestParam(name="size") int size) { return notifications.listNotifications(auth, page, size); } @RequestMapping(method=RequestMethod.POST, value="/contacts") public String uploadContacts( @RequestHeader(name="auth", required=true) String auth, @RequestBody List<ShadowUserDAO> contacts) { users.uploadContacts(auth, contacts); return "OK"; } @RequestMapping(method=RequestMethod.GET, value="/updatePush") public String updatePushKey( @RequestHeader(name="auth", required=true) String auth, String key) { notifications.updatePushKey(auth, key); return "OK"; } } UserWebService Next, we expose the push registration method as a restful webservice. This is a direct mapping to the update method so there isn't much to say about that…
  • 25. @SpringBootApplication @EnableAsync public class FacebookCloneServerApplication { public static void main(String[] args) { SpringApplication.run(FacebookCloneServerApplication.class, args); } @Bean public Executor asyncExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(2); executor.setMaxPoolSize(2); executor.setQueueCapacity(5000); executor.setThreadNamePrefix("PushThread-"); executor.initialize(); return executor; } } FacebookCloneServerApplication The last piece of the server code is the changes we need to make to the FacebookCloneServerApplication class. We didn't touch that class at all when we started off but now we need some changes to support the asynchronous sendPushNotification method. First we need the @EnableAsync annotation so @Async will work in the app
  • 26. @SpringBootApplication @EnableAsync public class FacebookCloneServerApplication { public static void main(String[] args) { SpringApplication.run(FacebookCloneServerApplication.class, args); } @Bean public Executor asyncExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(2); executor.setMaxPoolSize(2); executor.setQueueCapacity(5000); executor.setThreadNamePrefix("PushThread-"); executor.initialize(); return executor; } } FacebookCloneServerApplication The @Bean for the asyncExecutor creates the thread pool used when we invoke an @Async method. We define reasonable capacities, we don't want an infinite queue as we can run out of RAM & it can be used in a Denial of Service attack. We also don't want too many threads in the pool as our server might overcrowd the push servers and receive rate limits. This level should work fine. With that the server side portion of push support is done.