Does anyone know if there is a Java client framework / API to call Windows phone push notification service? I know there is a JavaPNS project, which is specific to Apple PNS.
I am looking for something similar to that, but specific to Windows phone.
Any help?
Java-mpns seems to be close to what you are looking for.
https://github.com/notnoop/java-mpns
I've developed a solution with Spring and Apache Commons HTTP Client for sending RAW messages using WNS (Windows Notification Push Service)
Insert this dependencies on your pom.xml:
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.2.1</version>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
<version>1.9.12</version>
</dependency>
And the following lines on applicationContext.xml
<bean class="java.lang.String" id="authenticateUrlPostWNS">
<constructor-arg>
<value>https://login.live.com/accesstoken.srf</value>
</constructor-arg>
</bean>
<util:map id="authenticateWNSHeaders">
<entry key="Content-Type" value="application/x-www-form-urlencoded" />
</util:map>
<util:map id="authenticateWNSPostParams">
<entry key="grant_type" value="client_credentials" />
<entry key="client_id" value="ms-app://" />
<entry key="client_secret" value="" />
<entry key="scope" value="notify.windows.com" />
</util:map>
<util:map id="sendMessageWNSHeaders">
<entry key="X-WNS-Type" value="wns/raw" />
<entry key="Content-Type" value="application/octet-stream" />
<entry key="X-WNS-RequestForStatus" value="true" />
<entry key="X-NotificationClass" value="3" />
</util:map>
*Remember to fill client_id and client_secret
In my project, I've divided the implementation in four classes, which I'm going to describe below
OAuthToken is used to store and format the access token:
public class OAuthToken {
private String token;
private String tokenType;
public OAuthToken(String token, String tokenType) {
super();
this.token = token;
this.tokenType = tokenType;
}
public String getAuthorization() {
return StringUtils.capitalize(tokenType) + " " + token;
}
}
The HttpClientFactory (shown bellow) is used to create a new client whenever you send a message. If you reuse the same HttpClient to send a message, WNS time-out after the second message sent. I don't know why that happens, but it solved when I stopped reusing the client.
@Service
public class HttpClientFactory {
private static final int TIMEOUT = 20 * 1000;
public HttpClient create() {
SystemDefaultHttpClient httpClient = new SystemDefaultHttpClient();
httpClient.getParams().setIntParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, TIMEOUT);
httpClient.getParams().setIntParameter(CoreConnectionPNames.SO_TIMEOUT, TIMEOUT);
httpClient.setHttpRequestRetryHandler(new DefaultHttpRequestRetryHandler(NumberUtils.INTEGER_ZERO, false));
return httpClient;
}
}
This is a base class, as you wish, you can pull down all code to WindowsPushNotificationMediator
public abstract class AbstractCloudMediator {
private static final Logger LOG = Logger.getLogger(AbstractCloudMediator.class.getSimpleName());
@Autowired
private HttpClientFactory clientFactory;
@Autowired
protected ObjectMapper mapper;
public abstract boolean sendMessage(Jogador destinatario, Action mensagem);
protected String postToString(HttpPost post) throws IOException, ClientProtocolException {
HttpResponse response = executeMethod(post);
return responseToString(response);
}
protected String responseToString(HttpResponse response) throws IOException {
InputStream conteudoResposta = response.getEntity().getContent();
try {
return IOUtils.toString(conteudoResposta);
} finally {
IOUtils.closeQuietly(conteudoResposta);
}
}
protected HttpResponse executeMethod(HttpPost post) throws IOException, ClientProtocolException {
LOG.info("posting to... " + post);
return clientFactory.create().execute(post);
}
}
The next class should do the main work, but remember to create a cloudMessagingDAO
to retrieve and store your access token.
You should replace the class Jogador
for another class that contains the client URL, used to send a message to a Windows Phone device.
@Service(SharedConstants.WINDOWS_CLOUD_BEAN)
public class WindowsPushNotificationMediator extends AbstractCloudMediator { // NO_UCD (test only)
private static final Logger LOG = Logger.getLogger(WindowsPushNotificationMediator.class.getName());
private static final String KEY_ACCESS_TOKEN = "access_token";
private static final String KEY_TOKEN_TYPE = "token_type";
@Resource(name = "authenticateWNSHeaders")
private Map<String, String> authenticateWNSHeaders;
@Resource(name = "authenticateWNSPostParams")
private Map<String, String> authenticateWNSPostParams;
@Resource(name = "sendMessageWNSHeaders")
private Map<String, String> sendMessageWNSHeaders;
@Autowired
@Qualifier("authenticateUrlPostWNS")
private String authenticateUrlPostWNS;
@Autowired
private CloudMessagingDAO cloudMessagingDAO;
private OAuthToken oathToken;
@Override
public boolean sendMessage(Jogador destinatario, Action mensagem) {
try {
OAuthToken token = getToken();
String jsonString = mapper.writeValueAsString(mensagem);
StringEntity entity = new StringEntity(jsonString, Consts.UTF_8);
return sendMessage(destinatario, entity, token);
} catch (IOException e) {
LOG.log(Level.SEVERE, e.getMessage(), e);
throw new RuntimeException(e);
}
}
private boolean sendMessage(Jogador destinatario, HttpEntity entity, OAuthToken token) throws IOException {
HttpPost post = new HttpPost(destinatario.getCloudMessagingInfo());// this is the client url
post.addHeader("Authorization", token.getAuthorization());
addPostHeaders(post, sendMessageWNSHeaders);
post.setEntity(entity);
HttpResponse response = executeMethod(post);
if (response.getStatusLine().getStatusCode() == HttpStatus.SC_UNAUTHORIZED) {
return sendMessage(destinatario, entity, getNewToken());
}
Header[] allHeaders = response.getAllHeaders();
StringBuilder builder = new StringBuilder();
for (Header header : allHeaders) {
builder.append(header.getName() + ": " + header.getValue());
builder.append('\n');
}
LOG.info(builder.toString());
return response.getStatusLine().getStatusCode() == HttpStatus.SC_OK;
}
private void addPostHeaders(HttpPost post, Map<String, String> postHeaders) {
for (String key : postHeaders.keySet()) {
post.addHeader(key, postHeaders.get(key));
}
}
private OAuthToken getToken() throws IOException {
if (oathToken == null) {
//You should store your access token, so you can reuse it until it expires
String token = cloudMessagingDAO.getValue(KEY_ACCESS_TOKEN);
String tokenType = cloudMessagingDAO.getValue(KEY_TOKEN_TYPE);
if (StringUtils.isNotBlank(token) && StringUtils.isNotBlank(tokenType)) {
return oathToken = new OAuthToken(token, tokenType);
}
return getNewToken();
}
return oathToken;
}
private OAuthToken getNewToken() throws IOException {
HttpPost post = new HttpPost(authenticateUrlPostWNS);
addPostHeaders(post, authenticateWNSHeaders);
List<NameValuePair> params = new ArrayList<>();
for (String key : authenticateWNSPostParams.keySet()) {
params.add(new BasicNameValuePair(key, authenticateWNSPostParams.get(key)));
}
post.setEntity(new UrlEncodedFormEntity(params));
HttpResponse response = executeMethod(post);
String conteudo = responseToString(response);
LOG.info(conteudo);
if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
throw new NegocioException("Falha ao autenticar no serviço: " + conteudo);
}
@SuppressWarnings("unchecked")
Map<String, String> resultMap = mapper.readValue(conteudo, HashMap.class);
cloudMessagingDAO.setValues(resultMap);
return oathToken = new OAuthToken(resultMap.get(KEY_ACCESS_TOKEN), resultMap.get(KEY_TOKEN_TYPE));
}
}