Skip to content

Instantly share code, notes, and snippets.

@sumew
Last active February 3, 2016 02:50
Show Gist options
  • Save sumew/ad26e4c311180d989c0e to your computer and use it in GitHub Desktop.
Save sumew/ad26e4c311180d989c0e to your computer and use it in GitHub Desktop.
public enum CouchbaseCachingClient implements ICachingClient {
INSTANCE;
private final org.slf4j.Logger logger = Logger.init(CouchbaseCachingClient.class);
/**
* A default key used for constructing a JsonNode from a String
* when storing Strings into Couchbase.
*/
private static final String DEFAULT_KEY = "DEFAULT_KEY";
private final CouchbaseCluster cluster;
private Map<String, Bucket> bucketList = new HashMap<String, Bucket>();
/**
* Constructor: Creates the cluster and opens the two buckets
*/
private CouchbaseCachingClient() {
String host = Play.application().configuration().getString("couchbase.server.host");
logger.debug("Creating cluster with host: {}", host);
cluster = CouchbaseCluster.create(host);
}
@Override
public <T> Promise<T> insert(String bucketName, String key, T value, long ttl) {
return insert(bucketName, key, value)
.map(t -> {
touch(bucketName, key, (int)ttl);
return t;
});
}
@SuppressWarnings("unchecked")
@Override
public <T> Promise<T> insert(String bucketName, String key, T value) {
logger.debug("Inserting entry with key: {} into bucket: {}",key, bucketName);
return getCachingRegion(bucketName)
.map(bucket -> {
if(StringUtils.isBlank(key))
throw new EmptyKeyException("Key cannot be empty!");
return bucket.insert(createJsonDocument(key, value));
}).recover(throwable -> {
if(throwable instanceof DocumentAlreadyExistsException)
throw new DuplicateEntryException();
throw new DocumentDatabaseException(throwable);
})
.map(jsonDocument -> getJsonNode(jsonDocument.content()) )
.map(jsonNode -> (T) Json.fromJson(jsonNode, value.getClass()));
}
}
public abstract class SessionAbstractCache implements ISession {
private static final org.slf4j.Logger logger = Logger.init(SessionAbstractCache.class);
public static final String AUTH_TOKEN = "authToken";
public static final String SSO_TOKEN = "ssoToken";
public static final String CSRF_TOKEN = "csrfToken";
protected ICachingClient cacheClient;
protected String CACHE_NAME;
protected int sessionTimeOut;
public SessionAbstractCache(int timeOut) {
this.sessionTimeOut = timeOut;
this.cacheClient = CachingFactory.instance();
logger.info(MessageFormat.format("Session time out set to {0}",
timeOut));
}
protected static int getDefaultTimeOut() {
return Play.application().configuration()
.getInt("aries.platform.session.timeout", DEFAULT_SESSION_TTL);
}
/*
* (non-Javadoc)
*
* @see
* com.marriott.app.aries.platform.session.impl.ISession#create(java.lang
* .String)
*/
@Override
public Promise<Boolean> create(String sessionId, String createdTime) {
logger.info(MessageFormat.format(
"SessionAbstract create new session {0}", sessionId));
return cacheClient.insert(CACHE_NAME, sessionId,
JsonNodeFactory.instance.objectNode().put("createdAt", createdTime), sessionTimeOut).map(
node -> true).recover(throwable -> {
if( throwable instanceof DuplicateEntryException ) {
throw new DuplicateSession(sessionId, throwable);
} else if (throwable.getMessage().contains("The Document ID must not be larger than")) {
throw new InvalidKeyException();
}
throw new DocumentDatabaseException(throwable);
});
}
/*
* (non-Javadoc)
*
* @see
* com.marriott.app.aries.platform.session.impl.ISession#keepAlive(java.
* lang.String)
*/
@Override
public Promise<Boolean> keepAlive(String sessionId) {
logger.info(MessageFormat.format(
"SessionAbstract keep alive session {0}", sessionId));
return cacheClient.touch(CACHE_NAME, sessionId, sessionTimeOut);
}
/*
* (non-Javadoc)
*
* @see
* com.marriott.app.aries.platform.session.impl.ISession#unlink(java.lang
* .String)
*/
@Override
public Promise<Boolean> unlink(String sessionId) {
logger.info(MessageFormat.format(
"SessionAbstract unlink session {0}", sessionId));
return cacheClient.delete(CACHE_NAME, sessionId)
.recover(throwable -> {
if(throwable instanceof EntryNotFoundException)
throw new InvalidSession(sessionId, throwable);
throw new DocumentDatabaseException(throwable);
}).map(sesid -> true);
}
@Override
public Promise<SessionCredentials> setCredentials(String sessionId, SessionCredentials credentials) {
logger.info(MessageFormat.format(
"SessionAbstract update credentials into session {0}",
sessionId));
return this.get(sessionId)
.map(node -> {
ObjectNode session = ((ObjectNode) node);
logger.info(MessageFormat.format(
"SessionAbstract is session null {0}",
session == null));
session.set(AUTH_TOKEN,
TextNode.valueOf(credentials.getAuthToken()));
session.set(SSO_TOKEN,
TextNode.valueOf(credentials.getSSOToken()));
session.set(CSRF_TOKEN,
TextNode.valueOf(credentials.getCsrfToken()));
return node;
})
.map(node -> this.cacheClient.update(CACHE_NAME, sessionId,
node)).map(node -> credentials);
}
@Override
public Promise<SessionCredentials> getCredentials(String sessionId) {
return this
.get(sessionId)
.map(node -> {
ObjectNode session = ((ObjectNode) node);
logger.info(MessageFormat.format(
"SessionAbstract is session null {0}",
session == null));
SessionCredentials credentials = new SessionCredentials();
credentials.setAuthToken(toTextValue(session.get(AUTH_TOKEN)));
credentials.setSSOToken(toTextValue(session.get(SSO_TOKEN)));
credentials.setCsrfToken(toTextValue(session.get(CSRF_TOKEN)));
return credentials;
});
}
private static String toTextValue(JsonNode node) {
return node == null? null: node.textValue();
}
}
@Before
public void setUp() throws Exception {
session = PowerMockito.mock(SessionAbstractCache.class);
PowerMockito.when(session.getCredentials(Mockito.anyString()))
.thenCallRealMethod();
}
@Test
public void testSuccessfulObjectInsert(){
String uuid = UUID.randomUUID().toString();
keys.add(uuid);
cachingClient.insert(bucketName,uuid, testObjectData)
.map(stored -> {
assertEquals(testObjectData, stored);
return stored;
});
}
@Test(expected=DuplicateEntryException.class)
public void testMultipleInsertThrows(){
String uuid = UUID.randomUUID().toString();
keys.add(uuid);
cachingClient.insert(bucketName,uuid, testObjectData).get(TIME_OUT);
cachingClient.insert(bucketName,uuid, testObjectData).get(TIME_OUT);
}
@Test
public void testReadExistingDocument() {
String uuid = UUID.randomUUID().toString();
keys.add(uuid);
cachingClient.insert(bucketName,uuid, testObjectData).get(TIME_OUT);
JsonNode node = cachingClient.read(bucketName, uuid).get(TIME_OUT);
assertEquals("Retrieved JsonNode doesn't match original",node,testObjectDataJson);
}
@Test
public void testGetCredentialsShouldReturnCredentialsWithValues() {
PowerMockito
.when(session.get(Mockito.anyString()))
.thenReturn(
Promise.pure(Json
.parse("{\"authToken\": \"dummy\", \"ssoToken\": \"dummy\"}")));
assertThat(session.getCredentials("dummy").get(TIME_PROMISE_OUT)).isNotNull().satisfies(
new Condition<Object>() {
@Override
public boolean matches(Object arg0) {
if (arg0 instanceof SessionCredentials) {
SessionCredentials credentials = (SessionCredentials) arg0;
return "dummy".equals(credentials.getAuthToken())
&& "dummy".equals(credentials.getSSOToken());
}
return false;
}
});
}
/**
* Utility class to handle web service calls going out of ARIES applications.
*
* @author M1004972
*
*/
public final class WSClient {
private static final org.slf4j.Logger logger = Logger.init(WSClient.class);
/**
* Name of cache partition for web service response.
*/
private static final String WS_CACHE_NAME = "ARIES_CACHE";
private static final String CACHE_CONTROL = "Cache-Control";
public static final IWSTypeCast<JsonNode> RESPONSE_JSON = ResponseAsJson.INSTANCE;
public static final IWSTypeCast<Document> RESPONSE_XML = ResponseAsXml.INSTANCE;
public static final IWSTypeCast<String> RESPONSE_TEXT = ResponseAsText.INSTANCE;
/**
* Default Constructor
*/
private WSClient() {
}
/**
* Perform an HTTP request, if the request has a cacheTime set and a
* previous response is cached then return the cached response.
*
* @param request
* WSRequest instances providing the details for the request
* @param transform
* IWSTypeCast instance help transform the WSRequest to the need
* Type of output or deserialize the coherence cached data to the
* needed Type
* @return
*/
public static <R> Promise<R> execute(WSRequest request, IWSTypeCast<R> transform) {
if (request.getCacheTime() > 0) {
return CachingFactory.instance()
.read(WS_CACHE_NAME, HashUtility.hash(request.toString()))
.map(transform.unpack())
.recoverWith(throwable -> {
if (throwable instanceof EntryNotFoundException) {
//If the resource is not found in the cache, fetch it from the web, cache it before returning it
Promise<WSResponse> response = executeNoCache(request);
return response.flatMap(wsResponse ->
{
Map<String, List<String>> headers = wsResponse.getAllHeaders();
return CachingFactory.instance()
.insert(WS_CACHE_NAME, HashUtility.hash(request.toString()), transform.cast().apply(wsResponse), getTTLForResponse(headers, request.getCacheTime()))
.flatMap(success -> {
logger.info("Request `{}` cached {}", request.toString(), success);//change to print output
return response.map(transform.cast());
});
}
);
}
throw throwable;
});
}
return executeNoCache(request, transform);//For resources that can't/shouldn't be cached
}
/**
* Do a HTTP GET request and does not cache the response.
*
* @param request
* WSRequest instances providing the details for the request
* @param transform
* IWSTypeCast instance help transform the WSRequest to the need
* Type of output
* @return
*/
public static <R> Promise<R> executeNoCache(WSRequest request,
IWSTypeCast<R> transform) {
// logger.info("WS Get request: " + request);
WSRequestHolder holder = request.request();
logger.info("Request for URL {}", holder.getUrl());
return holder.execute().map(transform.cast());
}
public static Promise<WSResponse> executeNoCache(WSRequest request) {
WSRequestHolder holder = request.request();
logger.info("Request for URL {}", holder.getUrl());
return holder.execute();
}
/**
* Go through the response headers, determine if there
* is information on cache-control (ETag or max-age)
* @param headers resposne headers
* @param defaultTTL
* @return
*/
public static long getTTLForResponse(Map<String, List<String>> headers, long defaultTTL) {
return headers.get(CACHE_CONTROL).stream().filter(entry -> entry.contains("max-age=")).mapToLong(entry -> {
return Long.parseLong(entry.split("max-age=")[1]);
}).filter(age -> age > 0).findFirst().orElse(defaultTTL);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment