Last active
February 3, 2016 02:50
-
-
Save sumew/ad26e4c311180d989c0e to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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())); | |
} | |
} | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@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; | |
} | |
}); | |
} | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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