/** * */ package com.trendrr.oss.networking.cheshire; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.nio.channels.AlreadyConnectedException; import java.util.Date; import java.util.Map; import java.util.Timer; import java.util.TimerTask; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import com.trendrr.oss.DynMap; import com.trendrr.oss.DynMapFactory; import com.trendrr.oss.concurrent.Sleep; import com.trendrr.oss.exceptions.TrendrrDisconnectedException; import com.trendrr.oss.exceptions.TrendrrException; import com.trendrr.oss.exceptions.TrendrrTimeoutException; import com.trendrr.oss.networking.strest.RequestBuilder; import com.trendrr.oss.networking.strest.StrestClient; import com.trendrr.oss.networking.strest.StrestRequest; import com.trendrr.oss.networking.strest.StrestResponse; /** * * This handles connections to a cheshire server. * * This will maintain a single connection to the server. since the STREST protocol is * asynch, a single connection can handle all your requests. this client is threadsafe and is * expected to be shared across all your threads. * * The connection to the server will automatically reconnect if the connection is broken. * * @author Dustin Norlander * @created Apr 6, 2011 * * @deprecated use com.trendrr.oss.strest */ @Deprecated public class CheshireClient implements CheshireApiCaller{ protected static Log log = LogFactory.getLog(CheshireClient.class); protected DynMap paramsForEveryRequest = new DynMap(); private String host = "strest.trendrr.com"; private int port = 80; protected StrestClient strest = null; protected int maxReconnectAttempts = -1; protected int reconnectWaitSeconds = 5; protected boolean keepalive = false; protected Date lastSuccessfulPing = null; protected Timer timer = null; //timer for keepalive pings public synchronized boolean isKeepalive() { return keepalive; } /** * the date of the last successful ping. could be null * @return */ public Date getLastSuccessfulPing() { return lastSuccessfulPing; } public synchronized void setLastSuccessfullPing(Date d) { this.lastSuccessfulPing = d; } /** * setting this to true will keep the connection open. * * @param keepalive */ public synchronized void setKeepalive(boolean keepalive) { if (this.keepalive == keepalive) { return; } this.keepalive = keepalive; if (this.keepalive) { //start the timer. final CheshireClient self = this; this.timer = new Timer(true); this.timer.scheduleAtFixedRate(new TimerTask() { @Override public void run() { try { self.ping(); self.lastSuccessfulPing = new Date(); } catch (TrendrrDisconnectedException x) { //make one reconnect attempt. try { self.connect(); } catch (IOException e) { } } catch (TrendrrException x) { log.error("Caught", x); } } }, 1000*1, 1000*30); } else { timer.cancel(); } } /** * Maximum number of queued writes. once this limit is reached exceptions will be thrown on write. * @return */ public int getMaxQueuedWrites() { return this.strest.getMaxQueuedWrites(); } public void setMaxQueuedWrites(int maxQueuedWrites) { this.strest.setMaxQueuedWrites(maxQueuedWrites); } public static void main(String ...strings) throws Exception { CheshireClient client = new CheshireClient("strest.trendrr.com", 80); client.connect(); } public CheshireClient(String host, int port) { this.host = host; this.port = port; this.strest = new StrestClient(host, port); } /** * specialized constructor. * * The params from the passed in map will be include in every request * It is only useful in specialized cases, we suggest you use the authToken constructor. * @param paramsForEveryRequest */ public CheshireClient(String host, int port, Map paramsForEveryRequest) { this(host, port); this.paramsForEveryRequest.putAll(paramsForEveryRequest); } public void connect() throws IOException{ this.strest.connect(); } public void setApiHost(String host, int port) throws AlreadyConnectedException { if (this.strest.isConnected()) { throw new AlreadyConnectedException(); } this.host = host; this.port = port; this.strest = new StrestClient(host, port); } public void close() { this.strest.close(); } /** * Does an asynchronous api call. This method returns immediately. the Response or error is sent to the callback. * * * @param endPoint * @param method * @param params * @param callback */ public void apiCall(String endPoint, Verb method, Map params, CheshireApiCallback callback) { if (!this.strest.isConnected()) { this.attemptReconnect(); } StrestRequest request = this.createRequest(endPoint, method, params); strest.sendRequest(request, new CallbackWrapper(callback)); } /** * Does a synchronous ping. will throw an exception. This method will *NOT* trigger a reconnect attempt. * @throws Exception */ public void ping() throws TrendrrException { strest.sendRequest( this.createRequest("/ping", Verb.GET, null)); this.setLastSuccessfullPing(new Date()); } /** * A synchronous call. blocks until response is available. Please note that this does *NOT* block concurrent api calls, so you can continue to * make calls in other threads. * * If the maxReconnectAttempts is non-zero (-1 is infinit reconnect attempts), then this will attempt to reconnect and send on any io problems. * * @param endPoint * @param method * @param params * @return * @throws Exception */ @Override public DynMap apiCall(String endPoint, Verb method, Map params, long timeoutMillis) throws TrendrrTimeoutException, TrendrrException { StrestRequest request = this.createRequest(endPoint, method, params); StrestResponse response = this.sendWithReconnect(request, timeoutMillis); String res; try { res = new String(response.getContent(), "utf8"); } catch (UnsupportedEncodingException e) { throw new TrendrrException("WHAT bad encoding!!?", e); } return DynMapFactory.instanceFromJSON(res); } private StrestRequest createRequest(String endPoint, Verb method, Map params) { RequestBuilder builder = RequestBuilder.instance(); builder.method(method.toString()); builder.uri(endPoint + "?" + this.paramsForEveryRequest.toURLString()); if (params != null) { DynMap pms = null; if (params instanceof DynMap){ pms = (DynMap)params; } else { pms = DynMap.instance(params); } if (method == Verb.POST) { builder.paramsPOST(pms); } else { builder.paramsGET(pms); } } return builder.getRequest(); } /** * Called if we get an exception when making a request, repeatedly attempts to reconnect * Returns true if successful, false otherwise */ protected synchronized boolean attemptReconnect(){ if(this.strest.isConnected()) return true; if (this.maxReconnectAttempts == 0) { return false; } int attempts = 0; while(true) { try { log.warn("Attempting to reconnect to trendrr api"); this.connect(); //if we get to this point then we have succeeded?? return true; } catch (IOException e) { log.info("Caught", e); } finally { attempts++; } if (this.maxReconnectAttempts != -1 && attempts >= this.maxReconnectAttempts) { return false; } Sleep.seconds(this.reconnectWaitSeconds); } } protected StrestResponse sendWithReconnect(StrestRequest req, long timeoutMillis) throws TrendrrTimeoutException, TrendrrDisconnectedException{ StrestResponse response = null; try { // log.info("Sending request "); // log.info(req); // log.info("**************************"); response = this.strest.sendRequest(req, timeoutMillis); } catch (TrendrrTimeoutException e) { throw e; } catch (TrendrrException e) { // log.info("Caught", e); //we are evidently not connected, so update that if(this.attemptReconnect()){ //able to reconnect, so try one more time try{ // log.info("Sending reconnect request "); // log.info(req); // log.info("************************"); response = this.strest.sendRequest(req); }catch (TrendrrException e1){ //disconnected again? this is some craziness, fail throw new TrendrrDisconnectedException(e1); } }else{ throw new TrendrrDisconnectedException(e); } } return response; } public int getMaxReconnectAttempts() { return maxReconnectAttempts; } public void setMaxReconnectAttempts(int maxReconnectAttempts) { this.maxReconnectAttempts = maxReconnectAttempts; } public int getReconnectWaitSeconds() { return reconnectWaitSeconds; } public void setReconnectWaitSeconds(int reconnectWaitSeconds) { this.reconnectWaitSeconds = reconnectWaitSeconds; } public String getHost() { return host; } public int getPort() { return port; } }