Java tutorial
/** * Copyright (C) 2015 Open Whisper Systems * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package net.simondieterle.wns.server; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.annotations.VisibleForTesting; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; import com.nurkiewicz.asyncretry.AsyncRetryExecutor; import com.nurkiewicz.asyncretry.RetryContext; import com.nurkiewicz.asyncretry.RetryExecutor; import com.nurkiewicz.asyncretry.function.RetryCallable; import net.simondieterle.wns.server.internal.WnsResponseHeader; import net.simondieterle.wns.server.messages.elements.WNSOAuthError; import net.simondieterle.wns.server.messages.elements.WnsOAuthToken; import org.apache.http.HttpResponse; import org.apache.http.NameValuePair; import org.apache.http.Header; import org.apache.http.client.HttpClient; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpPost; import org.apache.http.concurrent.FutureCallback; import org.apache.http.entity.ContentType; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.nio.client.CloseableHttpAsyncClient; import org.apache.http.impl.nio.client.HttpAsyncClients; import org.apache.http.message.BasicNameValuePair; import org.apache.http.util.EntityUtils; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.Socket; import java.util.ArrayList; import java.util.List; import java.util.concurrent.*; /** * The main interface to sending WNS messages. Thread safe. * * @author Simon Dieterle */ public class Sender {// TODO: private final CloseableHttpAsyncClient client; private final RetryExecutor executor; private final RetryExecutor authExecutor; private static final String SCOPE = "notify.windows.com"; private static final String GRANT_TYPE_CLIENT_CREDENTIALS = "client_credentials"; private static final String AUTHENTICATION_URI = "https://login.live.com/accesstoken.srf"; private final String clientId; private final String clientSecret; private WnsOAuthToken accessToken; private static void printResponse(HttpResponse response, String body) { System.out.println("=========STATUS======"); System.out.println(response.getStatusLine()); System.out.println("=========HEADER======"); Header[] headers = response.getAllHeaders(); for (Header header : headers) { System.out.println(header.getName() + ":" + header.getValue()); } System.out.println("=========BODY======"); System.out.println(body); System.out.println("=========END======="); } public Sender(String id, String secret) { this(id, secret, 10); } @VisibleForTesting public Sender(String id, String secret, int retryCount) { ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); this.clientId = id; this.clientSecret = secret; this.client = HttpAsyncClients.custom().setMaxConnTotal(100).setMaxConnPerRoute(10).build(); this.authExecutor = new AsyncRetryExecutor(scheduler).retryOn(ServerFailedException.class) .withExponentialBackoff(10, 2.0).withMaxRetries(5).withUniformJitter().withMaxDelay(60000); this.executor = new AsyncRetryExecutor(scheduler).retryOn(ServerFailedException.class) .retryOn(TimeoutException.class).retryOn(IOException.class).withExponentialBackoff(100, 2.0) .withUniformJitter().withMaxDelay(4000).withMaxRetries(retryCount); this.client.start(); } public void setAuthToken(WnsOAuthToken token) { this.accessToken = token; } public ListenableFuture<WnsOAuthToken> auth() { return auth(AUTHENTICATION_URI); } public ListenableFuture<WnsOAuthToken> auth(final String url) { return authExecutor.getFutureWithRetry(new RetryCallable<ListenableFuture<WnsOAuthToken>>() { @Override public ListenableFuture<WnsOAuthToken> call(RetryContext context) throws Exception { SettableFuture<WnsOAuthToken> future = SettableFuture.create(); HttpPost request = new HttpPost(url); List<NameValuePair> urlParameters = new ArrayList<>(); urlParameters.add(new BasicNameValuePair("grant_type", GRANT_TYPE_CLIENT_CREDENTIALS)); urlParameters.add(new BasicNameValuePair("client_id", clientId)); urlParameters.add(new BasicNameValuePair("client_secret", clientSecret)); urlParameters.add(new BasicNameValuePair("scope", SCOPE)); request.setEntity(new UrlEncodedFormEntity(urlParameters)); client.execute(request, new AuthResponseHandler(future)); return future; } }); } /** * Asynchronously send a message. * * @param message The message to send. * @return A future. */ public ListenableFuture<Result> send(String url, Message message) { return send(url, message, null); } /** * Asynchronously send a message with a context to be passed in the future result. * * @param message The message to send. * @param requestContext An opaque context to include the future result. * @return The future. */ public ListenableFuture<Result> send(final String url, final Message message, final Object requestContext) { if (accessToken == null) { System.out.println("sucks!!!!!!!!!!!!"); return null; } // not yet able to send return executor.getFutureWithRetry(new RetryCallable<ListenableFuture<Result>>() { @Override public ListenableFuture<Result> call(RetryContext context) throws Exception { SettableFuture<Result> future = SettableFuture.create(); HttpPost request = new HttpPost(url); request.setHeader("Authorization", "Bearer " + accessToken.access_token); request.setHeader("X-WNS-Type", message.getType().toString()); if (message.getCachePolicy() != null) request.setHeader("X-WNS-Cache-Policy", message.getCachePolicy().toString()); if (message.getRequestForStatus() != null) request.setHeader("X-WNS-RequestForStatus", message.getRequestForStatus().toString()); if (message.getTag() != null) request.setHeader("X-WNS-Tag", message.getTag().toString()); if (message.getTtl() != null) request.setHeader("X-WNS-TTL", message.getTtl().toString()); if (message.getSuppressPopup() != null) request.setHeader("X-WNS-SuppressPopup", message.getSuppressPopup().toString()); if (message.getGroup() != null) request.setHeader("X-WNS-Group", message.getGroup().toString()); if (message.getMatch() != null) request.setHeader("X-WNS-Match", message.getMatch().toString()); // set appropriate content types StringEntity entity; if (message.getType() == Message.Type.RAW) { entity = new StringEntity(message.serialize(), ContentType.parse("application/octet-stream")); } else { entity = new StringEntity(message.serialize(), ContentType.parse("text/xml")); } request.setEntity(entity); client.execute(request, new ResponseHandler(future, requestContext)); return future; } }); } /** * Shut down all existing HTTP connections. * @throws IOException */ public void stop() throws IOException { this.client.close(); } private static final class AuthResponseHandler implements FutureCallback<HttpResponse> { private static final ObjectMapper objectMapper = new ObjectMapper(); static { objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); } private final SettableFuture<WnsOAuthToken> future; public AuthResponseHandler(SettableFuture<WnsOAuthToken> future) { this.future = future; } @Override public void completed(HttpResponse result) { try { String responseBody = EntityUtils.toString(result.getEntity()); WnsResponseHeader responseHeader = new WnsResponseHeader(result); switch (result.getStatusLine().getStatusCode()) { case 400: future.setException(parseError(responseBody, responseHeader)); break; case 204: case 200: future.set(parseResult(responseBody, responseHeader)); break; default: future.setException( new ServerFailedException("Bad status: " + result.getStatusLine().getStatusCode())); } } catch (IOException e) { future.setException(e); } } @Override public void failed(Exception ex) { future.setException(ex); } @Override public void cancelled() { future.setException(new ServerFailedException("Canceled!")); } private WnsOAuthToken parseResult(String body, WnsResponseHeader header) throws IOException { return objectMapper.readValue(body, WnsOAuthToken.class); } private Throwable parseError(String body, WnsResponseHeader header) throws IOException { WNSOAuthError error = objectMapper.readValue(body, WNSOAuthError.class); switch (error.error) { case "invalid_client": return new InvalidClientException(); default: return new Exception(); } } } private static final class ResponseHandler implements FutureCallback<HttpResponse> { private static final ObjectMapper objectMapper = new ObjectMapper(); static { objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); } private final SettableFuture<Result> future; private final Object requestContext; public ResponseHandler(SettableFuture<Result> future, Object requestContext) { this.future = future; this.requestContext = requestContext; } @Override public void completed(HttpResponse result) { try { String responseBody = EntityUtils.toString(result.getEntity()); WnsResponseHeader responseHeader = new WnsResponseHeader(result); switch (result.getStatusLine().getStatusCode()) { case 413: future.setException(new ServerFailedException("Payload too large")); case 410: future.setException(new ChannelExpiredException(requestContext)); case 406: int a = 0; // sent to much case 405: int b = 1; // invalid method case 404: future.setException(new ServerFailedException("Channel not found")); case 400: future.setException(new InvalidRequestException()); break; case 401: future.setException(new AuthenticationFailedException()); break; case 204: case 200: future.set(parseResult(responseBody, responseHeader)); break; default: future.setException( new ServerFailedException("Bad status: " + result.getStatusLine().getStatusCode())); } } catch (IOException e) { future.setException(e); } } @Override public void failed(Exception ex) { future.setException(ex); } @Override public void cancelled() { future.setException(new ServerFailedException("Canceled!")); } private Result parseResult(String body, WnsResponseHeader header) throws IOException { return new Result(this.requestContext, header); } } }