package Classes; import java.io.BufferedReader; import java.io.DataOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.URL; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import org.json.JSONException; import org.json.JSONObject; import Interfaces.Callback; import Interfaces.DelayedCallback; import Interfaces.Event; import Interfaces.EventObserver; import javafx.util.Pair; public class ApiCall implements Runnable, EventObserver { /* (1) Attributes ---------------------------------------------------------*/ /* (1) Request data */ private String httpMethod; // HTTP method private HttpURLConnection httpConnection; // HTTP connection /* (2) Callback data */ private Boolean isDelayed = false; // If must wait for an event to start private Callback callback; // Callback ran when the request ends private DelayedCallback delayedCallback; // Callback ran when event handled, returns new URL and POST parameters private String eventID; // awaited event ID private String eventType; // awaited event Type private Thread thread; // main Thread private Boolean shouldStop = false; // If delayedCallback error -> run() must stop (notify) /* (2) API call constructor * * @tUrl Target URL * @tMethod HTTP method * @callback Callback run after the HTTP response received * ---------------------------------------------------------*/ public ApiCall(String tUrl, String tMethod, Callback callback) { /* (1) Store attributes ---------------------------------------------------------*/ this.callback = callback; this.httpMethod = tMethod; /* (2) Create the connection ---------------------------------------------------------*/ try { /* (1) Set the target URL */ URL url = new URL(tUrl); /* (2) Create the connection from URL */ this.httpConnection = (HttpURLConnection) url.openConnection(); /* (3) Set the HTTP method */ this.httpConnection.setRequestMethod(this.httpMethod); /* (4) If GET request, forbid POST data to be sent */ this.httpConnection.setDoOutput( httpMethod == "POST" ); }catch( IOException e ){ /* (x) If cannot create connection -> trigger the onError() callback */ this.callback.onError("cannot create http connection"); } } /* (3) Add headers to the HTTP connection * * @headerMap> HashMap containing the header pairs (key, value) * ---------------------------------------------------------*/ public void addHeaders(HashMap headerMap){ /* (1) Create iterator */ Iterator it = headerMap.entrySet().iterator(); /* (2) Iterate until the end */ while( it.hasNext() ){ /* (2.1) Get the current header pair */ Map.Entry pair = (Map.Entry) it.next(); /* (2.2) Add the header to the @httpConnection */ this.httpConnection.setRequestProperty( (String) pair.getKey(), (String) pair.getValue() ); /* (2.3) Avoid concurrency errors -> remove current header */ it.remove(); } } @Override public void run() { /* (1) If delayed call -> sleep the thread, wait for an event ---------------------------------------------------------*/ if( this.isDelayed ){ /* (1) Try to sleep the thread */ try{ synchronized(this.thread){ this.thread.wait(); } /* (2) If cannot -> call callback onError() */ }catch(InterruptedException e){ this.callback.onError("cannot sleep thread"); return; } } /* (2) If thread must stop (error during delayedCallback) ---------------------------------------------------------*/ if( this.shouldStop ) return; /* (3) Manage response ---------------------------------------------------------*/ try { /* (1) Get output stream to set Post data */ if( this.httpMethod == "POST" ){ DataOutputStream wr = new DataOutputStream( httpConnection.getOutputStream() ); // TODO: manage post data wr.flush (); wr.close (); } /* (2) Prepare to get response */ InputStream is = httpConnection.getInputStream(); // Get input stream BufferedReader rd = new BufferedReader(new InputStreamReader(is)); // Input stream into readable buffer String line; StringBuffer response = new StringBuffer(); /* (3) Get response */ while( ( line = rd.readLine() ) != null ){ response.append(line); } rd.close(); /* (4) Parse JSON */ try{ // {4.1} Specific: replace 'null' by empty strings // JSONObject parsedJson = new JSONObject(response.toString().replaceAll(":null,", ":\"\",")); // {4.2} If no error -> trigger onSuccess() // this.callback.onSuccess(parsedJson); /* (5) If cannot parse JSON -> trigger onError() */ }catch(JSONException jsonex){ this.callback.onError("json parse error"); } /* (4) If cannot get response -> trigger onError() ---------------------------------------------------------*/ }catch (IOException ioex){ this.callback.onError("cannot get response"); } } /* (5) Launches the run() loop in a Thread * ---------------------------------------------------------*/ public void send() { /* (1) Create a new Thread for the current class */ this.thread = new Thread(this); /* (2) Start the thread */ this.thread.start(); } /* (6) Bind a delayed callback to the API call (chain another API call) * * @eventID Event ID * @eventType Event Type * @delayedCallback Delayed callback * ---------------------------------------------------------*/ public void setAsDelayedCall(String eventID, String eventType, DelayedCallback delayedCallback) { /* (1) Notify the delayed callback */ this.isDelayed = true; this.delayedCallback = delayedCallback; this.eventID = eventID; this.eventType = eventType; } @Override public void handleEvent(Event e) { /* (1) If delayed callback and right event received ---------------------------------------------------------*/ if( this.isDelayed && e.getObjectId() == this.eventID && e.getEventType() == this.eventType ){ try{ /* (1) Call the callback and fetch its retusn URL+POST params */ Pair> newData = this.delayedCallback.call(); URL newURL = new URL(newData.getKey()); //HashMap newPostData = newData.getValue(); /* (2) Create the new connection */ HttpURLConnection newConnection = (HttpURLConnection) newURL.openConnection(); /* (3) Keep the same HTTP method as previous request */ String lastHttpMethod = this.httpConnection.getRequestMethod(); newConnection.setRequestMethod( lastHttpMethod ); /* (4) If GET request, forbid POST data to be sent */ newConnection.setDoOutput( lastHttpMethod == "POST" ); /* (5) Manage POST data */ if( this.httpMethod == "POST" ){ // TODO: implement POST params management } /* (6) Update current connection (replace with new) */ this.httpConnection = newConnection; /* (7) Replay main thread */ synchronized(this.thread){ this.thread.notify(); } /* (1.8) If error while creating new connection -> onError() ---------------------------------------------------------*/ }catch( IOException ioe ){ /* (1) Launch onError() callback method */ this.callback.onError("cannot create delayed http connection"); /* (2) Stop the main thread */ this.shouldStop = true; synchronized(this.thread){ this.thread.notify(); } } /* (2) If not a delayed call, or not same event (id/type) ---------------------------------------------------------*/ }else{ /* (1) Launch onError() callback method */ this.callback.onError("unknown event"); /* (2) Stop the main thread */ this.shouldStop = true; synchronized(this.thread){ this.thread.notify(); } } } }