I have an XMPP Server which I have set up to send and receive Firebase Cloud Messages which has been working fine during testing with debugging turned on through the following 2 lines in my server code:
config.setDebuggerEnabled(true);
XMPPConnection.DEBUG_ENABLED = true;
I would now like to move this to my production ec2 instance and by doing so I have to change the 2 lines of code above to be false. After making this change my jar file no longer works.
Running the jar from CMD:
Microsoft Windows [Version 10.0.10586]
(c) 2015 Microsoft Corporation. All rights reserved.
C:\Users\Riley>cd "C:\Program Files\Java\jdk1.8.0_101\bin"
C:\Program Files\Java\jdk1.8.0_101\bin>java -jar D:\Downloads\XMPP-Chat-Server\server.jar
Sep 16, 2016 10:28:14 AM org.jivesoftware.smack.provider.UrlProviderFileInitializer initialize
INFO: Loading providers for file [classpath:META-INF/core.providers]
Sep 16, 2016 10:28:14 AM org.jivesoftware.smack.SmackConfiguration parseClassToLoad
WARNING: A startup class [org.jivesoftware.smackx.provider.ExtensionInitializer] specified in smack-config.xml could not be loaded:
Sep 16, 2016 10:28:14 AM org.jivesoftware.smack.SmackConfiguration parseClassToLoad
WARNING: A startup class [org.jivesoftware.smackx.ServiceDiscoveryManager] specified in smack-config.xml could not be loaded:
Sep 16, 2016 10:28:14 AM org.jivesoftware.smack.SmackConfiguration parseClassToLoad
WARNING: A startup class [org.jivesoftware.smackx.XHTMLManager] specified in smack-config.xml could not be loaded:
Sep 16, 2016 10:28:14 AM org.jivesoftware.smack.SmackConfiguration parseClassToLoad
WARNING: A startup class [org.jivesoftware.smackx.muc.MultiUserChat] specified in smack-config.xml could not be loaded:
Sep 16, 2016 10:28:14 AM org.jivesoftware.smack.SmackConfiguration parseClassToLoad
WARNING: A startup class [org.jivesoftware.smackx.bytestreams.ibb.InBandBytestreamManager] specified in smack-config.xml could not be loaded:
Sep 16, 2016 10:28:14 AM org.jivesoftware.smack.SmackConfiguration parseClassToLoad
WARNING: A startup class [org.jivesoftware.smackx.bytestreams.socks5.Socks5BytestreamManager] specified in smack-config.xml could not be loaded:
Sep 16, 2016 10:28:14 AM org.jivesoftware.smack.SmackConfiguration parseClassToLoad
WARNING: A startup class [org.jivesoftware.smackx.filetransfer.FileTransferManager] specified in smack-config.xml could not be loaded:
Sep 16, 2016 10:28:14 AM org.jivesoftware.smack.SmackConfiguration parseClassToLoad
WARNING: A startup class [org.jivesoftware.smackx.LastActivityManager] specified in smack-config.xml could not be loaded:
Sep 16, 2016 10:28:14 AM org.jivesoftware.smack.SmackConfiguration parseClassToLoad
WARNING: A startup class [org.jivesoftware.smackx.commands.AdHocCommandManager] specified in smack-config.xml could not be loaded:
Sep 16, 2016 10:28:14 AM org.jivesoftware.smack.SmackConfiguration parseClassToLoad
WARNING: A startup class [org.jivesoftware.smackx.ping.PingManager] specified in smack-config.xml could not be loaded:
C:\Program Files\Java\jdk1.8.0_101\bin>
Here is my XMPP Server code:
package com.fcmserver;
/*
* Most part of this class is copyright Google.
* It is from https://developer.android.com/google/gcm/ccs.html
*/
import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
import org.jivesoftware.smack.ConnectionListener;
import org.jivesoftware.smack.PacketInterceptor;
import org.jivesoftware.smack.PacketListener;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.filter.PacketTypeFilter;
import org.jivesoftware.smack.packet.DefaultPacketExtension;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Packet;
import org.jivesoftware.smack.packet.PacketExtension;
import org.jivesoftware.smack.provider.PacketExtensionProvider;
import org.jivesoftware.smack.provider.ProviderManager;
import org.jivesoftware.smack.util.StringUtils;
import org.json.simple.JSONValue;
import org.json.simple.parser.ParseException;
import org.xmlpull.v1.XmlPullParser;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.io.*;
import javax.net.ssl.SSLSocketFactory;
/**
* Sample Smack implementation of a client for GCM Cloud Connection Server.
*
* For illustration purposes only.
*/
public class SmackCcsClient {
static final String REG_ID_STORE = "gcmchat.txt";
static final String MESSAGE_KEY = "SM";
Logger logger = Logger.getLogger("SmackCcsClient");
public static final String GCM_SERVER = "fcm-xmpp.googleapis.com";
public static final int GCM_PORT = 5235;
public static final String GCM_ELEMENT_NAME = "gcm";
public static final String GCM_NAMESPACE = "google:mobile:data";
static Random random = new Random();
XMPPConnection connection;
ConnectionConfiguration config;
/**
* XMPP Packet Extension for GCM Cloud Connection Server.
*/
class GcmPacketExtension extends DefaultPacketExtension {
String json;
public GcmPacketExtension(String json) {
super(GCM_ELEMENT_NAME, GCM_NAMESPACE);
this.json = json;
}
public String getJson() {
return json;
}
#Override
public String toXML() {
return String.format("<%s xmlns=\"%s\">%s</%s>", GCM_ELEMENT_NAME,
GCM_NAMESPACE, json, GCM_ELEMENT_NAME);
}
#SuppressWarnings("unused")
public Packet toPacket() {
return new Message() {
// Must override toXML() because it includes a <body>
#Override
public String toXML() {
StringBuilder buf = new StringBuilder();
buf.append("<message");
if (getXmlns() != null) {
buf.append(" xmlns=\"").append(getXmlns()).append("\"");
}
if (getLanguage() != null) {
buf.append(" xml:lang=\"").append(getLanguage())
.append("\"");
}
if (getPacketID() != null) {
buf.append(" id=\"").append(getPacketID()).append("\"");
}
if (getTo() != null) {
buf.append(" to=\"")
.append(StringUtils.escapeForXML(getTo()))
.append("\"");
}
if (getFrom() != null) {
buf.append(" from=\"")
.append(StringUtils.escapeForXML(getFrom()))
.append("\"");
}
buf.append(">");
buf.append(GcmPacketExtension.this.toXML());
buf.append("</message>");
return buf.toString();
}
};
}
}
public SmackCcsClient() {
// Add GcmPacketExtension
ProviderManager.getInstance().addExtensionProvider(GCM_ELEMENT_NAME,
GCM_NAMESPACE, new PacketExtensionProvider() {
#Override
public PacketExtension parseExtension(XmlPullParser parser)
throws Exception {
String json = parser.nextText();
GcmPacketExtension packet = new GcmPacketExtension(json);
return packet;
}
});
}
/**
* Returns a random message id to uniquely identify a message.
*
* <p>
* Note: This is generated by a pseudo random number generator for
* illustration purpose, and is not guaranteed to be unique.
*
*/
public String getRandomMessageId() {
return "m-" + Long.toString(random.nextLong());
}
/**
* Sends a downstream GCM message.
*/
public void send(String jsonRequest) {
Packet request = new GcmPacketExtension(jsonRequest).toPacket();
connection.sendPacket(request);
}
/**
* Handles an upstream data message from a device application.
*
* <p>
* This sample echo server sends an echo message back to the device.
* Subclasses should override this method to process an upstream message.
*/
public void handleIncomingDataMessage(Map<String, Object> jsonObject) {
String from = jsonObject.get("from").toString();
// PackageName of the application that sent this message.
String category = jsonObject.get("category").toString();
// Use the packageName as the collapseKey in the echo packet
String collapseKey = "echo:CollapseKey";
#SuppressWarnings("unchecked")
Map<String, String> payload = (Map<String, String>) jsonObject
.get("data");
String messageText = payload.get("message_text");
String messageFrom = payload.get("message_userfrom");
String messageTime = payload.get("message_timestamp");
String toUser = payload.get("message_recipient");
String id = payload.get("message_alarm_id");
String messageType = payload.get("binder_message_type");
payload.put("message_text", messageText);
payload.put("message_userfrom", messageFrom);
payload.put("message_timestamp", messageTime);
payload.put("message_alarm_id", id);
payload.put("binder_message_type", messageType);
String message = createJsonMessage(toUser, getRandomMessageId(),
payload, collapseKey, null, false);
send(message);
}
/**
* Handles an ACK.
*
* <p>
* By default, it only logs a INFO message, but subclasses could override it
* to properly handle ACKS.
*/
public void handleAckReceipt(Map<String, Object> jsonObject) {
String messageId = jsonObject.get("message_id").toString();
String from = jsonObject.get("from").toString();
logger.log(Level.INFO, "handleAckReceipt() from: " + from
+ ", messageId: " + messageId);
}
/**
* Handles a NACK.
*
* <p>
* By default, it only logs a INFO message, but subclasses could override it
* to properly handle NACKS.
*/
public void handleNackReceipt(Map<String, Object> jsonObject) {
String messageId = jsonObject.get("message_id").toString();
String from = jsonObject.get("from").toString();
logger.log(Level.INFO, "handleNackReceipt() from: " + from
+ ", messageId: " + messageId);
}
/**
* Creates a JSON encoded GCM message.
*
* #param to
* RegistrationId of the target device (Required).
* #param messageId
* Unique messageId for which CCS will send an "ack/nack"
* (Required).
* #param payload
* Message content intended for the application. (Optional).
* #param collapseKey
* GCM collapse_key parameter (Optional).
* #param timeToLive
* GCM time_to_live parameter (Optional).
* #param delayWhileIdle
* GCM delay_while_idle parameter (Optional).
* #return JSON encoded GCM message.
*/
public static String createJsonMessage(String to, String messageId,
Map<String, String> payload, String collapseKey, Long timeToLive,
Boolean delayWhileIdle) {
Map<String, Object> message = new HashMap<String, Object>();
message.put("to", to);
if (collapseKey != null) {
message.put("collapse_key", collapseKey);
}
if (timeToLive != null) {
message.put("time_to_live", timeToLive);
}
if (delayWhileIdle != null && delayWhileIdle) {
message.put("delay_while_idle", true);
}
message.put("priority", "high");
message.put("message_id", messageId);
message.put("data", payload);
return JSONValue.toJSONString(message);
}
/**
* Creates a JSON encoded ACK message for an upstream message received from
* an application.
*
* #param to
* RegistrationId of the device who sent the upstream message.
* #param messageId
* messageId of the upstream message to be acknowledged to CCS.
* #return JSON encoded ack.
*/
public static String createJsonAck(String to, String messageId) {
Map<String, Object> message = new HashMap<String, Object>();
message.put("message_type", "ack");
message.put("to", to);
message.put("message_id", messageId);
return JSONValue.toJSONString(message);
}
/**
* Connects to GCM Cloud Connection Server using the supplied credentials.
*
* #param username
* GCM_SENDER_ID#gcm.googleapis.com
* #param password
* API Key
* #throws XMPPException
*/
public void connect(String username, String password) throws XMPPException {
config = new ConnectionConfiguration(GCM_SERVER, GCM_PORT);
config.setSecurityMode(SecurityMode.enabled);
config.setReconnectionAllowed(true);
config.setRosterLoadedAtLogin(false);
config.setSendPresence(false);
config.setSocketFactory(SSLSocketFactory.getDefault());
// NOTE: Set to true to launch a window with information about packets
// sent and received
config.setDebuggerEnabled(false);
// -Dsmack.debugEnabled=true
XMPPConnection.DEBUG_ENABLED = false;
connection = new XMPPConnection(config);
connection.connect();
connection.addConnectionListener(new ConnectionListener() {
#Override
public void reconnectionSuccessful() {
logger.info("Reconnecting..");
}
#Override
public void reconnectionFailed(Exception e) {
logger.log(Level.INFO, "Reconnection failed.. ", e);
}
#Override
public void reconnectingIn(int seconds) {
logger.log(Level.INFO, "Reconnecting in %d secs", seconds);
}
#Override
public void connectionClosedOnError(Exception e) {
logger.log(Level.INFO, "Connection closed on error.");
}
#Override
public void connectionClosed() {
logger.info("Connection closed.");
}
});
// Handle incoming packets
connection.addPacketListener(new PacketListener() {
#Override
public void processPacket(Packet packet) {
logger.log(Level.INFO, "Received: " + packet.toXML());
Message incomingMessage = (Message) packet;
GcmPacketExtension gcmPacket = (GcmPacketExtension) incomingMessage
.getExtension(GCM_NAMESPACE);
String json = gcmPacket.getJson();
try {
#SuppressWarnings("unchecked")
Map<String, Object> jsonObject = (Map<String, Object>) JSONValue
.parseWithException(json);
// present for "ack"/"nack", null otherwise
Object messageType = jsonObject.get("message_type");
if (messageType == null) {
// Normal upstream data message
handleIncomingDataMessage(jsonObject);
// Send ACK to CCS
String messageId = jsonObject.get("message_id")
.toString();
String from = jsonObject.get("from").toString();
String ack = createJsonAck(from, messageId);
send(ack);
} else if ("ack".equals(messageType.toString())) {
// Process Ack
handleAckReceipt(jsonObject);
} else if ("nack".equals(messageType.toString())) {
// Process Nack
handleNackReceipt(jsonObject);
} else {
logger.log(Level.WARNING,
"Unrecognized message type (%s)",
messageType.toString());
}
} catch (ParseException e) {
logger.log(Level.SEVERE, "Error parsing JSON " + json, e);
} catch (Exception e) {
logger.log(Level.SEVERE, "Couldn't send echo.", e);
}
}
}, new PacketTypeFilter(Message.class));
// Log all outgoing packets
connection.addPacketInterceptor(new PacketInterceptor() {
#Override
public void interceptPacket(Packet packet) {
logger.log(Level.INFO, "Sent: {0}", packet.toXML());
}
}, new PacketTypeFilter(Message.class));
connection.login(username, password);
}
public void writeToFile(String name, String regId) throws IOException {
Map<String, String> regIdMap = readFromFile();
regIdMap.put(name, regId);
PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(
REG_ID_STORE, false)));
for (Map.Entry<String, String> entry : regIdMap.entrySet()) {
out.println(entry.getKey() + "," + entry.getValue());
}
out.println(name + "," + regId);
out.close();
}
public Map<String, String> readFromFile() {
Map<String, String> regIdMap = null;
try {
BufferedReader br = new BufferedReader(new FileReader(REG_ID_STORE));
String regIdLine = "";
regIdMap = new HashMap<String, String>();
while ((regIdLine = br.readLine()) != null) {
String[] regArr = regIdLine.split(",");
regIdMap.put(regArr[0], regArr[1]);
}
br.close();
} catch(IOException ioe) {
}
return regIdMap;
}
public static void main(String [] args) {
final String userName = "USERNAME" + "#gcm.googleapis.com";
final String password = "PASSWORD";
SmackCcsClient ccsClient = new SmackCcsClient();
try {
ccsClient.connect(userName, password);
} catch (XMPPException e) {
e.printStackTrace();
}
}
}
So I figured this out. I changed the following code:
public static void main(String [] args) {
final String userName = "USERNAME" + "#gcm.googleapis.com";
final String password = "PASSWORD";
SmackCcsClient ccsClient = new SmackCcsClient();
try {
ccsClient.connect(userName, password);
} catch (XMPPException e) {
e.printStackTrace();
}
}
}
To the following:
public static void main(String [] args) {
final String userName = "USERNAME" + "#gcm.googleapis.com";
final String password = "PASSWORD";
SmackCcsClient ccsClient = new SmackCcsClient();
try {
ccsClient.connect(userName, password);
} catch (XMPPException e) {
e.printStackTrace();
}
try {
CountDownLatch latch = new CountDownLatch(1);
latch.await();
} catch (InterruptedException e) {
logger.log(Level.SEVERE, "An error occurred while latch was waiting.", e);
}
}
}
}
Related
How to get OAuth2.0 access token using refresh token in scala .
Sample code using the HttpsURLConnection, without any libraries
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.Map;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
public class Sample_OAuth {
public static void main(String[] args) throws Exception {
Sample_OAuth outhDriver = new Sample_OAuth();
outhDriver.sendRefreshTokenRequestWithOnlyHeader(null);
}
/**
* Send refresh token request to 3rd party with client cred either in body or
* request header.
*
* #param conn
* #param mapListTagToMetaDataName
* #return
* #throws Exception
*/
private Map<String, Object> sendRefreshTokenRequestWithOnlyHeader(Map<String, String> mapListTagToMetaDataName)
throws Exception {
String tokenUrl = "https://xyz.snowflakecomputing.com/oauth/token-request";
URL url = new URL(tokenUrl);
StringBuilder stringBuilder = new StringBuilder();
appendURLParam(stringBuilder, "grant_type", "refresh_token", true);
appendURLParam(stringBuilder, "refresh_token", "ver:2-hint:2065896170862-did:1003-ETMsDgAAAYWAOC1cABRBRVMvQ0JDL1BLQ1M1UGFkZGluZwEAABAAEDu3hgX2UvrDbaMif7uC+ygAAADwoZhxL+aOCvvsmNh0wy0FdNGuDRLCtOq7iQTsZPPmfkRZJnkj3nXgKDxTFeFOmty4ej/O6Fsf17HfNvKdLrqfN3V29FkFQ5S+FktFIznTSjtd7+xaMS+sPEAyey2SFfbSyMvrknjq9F+CQZ50H181OO8Ak4v1uW4ON9Q1UBRd9ywM2Yg5g59hPgy90jtAW0DPQ8gvfAwRJCgg2wzV7tXrQ1H2TQhFEkQH418s5pSNB5V6BSW0fFqOUW3O8X4MmHcWcpTbghZ5aga8+dSKSR8jd2KMmfawyXMdkIYdWEsrpcJozuYDAjFwIT1lkqLxqBnuABQIbwjao0KeWXYU3sYanTb0WoR4Ng==",
false);
String clientRequestType = "Basic Auth Header";
return sendOAuthRequestWithHeader(mapListTagToMetaDataName, url, stringBuilder.toString());
}
private static void appendURLParam(StringBuilder stringBuilder, String name, String value, boolean appendAmpersand)
throws UnsupportedEncodingException {
/*
* if (StringUtil.isNullOrEmptyTrim(name) ||
* StringUtil.isNullOrEmptyTrim(value)) { //log.error(String.
* format("Either Auth attr name : %s or value : %s is Null or empty", name,
* value)); return; }
*/
stringBuilder.append(name);
stringBuilder.append("=");
stringBuilder.append(URLEncoder.encode(value, "UTF-8"));
//stringBuilder.append(value);
if (appendAmpersand) {
stringBuilder.append("&");
}
}
private Map<String, Object> sendOAuthRequestWithHeader(Map<String, String> mapListTagToMetaDataName, URL url,
String postBody) throws Exception {
String clientId = "S0RpOorCRUQOqVncoxc8fXUO22A=";
String client_secret = "MHTOaEmQeLB359K+kEAs/+2ow4AcmqD5/ABckC4E2fQ=";
clientId = clientId + ":" + client_secret;
// Should not be MIME-encode since value is used as a HTTP header and MIME adds
// new lines if over 76 characters
String value = java.util.Base64.getEncoder().encodeToString(clientId.getBytes());
return sendOAuthRequest(url, postBody, value);
}
private Map<String, Object> sendOAuthRequest(URL url, String postBody, String authorizationHeader) throws Exception {
HttpsURLConnection urlConn = (HttpsURLConnection) url.openConnection();
urlConn.setRequestMethod("POST");
urlConn.addRequestProperty("Accept", "application/json");
if (notNullOrEmptyTrim(authorizationHeader)) {
urlConn.addRequestProperty("Content-Type", "application/x-www-form-urlencoded");
urlConn.addRequestProperty("Authorization", "Basic " + authorizationHeader);
}
return sendAccessTokenRequest(urlConn, postBody);
}
public Map<String, Object> getToken(String tokenUrl, String requestBody, String authorizationHeader)
throws Exception {
// LOG.info("Request : getToken "+ tokenUrl);
URL url = new URL(tokenUrl);
HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection();
urlConnection.setRequestMethod("POST");
urlConnection.addRequestProperty("Accept", "application/json");
if (null != authorizationHeader && !authorizationHeader.isEmpty()) {
urlConnection.addRequestProperty("Content-Type", "application/x-www-form-urlencoded");
urlConnection.addRequestProperty("Authorization", "Basic " + authorizationHeader);
}
return sendAccessTokenRequest(urlConnection, requestBody);
}
private Map<String, Object> sendAccessTokenRequest(HttpsURLConnection urlConn, String postBody) throws Exception {
// Write to input stream
try {
//optional
enableAllSSLCert(urlConn);
urlConn.setDoOutput(true);
//optional
urlConn.setHostnameVerifier((name, session) -> true);
DataOutputStream outputStream = new DataOutputStream(urlConn.getOutputStream());
outputStream.writeBytes(postBody);
outputStream.flush();
outputStream.close();
int responseCode = urlConn.getResponseCode();
InputStream is = null;
// LOG.info(" response code is:" + responseCode);
if (responseCode >= 200 && responseCode < 400) {
is = urlConn.getInputStream();
} else {
// LOG.error("Error, sendOAuthRequest response code is:" + responseCode);
is = urlConn.getErrorStream();
}
// Read response
String response = null;
if (null != is) {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8));
StringBuilder stringBuilder = new StringBuilder();
while (true) {
String line = bufferedReader.readLine();
if (line == null) {
break;
} else {
stringBuilder.append(line);
}
}
response = stringBuilder.toString();
bufferedReader.close();
}
// Logging in case of error
if (responseCode != 200) {
// LOG.error("Get Token error response : " + response);
}
System.out.println(response);
Map<String, Object> retValue = new HashMap<>();
retValue.put("errorCode", responseCode);
retValue.put("token", response);
return retValue;
} catch (Exception exp) {
// LOG.info(" Exception while getting Access Token:", exp);
throw exp;
}
}
public static boolean notNullOrEmptyTrim(String str) {
return !isNullOrEmptyTrim(str);
}
public static boolean isNullOrEmptyTrim(String s) {
return s == null || s.trim().isEmpty();
}
/**
* Enable https client for all SSL cert
*
* #param urlConn
* #throws NoSuchAlgorithmException
* #throws KeyManagementException
*/
private void enableAllSSLCert(HttpsURLConnection urlConn) throws NoSuchAlgorithmException, KeyManagementException {
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, new TrustManager[] { new TrustAllTrustManager() }, new java.security.SecureRandom());
// Create all-trusting host name verifier
HostnameVerifier allHostsValid = (hostname, session) -> true;
urlConn.setHostnameVerifier(allHostsValid);
}
/**
* Override Trust All trust manager
*/
private class TrustAllTrustManager implements X509TrustManager {
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
public X509Certificate[] getAcceptedIssuers() {
return null;
}
}
}
I know that there are several similar questions that have either been answered or still outstanding, however, for the life of me...
Later Edit 2016-08-25 10:05 CST - Actually, I asked the wrong question.
The question is the following: given that I have both a netty server (taken from DiscardServer example) and a netty client - (see above) what must I do to force the DiscardServer to immediately send the client a request?
I have added an OutboundHandler to the server and to the client.
After looking at both the DiscardServer and PingPongServer examples, there is an external event occurring to kick off all the action. In the case of Discard server, it is originally waiting for a telnet connection, then will transmit whatever was in the telnet msg to the client.
In the case of PingPongServer, the SERVER is waiting on the client to initiate action.
What I want is for the Server to immediately start transmitting after connection with the client. None of the examples from netty seem to do this.
If I have missed something, and someone can point it out, much good karma.
My client:
public final class P4Listener {
static final Logger LOG;
static final String HOST;
static final int PORT;
static final Boolean SSL = Boolean.FALSE;
public static Dto DTO;
static {
LOG = LoggerFactory.getLogger(P4Listener.class);
HOST = P4ListenerProperties.getP4ServerAddress();
PORT = Integer.valueOf(P4ListenerProperties.getListenerPort());
DTO = new Dto();
}
public static String getId() { return DTO.getId(); }
public static void main(String[] args) throws Exception {
final SslContext sslCtx;
if (SSL) {
LOG.info("{} creating SslContext", getId());
sslCtx = SslContextBuilder.forClient().trustManager(InsecureTrustManagerFactory.INSTANCE).build();
} else {
sslCtx = null;
}
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(group).channel(NioSocketChannel.class)
.handler(new LoggingHandler(LogLevel.INFO))
.handler(new P4ListenerInitializer(sslCtx));
// Start the connection attempt.
LOG.debug(" {} starting connection attempt...", getId());
Channel ch = b.connect(HOST, PORT).sync().channel();
// ChannelFuture localWriteFuture = ch.writeAndFlush("ready\n");
// localWriteFuture.sync();
} finally {
group.shutdownGracefully();
}
}
}
public class P4ListenerHandler extends SimpleChannelInboundHandler<String> {
static final Logger LOG = LoggerFactory.getLogger(P4ListenerHandler.class);
static final DateTimeFormatter DTFormatter = DateTimeFormatter.ofPattern("yyyyMMdd-HHMMss.SSS");
static final String EndSOT;
static final String StartSOT;
static final String EOL = "\n";
static final ClassPathXmlApplicationContext AppContext;
static {
EndSOT = P4ListenerProperties.getEndSOT();
StartSOT = P4ListenerProperties.getStartSOT();
AppContext = new ClassPathXmlApplicationContext(new String[] { "applicationContext.xml" });
}
private final RequestValidator rv = new RequestValidator();
private JAXBContext jaxbContext = null;
private Unmarshaller jaxbUnmarshaller = null;
private boolean initialized = false;
private Dto dto;
public P4ListenerHandler() {
dto = new Dto();
}
public Dto getDto() { return dto; }
public String getId() { return getDto().getId(); }
Message convertXmlToMessage(String xml) {
if (xml == null)
throw new IllegalArgumentException("xml message is null!");
try {
jaxbContext = JAXBContext.newInstance(p4.model.xml.request.Message.class, p4.model.xml.request.Header.class,
p4.model.xml.request.Claims.class, p4.model.xml.request.Insurance.class,
p4.model.xml.request.Body.class, p4.model.xml.request.Prescriber.class,
p4.model.xml.request.PriorAuthorization.class,
p4.model.xml.request.PriorAuthorizationSupportingDocumentation.class);
jaxbUnmarshaller = jaxbContext.createUnmarshaller();
StringReader strReader = new StringReader(xml);
Message m = (Message) jaxbUnmarshaller.unmarshal(strReader);
return m;
} catch (JAXBException jaxbe) {
String error = StacktraceUtil.getCustomStackTrace(jaxbe);
LOG.error(error);
throw new P4XMLUnmarshalException("Problems when attempting to unmarshal transmission string: \n" + xml,
jaxbe);
}
}
#Override
public void channelActive(ChannelHandlerContext ctx) {
LOG.debug("{} let server know we are ready", getId());
ctx.writeAndFlush("Ready...\n");
}
/**
* Important - this method will be renamed to
* <code><b>messageReceived(ChannelHandlerContext, I)</b></code> in netty 5.0
*
* #param ctx
* #param msg
*/
#Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
ChannelFuture lastWriteFuture = null;
LOG.debug("{} -- received message: {}", getId(), msg);
Channel channel = ctx.channel();
Message m = null;
try {
if (msg instanceof String && msg.length() > 0) {
m = convertXmlToMessage(msg);
m.setMessageStr(msg);
dto.setRequestMsg(m);
LOG.info("{}: received TIMESTAMP: {}", dto.getId(), LocalDateTime.now().format(DTFormatter));
LOG.debug("{}: received from server: {}", dto.getId(), msg);
/*
* theoretically we have a complete P4(XML) request
*/
final List<RequestFieldError> errorList = rv.validateMessage(m);
if (!errorList.isEmpty()) {
for (RequestFieldError fe : errorList) {
lastWriteFuture = channel.writeAndFlush(fe.toString().concat(EOL));
}
}
/*
* Create DBHandler with message, messageStr, clientIp to get
* dbResponse
*/
InetSocketAddress socketAddress = (InetSocketAddress) channel.remoteAddress();
InetAddress inetaddress = socketAddress.getAddress();
String clientIp = inetaddress.getHostAddress();
/*
* I know - bad form to ask the ApplicationContext for the
* bean... BUT ...lack of time turns angels into demons
*/
final P4DbRequestHandler dbHandler = (P4DbRequestHandler) AppContext.getBean("dbRequestHandler");
// must set the requestDTO for the dbHandler!
dbHandler.setClientIp(clientIp);
dbHandler.setRequestDTO(dto);
//
// build database request and receive response (string)
String dbResponse = dbHandler.submitDbRequest();
/*
* create ResponseHandler and get back response string
*/
P4ResponseHandler responseHandler = new P4ResponseHandler(dto, dbHandler);
String responseStr = responseHandler.decodeDbServiceResponse(dbResponse);
/*
* write response string to output and repeat exercise
*/
LOG.debug("{} -- response to be written back to server:\n {}", dto.getId(), responseStr);
lastWriteFuture = channel.writeAndFlush(responseStr.concat(EOL));
//
LOG.info("{}: response sent TIMESTAMP: {}", dto.getId(), LocalDateTime.now().format(DTFormatter));
} else {
throw new P4EventException(dto.getId() + " -- Message received is not a String");
}
processWriteFutures(lastWriteFuture);
} catch (Throwable t) {
String tError = StacktraceUtil.getCustomStackTrace(t);
LOG.error(tError);
} finally {
if (lastWriteFuture != null) {
lastWriteFuture.sync();
}
}
}
private void processWriteFutures(ChannelFuture writeFuture) throws InterruptedException {
// Wait until all messages are flushed before closing the channel.
if (writeFuture != null) {
writeFuture.sync();
}
}
#Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}
/**
* Creates a newly configured {#link ChannelPipeline} for a new channel.
*/
public class P4ListenerInitializer extends ChannelInitializer<SocketChannel> {
private static final StringDecoder DECODER = new StringDecoder();
private static final StringEncoder ENCODER = new StringEncoder();
private final SslContext sslCtx;
public P4ListenerInitializer(SslContext sslCtx) {
this.sslCtx = sslCtx;
}
#Override
public void initChannel(SocketChannel ch) {
P4ListenerHandler lh = null;
ChannelPipeline pipeline = ch.pipeline();
if (sslCtx != null) {
P4Listener.LOG.info("{} -- constructing SslContext new handler ", P4Listener.getId());
pipeline.addLast(sslCtx.newHandler(ch.alloc(), P4Listener.HOST, P4Listener.PORT));
} else {
P4Listener.LOG.info("{} -- SslContext null; bypassing adding sslCtx.newHandler(ch.alloc(), P4Listener.HOST, P4Listener.PORT) ", P4Listener.getId());
}
// Add the text line codec combination first,
pipeline.addLast(new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
pipeline.addLast(DECODER);
P4Listener.LOG.debug("{} -- added Decoder ", P4Listener.getId());
pipeline.addLast(ENCODER);
P4Listener.LOG.debug("{} -- added Encoder ", P4Listener.getId());
// and then business logic.
pipeline.addLast(lh = new P4ListenerHandler());
P4Listener.LOG.debug("{} -- added P4ListenerHandler: {} ", P4Listener.getId(), lh.getClass().getSimpleName());
}
}
#Sharable
public class P4ListenerOutboundHandler extends ChannelOutboundHandlerAdapter {
static final Logger LOG = LoggerFactory.getLogger(P4ListenerOutboundHandler.class);
private Dto outBoundDTO = new Dto();
public String getId() {return this.outBoundDTO.getId(); }
#Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) {
try {
ChannelFuture lastWrite = ctx.write(Unpooled.copiedBuffer((String) msg, CharsetUtil.UTF_8));
try {
if (lastWrite != null) {
lastWrite.sync();
promise.setSuccess();
}
} catch (InterruptedException e) {
promise.setFailure(e);
e.printStackTrace();
}
} finally {
ReferenceCountUtil.release(msg);
}
}
}
output from client
Just override channelActive(...) on the handler of the server and trigger a write there.
I'm trying to adapt the example provided here for Smack 4.1.0. and getting a little confused.
Specifically I'm struggling to understand what the GcmPacketExtension should now extend, how the constructor should work and how the Providermanager.addExtensionProvider should be updated to tie in with it.
I'm sure someone must have done this before but I can't find any examples
and I seem to be going round in circles using just the documentation.
Any help would be much appreciated, I'm sure the answer is very simple!
Current Code (is compiling but not running):
static {
ProviderManager.addExtensionProvider(GCM_ELEMENT_NAME, GCM_NAMESPACE, new ExtensionElementProvider<ExtensionElement>() {
#Override
public DefaultExtensionElement parse(XmlPullParser parser,int initialDepth) throws org.xmlpull.v1.XmlPullParserException,
IOException {
String json = parser.nextText();
return new GcmPacketExtension(json);
}
});
}
and:
private static final class GcmPacketExtension extends DefaultExtensionElement {
private final String json;
public GcmPacketExtension(String json) {
super(GCM_ELEMENT_NAME, GCM_NAMESPACE);
this.json = json;
}
public String getJson() {
return json;
}
#Override
public String toXML() {
return String.format("<%s xmlns=\"%s\">%s</%s>",
GCM_ELEMENT_NAME, GCM_NAMESPACE,
StringUtils.escapeForXML(json), GCM_ELEMENT_NAME);
}
public Stanza toPacket() {
Message message = new Message();
message.addExtension(this);
return message;
}
}
Current exception:
Exception in thread "main" java.lang.NoClassDefFoundError: de/measite/minidns/DNSCache
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Unknown Source)
at org.jivesoftware.smack.SmackInitialization.loadSmackClass(SmackInitialization.java:213)
at org.jivesoftware.smack.SmackInitialization.parseClassesToLoad(SmackInitialization.java:193)
at org.jivesoftware.smack.SmackInitialization.processConfigFile(SmackInitialization.java:163)
at org.jivesoftware.smack.SmackInitialization.processConfigFile(SmackInitialization.java:148)
at org.jivesoftware.smack.SmackInitialization.<clinit>(SmackInitialization.java:116)
at org.jivesoftware.smack.SmackConfiguration.getVersion(SmackConfiguration.java:96)
at org.jivesoftware.smack.provider.ProviderManager.<clinit>(ProviderManager.java:121)
at SmackCcsClient.<clinit>(SmackCcsClient.java:58)
Caused by: java.lang.ClassNotFoundException: de.measite.minidns.DNSCache
at java.net.URLClassLoader$1.run(Unknown Source)
at java.net.URLClassLoader$1.run(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
... 10 more
OK, so I managed to get it working after much reading and pain, so here is a VERY crude server implementation that actually works. Obviously not for production and feel free to correct anything that's wrong. I'm not saying this is the best way to do it but it does work. It will send a message and receive messages but only shows them in the log.
import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
import org.jivesoftware.smack.ConnectionListener;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.packet.DefaultExtensionElement;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Stanza;
import org.jivesoftware.smack.filter.StanzaFilter;
import org.jivesoftware.smack.provider.ProviderManager;
import org.jivesoftware.smack.tcp.XMPPTCPConnection;
import org.jivesoftware.smack.tcp.XMPPTCPConnectionConfiguration;
import org.jivesoftware.smack.util.StringUtils;
import org.json.simple.JSONValue;
import org.json.simple.parser.ParseException;
import org.xmlpull.v1.XmlPullParser;
import org.jivesoftware.smack.*;
import org.jivesoftware.smack.packet.ExtensionElement;
import org.jivesoftware.smack.provider.ExtensionElementProvider;
import org.jivesoftware.smack.roster.Roster;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.net.ssl.SSLSocketFactory;
/**
* Sample Smack implementation of a client for GCM Cloud Connection Server. This
* code can be run as a standalone CCS client.
*
* <p>For illustration purposes only.
*/
public class SmackCcsClient {
private static final Logger logger = Logger.getLogger("SmackCcsClient");
private static final String GCM_SERVER = "gcm.googleapis.com";
private static final int GCM_PORT = 5235;
private static final String GCM_ELEMENT_NAME = "gcm";
private static final String GCM_NAMESPACE = "google:mobile:data";
private static final String YOUR_PROJECT_ID = "<your ID here>";
private static final String YOUR_API_KEY = "<your API Key here>"; // your API Key
private static final String YOUR_PHONE_REG_ID = "<your test phone's registration id here>";
static {
ProviderManager.addExtensionProvider(GCM_ELEMENT_NAME, GCM_NAMESPACE, new ExtensionElementProvider<ExtensionElement>() {
#Override
public DefaultExtensionElement parse(XmlPullParser parser,int initialDepth) throws org.xmlpull.v1.XmlPullParserException,
IOException {
String json = parser.nextText();
return new GcmPacketExtension(json);
}
});
}
private XMPPTCPConnection connection;
/**
* Indicates whether the connection is in draining state, which means that it
* will not accept any new downstream messages.
*/
protected volatile boolean connectionDraining = false;
/**
* Sends a downstream message to GCM.
*
* #return true if the message has been successfully sent.
*/
public boolean sendDownstreamMessage(String jsonRequest) throws
NotConnectedException {
if (!connectionDraining) {
send(jsonRequest);
return true;
}
logger.info("Dropping downstream message since the connection is draining");
return false;
}
/**
* Returns a random message id to uniquely identify a message.
*
* <p>Note: This is generated by a pseudo random number generator for
* illustration purpose, and is not guaranteed to be unique.
*/
public String nextMessageId() {
return "m-" + UUID.randomUUID().toString();
}
/**
* Sends a packet with contents provided.
*/
protected void send(String jsonRequest) throws NotConnectedException {
Stanza request = new GcmPacketExtension(jsonRequest).toPacket();
connection.sendStanza(request);
}
/**
* Handles an upstream data message from a device application.
*
* <p>This sample echo server sends an echo message back to the device.
* Subclasses should override this method to properly process upstream messages.
*/
protected void handleUpstreamMessage(Map<String, Object> jsonObject) {
// PackageName of the application that sent this message.
String category = (String) jsonObject.get("category");
String from = (String) jsonObject.get("from");
#SuppressWarnings("unchecked")
Map<String, String> payload = (Map<String, String>) jsonObject.get("data");
payload.put("ECHO", "Application: " + category);
// Send an ECHO response back
String echo = createJsonMessage(from, nextMessageId(), payload,
"echo:CollapseKey", null, false);
try {
sendDownstreamMessage(echo);
} catch (NotConnectedException e) {
logger.log(Level.WARNING, "Not connected anymore, echo message is not sent", e);
}
}
/**
* Handles an ACK.
*
* <p>Logs a INFO message, but subclasses could override it to
* properly handle ACKs.
*/
protected void handleAckReceipt(Map<String, Object> jsonObject) {
String messageId = (String) jsonObject.get("message_id");
String from = (String) jsonObject.get("from");
logger.log(Level.INFO, "handleAckReceipt() from: " + from + ",messageId: " + messageId);
}
/**
* Handles a NACK.
*
* <p>Logs a INFO message, but subclasses could override it to
* properly handle NACKs.
*/
protected void handleNackReceipt(Map<String, Object> jsonObject) {
String messageId = (String) jsonObject.get("message_id");
String from = (String) jsonObject.get("from");
logger.log(Level.INFO, "handleNackReceipt() from: " + from + ",messageId: " + messageId);
}
protected void handleControlMessage(Map<String, Object> jsonObject) {
logger.log(Level.INFO, "handleControlMessage(): " + jsonObject);
String controlType = (String) jsonObject.get("control_type");
if ("CONNECTION_DRAINING".equals(controlType)) {
connectionDraining = true;
} else {
logger.log(Level.INFO, "Unrecognized control type: %s. This could happen if new features are " + "added to the CCS protocol.",
controlType);
}
}
/**
* Creates a JSON encoded GCM message.
*
* #param to RegistrationId of the target device (Required).
* #param messageId Unique messageId for which CCS sends an
* "ack/nack" (Required).
* #param payload Message content intended for the application. (Optional).
* #param collapseKey GCM collapse_key parameter (Optional).
* #param timeToLive GCM time_to_live parameter (Optional).
* #param delayWhileIdle GCM delay_while_idle parameter (Optional).
* #return JSON encoded GCM message.
*/
public static String createJsonMessage(String to, String messageId,
Map<String, String> payload, String collapseKey, Long timeToLive,
Boolean delayWhileIdle) {
Map<String, Object> message = new HashMap<String, Object>();
message.put("to", to);
if (collapseKey != null) {
message.put("collapse_key", collapseKey);
}
if (timeToLive != null) {
message.put("time_to_live", timeToLive);
}
if (delayWhileIdle != null && delayWhileIdle) {
message.put("delay_while_idle", true);
}
message.put("message_id", messageId);
message.put("data", payload);
return JSONValue.toJSONString(message);
}
/**
* Creates a JSON encoded ACK message for an upstream message received
* from an application.
*
* #param to RegistrationId of the device who sent the upstream message.
* #param messageId messageId of the upstream message to be acknowledged to CCS.
* #return JSON encoded ack.
*/
protected static String createJsonAck(String to, String messageId) {
Map<String, Object> message = new HashMap<String, Object>();
message.put("message_type", "ack");
message.put("to", to);
message.put("message_id", messageId);
return JSONValue.toJSONString(message);
}
/**
* Connects to GCM Cloud Connection Server using the supplied credentials.
*
* #param senderId Your GCM project number
* #param apiKey API Key of your project
*/
public void connect(String senderId, String apiKey)
throws XMPPException, IOException, SmackException {
XMPPTCPConnectionConfiguration config =
XMPPTCPConnectionConfiguration.builder()
.setServiceName(GCM_SERVER)
.setHost(GCM_SERVER)
.setCompressionEnabled(false)
.setPort(GCM_PORT)
.setConnectTimeout(30000)
.setSecurityMode(SecurityMode.disabled)
.setSendPresence(false)
.setSocketFactory(SSLSocketFactory.getDefault())
.build();
connection = new XMPPTCPConnection(config);
//disable Roster as I don't think this is supported by GCM
Roster roster = Roster.getInstanceFor(connection);
roster.setRosterLoadedAtLogin(false);
logger.info("Connecting...");
connection.connect();
connection.addConnectionListener(new LoggingConnectionListener());
// Handle incoming packets
connection.addAsyncStanzaListener(new MyStanzaListener() , new MyStanzaFilter() );
// Log all outgoing packets
connection.addPacketInterceptor(new MyStanzaInterceptor(), new MyStanzaFilter() );
connection.login(senderId + "#gcm.googleapis.com" , apiKey);
}
private class MyStanzaFilter implements StanzaFilter
{
#Override
public boolean accept(Stanza arg0) {
// TODO Auto-generated method stub
if(arg0.getClass() == Stanza.class )
return true;
else
{
if(arg0.getTo()!= null)
if(arg0.getTo().startsWith(YOUR_PROJECT_ID) )
return true;
}
return false;
}
}
private class MyStanzaListener implements StanzaListener{
#Override
public void processPacket(Stanza packet) {
logger.log(Level.INFO, "Received: " + packet.toXML());
Message incomingMessage = (Message) packet;
GcmPacketExtension gcmPacket =
(GcmPacketExtension) incomingMessage.
getExtension(GCM_NAMESPACE);
String json = gcmPacket.getJson();
try {
#SuppressWarnings("unchecked")
Map<String, Object> jsonObject =
(Map<String, Object>) JSONValue.
parseWithException(json);
// present for "ack"/"nack", null otherwise
Object messageType = jsonObject.get("message_type");
if (messageType == null) {
// Normal upstream data message
handleUpstreamMessage(jsonObject);
// Send ACK to CCS
String messageId = (String) jsonObject.get("message_id");
String from = (String) jsonObject.get("from");
String ack = createJsonAck(from, messageId);
send(ack);
} else if ("ack".equals(messageType.toString())) {
// Process Ack
handleAckReceipt(jsonObject);
} else if ("nack".equals(messageType.toString())) {
// Process Nack
handleNackReceipt(jsonObject);
} else if ("control".equals(messageType.toString())) {
// Process control message
handleControlMessage(jsonObject);
} else {
logger.log(Level.WARNING,
"Unrecognized message type (%s)",
messageType.toString());
}
} catch (ParseException e) {
logger.log(Level.SEVERE, "Error parsing JSON " + json, e);
} catch (Exception e) {
logger.log(Level.SEVERE, "Failed to process packet", e);
}
}
}
private class MyStanzaInterceptor implements StanzaListener
{
#Override
public void processPacket(Stanza packet) {
logger.log(Level.INFO, "Sent: {0}", packet.toXML());
}
}
public static void main(String[] args) throws Exception {
SmackCcsClient ccsClient = new SmackCcsClient();
ccsClient.connect(YOUR_PROJECT_ID, YOUR_API_KEY);
// Send a sample hello downstream message to a device.
String messageId = ccsClient.nextMessageId();
Map<String, String> payload = new HashMap<String, String>();
payload.put("Message", "Ahha, it works!");
payload.put("CCS", "Dummy Message");
payload.put("EmbeddedMessageId", messageId);
String collapseKey = "sample";
Long timeToLive = 10000L;
String message = createJsonMessage(YOUR_PHONE_REG_ID, messageId, payload,
collapseKey, timeToLive, true);
ccsClient.sendDownstreamMessage(message);
logger.info("Message sent.");
//crude loop to keep connection open for receiving messages
while(true)
{;}
}
/**
* XMPP Packet Extension for GCM Cloud Connection Server.
*/
private static final class GcmPacketExtension extends DefaultExtensionElement {
private final String json;
public GcmPacketExtension(String json) {
super(GCM_ELEMENT_NAME, GCM_NAMESPACE);
this.json = json;
}
public String getJson() {
return json;
}
#Override
public String toXML() {
return String.format("<%s xmlns=\"%s\">%s</%s>",
GCM_ELEMENT_NAME, GCM_NAMESPACE,
StringUtils.escapeForXML(json), GCM_ELEMENT_NAME);
}
public Stanza toPacket() {
Message message = new Message();
message.addExtension(this);
return message;
}
}
private static final class LoggingConnectionListener
implements ConnectionListener {
#Override
public void connected(XMPPConnection xmppConnection) {
logger.info("Connected.");
}
#Override
public void reconnectionSuccessful() {
logger.info("Reconnecting..");
}
#Override
public void reconnectionFailed(Exception e) {
logger.log(Level.INFO, "Reconnection failed.. ", e);
}
#Override
public void reconnectingIn(int seconds) {
logger.log(Level.INFO, "Reconnecting in %d secs", seconds);
}
#Override
public void connectionClosedOnError(Exception e) {
logger.info("Connection closed on error.");
}
#Override
public void connectionClosed() {
logger.info("Connection closed.");
}
#Override
public void authenticated(XMPPConnection arg0, boolean arg1) {
// TODO Auto-generated method stub
}
}
}
I also imported the following external JARs:
(they might not all be required but most are!)
json-simple-1.1.1.jar
jxmpp-core-0.4.1.jar
jxmpp-util-cache-0.5.0-alpha2.jar
minidns-0.1.3.jar
commons-logging-1.2.jar
httpclient-4.3.4.jar
xpp3_xpath-1.1.4c.jar
xpp3-1.1.4c.jar
For the Client I used the GCM sample project here. (Scroll to the bottom of the page for the source link)
Hope this helps someone!
[23-Oct-2015] I'm editing this answer for others who are using Gradle ... below is all the dependencies that I needed to get this to compile (add to the bottom of your build.gradle file).
dependencies {
compile 'com.googlecode.json-simple:json-simple:1.1.1'
compile 'org.igniterealtime.smack:smack-java7:4.1.4'
compile 'org.igniterealtime.smack:smack-tcp:4.1.4'
compile 'org.igniterealtime.smack:smack-im:4.1.4'
compile 'org.jxmpp:jxmpp-core:0.5.0-alpha6'
compile 'org.jxmpp:jxmpp-util-cache:0.5.0-alpha6'
}
There are two reference implementations provided by Google for the GCM Cloud Connection Server (XMPP endpoint).
Both are here:
https://github.com/googlesamples/friendlyping/tree/master/server
The Java server uses the Smack XMPP library.
The Go server uses Google's own go-gcm library - https://github.com/google/go-gcm
The Go server is also used in the GCM Playground example - https://github.com/googlesamples/gcm-playground - so it seems like the Go server may be preferred by Google. Being Go, it can be deployed without any dependencies, which is an advantage over the Java server.
I'm new to socket programming and Netty framework. I was trying to modify the Echo Server example so that the message is not sent from client as soon as a message is received, but a call from another thread would trigger the client send a message to the server.
The problem is, the server does not get the message unless the client sends it from readChannel or MessageReceived or channelActive which are where the server is specified with a parameter (ChannelHandlerContext). I couldn't manage to find a way to save the server channel and send a message later and repeatedly.
Here's my Client Handler code;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
public class EchoClientHandler extends ChannelHandlerAdapter {
ChannelHandlerContext server;
#Override
public void channelActive(ChannelHandlerContext ctx) {
this.server = ctx;
}
#Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
// ctx.write(msg); //not
}
#Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
//ctx.flush();
}
public void externalcall(String msg) throws Exception {
if(server!=null){
server.writeAndFlush("[" + "] " + msg + '\n');
}
}
#Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
// Close the connection when an exception is raised.
ctx.close();
}
}
When Client creates the handler, it also creates a thread with a "SourceGenerator" object which gets the handler as parameter so as to call the externalcall() method.
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
/**
* Sends one message when a connection is open and echoes back any received
* data to the server. Simply put, the echo client initiates the ping-pong
* traffic between the echo client and server by sending the first message to
* the server.
*/
public class EchoClient {
private final String host;
private final int port;
public EchoClient(String host, int port, int firstMessageSize) {
this.host = host;
this.port = port;
}
public void run() throws Exception {
// Configure the client.
EventLoopGroup group = new NioEventLoopGroup();
final EchoClientHandler x = new EchoClientHandler();
SourceGenerator sg = new SourceGenerator(x);
new Thread(sg).start();
try {
Bootstrap b = new Bootstrap();
b.group(group)
.channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.handler(new ChannelInitializer<SocketChannel>() {
#Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(x);
}
});
// Start the client.
ChannelFuture f = b.connect(host, port).sync();
// Wait until the connection is closed.
f.channel().closeFuture().sync();
} finally {
// Shut down the event loop to terminate all threads.
group.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
// Print usage if no argument is specified.
if (args.length < 2 || args.length > 3) {
System.err.println(
"Usage: " + EchoClient.class.getSimpleName() +
" <host> <port> [<first message size>]");
return;
}
// Parse options.
final String host = args[0];
final int port = Integer.parseInt(args[1]);
final int firstMessageSize;
if (args.length == 3) {
firstMessageSize = Integer.parseInt(args[2]);
} else {
firstMessageSize = 256;
}
new EchoClient(host, port, firstMessageSize).run();
}
}
and the SourceGenerator class;
public class SourceGenerator implements Runnable {
public String dat;
public EchoClientHandler asd;
public SourceGenerator(EchoClientHandler x) {
asd = x;
System.out.println("initialized source generator");
dat = "";
}
#Override
public void run() {
try{
while(true){
Thread.sleep(2000);
dat += "a";
asd.externalcall(dat);
System.out.print("ha!");
}
}catch(Exception e){
e.printStackTrace();
}
}
}
Thanks in advance!
If you want to write a String you need to have the StringEncoder in the ChannelPipeline.
Otherwise you can only send ByteBuf instances.
I'm trying to upload an image from the signup form using InscriptionForm.java to check the respectieve fields. The upload has no problem (Thanks to BalusC's tutorials), but, when I try to display the uploaded image, I can't; refering to the HTTP network monitor I got 404 error as response. Also, when I try to enter the above link into the browser's adress bar http://localhost:8080/projetForum/images/bd/Id_21082013184506.png I've got a 404 error.
InscriptionForm.java
public final class InscriptionForm {
private static final String CHAMP_EMAIL = "email";
private static final String CHAMP_PASS = "motdepasse";
private static final String CHAMP_CONF = "confirmation";
private static final String CHAMP_NOM = "nom";
private static final String CHAMP_DESC = "description";
private static final String CHAMP_LOC = "localisation";
private static final String CHAMP_SW = "siteweb";
public static final String CHAMP_IMAGE = "avatar";
public static final String CHAMP_JOURDENAISS = "jourdenaissance";
public static final String CHAMP_MOISDENAISS = "moisdenaissance";
public static final String CHAMP_ANNEEDENAISS = "anneedenaissance";
public static final String CHAMP_DATEDENAISS = "datedenaissance";
public static final int TAILLE_TAMPON = 10240; // 10 ko
public static final String CHEMIN = "E:\\Bibliothèque logicielle\\workspace\\projetForum\\WebContent\\images\\bd\\";
private String resultat;
private static Map<String, String> erreurs = new HashMap<String, String>();
public String getResultat() {
return resultat;
}
public Map<String, String> getErreurs() {
return erreurs;
}
public Utilisateur inscrireUtilisateur(HttpServletRequest request) {
String email = getValeurChamp(request, CHAMP_EMAIL);
String motDePasse = getValeurChamp(request, CHAMP_PASS);
String confirmation = getValeurChamp(request, CHAMP_CONF);
String nom = getValeurChamp(request, CHAMP_NOM);
String description = getValeurChamp(request, CHAMP_DESC);
String localisation = getValeurChamp(request, CHAMP_LOC);
String siteweb = getValeurChamp(request, CHAMP_SW);
String image = getValeurChamp(request, CHAMP_IMAGE);
String jourdenaissance = getValeurChamp(request, CHAMP_JOURDENAISS);
String moisdenaissance = getValeurChamp(request, CHAMP_MOISDENAISS);
String anneedenaissance = getValeurChamp(request, CHAMP_ANNEEDENAISS);
Integer datedenaissance = null;
try {
validationEmail(email);
} catch (Exception e) {
setErreur(CHAMP_EMAIL, e.getMessage());
}
try {
validationMotsDePasse(motDePasse, confirmation);
} catch (Exception e) {
setErreur(CHAMP_PASS, e.getMessage());
}
try {
validationNom(nom);
} catch (Exception e) {
setErreur(CHAMP_NOM, e.getMessage());
}
try {
image = validationImage(request, CHEMIN);
} catch (Exception e) {
setErreur(CHAMP_IMAGE, e.getMessage());
}
if (!jourdenaissance.equals("defaut")
&& !moisdenaissance.equals("defaut")
&& !anneedenaissance.equals("defaut")) {
try {
validationDateDeNaiss(Integer.parseInt(jourdenaissance),
Integer.parseInt(moisdenaissance),
Integer.parseInt(anneedenaissance));
} catch (Exception e) {
setErreur(CHAMP_DATEDENAISS, e.getMessage());
}
datedenaissance = Integer.parseInt((jourdenaissance + ""
+ moisdenaissance + "" + anneedenaissance));
}
if (jourdenaissance.equals("defaut")
&& moisdenaissance.equals("defaut")
&& anneedenaissance.equals("defaut")) {
} else {
setErreur(CHAMP_DATEDENAISS,
"Merci de vérifier votre date de naissance.");
}
Utilisateur utilisateur = new Utilisateur(email, motDePasse, nom,
localisation, siteweb, description, datedenaissance, image);
if (erreurs.isEmpty()) {
resultat = "Succès de l'inscription.";
createORupdate(utilisateur, request);
} else {
resultat = "Échec de l'inscription.";
}
return utilisateur;
}
private String validationImage(HttpServletRequest request, String chemin)
throws Exception {
File uploadFilePath = new File(chemin);
// Validate file.
Object fileObject = request.getAttribute("avatar");
if (fileObject == null) {
// No file uploaded.
throw new Exception("Please select file to upload.");
} else if (fileObject instanceof FileUploadException) {
// File upload is failed.
FileUploadException fileUploadException = (FileUploadException) fileObject;
throw new Exception(fileUploadException.getMessage());
}
// If there are no errors, proceed with writing file.
FileItem fileItem = (FileItem) fileObject;
// Get file name from uploaded file and trim path from it.
// Some browsers (e.g. IE, Opera) also sends the path, which is
// completely irrelevant.
String fileName = FilenameUtils.getName(fileItem.getName());
// Prepare filename prefix and suffix for an unique filename in upload
// folder.
String prefix = FilenameUtils.getBaseName(fileName) + "_";
String suffix = "." + FilenameUtils.getExtension(fileName);
File file = null;
try {
// Prepare unique local file based on file name of uploaded file.
file = File.createTempFile(prefix, suffix, uploadFilePath);
// Write uploaded file to local file.
fileItem.write(file);
} catch (Exception e) {
// Can be thrown by uniqueFile() and FileItem#write().
throw new Exception(e.getMessage());
}
return file.getName();
}
private void setErreur(String champ, String message) {
erreurs.put(champ, message);
}
private static String getValeurChamp(HttpServletRequest request,
String nomChamp) {
String valeur = request.getParameter(nomChamp);
if (valeur == null || valeur.trim().length() == 0) {
return null;
} else {
return valeur;
}
}
private static void createORupdate(Utilisateur u, HttpServletRequest request) {
Session s = HibernateUtils.getSession();
Transaction tx = s.beginTransaction();
Query q = s
.createQuery("from Utilisateur where Utilisateur_email = :email");
q.setString("email", u.getEmail());
Utilisateur userUpdt = (Utilisateur) q.uniqueResult();
if (userUpdt != null) {
userUpdt.setNom(u.getNom());
userUpdt.setEmail(u.getEmail());
userUpdt.setSiteweb(u.getSiteweb());
userUpdt.setLocalisation(u.getLocalisation());
userUpdt.setDescription(u.getDescription());
s.update(userUpdt);
} else {
SimpleDateFormat formater = new SimpleDateFormat(
"dd-MM-yyyy hh:mm:ss");
Date aujourdhui = new Date();
u.setDateInscrit(formater.format(aujourdhui));
s.persist(u);
}
tx.commit();
}
private void validationEmail(String email) throws Exception {
UtilisateurDAO<Utilisateur, String> ud = new UtilisateurDAO<Utilisateur, String>();
if (ud.findByID(email) != null)
throw new Exception("Adresse mail déjà utilisée.");
else if (email == null || ud.findByID(email) != null
|| !email.matches("([^.#]+)(\\.[^.#]+)*#([^.#]+\\.)+([^.#]+)")) {
throw new Exception("Merci de saisir une adresse mail valide.");
}
}
private void validationDateDeNaiss(Integer jj, Integer mm, Integer aaaa)
throws Exception {
switch (mm) {
case 2:
if (jj > 28 && ((aaaa / 4) % 100 == 0 && aaaa % 400 == 0))
throw new Exception(
"Merci de vérifier votre date de naissance.");
break;
case 4:
if (jj == 31)
throw new Exception(
"Merci de vérifier votre date de naissance.");
break;
case 6:
if (jj == 31)
throw new Exception(
"Merci de vérifier votre date de naissance.");
break;
case 9:
if (jj == 31)
throw new Exception(
"Merci de vérifier votre date de naissance.");
break;
case 11:
if (jj == 31)
throw new Exception(
"Merci de vérifier votre date de naissance.");
break;
}
}
private void validationMotsDePasse(String motDePasse, String confirmation)
throws Exception {
if (motDePasse != null && confirmation != null) {
if (!motDePasse.equals(confirmation)) {
throw new Exception(
"Les mots de passe entrés sont différents, merci de les saisir à nouveau.");
} else if (motDePasse.length() < 6) {
throw new Exception(
"Les mots de passe doivent contenir au moins 6 caractères.");
}
} else {
throw new Exception(
"Merci de saisir et confirmer votre mot de passe.");
}
}
private static void validationNom(String nom) throws Exception {
ConfigFDAO<ConfigF, Integer> cfd = new ConfigFDAO<ConfigF, Integer>();
UtilisateurDAO<Utilisateur, String> ud = new UtilisateurDAO<Utilisateur, String>();
if (ud.findByNom(nom) != null)
throw new Exception("Nom d'utilisateur déjà utilisée.");
else if (nom == null
|| nom.length() < cfd.findByID(0).getPseudominsize()
|| nom.length() > cfd.findByID(0).getPseudomaxsize()) {
throw new Exception("Le nom d'utilisateur doit contenir au moins "
+ cfd.findByID(0).getPseudominsize() + " et au maximum "
+ cfd.findByID(0).getPseudomaxsize() + " caractères.");
}
}
private static String getNomFichier(Part part) {
for (String cd : part.getHeader("content-disposition").split(";")) {
if (cd.trim().startsWith("filename")) {
String filename = cd.substring(cd.indexOf('=') + 1).trim()
.replace("\"", "");
return filename.substring(filename.lastIndexOf('/') + 1)
.substring(filename.lastIndexOf('\\') + 1);
}
}
return null;
}
}
MultipartFilter.java
public class MultipartFilter implements Filter {
// Init
// ---------------------------------------------------------------------------------------
private long maxFileSize;
// Actions
// ------------------------------------------------------------------------------------
/**
* Configure the 'maxFileSize' parameter.
*
* #throws ServletException
* If 'maxFileSize' parameter value is not numeric.
* #see javax.servlet.Filter#init(javax.servlet.FilterConfig)
*/
public void init(FilterConfig filterConfig) throws ServletException {
// Configure maxFileSize.
String maxFileSize = filterConfig.getInitParameter("maxFileSize");
if (maxFileSize != null) {
if (!maxFileSize.matches("^\\d+$")) {
throw new ServletException(
"MultipartFilter 'maxFileSize' is not numeric.");
}
this.maxFileSize = Long.parseLong(maxFileSize);
}
}
/**
* Check the type request and if it is a HttpServletRequest, then parse the
* request.
*
* #throws ServletException
* If parsing of the given HttpServletRequest fails.
* #see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest,
* javax.servlet.ServletResponse, javax.servlet.FilterChain)
*/
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws ServletException, IOException {
// Check type request.
if (request instanceof HttpServletRequest) {
// Cast back to HttpServletRequest.
HttpServletRequest httpRequest = (HttpServletRequest) request;
// Parse HttpServletRequest.
HttpServletRequest parsedRequest = parseRequest(httpRequest);
// Continue with filter chain.
chain.doFilter(parsedRequest, response);
} else {
// Not a HttpServletRequest.
chain.doFilter(request, response);
}
}
/**
* #see javax.servlet.Filter#destroy()
*/
public void destroy() {
// I am a boring method.
}
// Helpers
// ------------------------------------------------------------------------------------
/**
* Parse the given HttpServletRequest. If the request is a multipart
* request, then all multipart request items will be processed, else the
* request will be returned unchanged. During the processing of all
* multipart request items, the name and value of each regular form field
* will be added to the parameterMap of the HttpServletRequest. The name and
* File object of each form file field will be added as attribute of the
* given HttpServletRequest. If a FileUploadException has occurred when the
* file size has exceeded the maximum file size, then the
* FileUploadException will be added as attribute value instead of the
* FileItem object.
*
* #param request
* The HttpServletRequest to be checked and parsed as multipart
* request.
* #return The parsed HttpServletRequest.
* #throws ServletException
* If parsing of the given HttpServletRequest fails.
*/
#SuppressWarnings("unchecked")
// ServletFileUpload#parseRequest() does not return generic type.
private HttpServletRequest parseRequest(HttpServletRequest request)
throws ServletException {
// Check if the request is actually a multipart/form-data request.
if (!ServletFileUpload.isMultipartContent(request)) {
// If not, then return the request unchanged.
return request;
}
// Prepare the multipart request items.
// I'd rather call the "FileItem" class "MultipartItem" instead or so.
// What a stupid name ;)
List<FileItem> multipartItems = null;
try {
// Parse the multipart request items.
multipartItems = new ServletFileUpload(new DiskFileItemFactory())
.parseRequest(request);
// Note: we could use ServletFileUpload#setFileSizeMax() here, but
// that would throw a
// FileUploadException immediately without processing the other
// fields. So we're
// checking the file size only if the items are already parsed. See
// processFileField().
} catch (FileUploadException e) {
throw new ServletException("Cannot parse multipart request: "
+ e.getMessage());
}
// Prepare the request parameter map.
Map<String, String[]> parameterMap = new HashMap<String, String[]>();
// Loop through multipart request items.
for (FileItem multipartItem : multipartItems) {
if (multipartItem.isFormField()) {
// Process regular form field (input
// type="text|radio|checkbox|etc", select, etc).
processFormField(multipartItem, parameterMap);
} else {
// Process form file field (input type="file").
processFileField(multipartItem, request);
}
}
// Wrap the request with the parameter map which we just created and
// return it.
return wrapRequest(request, parameterMap);
}
/**
* Process multipart request item as regular form field. The name and value
* of each regular form field will be added to the given parameterMap.
*
* #param formField
* The form field to be processed.
* #param parameterMap
* The parameterMap to be used for the HttpServletRequest.
*/
private void processFormField(FileItem formField,
Map<String, String[]> parameterMap) {
String name = formField.getFieldName();
String value = formField.getString();
String[] values = parameterMap.get(name);
if (values == null) {
// Not in parameter map yet, so add as new value.
parameterMap.put(name, new String[] { value });
} else {
// Multiple field values, so add new value to existing array.
int length = values.length;
String[] newValues = new String[length + 1];
System.arraycopy(values, 0, newValues, 0, length);
newValues[length] = value;
parameterMap.put(name, newValues);
}
}
/**
* Process multipart request item as file field. The name and FileItem
* object of each file field will be added as attribute of the given
* HttpServletRequest. If a FileUploadException has occurred when the file
* size has exceeded the maximum file size, then the FileUploadException
* will be added as attribute value instead of the FileItem object.
*
* #param fileField
* The file field to be processed.
* #param request
* The involved HttpServletRequest.
*/
private void processFileField(FileItem fileField, HttpServletRequest request) {
if (fileField.getName().length() <= 0) {
// No file uploaded.
request.setAttribute(fileField.getFieldName(), null);
} else if (maxFileSize > 0 && fileField.getSize() > maxFileSize) {
// File size exceeds maximum file size.
request.setAttribute(fileField.getFieldName(),
new FileUploadException(
"File size exceeds maximum file size of "
+ maxFileSize + " bytes."));
// Immediately delete temporary file to free up memory and/or disk
// space.
fileField.delete();
} else {
// File uploaded with good size.
request.setAttribute(fileField.getFieldName(), fileField);
}
}
// Utility (may be refactored to public utility class)
// ----------------------------------------
/**
* Wrap the given HttpServletRequest with the given parameterMap.
*
* #param request
* The HttpServletRequest of which the given parameterMap have to
* be wrapped in.
* #param parameterMap
* The parameterMap to be wrapped in the given
* HttpServletRequest.
* #return The HttpServletRequest with the parameterMap wrapped in.
*/
private static HttpServletRequest wrapRequest(HttpServletRequest request,
final Map<String, String[]> parameterMap) {
return new HttpServletRequestWrapper(request) {
public Map<String, String[]> getParameterMap() {
return parameterMap;
}
public String[] getParameterValues(String name) {
return parameterMap.get(name);
}
public String getParameter(String name) {
String[] params = getParameterValues(name);
return params != null && params.length > 0 ? params[0] : null;
}
public Enumeration<String> getParameterNames() {
return Collections.enumeration(parameterMap.keySet());
}
};
}
}
Thanks in advance.