All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.afrigis.services.AfriGISServices Maven / Gradle / Ivy

There is a newer version: 3.0.2
Show newest version
package com.afrigis.services;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import org.apache.commons.codec.binary.StringUtils;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.afrigis.services.exceptions.AfriGISServicesException;
import com.afrigis.services.ext.AsyncCapable;
import com.afrigis.services.ext.AuthenticatedService;
import com.afrigis.services.ext.ConfigurationAware;
import com.afrigis.services.ext.SaasTimeAware;
import com.afrigis.services.ext.ServiceFactoryAware;
import com.afrigis.services.impl.AbstractService;
import com.afrigis.services.impl.GenericRequest;

/**
 * 

* Default provider of service implementations. *

* * @author hendrikc * */ public class AfriGISServices implements ServiceCallFactory { private static final int MILLIS_IN_A_SECOND = 1000; private static final int DEFAULT_HTTP_TIMEOUT = 30000; private static final String DEFAULT_SERVICE_BASE_URL = "https://saas.afrigis.co.za/rest/2/"; private String clientId; private byte[] clientSecret; private String baseServiceEndpointUrl = DEFAULT_SERVICE_BASE_URL; private static final Logger LOG = LoggerFactory.getLogger(AfriGISServices.class); private Integer saasTimeOffset = null; private Boolean useSaasTime = Boolean.TRUE; private long timeout = DEFAULT_HTTP_TIMEOUT; private static final Map FACTORY_CACHE = new HashMap<>(); // private static final Map serviceCache = new HashMap<>(); /* * Not ideal - this should be provided from outside (IoC anybody?) */ private static final ExecutorService EXEC_SERVICE = Executors.newCachedThreadPool(); private static final Service FALL_BACK_SERVICE = AbstractService.defaultInstance(); /** * * @param desiredInterface * the interface class of the desired service * @return the service instance, if found * @deprecated superseded by the SPI implementation. */ protected Service getServiceInternal(final Class desiredInterface) { try { final Class theImplClass = getDefaultImplClass(desiredInterface); final Service service = (Service) theImplClass.newInstance(); postProcess(service); return service; } catch (Exception e) { LOG.error("Failed to instantiate service of class {}", desiredInterface, e); } return null; } /** *

* Examines the received object for certain criteria, and if a match is * found, performs additional configuration on the object. *

*

* This method is only applicable to custom implementors. *

* * @param service * the service instance * @see ServiceFactoryAware * @see AuthenticatedService * @see AsyncCapable * @see ConfigurationAware * @see SaasTimeAware */ protected void postProcess(final Object service) { if (service instanceof ServiceFactoryAware) { ((ServiceFactoryAware) service).setServiceFactory(this); } if (service instanceof AuthenticatedService) { ((AuthenticatedService) service).setCredentials(clientId, clientSecret); } if (service instanceof AsyncCapable) { ((AsyncCapable) service).setExecService(EXEC_SERVICE); } else { log().warn("Service is not AsyncCapable, Async methods will fail!"); } if (service instanceof ConfigurationAware) { ((ConfigurationAware) service).setServer(baseServiceEndpointUrl); ((ConfigurationAware) service).setTimeout(timeout); } if (service instanceof SaasTimeAware) { ((SaasTimeAware) service).setUseSaasTime(useSaasTime); if (getServerTimeOffset() != null) { ((SaasTimeAware) service) .setSaasOffsetInSeconds(getServerTimeOffset()); } } } private Logger log() { return LoggerFactory.getLogger(getClass()); } /** *

* Allows caller to completely specify the service end point. *

*

* CAUTION: we assume you know what you are doing if you take control of the * end point URL. *

*/ @Override public void setServiceEndpoint(String server) { this.baseServiceEndpointUrl = server; } /** *

* Attempts to load default implementation class. *

* * @deprecated replaced by use of SPI mechanisms * @param serviceClass2 * interface class that we want an implementation for * @return the load default implementation of provided interface class * @throws ClassNotFoundException * when we can't find the default implementation class */ private Class getDefaultImplClass(final Class serviceClass2) throws ClassNotFoundException { final StringBuffer className = new StringBuffer(); className.append(serviceClass2.getPackage().getName()) .append(".impl.Default").append(serviceClass2.getSimpleName()); final Class realClass = Class.forName(className.toString()); return (Class) realClass; } /** *

* Underlying HTTP client implementation dependent. *

*

* IF the internal HTTP client implementation supports a timeout setting, * then we will use it. *

* * @param timeoutInSec * timeout in seconds to apply to HTTPS calls. */ public void setTimeout(int timeoutInSec) { this.timeout = Math.abs(timeoutInSec * MILLIS_IN_A_SECOND); } /** * * @param key * the key obtained from AfriGIS * @param serviceSecret * the secret obtained from AfriGIS */ protected AfriGISServices(String key, String serviceSecret) { this.clientId = key; this.clientSecret = StringUtils.getBytesUtf8(serviceSecret); } @Override public Future getCreditBalanceAsync() { log().trace("In getCreditBalanceAsync"); return EXEC_SERVICE.submit(new Callable() { @Override public Integer call() throws Exception { log().trace("Making creditBalance call from async"); return Integer.valueOf(getCreditBalance(LibMode.async)); } }); } /** * * @param libMode * the mode we are supposed to be executing in * @return the credit balance for the account */ protected int getCreditBalance(LibMode libMode) { log().trace("In getCreditBalance"); int balance = -1; final Request request = (Request) GenericRequest .build(AfriGISService.getCredits.toString()); request.setLibMode(libMode); final String response = getString(request); try { final JSONObject obj = new JSONObject(response); final JSONObject result = obj.getJSONObject("result"); balance = result.getInt("creditBalance"); } catch (Exception e) { LOG.trace("Server responded with\n {}", response); throw new AfriGISServicesException( "Unexpected response received from server (full output logged at trace level)", e); } return balance; } @Override public int getCreditBalance() { return getCreditBalance(LibMode.sync); } /** *

* The current offset to be applied to local time (based on server time). *

* * @return the current offset to be applied to local time (based on server * time). */ public Integer getServerTimeOffset() { return saasTimeOffset; } /** *

* Finds and instantiates a viable {@link Service} object. *

* * @param * the specific type of the {@link Service} * @param * the specific type of the {@link Request} * @param requestType * the type of the request object * @return a {@link Service} instance */ protected S getService( Class requestType) { postProcess(FALL_BACK_SERVICE); return (S) FALL_BACK_SERVICE; } /** *

* Null constructor. *

*/ public AfriGISServices() { super(); } /** *

* Instantiates (and caches) factory objects. *

*

* The purposes of a {@link ServiceCallFactory} is to give the caller access * to {@link Service} implementations, that in turn give the caller * programmatic access to services provided by AfriGIS. *

* * @param key * a valid AfriGIS key * @param secret * a valid secret * @return a {@link ServiceCallFactory} instance that utilizes the provided * credentials */ public static ServiceCallFactory instance(String key, String secret) { final StringBuffer keyBuff = new StringBuffer(Integer.toHexString(key.hashCode())); keyBuff.append(Integer.toHexString(secret.hashCode())); ServiceCallFactory retVal = FACTORY_CACHE.get(keyBuff.toString()); if (retVal == null) { synchronized (AfriGISServices.class) { if (retVal == null) { retVal = new AfriGISServices(key, secret); FACTORY_CACHE.put(keyBuff.toString(), retVal); } } } return retVal; } @Override public String getServiceEndpoint() { return baseServiceEndpointUrl; } @Override public R get(T request) { if (request == null) { throw new AfriGISServicesException("Request can not be null"); } final Service s = getService(request.getClass()); return s.get(request); } @Override public Future getAsync( T request) { if (request == null) { throw new AfriGISServicesException("Request can not be null"); } final Service s = getService(request.getClass()); return s.getAsync(request); } @Override public String getString(T request) { if (request == null) { throw new AfriGISServicesException("Request can not be null"); } final Service s = getService(request.getClass()); return s.getString(request); } @Override public Future getStringAsync(T request) { if (request == null) { throw new AfriGISServicesException("Request can not be null"); } final Service s = getService(request.getClass()); return s.getStringAsync(request); } @Override public byte[] getByteArray(T request) { if (request == null) { throw new AfriGISServicesException("Request can not be null"); } final Service s = getService(request.getClass()); return s.getByteArray(request); } @Override public Future getByteArrayAsync(T request) { if (request == null) { throw new AfriGISServicesException("Request can not be null"); } final Service s = getService(request.getClass()); return s.getByteArrayAsync(request); } @Override public String buildUrl(Request request) { if (request == null) { throw new AfriGISServicesException("Request can not be null"); } final Service s = getService(GenericRequest.class); return s.buildUrl(request); } @Override public String getString(String serviceName, String queryString) { final Service s = getService(GenericRequest.class); return s.getString(serviceName, queryString); } }