diff --git a/src/test/java/org/gcube/lb2pc/HttpMethod.java b/src/test/java/org/gcube/lb2pc/HttpMethod.java new file mode 100644 index 0000000..c09fe0b --- /dev/null +++ b/src/test/java/org/gcube/lb2pc/HttpMethod.java @@ -0,0 +1,5 @@ +package org.gcube.lb2pc; + +public enum HttpMethod { + POST, GET, PUT, DELETE, OPTIONS, HEAD, TRACE, CONNECT +} \ No newline at end of file diff --git a/src/test/java/org/gcube/lb2pc/HttpRequest.java b/src/test/java/org/gcube/lb2pc/HttpRequest.java new file mode 100644 index 0000000..1afa9dd --- /dev/null +++ b/src/test/java/org/gcube/lb2pc/HttpRequest.java @@ -0,0 +1,38 @@ +package org.gcube.lb2pc; + +public class HttpRequest { + + /** + * + */ + private final Proxy ok; + + /** + * @param proxy + */ + HttpRequest(Proxy proxy) { + ok = proxy; + } + + public String getHeader(String string) { + return null; + } + + public String getRequestURI() { + return null; + } + + public HttpMethod getMethod() { + return null; + } + + public String getContentType() { + return null; + } + + public String getContent() { + // TODO Auto-generated method stub + return null; + } + +} \ No newline at end of file diff --git a/src/test/java/org/gcube/lb2pc/HttpResponse.java b/src/test/java/org/gcube/lb2pc/HttpResponse.java new file mode 100644 index 0000000..57611df --- /dev/null +++ b/src/test/java/org/gcube/lb2pc/HttpResponse.java @@ -0,0 +1,32 @@ +package org.gcube.lb2pc; + +import java.util.Collection; + +public class HttpResponse { + + public String getHeader(String string) { + return null; + } + + public Collection getHeaderNames() { + return null; + } + + public String getContentType() { + return null; + } + + public String getContent() { + return null; + } + + public HttpStatusCode getHttpStatusCode() { + return null; + } + + public void setHeader(String string, String lockURI) { + // TODO Auto-generated method stub + + } + +} diff --git a/src/test/java/org/gcube/lb2pc/HttpStatusCode.java b/src/test/java/org/gcube/lb2pc/HttpStatusCode.java new file mode 100644 index 0000000..1e5ccb5 --- /dev/null +++ b/src/test/java/org/gcube/lb2pc/HttpStatusCode.java @@ -0,0 +1,32 @@ +package org.gcube.lb2pc; + +public enum HttpStatusCode { + + // 2×× Success + _200(200, "OK"), _201(201, "Created"), _204(204, "No Content"), + + // 4×× Client Error + _400(400, "Bad Request"), _401(401, "Unauthorized"), _403(403, "Forbidden"), _404(404, "Not Found"), + _405(405, "Method Not Allowed"), _406(406, "Not Acceptable"), _408(408, "Request Timeout"), + _423(423, "Locked"), + + // 5×× Server Error + _500(500, "Internal Server Error"), _503(503, "Service Unavailable"); + + private int code; + private String messageCode; + + private HttpStatusCode(int code, String messageCode) { + this.code = code; + this.messageCode = messageCode; + } + + public int getCode() { + return code; + } + + public String getMessageCode() { + return messageCode; + } + +} \ No newline at end of file diff --git a/src/test/java/org/gcube/lb2pc/Lock.java b/src/test/java/org/gcube/lb2pc/Lock.java new file mode 100644 index 0000000..2968e5b --- /dev/null +++ b/src/test/java/org/gcube/lb2pc/Lock.java @@ -0,0 +1,100 @@ +package org.gcube.lb2pc; + +public class Lock { + + protected String lockURI; + + protected LockType type; + protected String transactionURI; + protected String resourceURI; + + protected String contentType; + + public Lock(LockType type, String transactionURI, String resourceURI) { + super(); + this.type = type; + this.transactionURI = transactionURI; + this.resourceURI = resourceURI; + } + + public Lock(String lockURI) { + this.lockURI = lockURI; + } + + public LockType getType() { + return type; + } + + public void setType(LockType type) { + this.type = type; + } + + public String getTransactionURI() { + return transactionURI; + } + + public void setTransactionURI(String transactionURI) { + this.transactionURI = transactionURI; + } + + public String getResourceURI() { + return resourceURI; + } + + public void setResourceURI(String resourceURI) { + this.resourceURI = resourceURI; + } + + public String getLockURI() { + return lockURI; + } + + public String getContentType() { + return contentType; + } + + public void setContentType(String contentType) { + this.contentType = contentType; + } + + public void setLockURI(String lockURI) { + this.lockURI = lockURI; + } + + public void create() { + + } + + public void read(String contentType) { + this.contentType = contentType; + + HttpResponse lockResponse = Proxy.createHttpRequest(HttpMethod.GET, contentType, lockURI); + if(lockResponse.getHttpStatusCode() != HttpStatusCode._200) { + // Lock Not Found or any error which does not allow to proceed + Proxy.sendErrorResponseToClient(HttpStatusCode._401, "Provided Lock does not exist"); + } + + unmarshalContent(lockResponse.getContent(), contentType); + } + + private void unmarshalContent(String content, String contentType) { + // Set fields + } + + public void upgrade() { + if(type == LockType.X) { + return; + } + + type = LockType.S; + + HttpResponse lockResponse = Proxy.createHttpRequest(HttpMethod.PUT, contentType, lockURI); + + + } + + public void delete() { + HttpResponse lockResponse = Proxy.createHttpRequest(HttpMethod.DELETE, null, lockURI); + + } +} diff --git a/src/test/java/org/gcube/lb2pc/LockType.java b/src/test/java/org/gcube/lb2pc/LockType.java new file mode 100644 index 0000000..ffc2311 --- /dev/null +++ b/src/test/java/org/gcube/lb2pc/LockType.java @@ -0,0 +1,5 @@ +package org.gcube.lb2pc; + +public enum LockType { + S, X +} \ No newline at end of file diff --git a/src/test/java/org/gcube/lb2pc/Log.java b/src/test/java/org/gcube/lb2pc/Log.java new file mode 100644 index 0000000..24a1286 --- /dev/null +++ b/src/test/java/org/gcube/lb2pc/Log.java @@ -0,0 +1,81 @@ +package org.gcube.lb2pc; + +import java.net.MalformedURLException; +import java.net.URL; + +public class Log { + + protected HttpMethod operation; + protected String resource; + protected URL locationURI; + + protected String contentType; + protected String shadowResourceURI; + + public Log(HttpMethod httpMethod) { + super(); + this.operation = httpMethod; + this.resource = null; + } + + public Log(HttpMethod httpMethod, String resource, String locationURI) throws MalformedURLException { + super(); + this.operation = httpMethod; + this.resource = resource; + this.locationURI = new URL(locationURI); + } + + public HttpMethod getOperation() { + return operation; + } + + public void setOperation(HttpMethod operation) { + this.operation = operation; + } + + public String getResource() { + return resource; + } + + public void setResource(String resource) { + this.resource = resource; + } + + public String getContentType() { + return contentType; + } + + public void setContentType(String contentType) { + this.contentType = contentType; + } + + public String getShadowResourceURI() { + return shadowResourceURI; + } + + public void setShadowResourceURI(String shadowResourceURI) { + this.shadowResourceURI = shadowResourceURI; + } + + public void create(String shadowResourceURI, String contentType) { + this.contentType = contentType; + this.shadowResourceURI = shadowResourceURI; + + HttpResponse logResponse = Proxy.createHttpRequest(HttpMethod.POST, contentType, getLogCollectionURI()); + if(logResponse.getHttpStatusCode() != HttpStatusCode._201) { + // Unable to create log. This does not allow to proceed + Proxy.sendErrorResponseToClient(HttpStatusCode._401, "Provided Lock does not exist"); + } + + unmarshalContent(this, logResponse.getContent(), contentType); + } + + protected String getLogCollectionURI() { + return shadowResourceURI + "/operations"; + } + + public static void unmarshalContent(Log operationLog, String content, String contentType) { + // Set fields + } + +} diff --git a/src/test/java/org/gcube/lb2pc/Proxy.java b/src/test/java/org/gcube/lb2pc/Proxy.java new file mode 100644 index 0000000..3e32053 --- /dev/null +++ b/src/test/java/org/gcube/lb2pc/Proxy.java @@ -0,0 +1,229 @@ +package org.gcube.lb2pc; + +import java.net.MalformedURLException; + +public class Proxy { + + public HttpResponse service(HttpRequest request) throws Exception { + + HttpMethod httpMethod = request.getMethod(); + + if(httpMethod == HttpMethod.OPTIONS) { + sendTransactionServerList(); + } + + String transactionURI = request.getHeader("X-Transaction-URI"); + String requestURI = request.getRequestURI(); + String contentType = "application/json"; + + if(transactionURI!=null) { + // Transactional Client + + if(request.getHeader("Accept")!=null) { + contentType = request.getHeader("Accept"); + } + + + String lockURI = request.getHeader("X-Lock-URI"); + + if(lockURI!=null) { + // Client already own a lock, verifying and upgrading if needed + verifyAndUpgradeLock(httpMethod, contentType, lockURI, transactionURI, requestURI); + + String shadowResourceURI = ShadowResource.getShadowResourceURI(transactionURI, requestURI); + logRequest(shadowResourceURI, contentType, httpMethod, null); + + HttpResponse actionResponse = forwardAction(request); + + actionResponse.setHeader("X-Lock-URI", lockURI); + actionResponse.setHeader("X-Transaction-URI", transactionURI); + return replyToClient(actionResponse); + + }else { + // Client does not own a lock + + if(httpMethod != HttpMethod.POST && httpMethod != HttpMethod.PUT && + httpMethod != HttpMethod.GET && httpMethod != HttpMethod.HEAD) { + // The first action on a resource must be a GET or a CREATE + sendErrorResponseToClient(HttpStatusCode._403, "A resource must be read before to be able to modify or delete it."); + } + + HttpResponse actionResponse = null; + + Lock lock = createLock(httpMethod, transactionURI, requestURI); + lockURI = lock.getLockURI(); + + String resource = null; + String shadowResourceURI= null; + + switch(httpMethod) { + case PUT: + + HttpResponse r = createHttpRequest(HttpMethod.HEAD, null, null, requestURI); + if(r.getHttpStatusCode() == HttpStatusCode._204 || (r.getHttpStatusCode().getCode()>=200 && r.getHttpStatusCode().getCode()<300)) { + lock.delete(); + sendErrorResponseToClient(HttpStatusCode._403, "A resource must be read before to be able to modify or delete it."); + }else { + // Create with PUT + shadowResourceURI = createShadowResource(transactionURI, requestURI, lockURI, contentType, null); + logRequest(shadowResourceURI, contentType, httpMethod, request.getContent()); + actionResponse = forwardAction(request); + } + break; + + case POST: + HttpResponse getResponse = getResourceOnEffectiveService(requestURI); + resource = getResponse.getContent(); + + shadowResourceURI = createShadowResource(transactionURI, requestURI, lockURI, contentType, resource); + logRequest(shadowResourceURI, contentType, httpMethod, request.getContent()); + + actionResponse = forwardAction(request); + + String locationURI = actionResponse.getHeader("Location"); + logRequest(shadowResourceURI, contentType, httpMethod, null, locationURI); + + break; + + default: + + HttpResponse response = getResourceOnEffectiveService(requestURI); + resource = response.getContent(); + + shadowResourceURI = createShadowResource(transactionURI, requestURI, lockURI, contentType, resource); + + // We don't need any log + // logRequest(shadowResourceURI, contentType, httpMethod, requestURI, request.getContent()); + + actionResponse = forwardAction(request); + + break; + } + + actionResponse.setHeader("X-Lock-URI", lockURI); + + actionResponse.setHeader("X-Transaction-URI", transactionURI); + + return replyToClient(actionResponse); + } + + + }else { + // Non-Transactional Client + return createMiniTransaction(); + } + + } + + private void logRequest(String shadowResourceURI, String contentType, HttpMethod httpMethod, String resource) + throws MalformedURLException { + this.logRequest(shadowResourceURI, contentType, httpMethod, resource, null); + } + + private void logRequest(String shadowResourceURI, String contentType, HttpMethod httpMethod, String resource, String locationURI) throws MalformedURLException { + Log log = new Log(httpMethod, resource, locationURI); + log.create(shadowResourceURI, contentType); + } + + private HttpResponse createMiniTransaction() { + // TODO Auto-generated method stub + return null; + } + + private void sendTransactionServerList() { + // TODO Auto-generated method stub + + } + + private static HttpResponse replyToClient(HttpResponse actionResponse) { + return null; + } + + private static HttpResponse forwardAction(HttpRequest request) { + return null; + } + + private HttpResponse getResourceOnEffectiveService(String resourceURI) { + return null; + } + + private String createShadowResource(String transactionURI, String requestURI, String lockURI, String contentType, + String resource) throws MalformedURLException { + ShadowResource shadowResource = new ShadowResource(transactionURI, requestURI, lockURI, contentType); + shadowResource.create(); + return shadowResource.getURI(); + } + + protected Lock createLock(HttpMethod httpMethod, String transacationURI, String resourceURI) { + LockType type = getRequiredType(httpMethod); + Lock lock = new Lock(type, transacationURI, resourceURI); + lock.create(); + return lock; + } + + private LockType getRequiredType(HttpMethod httpMethod) { + return null; + } + + protected void verifyAndUpgradeLock(HttpMethod httpMethod, String contentType, String lockURI, + String transactionURI, String resourceURI) { + Lock lock = retrieveLock(lockURI, contentType); + + if(lock.getTransactionURI().compareTo(transactionURI) != 0) { + // Invalid Lock e.g. the lock does not belong to the transaction or does not exist + sendErrorResponseToClient(HttpStatusCode._401, "Provided Lock does belong to provided Transaction"); + } + + if(lock.getResourceURI().compareTo(resourceURI) != 0) { + sendErrorResponseToClient(HttpStatusCode._401, "Provided Lock does belong to requested URI"); + } + + if(lock.type == LockType.S) { + if(httpMethod != HttpMethod.GET) { + upgradeLock(lock, lockURI); + // lock.upgrade(); + } + } + + } + + public static void sendErrorResponseToClient(HttpStatusCode name, String string) { + return; + } + + /** + * GET lock from LockURI + * @param lockURI + * @return + */ + private Lock retrieveLock(String lockURI, String contentType) { + Lock lock = new Lock(lockURI); + lock.read(contentType); + return lock; + } + + public static HttpResponse createHttpRequest(HttpMethod get, String contentType, String uri) { + return null; + } + + public static HttpResponse createHttpRequest(HttpMethod get, String contentType, String content, String uri) { + return null; + } + + protected Lock upgradeLock(Lock lock, String lockURI) { + if(lock.getType() == LockType.X) { + return lock; + } + + lock.setType(LockType.S); + + sendLockUpdate(lock, lockURI); + + return lock; + } + + private void sendLockUpdate(Lock lock, String lockURI) { + return; + } + +} diff --git a/src/test/java/org/gcube/lb2pc/ShadowResource.java b/src/test/java/org/gcube/lb2pc/ShadowResource.java new file mode 100644 index 0000000..afdb0e5 --- /dev/null +++ b/src/test/java/org/gcube/lb2pc/ShadowResource.java @@ -0,0 +1,110 @@ +package org.gcube.lb2pc; + +import java.net.MalformedURLException; +import java.net.URL; + +public class ShadowResource { + + protected String transactionURI; + protected String requestURI; + protected String lockURI; + protected String contentType; + protected String content; + + public ShadowResource(String transactionURI, String requestURI, String lockURI, String contentType) { + super(); + this.transactionURI = transactionURI; + this.requestURI = requestURI; + this.lockURI = lockURI; + this.contentType = contentType; + } + + public ShadowResource(String transactionURI, String requestURI, String lockURI, String contentType, + String content) { + super(); + this.transactionURI = transactionURI; + this.requestURI = requestURI; + this.lockURI = lockURI; + this.contentType = contentType; + this.content = content; + } + + public String getTransactionURI() { + return transactionURI; + } + + public void setTransactionURI(String transactionURI) { + this.transactionURI = transactionURI; + } + + public String getRequestURI() { + return requestURI; + } + + public void setRequestURI(String requestURI) { + this.requestURI = requestURI; + } + + public String getLockURI() { + return lockURI; + } + + public void setLockURI(String lockURI) { + this.lockURI = lockURI; + } + + public String getContentType() { + return contentType; + } + + public void setContentType(String contentType) { + this.contentType = contentType; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public void create() throws MalformedURLException { + String shadowResourceURI = getURI(); + HttpResponse response = Proxy.createHttpRequest(HttpMethod.PUT, contentType, marshall(), shadowResourceURI); + if(response.getHttpStatusCode() != HttpStatusCode._201) { + Proxy.sendErrorResponseToClient(HttpStatusCode._403, "Unable to fullfill request"); + } + } + + protected String marshall() { + return null; + } + + protected String getURI() throws MalformedURLException { + return getShadowResourceURI(transactionURI, requestURI); + } + + protected static String getPath(String uri) throws MalformedURLException { + URL url = new URL(uri); + return url.getPath(); + } + + public static String getShadowResourceURI(String transactionURI, String resourceURI) throws MalformedURLException { + String path = getPath(resourceURI); + + if(path.contains("/operations/")) { + path.replace("/operations/", "/_operations/"); + } + + if(path.endsWith("/operations")) { + path.replace("/operations", "/_operations"); + } + + if(transactionURI.endsWith("/")) { + transactionURI = transactionURI.substring(0, transactionURI.length() - 2); + } + + return transactionURI + path; + } +} diff --git a/src/test/java/org/gcube/lb2pc/Utility.java b/src/test/java/org/gcube/lb2pc/Utility.java new file mode 100644 index 0000000..51acc56 --- /dev/null +++ b/src/test/java/org/gcube/lb2pc/Utility.java @@ -0,0 +1,5 @@ +package org.gcube.lb2pc; + +public class Utility { + +}