commit 7bba7ebe6e18a6950926cb00e289efc771b6b6b8 Author: luca.frosini Date: Fri Mar 22 11:21:36 2019 +0000 Aligning pom artifactId to svn location git-svn-id: https://svn.d4science.research-infrastructures.eu/gcube/trunk/Common/gxREST@178656 82a268e6-3cf1-43bd-a215-b396298e98cf diff --git a/.classpath b/.classpath new file mode 100644 index 0000000..ac37fb2 --- /dev/null +++ b/.classpath @@ -0,0 +1,5 @@ + + + + + diff --git a/.project b/.project new file mode 100644 index 0000000..79f0323 --- /dev/null +++ b/.project @@ -0,0 +1,23 @@ + + + gxRest + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + + diff --git a/.settings/org.eclipse.core.resources.prefs b/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000..99f26c0 --- /dev/null +++ b/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +encoding/=UTF-8 diff --git a/gxHTTP/.classpath b/gxHTTP/.classpath new file mode 100644 index 0000000..fae1a2b --- /dev/null +++ b/gxHTTP/.classpath @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gxHTTP/.project b/gxHTTP/.project new file mode 100644 index 0000000..c6f39d6 --- /dev/null +++ b/gxHTTP/.project @@ -0,0 +1,23 @@ + + + gxHTTP + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + + diff --git a/gxHTTP/.settings/org.eclipse.core.resources.prefs b/gxHTTP/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000..29abf99 --- /dev/null +++ b/gxHTTP/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,6 @@ +eclipse.preferences.version=1 +encoding//src/main/java=UTF-8 +encoding//src/main/resources=UTF-8 +encoding//src/test/java=UTF-8 +encoding//src/test/resources=UTF-8 +encoding/=UTF-8 diff --git a/gxHTTP/.settings/org.eclipse.jdt.core.prefs b/gxHTTP/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..714351a --- /dev/null +++ b/gxHTTP/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,5 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 +org.eclipse.jdt.core.compiler.compliance=1.8 +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning +org.eclipse.jdt.core.compiler.source=1.8 diff --git a/gxHTTP/distro/LICENSE b/gxHTTP/distro/LICENSE new file mode 100644 index 0000000..cdf50bd --- /dev/null +++ b/gxHTTP/distro/LICENSE @@ -0,0 +1,4 @@ +gCube System - License +------------------------------------------------------------ + +${gcube.license} \ No newline at end of file diff --git a/gxHTTP/distro/README b/gxHTTP/distro/README new file mode 100644 index 0000000..b55442a --- /dev/null +++ b/gxHTTP/distro/README @@ -0,0 +1,67 @@ +The gCube System - ${name} +-------------------------------------------------- + +${description} + + +${gcube.description} + +${gcube.funding} + + +Version +-------------------------------------------------- + +${version} (${buildDate}) + +Please see the file named "changelog.xml" in this directory for the release notes. + + +Authors +-------------------------------------------------- + +* Manuele Simi (manuele.simi-AT-isti.cnr.it), Istituto di Scienza e Tecnologie dell'Informazione "A. Faedo" - CNR, Pisa (Italy). +* Luca Frosini (luca.frosini-AT-isti.cnr.it), Istituto di Scienza e Tecnologie dell'Informazione "A. Faedo" - CNR, Pisa (Italy). + + +Maintainers +----------- + +* Manuele Simi (manuele.simi-AT-isti.cnr.it), Istituto di Scienza e Tecnologie dell'Informazione "A. Faedo" - CNR, Pisa (Italy). +* Luca Frosini (luca.frosini-AT-isti.cnr.it), Istituto di Scienza e Tecnologie dell'Informazione "A. Faedo" - CNR, Pisa (Italy). + +Download information +-------------------------------------------------- + +Source code is available from SVN: + ${scm.url} + +Binaries can be downloaded from the gCube website: + ${gcube.website} + + +Installation +-------------------------------------------------- + +Installation documentation is available on-line in the gCube Wiki: + ${gcube.wikiRoot}/ + + +Documentation +-------------------------------------------------- + +Documentation is available on-line in the gCube Wiki: + ${gcube.wikiRoot}/ + + +Support +-------------------------------------------------- + +Bugs and support requests can be reported in the gCube issue tracking tool: + ${gcube.issueTracking} + + +Licensing +-------------------------------------------------- + +This software is licensed under the terms you may find in the file named "LICENSE" in this directory. diff --git a/gxHTTP/distro/changelog.xml b/gxHTTP/distro/changelog.xml new file mode 100644 index 0000000..1fdd32d --- /dev/null +++ b/gxHTTP/distro/changelog.xml @@ -0,0 +1,13 @@ + + + + + + + + Separated plaing HTTP requests from gxRest + + + First Release + + \ No newline at end of file diff --git a/gxHTTP/distro/profile.xml b/gxHTTP/distro/profile.xml new file mode 100644 index 0000000..1e5db40 --- /dev/null +++ b/gxHTTP/distro/profile.xml @@ -0,0 +1,28 @@ + + + + + Service + + ${description} + ${serviceClass} + ${artifactId} + 1.0.0 + + + ${description} + ${artifactId} + ${version} + + ${groupId} + ${artifactId} + ${version} + + Library + + ${build.finalName}.${project.packaging} + + + + + \ No newline at end of file diff --git a/gxHTTP/pom.xml b/gxHTTP/pom.xml new file mode 100644 index 0000000..e76e55a --- /dev/null +++ b/gxHTTP/pom.xml @@ -0,0 +1,63 @@ + + + 4.0.0 + + org.gcube.common + gxREST + 1.1.2-SNAPSHOT + + org.gcube.common + gxHTTP + + gCube eXtensions to REST with HTTP + gCube eXtensions to REST based on HTTP + + + + UTF-8 + ${project.basedir}/distro + + + + scm:https://svn.d4science.research-infrastructures.eu/gcube/trunk/common/${parent.artifactId}/${project.artifactId} + scm:https://svn.d4science.research-infrastructures.eu/gcube/trunk/common/${parent.artifactId}/${project.artifactId} + https://svn.d4science.research-infrastructures.eu/gcube/trunk/common/${parent.artifactId}/${project.artifactId} + + + + + org.gcube.common + authorization-client + + + + org.slf4j + slf4j-api + provided + + + junit + junit + 4.12 + test + + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + make-servicearchive + package + + + + + + diff --git a/gxHTTP/src/main/java/org/gcube/common/gxhttp/reference/GXConnection.java b/gxHTTP/src/main/java/org/gcube/common/gxhttp/reference/GXConnection.java new file mode 100644 index 0000000..06109c6 --- /dev/null +++ b/gxHTTP/src/main/java/org/gcube/common/gxhttp/reference/GXConnection.java @@ -0,0 +1,263 @@ +package org.gcube.common.gxhttp.reference; + +import java.io.DataOutputStream; +import java.io.InputStream; +import java.io.StringWriter; +import java.io.UnsupportedEncodingException; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLEncoder; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + +import org.gcube.common.authorization.library.provider.SecurityTokenProvider; +import org.gcube.common.gxhttp.request.GXHTTPStringRequest; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A remote connection for a {@link GXHTTPStringRequest}. + * + * @author Manuele Simi (ISTI-CNR) + * @author Luca Frosini (ISTI-CNR) + */ +public class GXConnection { + + public static final String APPLICATION_JSON_CHARSET_UTF_8 = "application/json;charset=UTF-8"; + public static final String PATH_SEPARATOR = "/"; + public static final String PARAM_STARTER = "?"; + public static final String PARAM_EQUALS = "="; + public static final String PARAM_SEPARATOR = "&"; + public static final String UTF8 = "UTF-8"; + + protected static final Logger logger = LoggerFactory.getLogger(GXConnection.class); + + public enum HTTPMETHOD { + HEAD, GET, POST, PUT, DELETE, TRACE, PATCH, OPTIONS, CONNECT; + + @Override + public String toString() { + return this.name(); + } + } + + protected final String address; + protected String path = "", agent; + private String queryParameters; + private String pathParameters; + private String body; + private InputStream bodyAsStream; + private Map properties = new HashMap<>(); + private boolean extCall = false; + + public GXConnection(String address) { + this.address = address; + } + + protected void addPath(String pathPart) throws UnsupportedEncodingException { + if (this.path.compareTo("")!=0 && !this.path.endsWith(GXConnection.PATH_SEPARATOR)) + this.path += GXConnection.PATH_SEPARATOR; + this.path += Arrays.stream(pathPart.split(GXConnection.PATH_SEPARATOR)) + .map(part -> encodePart(part, true)) + .collect(Collectors.joining(GXConnection.PATH_SEPARATOR)); + } + + private String encodePart(String part, boolean path) { + try { + // URL spaces are encoded with + for query parameter + // URL spaces are encoded with %20 for path parts + String encoded = URLEncoder.encode(part, GXConnection.UTF8); + if(path) { + encoded = encoded.replace("+","%20"); + } + return encoded; + } catch (UnsupportedEncodingException e) { + return part; + } + } + + private URL buildURL() throws MalformedURLException { + + StringWriter prepareURL = new StringWriter(); + prepareURL.append(address); + Objects.requireNonNull(path, "Null path detected in the request!"); + if (address.endsWith(PATH_SEPARATOR)) { + if (path.startsWith(PATH_SEPARATOR)) { + path = path.substring(1); + } + } else { + if (!path.startsWith(PATH_SEPARATOR) && !path.isEmpty()) { + prepareURL.append(PATH_SEPARATOR); + } + } + prepareURL.append(path); + if (Objects.nonNull(this.pathParameters)) + prepareURL.append(this.pathParameters); + if (Objects.nonNull(this.queryParameters) && !this.queryParameters.isEmpty()) { + prepareURL.append(PARAM_STARTER); + prepareURL.append(queryParameters); + } + URL url = new URL(prepareURL.toString()); + if (url.getProtocol().compareTo("https") == 0) { + url = new URL(url.getProtocol(), url.getHost(), url.getPort()==-1 ? url.getDefaultPort() : url.getPort(), url.getFile()); + } + return url; + } + + /** + * Sends the request with the given method + * + * @param method + * @return the connection + * @throws Exception + */ + public HttpURLConnection send(HTTPMETHOD method) throws Exception { + return send(this.buildURL(), method); + } + + private HttpURLConnection send(URL url, HTTPMETHOD method) throws Exception { + HttpURLConnection uConn = (HttpURLConnection) url.openConnection(); + if (!this.extCall) { + String token = SecurityTokenProvider.instance.get(); + if (Objects.isNull(token) || token.isEmpty()) + throw new IllegalStateException("The security token in the current environment is null."); + + uConn.setRequestProperty(org.gcube.common.authorization.client.Constants.TOKEN_HEADER_ENTRY, + token); + } + uConn.setDoOutput(true); + // uConn.setRequestProperty("Content-type", APPLICATION_JSON_CHARSET_UTF_8); + if(this.agent!=null) { + uConn.setRequestProperty("User-Agent", this.agent); + } + for (String key : properties.keySet()) { + uConn.setRequestProperty(key, properties.get(key)); + } + uConn.setRequestMethod(method.toString()); + HttpURLConnection.setFollowRedirects(true); + // attach the body + if (Objects.nonNull(this.body) && (method == HTTPMETHOD.POST || method == HTTPMETHOD.PUT)) { + DataOutputStream wr = new DataOutputStream(uConn.getOutputStream()); + wr.write(this.body.getBytes(GXConnection.UTF8)); + wr.flush(); + wr.close(); + } + // upload the stream + if (Objects.nonNull(this.bodyAsStream) && (method == HTTPMETHOD.POST || method == HTTPMETHOD.PUT)) { + DataOutputStream wr = new DataOutputStream(uConn.getOutputStream()); + byte[] buffer = new byte[1024]; + + int len; + while((len = this.bodyAsStream.read(buffer)) > 0) { + wr.write(buffer, 0, len); + } + wr.flush(); + wr.close(); + } + + int responseCode = uConn.getResponseCode(); + String responseMessage = uConn.getResponseMessage(); + logger.trace("{} {} : {} - {}", method, uConn.getURL(), responseCode, responseMessage); + + // if we get a redirect code, we invoke the connection to the new URL + if (responseCode == HttpURLConnection.HTTP_MOVED_TEMP || responseCode == HttpURLConnection.HTTP_MOVED_PERM + || responseCode == HttpURLConnection.HTTP_SEE_OTHER) { + URL redirectURL = getURL(uConn.getHeaderField("Location")); + logger.trace("{} is going to be redirected to {}", url.toString(), redirectURL.toString()); + return send(redirectURL, method); + } + return uConn; + } + + private URL getURL(String urlString) throws MalformedURLException { + URL url = new URL(urlString); + if (url.getProtocol().equals("https")) { + url = new URL(url.getProtocol(), url.getHost(), url.getDefaultPort(), url.getFile()); + } + return url; + } + + /** + * @param agent + */ + protected void setAgent(String agent) { + this.agent = agent; + } + + /** + * Sets the path parameters for the connection. + * + * @param parameters + */ + public void setPathParameters(String parameters) { + this.pathParameters = parameters; + } + + /** + * Sets the query parameters for the connection. + * + * @param parameters + */ + public void setQueryParameters(String parameters) { + this.queryParameters = parameters; + } + + /** + * Resets the connection. + */ + public void reset() { + this.pathParameters = ""; + this.queryParameters = ""; + this.body = ""; + } + + /** + * The body of the request. + * + * @param body + */ + public void addBody(String body) { + if (Objects.isNull(this.bodyAsStream)) + this.body = body; + else + throw new IllegalArgumentException("Cannot set the input stream because addBodyAsStream(InputStream) was already invoked."); + } + + /** + * @param bodyAsStream the stream to set as input + */ + public void addBodyAsStream(InputStream bodyAsStream) { + if (Objects.isNull(this.body)) + this.bodyAsStream = bodyAsStream; + else + throw new IllegalArgumentException("Cannot set the input stream because addBody(String) was already invoked."); + } + + /** + * Adds a property as header. + * @param name + * @param value + */ + public void setProperty(String name, String value) { + this.properties.put(name, value); + } + + /** + * @param extCall the extCall to set + */ + public void setExtCall(boolean extCall) { + this.extCall = extCall; + } + + /** + * @return the extCall + */ + public boolean isExtCall() { + return extCall; + } + +} diff --git a/gxHTTP/src/main/java/org/gcube/common/gxhttp/reference/GXHTTP.java b/gxHTTP/src/main/java/org/gcube/common/gxhttp/reference/GXHTTP.java new file mode 100644 index 0000000..19faf1a --- /dev/null +++ b/gxHTTP/src/main/java/org/gcube/common/gxhttp/reference/GXHTTP.java @@ -0,0 +1,101 @@ +package org.gcube.common.gxhttp.reference; + +/** + * + * HTTP methods for requests. + * + * @author Manuele Simi (ISTI-CNR) + * + * @param the type of the body request + * @param the type of the response + + */ +public interface GXHTTP { + + /** + * Sends the PUT request to the web application. + * @param body the body of the request + * @return the response + */ + RESPONSE put(BODY body) throws Exception; + + /** + * Sends the PUT request to the web application with no body. + * @return the response + */ + RESPONSE put() throws Exception; + + /** + * Sends the DELETE request to the web application. + * @return the response + */ + RESPONSE delete() throws Exception; + + /** + * Sends the HEAD request to the web application. + * @return the response + */ + RESPONSE head() throws Exception; + + /** + * Sends the GET request to the web application. + * @return the response + */ + RESPONSE get() throws Exception; + + /** + * Sends the POST request to the web application. + * @param body the body of the request + * @return the response + * @throws Exception + */ + RESPONSE post(BODY body) throws Exception; + + /** + * Sends the POST request to the web application with no body. + * @return the response + * @throws Exception + */ + RESPONSE post() throws Exception; + + /** + * Sends the TRACE request to the web application with no body. + * @return the response + * @throws Exception + */ + RESPONSE trace() throws Exception; + + /** + * Sends the PATCH request to the web application with no body. + * @return the response + * @throws Exception + */ + RESPONSE patch() throws Exception; + + /** + * Sends the OPTIONS request to the web application with no body. + * @return the response + * @throws Exception + */ + RESPONSE options() throws Exception; + + /** + * Sends the CONNECT request to the web application with no body. + * @return the response + * @throws Exception + */ + RESPONSE connect() throws Exception; + + /** + * Overrides the default security token. + * @param token the new token + */ + void setSecurityToken(String token); + + /** + * States if the service being called in an external service (not gCube). + * @param ext true if external, false otherwise + */ + void isExternalCall(boolean ext); + +} diff --git a/gxHTTP/src/main/java/org/gcube/common/gxhttp/reference/GXHTTPRequestBuilder.java b/gxHTTP/src/main/java/org/gcube/common/gxhttp/reference/GXHTTPRequestBuilder.java new file mode 100644 index 0000000..38663c1 --- /dev/null +++ b/gxHTTP/src/main/java/org/gcube/common/gxhttp/reference/GXHTTPRequestBuilder.java @@ -0,0 +1,207 @@ +package org.gcube.common.gxhttp.reference; + +import java.io.UnsupportedEncodingException; +import java.net.HttpURLConnection; +import java.net.URLEncoder; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; + +import org.gcube.common.gxhttp.reference.GXConnection.HTTPMETHOD; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Builder for GXHTTP Requests. + * + * @author Manuele Simi (ISTI CNR) + * + */ +public class GXHTTPRequestBuilder { + + protected static final Logger logger = LoggerFactory.getLogger(GXHTTPRequestBuilder.class); + + public GXConnection connection; + + /** + * Sets the identity user agent associated to the request. + * + * @param agent + * @return the request + */ + public GXHTTPRequestBuilder from(String agent) { + this.connection.setAgent(agent); + return this; + } + + /** + * Adds s positional path parameter to the request. + * + * @param path + * @return the request + * @throws UnsupportedEncodingException + */ + public GXHTTPRequestBuilder path(String path) throws UnsupportedEncodingException { + this.connection.addPath(path); + return this; + } + + /** + * Sets the query parameters for the request. + * + * @param parameters + * the parameters that go in the URL after the address and the + * path params. + * @return the request + * @throws UnsupportedEncodingException + */ + public GXHTTPRequestBuilder queryParams(Map parameters) throws UnsupportedEncodingException { + if (Objects.nonNull(parameters) && !parameters.isEmpty()) { + StringBuilder result = new StringBuilder(); + boolean first = true; + for (Entry parameter : parameters.entrySet()) { + if (first) { + first = false; + } else { + result.append(GXConnection.PARAM_SEPARATOR); + } + result.append(URLEncoder.encode(parameter.getKey(), GXConnection.UTF8)); + result.append(GXConnection.PARAM_EQUALS); + result.append(URLEncoder.encode(parameter.getValue(), GXConnection.UTF8)); + } + connection.setQueryParameters(result.toString()); + } + return this; + } + + + /** + * Overrides the default security token. + * + * @param token + */ + public void setSecurityToken(String token) { + if (!this.connection.isExtCall()) + this.connection.setProperty(org.gcube.common.authorization.client.Constants.TOKEN_HEADER_ENTRY, token); + else + throw new UnsupportedOperationException("Cannot set the security token on an external call"); + } + + /** + * Add headers to the request. + * + * @param name + * @param value + */ + public GXHTTPRequestBuilder header(String name, String value) { + this.connection.setProperty(name, value); + return this; + } + /* + * (non-Javadoc) + * + * @see org.gcube.common.gxrest.request.GXHTTP#put() + */ + public HttpURLConnection put() throws Exception { + logger.trace("Sending a PUT request..."); + return this.connection.send(HTTPMETHOD.PUT); + } + + /* + * (non-Javadoc) + * + * @see org.gcube.common.gxrest.request.GXHTTP#post() + */ + public HttpURLConnection post() throws Exception { + logger.trace("Sending a POST request..."); + return this.connection.send(HTTPMETHOD.POST); + } + /** + * Sends the GET request to the web application. + * + * @return the response + */ + public HttpURLConnection get() throws Exception { + logger.trace("Sending a GET request..."); + return this.connection.send(HTTPMETHOD.GET); + } + + /** + * Sends the DELETE request to the web application. + * + * @return the response + * @throws Exception + */ + public HttpURLConnection delete() throws Exception { + logger.trace("Sending a DELETE request..."); + return this.connection.send(HTTPMETHOD.DELETE); + } + + /** + * Sends the HEAD request to the web application. + * + * @return the response + * @throws Exception + */ + public HttpURLConnection head() throws Exception { + logger.trace("Sending a HEAD request..."); + return this.connection.send(HTTPMETHOD.HEAD); + } + + /** + * Clears all the parameter except the address. + */ + public void clear() { + this.connection.reset(); + } + + + /* + * (non-Javadoc) + * + * @see org.gcube.common.gxrest.request.GXHTTP#trace() + */ + public HttpURLConnection trace() throws Exception { + logger.trace("Sending a TRACE request..."); + return this.connection.send(HTTPMETHOD.TRACE); + } + + /* + * (non-Javadoc) + * + * @see org.gcube.common.gxrest.request.GXHTTP#patch() + */ + public HttpURLConnection patch() throws Exception { + logger.trace("Sending a TRACE request..."); + return this.connection.send(HTTPMETHOD.PATCH); + } + + /* + * (non-Javadoc) + * + * @see org.gcube.common.gxrest.request.GXHTTP#options() + */ + public HttpURLConnection options() throws Exception { + logger.trace("Sending an OPTIONS request..."); + return this.connection.send(HTTPMETHOD.OPTIONS); + } + + /* + * (non-Javadoc) + * + * @see org.gcube.common.gxrest.request.GXHTTP#connect() + */ + public HttpURLConnection connect() throws Exception { + logger.trace("Sending a CONNECT request..."); + return this.connection.send(HTTPMETHOD.CONNECT); + } + + /* + * (non-Javadoc) + * + * @see org.gcube.common.gxrest.request.GXHTTP#isExternalCall(boolean) + */ + public void isExternalCall(boolean ext) { + this.connection.setExtCall(ext); + } +} diff --git a/gxHTTP/src/main/java/org/gcube/common/gxhttp/reference/package-info.java b/gxHTTP/src/main/java/org/gcube/common/gxhttp/reference/package-info.java new file mode 100644 index 0000000..34dadce --- /dev/null +++ b/gxHTTP/src/main/java/org/gcube/common/gxhttp/reference/package-info.java @@ -0,0 +1,7 @@ +/** + * GXHTTP core. + * + * @author Manuele Simi (ISTI CNR) + * + */ +package org.gcube.common.gxhttp.reference; \ No newline at end of file diff --git a/gxHTTP/src/main/java/org/gcube/common/gxhttp/request/GXHTTPCommonRequest.java b/gxHTTP/src/main/java/org/gcube/common/gxhttp/request/GXHTTPCommonRequest.java new file mode 100644 index 0000000..e2bec16 --- /dev/null +++ b/gxHTTP/src/main/java/org/gcube/common/gxhttp/request/GXHTTPCommonRequest.java @@ -0,0 +1,107 @@ +package org.gcube.common.gxhttp.request; + +import java.net.HttpURLConnection; + +import org.gcube.common.gxhttp.reference.GXHTTPRequestBuilder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Common logic across GXHTTP requests. + * + * @author Manuele Simi (ISTI CNR) + * + */ +class GXHTTPCommonRequest { + + protected static final Logger logger = LoggerFactory.getLogger(GXHTTPStringRequest.class); + + protected GXHTTPRequestBuilder builder = new GXHTTPRequestBuilder(); + + /* (non-Javadoc) + * @see org.gcube.common.gxhttp.reference.GXHTTP#put() + */ + public HttpURLConnection put() throws Exception { + return builder.put(); + + } + + /* (non-Javadoc) + * @see org.gcube.common.gxhttp.reference.GXHTTP#delete() + */ + public HttpURLConnection delete() throws Exception { + return builder.delete(); + } + + /* (non-Javadoc) + * @see org.gcube.common.gxhttp.reference.GXHTTP#head() + */ + public HttpURLConnection head() throws Exception { + return builder.head(); + + } + + /* (non-Javadoc) + * @see org.gcube.common.gxhttp.reference.GXHTTP#get() + */ + public HttpURLConnection get() throws Exception { + return builder.get(); + } + + /* (non-Javadoc) + * @see org.gcube.common.gxhttp.reference.GXHTTP#post() + */ + public HttpURLConnection post() throws Exception { + return builder.post(); + } + + /* (non-Javadoc) + * @see org.gcube.common.gxhttp.reference.GXHTTP#trace() + */ + public HttpURLConnection trace() throws Exception { + return builder.trace(); + + } + + /* (non-Javadoc) + * @see org.gcube.common.gxhttp.reference.GXHTTP#patch() + */ + public HttpURLConnection patch() throws Exception { + return builder.patch(); + } + + /* (non-Javadoc) + * @see org.gcube.common.gxhttp.reference.GXHTTP#options() + */ + public HttpURLConnection options() throws Exception { + return builder.options(); + } + + /* (non-Javadoc) + * @see org.gcube.common.gxhttp.reference.GXHTTP#connect() + */ + public HttpURLConnection connect() throws Exception { + return builder.connect(); + } + + /* (non-Javadoc) + * @see org.gcube.common.gxhttp.reference.GXHTTP#setSecurityToken(java.lang.String) + */ + public void setSecurityToken(String token) { + builder.setSecurityToken(token); + } + + /* (non-Javadoc) + * @see org.gcube.common.gxhttp.reference.GXHTTP#isExternalCall(boolean) + */ + public void isExternalCall(boolean ext) { + builder.isExternalCall(ext); + } + + /** + * Clear up the request. + */ + public void clear() { + builder.clear(); + } +} diff --git a/gxHTTP/src/main/java/org/gcube/common/gxhttp/request/GXHTTPStreamRequest.java b/gxHTTP/src/main/java/org/gcube/common/gxhttp/request/GXHTTPStreamRequest.java new file mode 100644 index 0000000..623dec2 --- /dev/null +++ b/gxHTTP/src/main/java/org/gcube/common/gxhttp/request/GXHTTPStreamRequest.java @@ -0,0 +1,102 @@ +package org.gcube.common.gxhttp.request; + +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.net.HttpURLConnection; +import java.util.Map; +import java.util.Objects; + +import org.gcube.common.gxhttp.reference.GXConnection; +import org.gcube.common.gxhttp.reference.GXHTTP; +import org.gcube.common.gxhttp.reference.GXConnection.HTTPMETHOD; + +/** + * A context-aware request to a web application. + * It supports sending streams through Put/Post requests. + * + * @author Manuele Simi (ISTI CNR) + * + */ +public class GXHTTPStreamRequest extends GXHTTPCommonRequest + implements GXHTTP { + + /** + * A new request. + */ + private GXHTTPStreamRequest(String address) { + builder.connection = new GXConnection(address); + } + + /** + * Creates a new request. + * + * @param address + * the address of the web app to call + * @return the request + */ + public static GXHTTPStreamRequest newRequest(String address) { + return new GXHTTPStreamRequest(address); + } + + /* (non-Javadoc) + * @see org.gcube.common.gxhttp.reference.GXHTTP#put(java.lang.Object) + */ + @Override + public HttpURLConnection put(InputStream body) throws Exception { + if (Objects.nonNull(body)) + builder.connection.addBodyAsStream(body); + logger.trace("Sending a PUT request..."); + return builder.connection.send(HTTPMETHOD.PUT); + } + + /* (non-Javadoc) + * @see org.gcube.common.gxhttp.reference.GXHTTP#post(java.lang.Object) + */ + @Override + public HttpURLConnection post(InputStream body) throws Exception { + logger.trace("Sending a POST request..."); + if (Objects.nonNull(body)) + builder.connection.addBodyAsStream(body); + return builder.connection.send(HTTPMETHOD.POST); + } + + /** + * @param string + * @return the request + */ + public GXHTTPStreamRequest from(String agent) { + builder.from(agent) ; + return this; + } + + /** + * @param path + * @return the request + * @throws UnsupportedEncodingException + * + */ + public GXHTTPStreamRequest path(String path) throws UnsupportedEncodingException { + builder.path(path); + return this; + } + + /** + * @param name + * @param value + * @return the request + */ + public GXHTTPStreamRequest header(String name, String value) { + builder.header(name, value); + return this; + } + + /** + * @param queryParams + * @return the request + * @throws UnsupportedEncodingException + */ + public GXHTTPStreamRequest queryParams(Map queryParams) throws UnsupportedEncodingException { + builder.queryParams(queryParams); + return this; + } +} diff --git a/gxHTTP/src/main/java/org/gcube/common/gxhttp/request/GXHTTPStringRequest.java b/gxHTTP/src/main/java/org/gcube/common/gxhttp/request/GXHTTPStringRequest.java new file mode 100644 index 0000000..78eb0d0 --- /dev/null +++ b/gxHTTP/src/main/java/org/gcube/common/gxhttp/request/GXHTTPStringRequest.java @@ -0,0 +1,114 @@ +package org.gcube.common.gxhttp.request; + +import java.io.UnsupportedEncodingException; +import java.net.HttpURLConnection; +import java.util.Map; +import java.util.Objects; + +import org.gcube.common.gxhttp.reference.GXConnection; +import org.gcube.common.gxhttp.reference.GXHTTP; +import org.gcube.common.gxhttp.reference.GXConnection.HTTPMETHOD; + +/** + * A context-aware request to a web application. + * It supports sending strings through Put/Post requests. + * + * @author Manuele Simi (ISTI CNR) + * + */ +public class GXHTTPStringRequest extends GXHTTPCommonRequest implements GXHTTP { + + /** + * A new request. + */ + private GXHTTPStringRequest(String address) { + builder.connection = new GXConnection(address); + } + + /** + * Creates a new request. + * + * @param address + * the address of the web app to call + * @return the request + */ + public static GXHTTPStringRequest newRequest(String address) { + return new GXHTTPStringRequest(address); + } + + /** + * Sets the body of the request. + * + * @param body + * @return the request + */ + public GXHTTPStringRequest withBody(String body) { + builder.connection.addBody(body); + return this; + } + + /* (non-Javadoc) + * @see org.gcube.common.gxhttp.reference.GXHTTP#put(java.lang.Object) + */ + @Override + public HttpURLConnection put(String body) throws Exception { + if (Objects.nonNull(body)) + builder.connection.addBody(body); + logger.trace("Sending a PUT request..."); + return builder.connection.send(HTTPMETHOD.PUT); + } + + /* (non-Javadoc) + * @see org.gcube.common.gxhttp.reference.GXHTTP#post(java.lang.Object) + */ + @Override + public HttpURLConnection post(String body) throws Exception { + logger.trace("Sending a POST request..."); + if (Objects.nonNull(body)) + builder.connection.addBody(body); + return builder.connection.send(HTTPMETHOD.POST); + } + + /** + * @param string + * @return the request + */ + public GXHTTPStringRequest from(String agent) { + builder.from(agent) ; + return this; + } + + /** + * @param string + * @return the request + * @throws UnsupportedEncodingException + * + */ + public GXHTTPStringRequest path(String path) throws UnsupportedEncodingException { + builder.path(path); + return this; + } + + /** + * @param name + * @param value + * @return the request + */ + public GXHTTPStringRequest header(String name, String value) { + builder.header(name, value); + return this; + } + + /** + * @param queryParams + * @return the request + * @throws UnsupportedEncodingException + */ + public GXHTTPStringRequest queryParams(Map queryParams) throws UnsupportedEncodingException { + builder.queryParams(queryParams); + return this; + } + + + +} diff --git a/gxHTTP/src/main/java/org/gcube/common/gxhttp/request/package-info.java b/gxHTTP/src/main/java/org/gcube/common/gxhttp/request/package-info.java new file mode 100644 index 0000000..8ad3c4b --- /dev/null +++ b/gxHTTP/src/main/java/org/gcube/common/gxhttp/request/package-info.java @@ -0,0 +1,7 @@ +/** + * GXHTTP requests. + * + * @author Manuele Simi (ISTI CNR) + * + */ +package org.gcube.common.gxhttp.request; \ No newline at end of file diff --git a/gxHTTP/src/main/java/org/gcube/common/gxhttp/util/ContentUtils.java b/gxHTTP/src/main/java/org/gcube/common/gxhttp/util/ContentUtils.java new file mode 100644 index 0000000..4d8b6b6 --- /dev/null +++ b/gxHTTP/src/main/java/org/gcube/common/gxhttp/util/ContentUtils.java @@ -0,0 +1,140 @@ +package org.gcube.common.gxhttp.util; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInput; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.OutputStream; + +/** + * Manipulation of a response's content. + * + * @author Manuele Simi (ISTI CNR) + * + */ +final public class ContentUtils { + + /** + * Converts an object to an array of bytes + * @param obj + * @return the bytes + * @throws IOException + */ + public static byte[] toByteArray(Object obj) throws IOException { + byte[] bytes = null; + ByteArrayOutputStream bos = null; + ObjectOutputStream oos = null; + try { + bos = new ByteArrayOutputStream(); + oos = new ObjectOutputStream(bos); + oos.writeObject(obj); + oos.flush(); + bytes = bos.toByteArray(); + } finally { + if (oos != null) { + oos.close(); + } + if (bos != null) { + bos.close(); + } + } + return bytes; + } + + /** + * + * @param inputStream + * @param class1 + * @return an instance of type "type" + * @throws IOException + * @throws ClassNotFoundException + */ + @SuppressWarnings("unchecked") + public static T toObject(InputStream inputStream, Class class1) throws IOException, ClassNotFoundException { + ObjectInput in = null; + T o = null; + try { + in = new ObjectInputStream(inputStream); + o = (T) in.readObject(); + } finally { + try { + if (in != null) { + in.close(); + } + } catch (IOException ex) { + // ignore close exception + } + } + return o; + } + + /** + * Converts the array of bytes into an object. + * @param data + * @return the object + * @throws IOException + * @throws ClassNotFoundException + */ + public Object toObject(byte[] data) throws IOException, ClassNotFoundException { + ByteArrayInputStream in = new ByteArrayInputStream(data); + ObjectInputStream is = new ObjectInputStream(in); + return is.readObject(); + } + + + /** + * Gets the contents of an InputStream as a byte[]. + * + * @param input the InputStream to read from + * @return the requested byte array + * @throws NullPointerException if the input is null + * @throws IOException if an I/O error occurs + */ + public static byte[] toByteArray(final InputStream input) throws IOException { + return _toByteArray(input); + } + + /** + * Gets the contents of an InputStream as a byte[]. + *

+ * This method buffers the input internally, so there is no need to use a + * BufferedInputStream. + * + * @param input the InputStream to read from + * @return the requested byte array + * @throws NullPointerException if the input is null + * @throws IOException if an I/O error occurs + */ + private static byte[] _toByteArray(final InputStream input) throws IOException { + try (final ByteArrayOutputStream output = new ByteArrayOutputStream()) { + copyStream(input, output); + return output.toByteArray(); + } + } + + private static int copyStream(final InputStream input, final OutputStream output) throws IOException { + final byte[] buffer = new byte[1024 * 4]; + long count = 0; + int n; + while (-1 != (n = input.read(buffer))) { + output.write(buffer, 0, n); + count += n; + } + if (count > Integer.MAX_VALUE) { + return -1; + } + return (int) count; + } + + /** + * + * @param bytes + * @return the string + */ + public static String toString(byte[] bytes) { + return new String(bytes); + } +} diff --git a/gxHTTP/src/main/java/org/gcube/common/gxhttp/util/package-info.java b/gxHTTP/src/main/java/org/gcube/common/gxhttp/util/package-info.java new file mode 100644 index 0000000..a712608 --- /dev/null +++ b/gxHTTP/src/main/java/org/gcube/common/gxhttp/util/package-info.java @@ -0,0 +1,7 @@ +/** + * Utilities. + * + * @author Manuele Simi (ISTI CNR) + * + */ +package org.gcube.common.gxhttp.util; \ No newline at end of file diff --git a/gxHTTP/src/test/java/org/gcube/common/gxhttp/GXHTTPStringRequestTest.java b/gxHTTP/src/test/java/org/gcube/common/gxhttp/GXHTTPStringRequestTest.java new file mode 100644 index 0000000..e907e05 --- /dev/null +++ b/gxHTTP/src/test/java/org/gcube/common/gxhttp/GXHTTPStringRequestTest.java @@ -0,0 +1,116 @@ +package org.gcube.common.gxhttp; + +import static org.junit.Assert.*; + +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.util.Map; +import java.util.Properties; +import java.util.WeakHashMap; + +import org.gcube.common.authorization.client.Constants; +import org.gcube.common.authorization.client.exceptions.ObjectNotFound; +import org.gcube.common.authorization.library.AuthorizationEntry; +import org.gcube.common.authorization.library.provider.SecurityTokenProvider; +import org.gcube.common.gxhttp.request.GXHTTPStringRequest; +import org.gcube.common.gxhttp.util.ContentUtils; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + + +/** + * Test cases for {@link GXHTTPStringRequest} + * + * @author Manuele Simi (ISTI-CNR) + * + */ +public class GXHTTPStringRequestTest { + + private GXHTTPStringRequest request; + + public static String DEFAULT_TEST_SCOPE = ""; + + static String DEFAULT_RM_URL = ""; + + static String DEFAULT_RR_URL = ""; + + + private static boolean skipTest = false; + + static { + Properties properties = new Properties(); + try (InputStream input = GXHTTPStringRequestTest.class.getClassLoader().getResourceAsStream("token.props")) { + // load the properties file + properties.load(input); + DEFAULT_TEST_SCOPE = properties.getProperty("DEFAULT_SCOPE_TOKEN"); + if (DEFAULT_TEST_SCOPE.isEmpty()) + skipTest = true; + DEFAULT_RM_URL = properties.getProperty("RM_URL"); + DEFAULT_RR_URL = properties.getProperty("RR_URL"); + } catch (IOException | NullPointerException e) { + skipTest = true; + } + } + + @BeforeClass + public static void beforeClass() throws Exception { + setContext(DEFAULT_TEST_SCOPE); + } + + public static void setContext(String token) throws ObjectNotFound, Exception { + if (DEFAULT_TEST_SCOPE.isEmpty()) { + skipTest = true; + return; + } + SecurityTokenProvider.instance.set(token); + } + + public static String getCurrentScope(String token) throws ObjectNotFound, Exception { + AuthorizationEntry authorizationEntry = Constants.authorizationService().get(token); + String context = authorizationEntry.getContext(); + return context; + } + + @AfterClass + public static void afterClass() throws Exception { + SecurityTokenProvider.instance.reset(); + } + + /** + * Test method for {@link org.gcube.common.gxhttp.request.GXHTTPStringRequest#newRequest(java.lang.String)}. + */ + @Before + public void testNewRequest() { + request = GXHTTPStringRequest.newRequest(DEFAULT_RM_URL).from("GXRequestTest"); + } + + /** + * Test method for {@link org.gcube.common.gxhttp.request.GXHTTPStringRequest#post(java.lang.String)}. + */ + @Test + public void testPostString() { + if (skipTest) + return; + request.clear(); + String context ="{\"@class\":\"Context\",\"header\":{\"@class\":\"Header\",\"uuid\":\"6f86dc81-2f59-486b-8aa9-3ab5486313c4\",\"creator\":null,\"modifiedBy\":\"gxRestTest\",\"creationTime\":null,\"lastUpdateTime\":null},\"name\":\"gxTest\",\"parent\":null,\"children\":[]}"; + Map queryParams = new WeakHashMap<>(); + queryParams.put("rrURL", DEFAULT_RR_URL); + try { + HttpURLConnection response = request.path("gxrest") + .header("Another header", "GXHTTPRequestTest") + .queryParams(queryParams).post(context); + assertTrue("Unexpected returned code.", response.getResponseCode() == 200); + String body = ContentUtils.toString(ContentUtils.toByteArray(response.getInputStream())); + System.out.println("Returned string " + body); + + } catch (Exception e) { + e.printStackTrace(); + fail("Failed to send a POST request"); + } + + } + +} diff --git a/gxHTTP/src/test/resources/logback-test.xml b/gxHTTP/src/test/resources/logback-test.xml new file mode 100644 index 0000000..ce9b6c4 --- /dev/null +++ b/gxHTTP/src/test/resources/logback-test.xml @@ -0,0 +1,20 @@ + + + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{0}: %msg%n + + + + + + + + + + + + + \ No newline at end of file diff --git a/gxJRS/.classpath b/gxJRS/.classpath new file mode 100644 index 0000000..fae1a2b --- /dev/null +++ b/gxJRS/.classpath @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gxJRS/.project b/gxJRS/.project new file mode 100644 index 0000000..79f0323 --- /dev/null +++ b/gxJRS/.project @@ -0,0 +1,23 @@ + + + gxRest + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + + diff --git a/gxJRS/.settings/org.eclipse.core.resources.prefs b/gxJRS/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000..29abf99 --- /dev/null +++ b/gxJRS/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,6 @@ +eclipse.preferences.version=1 +encoding//src/main/java=UTF-8 +encoding//src/main/resources=UTF-8 +encoding//src/test/java=UTF-8 +encoding//src/test/resources=UTF-8 +encoding/=UTF-8 diff --git a/gxJRS/.settings/org.eclipse.jdt.core.prefs b/gxJRS/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..714351a --- /dev/null +++ b/gxJRS/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,5 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 +org.eclipse.jdt.core.compiler.compliance=1.8 +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning +org.eclipse.jdt.core.compiler.source=1.8 diff --git a/gxJRS/distro/LICENSE b/gxJRS/distro/LICENSE new file mode 100644 index 0000000..cdf50bd --- /dev/null +++ b/gxJRS/distro/LICENSE @@ -0,0 +1,4 @@ +gCube System - License +------------------------------------------------------------ + +${gcube.license} \ No newline at end of file diff --git a/gxJRS/distro/README b/gxJRS/distro/README new file mode 100644 index 0000000..b55442a --- /dev/null +++ b/gxJRS/distro/README @@ -0,0 +1,67 @@ +The gCube System - ${name} +-------------------------------------------------- + +${description} + + +${gcube.description} + +${gcube.funding} + + +Version +-------------------------------------------------- + +${version} (${buildDate}) + +Please see the file named "changelog.xml" in this directory for the release notes. + + +Authors +-------------------------------------------------- + +* Manuele Simi (manuele.simi-AT-isti.cnr.it), Istituto di Scienza e Tecnologie dell'Informazione "A. Faedo" - CNR, Pisa (Italy). +* Luca Frosini (luca.frosini-AT-isti.cnr.it), Istituto di Scienza e Tecnologie dell'Informazione "A. Faedo" - CNR, Pisa (Italy). + + +Maintainers +----------- + +* Manuele Simi (manuele.simi-AT-isti.cnr.it), Istituto di Scienza e Tecnologie dell'Informazione "A. Faedo" - CNR, Pisa (Italy). +* Luca Frosini (luca.frosini-AT-isti.cnr.it), Istituto di Scienza e Tecnologie dell'Informazione "A. Faedo" - CNR, Pisa (Italy). + +Download information +-------------------------------------------------- + +Source code is available from SVN: + ${scm.url} + +Binaries can be downloaded from the gCube website: + ${gcube.website} + + +Installation +-------------------------------------------------- + +Installation documentation is available on-line in the gCube Wiki: + ${gcube.wikiRoot}/ + + +Documentation +-------------------------------------------------- + +Documentation is available on-line in the gCube Wiki: + ${gcube.wikiRoot}/ + + +Support +-------------------------------------------------- + +Bugs and support requests can be reported in the gCube issue tracking tool: + ${gcube.issueTracking} + + +Licensing +-------------------------------------------------- + +This software is licensed under the terms you may find in the file named "LICENSE" in this directory. diff --git a/gxJRS/distro/changelog.xml b/gxJRS/distro/changelog.xml new file mode 100644 index 0000000..7221703 --- /dev/null +++ b/gxJRS/distro/changelog.xml @@ -0,0 +1,13 @@ + + + + + + + + Separated plaing HTTP requests from gxRest + + + First Release + + \ No newline at end of file diff --git a/gxJRS/distro/profile.xml b/gxJRS/distro/profile.xml new file mode 100644 index 0000000..1e5db40 --- /dev/null +++ b/gxJRS/distro/profile.xml @@ -0,0 +1,28 @@ + + + + + Service + + ${description} + ${serviceClass} + ${artifactId} + 1.0.0 + + + ${description} + ${artifactId} + ${version} + + ${groupId} + ${artifactId} + ${version} + + Library + + ${build.finalName}.${project.packaging} + + + + + \ No newline at end of file diff --git a/gxJRS/pom.xml b/gxJRS/pom.xml new file mode 100644 index 0000000..6255408 --- /dev/null +++ b/gxJRS/pom.xml @@ -0,0 +1,127 @@ + + 4.0.0 + + org.gcube.common + gxREST + 1.1.2-SNAPSHOT + + + gxJRS + jar + gCube eXtensions to JAX-RS + gCube eXtensions to REST based on JAX-RS + + + UTF-8 + ${project.basedir}/distro + + + + scm:https://svn.d4science.research-infrastructures.eu/gcube/trunk/common/${parent.artifactId}/${project.artifactId} + scm:https://svn.d4science.research-infrastructures.eu/gcube/trunk/common/${parent.artifactId}/${project.artifactId} + https://svn.d4science.research-infrastructures.eu/gcube/trunk/common/${parent.artifactId}/${project.artifactId} + + + + + + org.glassfish.jersey + jersey-bom + ${jersey.version} + pom + import + + + org.gcube.distribution + gcube-bom + LATEST + pom + import + + + org.gcube.information-system + information-system-bom + LATEST + pom + import + + + + + + + + javax.ws.rs + javax.ws.rs-api + ${jaxrs.version} + + + org.gcube.common + gxHTTP + ${project.version} + + + org.gcube.common + authorization-client + + + + org.slf4j + slf4j-api + provided + + + + com.fasterxml.jackson.core + jackson-core + + + com.fasterxml.jackson.core + jackson-databind + + + + + org.glassfish.jersey.core + jersey-client + test + + + org.glassfish.jersey.inject + jersey-hk2 + 2.27 + test + + + junit + junit + 4.12 + test + + + ch.qos.logback + logback-classic + 1.0.13 + test + + + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + make-servicearchive + package + + + + + + + diff --git a/gxJRS/src/main/java/org/gcube/common/gxrest/methods/package-info.java b/gxJRS/src/main/java/org/gcube/common/gxrest/methods/package-info.java new file mode 100644 index 0000000..3c110f6 --- /dev/null +++ b/gxJRS/src/main/java/org/gcube/common/gxrest/methods/package-info.java @@ -0,0 +1,5 @@ +/** + * @author Manuele Simi (ISTI - CNR) + * + */ +package org.gcube.common.gxrest.methods; \ No newline at end of file diff --git a/gxJRS/src/main/java/org/gcube/common/gxrest/request/GXHTTPStreamRequest.java b/gxJRS/src/main/java/org/gcube/common/gxrest/request/GXHTTPStreamRequest.java new file mode 100644 index 0000000..73c9943 --- /dev/null +++ b/gxJRS/src/main/java/org/gcube/common/gxrest/request/GXHTTPStreamRequest.java @@ -0,0 +1,202 @@ +package org.gcube.common.gxrest.request; + +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.util.Map; +import java.util.Objects; + +import org.gcube.common.gxhttp.reference.GXConnection; +import org.gcube.common.gxhttp.reference.GXHTTP; +import org.gcube.common.gxhttp.reference.GXHTTPRequestBuilder; +import org.gcube.common.gxhttp.reference.GXConnection.HTTPMETHOD; +import org.gcube.common.gxrest.response.inbound.GXInboundResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A context-aware request to a web application. + * It supports sending streams through Put/Post requests. + * + * @author Manuele Simi (ISTI-CNR) + * @author Luca Frosini (ISTI-CNR) + * + */ +public class GXHTTPStreamRequest implements GXHTTP { + + protected static final Logger logger = LoggerFactory.getLogger(GXHTTPStreamRequest.class); + + GXHTTPRequestBuilder builder = new GXHTTPRequestBuilder(); + /** + * A new request. + */ + private GXHTTPStreamRequest(String address) { + builder.connection = new GXConnection(address); + } + + /** + * Creates a new request. + * + * @param address + * the address of the web app to call + * @return the request + */ + public static GXHTTPStreamRequest newRequest(String address) { + return new GXHTTPStreamRequest(address); + } + /* (non-Javadoc) + * @see org.gcube.common.gxrest.request.GXHTTP#put(java.lang.Object) + */ + @Override + public GXInboundResponse put(InputStream body) throws Exception { + if (Objects.nonNull(body)) + builder.connection.addBodyAsStream(body); + logger.trace("Sending a PUT request..."); + return new GXInboundResponse(builder.connection.send(HTTPMETHOD.PUT)); + } + + /* (non-Javadoc) + * @see org.gcube.common.gxrest.request.GXHTTP#post(java.lang.Object) + */ + @Override + public GXInboundResponse post(InputStream body) throws Exception { + logger.trace("Sending a POST request..."); + if (Objects.nonNull(body)) + builder.connection.addBodyAsStream(body); + return new GXInboundResponse(builder.connection.send(HTTPMETHOD.POST)); + } + + /* (non-Javadoc) + * @see org.gcube.common.gxrest.request.GXHTTP#put() + */ + @Override + public GXInboundResponse put() throws Exception { + return new GXInboundResponse(builder.put()); + } + + /* (non-Javadoc) + * @see org.gcube.common.gxrest.request.GXHTTP#delete() + */ + @Override + public GXInboundResponse delete() throws Exception { + return new GXInboundResponse(builder.delete()); + } + + /* (non-Javadoc) + * @see org.gcube.common.gxrest.request.GXHTTP#head() + */ + @Override + public GXInboundResponse head() throws Exception { + return new GXInboundResponse(builder.head()); + } + + /* (non-Javadoc) + * @see org.gcube.common.gxrest.request.GXHTTP#get() + */ + @Override + public GXInboundResponse get() throws Exception { + return new GXInboundResponse(builder.get()); + } + + /* (non-Javadoc) + * @see org.gcube.common.gxrest.request.GXHTTP#post() + */ + @Override + public GXInboundResponse post() throws Exception { + return new GXInboundResponse(builder.post()); + } + + /* (non-Javadoc) + * @see org.gcube.common.gxrest.request.GXHTTP#trace() + */ + @Override + public GXInboundResponse trace() throws Exception { + return new GXInboundResponse(builder.trace()); + } + + /* (non-Javadoc) + * @see org.gcube.common.gxrest.request.GXHTTP#patch() + */ + @Override + public GXInboundResponse patch() throws Exception { + return new GXInboundResponse(builder.patch()); + } + + /* (non-Javadoc) + * @see org.gcube.common.gxrest.request.GXHTTP#options() + */ + @Override + public GXInboundResponse options() throws Exception { + return new GXInboundResponse(builder.options()); + } + + /* (non-Javadoc) + * @see org.gcube.common.gxrest.request.GXHTTP#connect() + */ + @Override + public GXInboundResponse connect() throws Exception { + return new GXInboundResponse(builder.connect()); + } + + /* (non-Javadoc) + * @see org.gcube.common.gxrest.request.GXHTTP#setSecurityToken(java.lang.String) + */ + @Override + public void setSecurityToken(String token) { + builder.setSecurityToken(token); + } + + /* (non-Javadoc) + * @see org.gcube.common.gxrest.request.GXHTTP#isExternalCall(boolean) + */ + @Override + public void isExternalCall(boolean ext) { + builder.isExternalCall(ext); + } + + /** + * @param string + * @return the request + */ + public GXHTTPStreamRequest from(String agent) { + builder.from(agent) ; + return this; + } + + /** + * Clear up the request. + */ + public void clear() { + builder.clear(); + } + + /** + * @param path + * @return the request + * @throws UnsupportedEncodingException + * + */ + public GXHTTPStreamRequest path(String path) throws UnsupportedEncodingException { + builder.path(path); + return this; + } + + /** + * @param name + * @param value + * @return the request + */ + public GXHTTPStreamRequest header(String name, String value) { + builder.header(name, value); + return this; + } + + /** + * @param queryParams + * @return the request + * @throws UnsupportedEncodingException + */ + public GXHTTPStreamRequest queryParams(Map queryParams) throws UnsupportedEncodingException { + builder.queryParams(queryParams); + return this; + } +} diff --git a/gxJRS/src/main/java/org/gcube/common/gxrest/request/GXHTTPStringRequest.java b/gxJRS/src/main/java/org/gcube/common/gxrest/request/GXHTTPStringRequest.java new file mode 100644 index 0000000..3dfb37a --- /dev/null +++ b/gxJRS/src/main/java/org/gcube/common/gxrest/request/GXHTTPStringRequest.java @@ -0,0 +1,223 @@ +package org.gcube.common.gxrest.request; + +import java.io.UnsupportedEncodingException; +import java.util.Map; +import java.util.Objects; + +import org.gcube.common.gxhttp.reference.GXConnection; +import org.gcube.common.gxhttp.reference.GXHTTP; +import org.gcube.common.gxhttp.reference.GXHTTPRequestBuilder; +import org.gcube.common.gxhttp.reference.GXConnection.HTTPMETHOD; +import org.gcube.common.gxrest.response.inbound.GXInboundResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +/** + * A context-aware request to a web application. + * It supports sending strings through Put/Post requests. + * + * @author Manuele Simi (ISTI-CNR) + * @author Luca Frosini (ISTI-CNR) + * + */ +public class GXHTTPStringRequest implements GXHTTP { + + protected static final Logger logger = LoggerFactory.getLogger(GXHTTPStringRequest.class); + + GXHTTPRequestBuilder builder = new GXHTTPRequestBuilder(); + + /** + * A new request. + */ + private GXHTTPStringRequest(String address) { + builder.connection = new GXConnection(address); + } + + /** + * Creates a new request. + * + * @param address + * the address of the web app to call + * @return the request + */ + public static GXHTTPStringRequest newRequest(String address) { + return new GXHTTPStringRequest(address); + } + + + /** + * Sets the body of the request. + * + * @param body + * @return the request + */ + public GXHTTPStringRequest withBody(String body) { + builder.connection.addBody(body); + return this; + } + + /* + * (non-Javadoc) + * + * @see org.gcube.common.gxrest.request.GXHTTP#put(java.lang.Object) + */ + @Override + public GXInboundResponse put(String body) throws Exception { + if (Objects.nonNull(body)) + builder.connection.addBody(body); + logger.trace("Sending a PUT request..."); + return new GXInboundResponse(builder.connection.send(HTTPMETHOD.PUT)); + } + + /* + * (non-Javadoc) + * + * @see org.gcube.common.gxrest.request.GXHTTP#post() + */ + @Override + public GXInboundResponse post(String body) throws Exception { + logger.trace("Sending a POST request..."); + if (Objects.nonNull(body)) + builder.connection.addBody(body); + return new GXInboundResponse(builder.connection.send(HTTPMETHOD.POST)); + } + + /* (non-Javadoc) + * @see org.gcube.common.gxrest.request.GXHTTP#put() + */ + @Override + public GXInboundResponse put() throws Exception { + return new GXInboundResponse(builder.put()); + } + + /* (non-Javadoc) + * @see org.gcube.common.gxrest.request.GXHTTP#delete() + */ + @Override + public GXInboundResponse delete() throws Exception { + return new GXInboundResponse(builder.delete()); + } + + /* (non-Javadoc) + * @see org.gcube.common.gxrest.request.GXHTTP#head() + */ + @Override + public GXInboundResponse head() throws Exception { + return new GXInboundResponse(builder.head()); + } + + /* (non-Javadoc) + * @see org.gcube.common.gxrest.request.GXHTTP#get() + */ + @Override + public GXInboundResponse get() throws Exception { + return new GXInboundResponse(builder.get()); + } + + /* (non-Javadoc) + * @see org.gcube.common.gxrest.request.GXHTTP#post() + */ + @Override + public GXInboundResponse post() throws Exception { + return new GXInboundResponse(builder.post()); + } + + /* (non-Javadoc) + * @see org.gcube.common.gxrest.request.GXHTTP#trace() + */ + @Override + public GXInboundResponse trace() throws Exception { + return new GXInboundResponse(builder.trace()); + } + + /* (non-Javadoc) + * @see org.gcube.common.gxrest.request.GXHTTP#patch() + */ + @Override + public GXInboundResponse patch() throws Exception { + return new GXInboundResponse(builder.patch()); + } + + /* (non-Javadoc) + * @see org.gcube.common.gxrest.request.GXHTTP#options() + */ + @Override + public GXInboundResponse options() throws Exception { + return new GXInboundResponse(builder.options()); + } + + /* (non-Javadoc) + * @see org.gcube.common.gxrest.request.GXHTTP#connect() + */ + @Override + public GXInboundResponse connect() throws Exception { + return new GXInboundResponse(builder.connect()); + } + + /* (non-Javadoc) + * @see org.gcube.common.gxrest.request.GXHTTP#setSecurityToken(java.lang.String) + */ + @Override + public void setSecurityToken(String token) { + builder.setSecurityToken(token); + } + + /* (non-Javadoc) + * @see org.gcube.common.gxrest.request.GXHTTP#isExternalCall(boolean) + */ + @Override + public void isExternalCall(boolean ext) { + builder.isExternalCall(ext); + + } + + /** + * @param string + * @return the request + */ + public GXHTTPStringRequest from(String agent) { + builder.from(agent) ; + return this; + } + + /** + * Clear up the request. + */ + public void clear() { + builder.clear(); + } + + /** + * @param string + * @return the request + * @throws UnsupportedEncodingException + * + */ + public GXHTTPStringRequest path(String path) throws UnsupportedEncodingException { + builder.path(path); + return this; + } + + /** + * @param name + * @param value + * @return the request + */ + public GXHTTPStringRequest header(String name, String value) { + builder.header(name, value); + return this; + } + + /** + * @param queryParams + * @return the request + * @throws UnsupportedEncodingException + */ + public GXHTTPStringRequest queryParams(Map queryParams) throws UnsupportedEncodingException { + builder.queryParams(queryParams); + return this; + } + + +} diff --git a/gxJRS/src/main/java/org/gcube/common/gxrest/request/GXWebTargetAdapterRequest.java b/gxJRS/src/main/java/org/gcube/common/gxrest/request/GXWebTargetAdapterRequest.java new file mode 100644 index 0000000..91259f2 --- /dev/null +++ b/gxJRS/src/main/java/org/gcube/common/gxrest/request/GXWebTargetAdapterRequest.java @@ -0,0 +1,397 @@ +package org.gcube.common.gxrest.request; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSession; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; +import javax.ws.rs.client.Client; +import javax.ws.rs.client.ClientBuilder; +import javax.ws.rs.client.Entity; +import javax.ws.rs.client.Invocation.Builder; +import javax.ws.rs.client.WebTarget; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedHashMap; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.core.Response; + +import org.gcube.common.authorization.library.provider.SecurityTokenProvider; +import org.gcube.common.gxhttp.reference.GXConnection; +import org.gcube.common.gxhttp.reference.GXHTTP; +import org.gcube.common.gxrest.response.inbound.GXInboundResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A GX request based on JAX-RS. It requires a runtime implementation of JAX-RS + * on the classpath (e.g. Jersey) to work. + * + * @author Manuele Simi (ISTI CNR) + * + */ +public class GXWebTargetAdapterRequest implements GXHTTP,GXInboundResponse> { + + private WebTarget adaptee; + private static final Logger logger = LoggerFactory.getLogger(GXWebTargetAdapterRequest.class); + private MediaType[] mediaType; + MultivaluedMap headers = new MultivaluedHashMap(); + private boolean extCall = false; + + /** + * Creates a new request. + * + * @param address + * the address of the web app to call + * @return the request + */ + public static GXWebTargetAdapterRequest newRequest(String address) { + return new GXWebTargetAdapterRequest(address, false); + } + + public static GXWebTargetAdapterRequest newHTTPSRequest(String address) { + return new GXWebTargetAdapterRequest(address, true); + } + + /** + * @param address + */ + private GXWebTargetAdapterRequest(String address, boolean withHTTPS) { + Client client = ClientBuilder.newClient(); + if (withHTTPS) { + try { + SSLContext sc = SSLContext.getInstance("TLSv1"); + System.setProperty("https.protocols", "TLSv1"); + TrustManager[] certs = new TrustManager[] { new X509TrustManager() { + @Override + public X509Certificate[] getAcceptedIssuers() { + return null; + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType) + throws CertificateException { + } + + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType) + throws CertificateException { + } + } }; + sc.init(null, certs, new java.security.SecureRandom()); + HostnameVerifier allHostsValid = new HostnameVerifier() { + // insecure host verifier + @Override + public boolean verify(String hostname, SSLSession session) { + return true; + } + }; + client = ClientBuilder.newBuilder().sslContext(sc).hostnameVerifier(allHostsValid).build(); + } catch (KeyManagementException | NoSuchAlgorithmException e) { + client = ClientBuilder.newClient(); + } + } + this.adaptee = client.target(address); + this.headers.add(org.gcube.common.authorization.client.Constants.TOKEN_HEADER_ENTRY, + SecurityTokenProvider.instance.get()); + this.headers.add("User-Agent", this.getClass().getSimpleName()); + } + + /** + * Overrides the default security token. + * + * @param token + * the new token + */ + @Override + public void setSecurityToken(String token) { + if (!this.extCall) + this.headers.add(org.gcube.common.authorization.client.Constants.TOKEN_HEADER_ENTRY, token); + + else + throw new UnsupportedOperationException("Cannot set the security token on an external call"); + } + + /** + * Sets the identity user agent associated to the request. + * + * @param agent + * @return the request + */ + public GXWebTargetAdapterRequest from(String agent) { + this.headers.add("User-Agent", this.getClass().getSimpleName()); + return this; + } + + /** + * Sets a new property in the request. + * + * @param name + * the name of the property + * @param value + * the value of the property + * @return the request + */ + public GXWebTargetAdapterRequest configProperty(String name, String value) { + this.adaptee = this.adaptee.property(name, value); + return this; + } + + /** + * Registers an instance of a custom JAX-RS component (such as an extension + * provider or a {@link javax.ws.rs.core.Feature feature} meta-provider) to + * be instantiated and used in the scope of this request. + * + * @param component + * the component to register + * @return the request + */ + public GXWebTargetAdapterRequest register(Object component) { + this.adaptee = this.adaptee.register(component); + return this; + + } + + /** + * Registers a class of a custom JAX-RS component (such as an extension + * provider or a {@link javax.ws.rs.core.Feature feature} meta-provider) to + * be instantiated and used in the scope of this request. + * + * @param component + * the class of the component to register + * @return the request + */ + public GXWebTargetAdapterRequest register(Class component) { + this.adaptee = this.adaptee.register(component); + return this; + + } + + /** + * Adds a positional path parameter to the request. + * + * @param path + * the new token in the path + * @return the request + * @throws UnsupportedEncodingException + */ + public GXWebTargetAdapterRequest path(String path) throws UnsupportedEncodingException { + this.adaptee = this.adaptee.path(path); + return this; + } + + /** + * Sets the query parameters for the request. + * + * @param parameters + * the parameters that go in the URL after the address and the + * path params. + * @return the request + * @throws UnsupportedEncodingException + */ + public GXWebTargetAdapterRequest queryParams(Map parameters) throws UnsupportedEncodingException { + if (Objects.nonNull(parameters) && !parameters.isEmpty()) { + for (Entry parameter : parameters.entrySet()) { + this.adaptee = this.adaptee.queryParam(URLEncoder.encode(parameter.getKey(), GXConnection.UTF8), + parameter.getValue()); + } + } + return this; + } + + /** + * Defines the accepted response media types. + * + * @param acceptedResponseTypes + * accepted response media types. + * @return builder for a request targeted at the URI referenced by this + * target instance. + */ + public GXWebTargetAdapterRequest setAcceptedResponseType(MediaType... acceptedResponseTypes) { + this.mediaType = acceptedResponseTypes; + return this; + } + + /* + * (non-Javadoc) + * + * @see org.gcube.common.gxrest.request.GXHTTP#delete() + */ + @Override + public GXInboundResponse delete() throws Exception { + logger.trace("Sending a DELETE request..."); + Response response = this.buildRequest().delete(); + return buildGXResponse(response); + } + + /* + * (non-Javadoc) + * + * @see org.gcube.common.gxrest.request.GXHTTP#head() + */ + @Override + public GXInboundResponse head() throws Exception { + logger.trace("Sending a HEAD request..."); + Response response = this.buildRequest().head(); + return buildGXResponse(response); + } + + /* + * (non-Javadoc) + * + * @see org.gcube.common.gxrest.request.GXHTTP#get() + */ + @Override + public GXInboundResponse get() throws Exception { + logger.trace("Sending a GET request..."); + Response response = this.buildRequest().get(Response.class); + return buildGXResponse(response); + } + + /** + * Builds the request builder. + * + * @return the builder + */ + private Builder buildRequest() { + Builder builder = this.adaptee.request(); + builder.headers(this.headers); + return builder; + } + + /** + * Add an arbitrary header. + * + * @return the builder + */ + public GXWebTargetAdapterRequest header(String name, Object value) { + headers.add(name, value); + return this; + } + + /* + * (non-Javadoc) + * + * @see org.gcube.common.gxrest.request.GXHTTP#put(java.lang.Object) + */ + @Override + public GXInboundResponse put(Entity body) throws Exception { + logger.trace("Sending a PUT request..."); + if (Objects.nonNull(body)) + return buildGXResponse(this.buildRequest().put(body)); + else + throw new IllegalArgumentException("Invalid body for the PUT request"); + } + + /* + * (non-Javadoc) + * + * @see org.gcube.common.gxrest.request.GXHTTP#put() + */ + @Override + public GXInboundResponse put() throws Exception { + logger.trace("Sending a PUT request with no body..."); + return buildGXResponse(this.buildRequest().put(null)); + } + + /* + * (non-Javadoc) + * + * @see org.gcube.common.gxrest.request.GXHTTP#post(java.lang.Object) + */ + @Override + public GXInboundResponse post(Entity body) throws Exception { + Objects.requireNonNull(body, "Cannot send a POST request with a null body."); + logger.trace("Sending a POST request..."); + Response response = this.buildRequest().post(body, Response.class); + return buildGXResponse(response); + } + + /* + * (non-Javadoc) + * + * @see org.gcube.common.gxrest.request.GXHTTP#post() + */ + @Override + public GXInboundResponse post() throws Exception { + logger.trace("Sending a POST request with no body..."); + Response response = this.buildRequest().post(null, Response.class); + return buildGXResponse(response); + } + + /** + * Builds the response. + * + * @param source + * the original response returned by the JAX-RS implementation + * @return the inbound response + */ + private GXInboundResponse buildGXResponse(Response source) { + return (Objects.isNull(this.mediaType)) ? new GXInboundResponse(source) + : new GXInboundResponse(source, this.mediaType); + + } + + /* + * (non-Javadoc) + * + * @see org.gcube.common.gxrest.request.GXHTTP#trace() + */ + @Override + public GXInboundResponse trace() throws Exception { + logger.trace("Sending a TRACE request with no body..."); + Response response = this.buildRequest().trace(Response.class); + return buildGXResponse(response); + } + + /* + * (non-Javadoc) + * + * @see org.gcube.common.gxrest.request.GXHTTP#patch() + */ + @Override + public GXInboundResponse patch() throws Exception { + throw new UnsupportedOperationException("WebTarget does not support PATCH"); + } + + /* + * (non-Javadoc) + * + * @see org.gcube.common.gxrest.request.GXHTTP#options() + */ + @Override + public GXInboundResponse options() throws Exception { + logger.trace("Sending an OPTIONS request with no body..."); + Response response = this.buildRequest().options(Response.class); + return buildGXResponse(response); + } + + /* + * (non-Javadoc) + * + * @see org.gcube.common.gxrest.request.GXHTTP#connect() + */ + @Override + public GXInboundResponse connect() throws Exception { + throw new UnsupportedOperationException("WebTarget does not support CONNECT"); + } + + /* + * (non-Javadoc) + * + * @see org.gcube.common.gxrest.request.GXHTTP#isExternalCall(boolean) + */ + @Override + public void isExternalCall(boolean ext) { + this.extCall = ext; + } +} diff --git a/gxJRS/src/main/java/org/gcube/common/gxrest/request/package-info.java b/gxJRS/src/main/java/org/gcube/common/gxrest/request/package-info.java new file mode 100644 index 0000000..a6be91c --- /dev/null +++ b/gxJRS/src/main/java/org/gcube/common/gxrest/request/package-info.java @@ -0,0 +1,7 @@ +/** + * Requests to web application. + * + * @author Manuele Simi (ISTI - CNR) + * + */ +package org.gcube.common.gxrest.request; \ No newline at end of file diff --git a/gxJRS/src/main/java/org/gcube/common/gxrest/response/entity/CodeEntity.java b/gxJRS/src/main/java/org/gcube/common/gxrest/response/entity/CodeEntity.java new file mode 100644 index 0000000..73067e3 --- /dev/null +++ b/gxJRS/src/main/java/org/gcube/common/gxrest/response/entity/CodeEntity.java @@ -0,0 +1,20 @@ +package org.gcube.common.gxrest.response.entity; + +import javax.ws.rs.core.GenericEntity; + +/** + * An entity to wrap {@link SerializableErrorEntity}s into a {@link WebCodeException}. + * + * @author Manuele Simi (ISTI CNR) + * + */ +public class CodeEntity extends GenericEntity { + + /** + * @param entity + */ + public CodeEntity(SerializableErrorEntity entity) { + super(entity); + } + +} diff --git a/gxJRS/src/main/java/org/gcube/common/gxrest/response/entity/EntityTag.java b/gxJRS/src/main/java/org/gcube/common/gxrest/response/entity/EntityTag.java new file mode 100644 index 0000000..63aafc9 --- /dev/null +++ b/gxJRS/src/main/java/org/gcube/common/gxrest/response/entity/EntityTag.java @@ -0,0 +1,17 @@ +package org.gcube.common.gxrest.response.entity; + +/** + * + * Values of the HTTP Entity Tag, used as the value + * of an ETag response header. + * + * @author Manuele Simi (ISTI CNR) + * @see HTTP/1.1 section 3.11 + */ +public final class EntityTag { + + public static final String gxSuccess = "GXSuccessResponse"; + + public static final String gxError = "GXErrorResponse"; + +} diff --git a/gxJRS/src/main/java/org/gcube/common/gxrest/response/entity/SerializableErrorEntity.java b/gxJRS/src/main/java/org/gcube/common/gxrest/response/entity/SerializableErrorEntity.java new file mode 100644 index 0000000..9827af6 --- /dev/null +++ b/gxJRS/src/main/java/org/gcube/common/gxrest/response/entity/SerializableErrorEntity.java @@ -0,0 +1,78 @@ +package org.gcube.common.gxrest.response.entity; + +import org.gcube.common.gxrest.response.outbound.ErrorCode; + +/** + * An entity that can be serialized in a {@link WebCodeException}. + * + * @author Manuele Simi (ISTI CNR) + * + */ +public class SerializableErrorEntity { + + private int id = -1; + private String message; + private String exceptionClass; + private String encodedTrace = ""; + + public SerializableErrorEntity() {} + + /** + * @param id + * @param message + */ + public SerializableErrorEntity(ErrorCode errorCode) { + this.id = errorCode.getId(); + this.message = errorCode.getMessage(); + } + + /** + * + * @param e + */ + public SerializableErrorEntity(Exception e) { + this.exceptionClass = e.getClass().getCanonicalName(); + this.message = e.getMessage(); + } + + /** + * + * @param e + */ + public SerializableErrorEntity(Exception e, int lines) { + this.exceptionClass = e.getClass().getCanonicalName(); + this.message = e.getMessage(); + this.encodedTrace = StackTraceEncoder.encodeTrace(e.getStackTrace(), lines); + } + + public int getId() { + return this.id; + } + + + public String getMessage() { + return this.message; + } + + /** + * @return the full qualified name of the embedded {@link Exception} + */ + public String getExceptionClass() { + return this.exceptionClass; + } + + /** + * @return the encoded stacktrace + */ + public String getEncodedTrace() { + return encodedTrace; + } + + /** + * Checks if a stacktrace is available. + * @return true if a stacktrace is serialized in the entity. + */ + public boolean hasStackTrace() { + return !this.encodedTrace.isEmpty(); + } +} diff --git a/gxJRS/src/main/java/org/gcube/common/gxrest/response/entity/StackTraceEncoder.java b/gxJRS/src/main/java/org/gcube/common/gxrest/response/entity/StackTraceEncoder.java new file mode 100644 index 0000000..f38283e --- /dev/null +++ b/gxJRS/src/main/java/org/gcube/common/gxrest/response/entity/StackTraceEncoder.java @@ -0,0 +1,57 @@ +package org.gcube.common.gxrest.response.entity; + +import java.util.StringJoiner; + +/** + * Encoder for {@link StackTraceElement}. + * + * @author Manuele Simi (ISTI-CNR) + * + */ +public class StackTraceEncoder { + + private final static String FIELD_SEPARATOR = "~~"; + + private final static String ELEMENT_SEPARATOR = "~!~"; + + private StackTraceEncoder() { + } + + /** + * Encodes the stacktrace element as string. + * + * @param element + * @return the encoded element + */ + public static String encodeElement(StackTraceElement element) { + return element.getClassName() + FIELD_SEPARATOR + element.getMethodName() + FIELD_SEPARATOR + + element.getFileName() + FIELD_SEPARATOR + element.getLineNumber(); + + } + + /** + * Decodes the string as stacktrace element. + * + * @param encoded + * @return the decoded element + */ + public static StackTraceElement decodeElement(String encoded) { + String[] elements = encoded.split(FIELD_SEPARATOR, 4); + return new StackTraceElement(elements[0], elements[1], elements[2], Integer.valueOf(elements[3])); + } + + public static String encodeTrace(StackTraceElement[] elements, int lines) { + StringJoiner joiner = new StringJoiner(ELEMENT_SEPARATOR); + for (int i = 0; i < lines; i++) + joiner.add(encodeElement(elements[i])); + return joiner.toString(); + } + + public static StackTraceElement[] decodeTrace(String joinedTrace) { + String[] encodedElements = joinedTrace.split(ELEMENT_SEPARATOR); + StackTraceElement[] elements = new StackTraceElement[encodedElements.length]; + for (int i = 0; i < encodedElements.length; i++) + elements[i] = StackTraceEncoder.decodeElement(encodedElements[i]); + return elements; + } +} diff --git a/gxJRS/src/main/java/org/gcube/common/gxrest/response/entity/package-info.java b/gxJRS/src/main/java/org/gcube/common/gxrest/response/entity/package-info.java new file mode 100644 index 0000000..b897dce --- /dev/null +++ b/gxJRS/src/main/java/org/gcube/common/gxrest/response/entity/package-info.java @@ -0,0 +1,7 @@ +/** + * Core data held by a response. + * + * @author Manuele Simi (ISTI CNR) + * + */ +package org.gcube.common.gxrest.response.entity; \ No newline at end of file diff --git a/gxJRS/src/main/java/org/gcube/common/gxrest/response/inbound/ErrorCodeDeserializer.java b/gxJRS/src/main/java/org/gcube/common/gxrest/response/inbound/ErrorCodeDeserializer.java new file mode 100644 index 0000000..1506efa --- /dev/null +++ b/gxJRS/src/main/java/org/gcube/common/gxrest/response/inbound/ErrorCodeDeserializer.java @@ -0,0 +1,40 @@ +package org.gcube.common.gxrest.response.inbound; + +import org.gcube.common.gxrest.response.outbound.ErrorCode; + +/** + * Deserializer for {@link ErrorCode}. + * + * @author Manuele Simi (ISTI CNR) + * + */ +final class ErrorCodeDeserializer { + + /** + * + */ + private ErrorCodeDeserializer() {} + + /** + * The error code, if any + * @return the error code or null + */ + protected static ErrorCode deserialize(int id, String message) { + if (id != 1) { + return new ErrorCode() { + + @Override + public String getMessage() { + return message; + } + + @Override + public int getId() { + return id; + } + }; + } else + return null; + + } +} diff --git a/gxJRS/src/main/java/org/gcube/common/gxrest/response/inbound/ExceptionDeserializer.java b/gxJRS/src/main/java/org/gcube/common/gxrest/response/inbound/ExceptionDeserializer.java new file mode 100644 index 0000000..8b29ed0 --- /dev/null +++ b/gxJRS/src/main/java/org/gcube/common/gxrest/response/inbound/ExceptionDeserializer.java @@ -0,0 +1,52 @@ +package org.gcube.common.gxrest.response.inbound; + +import java.lang.reflect.InvocationTargetException; +import java.util.Objects; + +import org.gcube.common.gxrest.response.entity.StackTraceEncoder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Deserializer for {@link Exception}. + * + * @author Manuele Simi (ISTI CNR) + * + */ +final class ExceptionDeserializer { + + /** + * + */ + private ExceptionDeserializer() {} + + private static final Logger logger = LoggerFactory.getLogger(ExceptionDeserializer.class); + + /** + * Deserializes the exception. + * + * @param exceptionClass the full qualified class name of the exception to deserialize + * @param message the error message to associate to the exception + * @return + */ + @SuppressWarnings("unchecked") + protected static E deserialize(String exceptionClass, String message) { + try { + final Class[] ctorParams = {String.class}; + return (E) Class.forName(exceptionClass).getConstructor(ctorParams).newInstance(message); + } catch (InstantiationException | IllegalAccessException | ClassNotFoundException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException e) { + logger.error("Failed to deserialize: " + exceptionClass); + return null; + } + } + + /** + * Enrich the exception with the stacktrace elements encoded with {@link StackTraceEncoder} + * @param exception + * @param joinedTrace + */ + protected static void addStackTrace(E exception, String joinedTrace) { + if (Objects.nonNull(exception)) + exception.setStackTrace(StackTraceEncoder.decodeTrace(joinedTrace)); + } +} diff --git a/gxJRS/src/main/java/org/gcube/common/gxrest/response/inbound/GXInboundResponse.java b/gxJRS/src/main/java/org/gcube/common/gxrest/response/inbound/GXInboundResponse.java new file mode 100644 index 0000000..f01cf73 --- /dev/null +++ b/gxJRS/src/main/java/org/gcube/common/gxrest/response/inbound/GXInboundResponse.java @@ -0,0 +1,361 @@ +package org.gcube.common.gxrest.response.inbound; + +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import javax.ws.rs.ProcessingException; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.Status; + +import org.gcube.common.gxhttp.util.ContentUtils; +import org.gcube.common.gxrest.response.entity.EntityTag; +import org.gcube.common.gxrest.response.entity.SerializableErrorEntity; +import org.gcube.common.gxrest.response.outbound.ErrorCode; +import org.gcube.common.gxrest.response.outbound.GXOutboundErrorResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The response returned from the web application. + * + * @author Manuele Simi (ISTI CNR) + * + */ +public class GXInboundResponse { + + private SerializableErrorEntity entity; + private final int responseCode; + private String contentType = ""; + private String message = ""; + private String body = ""; + private byte[] streamedContent; + private Map> headerFields; + private static final Logger logger = LoggerFactory.getLogger(GXInboundResponse.class); + private boolean hasGXError = false; + private Response source; + private HttpURLConnection connection; + // the content cannot be read more than one time from the response or input + // stream + private boolean contentAlreadyConsumed = false; + + private boolean fromConnection = false; + private boolean fromResponse = false; + + /** + * Builds a new inbound response. + * + * @param source + * the original response + */ + public GXInboundResponse(Response source) { + this.fromResponse = true; + this.source = source; + this.responseCode = source.getStatusInfo().getStatusCode(); + this.message = source.getStatusInfo().getReasonPhrase(); + this.headerFields = source.getStringHeaders(); + if (Objects.nonNull(source.getMediaType())) + this.contentType = source.getMediaType().getType(); + try { + if (Objects.nonNull(source.getEntityTag()) && source.getEntityTag().getValue().equals(EntityTag.gxError)) { + this.entity = source.readEntity(SerializableErrorEntity.class); + this.hasGXError = true; + this.contentAlreadyConsumed = true; + } + } catch (ProcessingException | IllegalStateException ie) { + // if it fails, it's likely message response + // this.message = (String) source.getEntity(); + } + } + + /** + * @param connection + * the connection from which to parse the information + * @throws IOException + */ + public GXInboundResponse(HttpURLConnection connection) throws IOException { + this.fromConnection = true; + this.connection = connection; + this.responseCode = connection.getResponseCode(); + this.message = connection.getResponseMessage(); + this.headerFields = connection.getHeaderFields(); + this.contentType = connection.getContentType(); + // header fields are usually wrapped around double quotes + String eTag = connection.getHeaderField("ETag"); + if (Objects.nonNull(eTag) && eTag.replaceAll("^\"|\"$", "").equals(EntityTag.gxError)) { + logger.debug("GXErrorResponse detected."); + this.hasGXError = true; + try { + this.streamedContent = ContentUtils.toByteArray(connection.getErrorStream()); + this.contentAlreadyConsumed = true; + this.body = ContentUtils.toString(streamedContent); + this.entity = JsonUtils.fromJson(this.body, SerializableErrorEntity.class); + logger.trace("Response's content: " + this.body); + } catch (Exception ioe) { + logger.warn("No data are available in the response."); + } + } else { + try { + // this.streamedContent = + // ContentUtils.toByteArray(connection.getInputStream()); + if (this.contentType.equals(MediaType.TEXT_PLAIN) + || this.contentType.equals(MediaType.APPLICATION_JSON)) { + this.body = ContentUtils.toString(ContentUtils.toByteArray(connection.getInputStream())); + logger.trace("Response's content: " + this.body); + this.contentAlreadyConsumed = true; + } + } catch (Exception ioe) { + logger.warn("No data are available in the response.", ioe); + } + } + } + + /** + * Builds a new inbound response. + * + * @param source + * the original response + * @param expectedMediaTypes + * the expected media type(s) in the response + */ + public GXInboundResponse(Response response, MediaType[] expectedMediaTypes) { + this(response); + if (Objects.isNull(expectedMediaTypes) || expectedMediaTypes.length == 0) + throw new IllegalArgumentException("No expected type was set)"); + + // validate the media type + boolean compatible = false; + for (MediaType media : expectedMediaTypes) { + if (Objects.nonNull(response.getMediaType()) && response.getMediaType().isCompatible(media)) + compatible = true; + } + if (!compatible) + throw new IllegalArgumentException("Received MediaType is not compatible with the expected type(s)"); + } + + /** + * Checks if there is an {@link Exception} in the entity. + * + * @return true if the entity holds an exception, false otherwise + */ + public boolean hasException() { + return Objects.nonNull(this.entity) && Objects.nonNull(this.entity.getExceptionClass()); + } + + /** + * Checks if the response is in the range 4xx - 5xx + * . + * + * @return true if it is an error response. + */ + public boolean isErrorResponse() { + return this.getHTTPCode() >= 400 && this.getHTTPCode() < 600; + } + + /** + * Checks if the response is in the range 2xx + * . + * + * @return true if it is a success response. + */ + public boolean isSuccessResponse() { + return this.getHTTPCode() >= 200 && this.getHTTPCode() < 300; + } + + /** + * Checks if the response was generated as a {@link GXOutboundErrorResponse} + * . + * + * @return true if it is an error response generated with GXRest. + */ + public boolean hasGXError() { + return this.hasGXError; + } + + /** + * Gets the {@link Exception} inside the entity. + * + * @return the exception or null + * @throws ClassNotFoundException + * if the exception's class is not available on the classpath + */ + public E getException() throws ClassNotFoundException { + if (Objects.nonNull(this.entity)) { + E e = ExceptionDeserializer.deserialize(this.entity.getExceptionClass(), this.entity.getMessage()); + if (Objects.nonNull(e)) { + if (this.entity.hasStackTrace()) + ExceptionDeserializer.addStackTrace(e, this.entity.getEncodedTrace()); + else + e.setStackTrace(new StackTraceElement[] {}); + return e; + } else + throw new ClassNotFoundException( + "Failed to deserialize: " + this.entity.getExceptionClass() + ". Not on the classpath?"); + } else + return null; + } + + /** + * Checks if there is an {@link ErrorCode} in the entity. + * + * @return true if the entity holds an errorcode, false otherwise + */ + public boolean hasErrorCode() { + if (Objects.nonNull(this.entity)) + return this.entity.getId() != -1; + else + return false; + } + + /** + * Gets the {@link ErrorCode} inside the entity. + * + * @return the error code or null + */ + public ErrorCode getErrorCode() { + if (Objects.nonNull(this.entity)) + return ErrorCodeDeserializer.deserialize(this.entity.getId(), this.entity.getMessage()); + else + return null; + }; + + /** + * Gets the message in the response + * + * @return the message + */ + public String getMessage() { + return this.message; + } + + /** + * Gets the streamed content as a string, if possible. + * + * @return the content + * @throws IOException + * if unable to read the content + */ + public String getStreamedContentAsString() throws IOException { + if (this.body.isEmpty()) { + this.body = ContentUtils.toString(ContentUtils.toByteArray(getInputStream())); + } + return this.body; + } + + public InputStream getInputStream() throws IOException { + if(!this.contentAlreadyConsumed) { + if (this.fromConnection) { + contentAlreadyConsumed = true; + return connection.getInputStream(); + } else if (this.fromResponse) { + contentAlreadyConsumed = true; + return (InputStream) source.getEntity(); + } + // This code should be never reached + return null; + } + throw new IOException("Content Already Consumed"); + } + + /** + * Returns the content of the response as byte array. + * + * @return the streamedContent + * @throws IOException + * if unable to read the content + */ + public byte[] getStreamedContent() throws IOException { + if (!this.body.isEmpty()) { + this.streamedContent = this.body.getBytes(); + } else { + this.streamedContent = ContentUtils.toByteArray(getInputStream()); + } + return this.streamedContent; + } + + /** + * Tries to convert the content from its Json serialization, if possible. + * + * @param + * the type of the desired object + * @return an object of type T from the content + * @throws Exception + * if the deserialization fails + */ + public T tryConvertStreamedContentFromJson(Class raw) throws Exception { + return JsonUtils.fromJson(this.getStreamedContentAsString(), raw); + } + + /** + * Gets the status code from the HTTP response message. + * + * @return the HTTP code + */ + public int getHTTPCode() { + return this.responseCode; + } + + /** + * Checks if the response has a CREATED (201) HTTP status. + * + * @return true if CREATED, false otherwise + */ + public boolean hasCREATEDCode() { + return (this.getHTTPCode() == Status.CREATED.getStatusCode()); + } + + /** + * Checks if the response has a OK (200) HTTP status. + * + * @return true if OK, false otherwise + */ + public boolean hasOKCode() { + return (this.getHTTPCode() == Status.OK.getStatusCode()); + } + + /** + * Checks if the response has a NOT_ACCEPTABLE (406) HTTP status. + * + * @return true if NOT_ACCEPTABLE, false otherwise + */ + public boolean hasNOT_ACCEPTABLECode() { + return (this.getHTTPCode() == Status.NOT_ACCEPTABLE.getStatusCode()); + } + + /** + * Checks if the response has a BAD_REQUEST (400) HTTP status. + * + * @return true if BAD_REQUEST, false otherwise + */ + public boolean hasBAD_REQUESTCode() { + return (this.getHTTPCode() == Status.BAD_REQUEST.getStatusCode()); + } + + /** + * Returns an unmodifiable Map of the header fields. The Map keys are + * Strings that represent the response-header field names. Each Map value is + * an unmodifiable List of Strings that represents the corresponding field + * values. + * + * @return a Map of header fields + */ + public Map> getHeaderFields() { + return this.headerFields; + } + + /** + * @return the source response, if available + * @throws UnsupportedOperationException + * if not available + */ + public Response getSource() throws UnsupportedOperationException { + if (Objects.isNull(this.source)) + new UnsupportedOperationException(); + return this.source; + } + +} diff --git a/gxJRS/src/main/java/org/gcube/common/gxrest/response/inbound/JsonUtils.java b/gxJRS/src/main/java/org/gcube/common/gxrest/response/inbound/JsonUtils.java new file mode 100644 index 0000000..9f23c95 --- /dev/null +++ b/gxJRS/src/main/java/org/gcube/common/gxrest/response/inbound/JsonUtils.java @@ -0,0 +1,46 @@ +package org.gcube.common.gxrest.response.inbound; + +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * Manipulation of an {@link GXInboundResponse}'s content. + * + * @author Manuele Simi (ISTI CNR) + * + */ +final public class JsonUtils { + + /** + * Deserializes the specified Json bytes into an object of the specified class + * @param the type of the desired object + * @param json the string from which the object is to be deserialized + * @param classOfT the class of T + * @return an object of type T from the bytes + * @throws Exception if the deserialization fails + */ + public static T fromJson(byte[] bytes, Class raw) throws Exception { + try { + ObjectMapper objectMapper = new ObjectMapper(); + return objectMapper.readValue(bytes, raw); + } catch (Exception e) { + throw new Exception("Cannot deserialize to the object.", e); + } + } + + /** + * Deserializes the specified Json bytes into an object of the specified class + * @param the type of the desired object + * @param json the string from which the object is to be deserialized + * @param raw the class of T + * @return an object of type T from the bytes + * @throws Exception if the deserialization fails + */ + public static T fromJson(String json, Class raw) throws Exception { + try { + ObjectMapper objectMapper = new ObjectMapper(); + return objectMapper.readValue(json, raw); + } catch (Exception e) { + throw new Exception("Cannot deserialize to the object.", e); + } + } +} diff --git a/gxJRS/src/main/java/org/gcube/common/gxrest/response/inbound/package-info.java b/gxJRS/src/main/java/org/gcube/common/gxrest/response/inbound/package-info.java new file mode 100644 index 0000000..79ae073 --- /dev/null +++ b/gxJRS/src/main/java/org/gcube/common/gxrest/response/inbound/package-info.java @@ -0,0 +1,8 @@ +/** + * Inbound responses for client applications. + * These responses are received at the client side following up a request sent to the web application. + * + * @author Manuele Simi (ISTI CNR) + * + */ +package org.gcube.common.gxrest.response.inbound; \ No newline at end of file diff --git a/gxJRS/src/main/java/org/gcube/common/gxrest/response/outbound/CodeFinder.java b/gxJRS/src/main/java/org/gcube/common/gxrest/response/outbound/CodeFinder.java new file mode 100644 index 0000000..cdfa014 --- /dev/null +++ b/gxJRS/src/main/java/org/gcube/common/gxrest/response/outbound/CodeFinder.java @@ -0,0 +1,24 @@ +package org.gcube.common.gxrest.response.outbound; + +import java.util.stream.Stream; + +/** + * Helper methods to find an error code in an enumeration implementing {@link ErrorCode}. + * + * @author Manuele Simi (ISTI CNR) + * + */ +public class CodeFinder { + + /** + * Finds and convert the given code as enum value. + * @param code the code to look for. + * @param codes the enum values. + * @return the code as enum value or null if the code is not found. + */ + public static & ErrorCode> E findAndConvert(ErrorCode code, E[] codes) { + return Stream.of(codes).filter(e -> e.getId() == code.getId() && e.getMessage().equals(code.getMessage())) + .findFirst().orElse(null); + } + +} diff --git a/gxJRS/src/main/java/org/gcube/common/gxrest/response/outbound/ErrorCode.java b/gxJRS/src/main/java/org/gcube/common/gxrest/response/outbound/ErrorCode.java new file mode 100644 index 0000000..64747b1 --- /dev/null +++ b/gxJRS/src/main/java/org/gcube/common/gxrest/response/outbound/ErrorCode.java @@ -0,0 +1,22 @@ +package org.gcube.common.gxrest.response.outbound; + +/** + * Interface for error codes. + * + * @author Manuele Simi (ISTI CNR) + * + */ +public interface ErrorCode { + + /** + * Identifier of the code. + */ + public int getId(); + + /** + * The message associated to the code + * @return the message + */ + public String getMessage(); + +} diff --git a/gxJRS/src/main/java/org/gcube/common/gxrest/response/outbound/GXOutboundErrorResponse.java b/gxJRS/src/main/java/org/gcube/common/gxrest/response/outbound/GXOutboundErrorResponse.java new file mode 100644 index 0000000..6d3a4b9 --- /dev/null +++ b/gxJRS/src/main/java/org/gcube/common/gxrest/response/outbound/GXOutboundErrorResponse.java @@ -0,0 +1,77 @@ +package org.gcube.common.gxrest.response.outbound; + +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.Response; + +/** + * An outbound error response message for applications. + * + * @author Manuele Simi (ISTI CNR) + * + */ +public class GXOutboundErrorResponse { + + private GXOutboundErrorResponse() {} + + /** + * Throws the exception to the client. + * @param exception + */ + public static void throwException(Exception exception) { + throw new WebStreamException(exception); + } + + /** + * Throws the exception to the client. + * @param exception + */ + public static void throwException(Exception exception, Response.Status status) { + throw new WebStreamException(exception, status); + } + + /** + * Throws the exception to the client. + * @param exception + * @param keepLines number of lines in the stacktrace to keep (max is 5) + */ + public static void throwExceptionWithTrace(Exception exception, int keepLines, Response.Status status) { + throw new WebStreamException(exception, keepLines, status); + } + + /** + * Throws the exception to the client. + * @param exception + * @param keepLines number of lines in the stacktrace to keep (max is 5) + */ + public static void throwExceptionWithTrace(Exception exception, int keepLines) { + throw new WebStreamException(exception, keepLines); + } + + /** + * Throws the error code to the client. + * @param code + */ + public static void throwErrorCode(ErrorCode code) { + throw new WebCodeException(code); + } + + /** + * Returns the error code to the client with the HTTP status. + * @param code + * @param status + */ + public static void throwErrorCode(ErrorCode code, Response.Status status) { + throw new WebCodeException(code, status); + } + + /** + * Returns the HTTP status to the client as error. + * @param status + * @param message + */ + public static void throwHTTPErrorStatus(Response.Status status, String message) { + if (status.getStatusCode() < 400) + throw new IllegalArgumentException("Error status must be >= 400."); + throw new WebApplicationException(message, status.getStatusCode()); + } +} diff --git a/gxJRS/src/main/java/org/gcube/common/gxrest/response/outbound/GXOutboundSuccessResponse.java b/gxJRS/src/main/java/org/gcube/common/gxrest/response/outbound/GXOutboundSuccessResponse.java new file mode 100644 index 0000000..b78a264 --- /dev/null +++ b/gxJRS/src/main/java/org/gcube/common/gxrest/response/outbound/GXOutboundSuccessResponse.java @@ -0,0 +1,132 @@ +package org.gcube.common.gxrest.response.outbound; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.ResponseBuilder; + +import org.gcube.common.gxrest.response.entity.EntityTag; + +/** + * An outbound success response message for applications. + * + * @author Manuele Simi (ISTI CNR) + * + */ +public class GXOutboundSuccessResponse { + + private ResponseBuilder delegate; + + private GXOutboundSuccessResponse() { + } + + /** + * Builds a new response with the OK HTTP status. + * + * @return the updated response + */ + public static GXOutboundSuccessResponse newOKResponse() { + GXOutboundSuccessResponse response = new GXOutboundSuccessResponse(); + response.delegate = Response.ok(); + response.delegate.tag(EntityTag.gxSuccess); + return response; + } + + /** + * Builds a new response with the CREATE HTTP status. + * + * @return the updated response + */ + public static GXOutboundSuccessResponse newCREATEResponse(URI location) { + GXOutboundSuccessResponse response = new GXOutboundSuccessResponse(); + response.delegate = Response.created(location); + response.delegate.tag(EntityTag.gxSuccess); + return response; + } + + /** + * Sets the object as response's content. + * Any Java type instance for a response entity, that is supported by the + * runtime can be passed. + * + * @param o the content + * @return the updated response + * @throws IOException + */ + public GXOutboundSuccessResponse withContent(Object o) throws IOException { + this.delegate.entity(o); + return this; + } + + /** + * Reads from the stream the content to set in the response. + * + * @param is the stream + * @return the updated response + * @throws IOException + */ + public GXOutboundSuccessResponse withContent(InputStream is) throws IOException { + this.delegate.entity(is); + return this; + } + + /** + * Sets the message as the response's content. + * + * @param message + * @return the updated response + */ + public GXOutboundSuccessResponse withContent(String message) { + this.delegate.entity(message); + this.delegate.type(MediaType.TEXT_PLAIN); + return this; + } + + /** + * Adds a type to the response message. + * + * @param type + * @return the updated response + */ + public GXOutboundSuccessResponse ofType(MediaType type) { + this.delegate.type(type); + return this; + } + + /** + * Adds a type to the response message. + * + * @param type + * @return the updated response + */ + public GXOutboundSuccessResponse ofType(String type) { + this.delegate.type(type); + return this; + } + /** + * Add an arbitrary header. + * + * @param name the name of the header + * @param value the value of the header, the header will be serialized + * using a {@link javax.ws.rs.ext.RuntimeDelegate.HeaderDelegate} if + * one is available via {@link javax.ws.rs.ext.RuntimeDelegate#createHeaderDelegate(java.lang.Class)} + * for the class of {@code value} or using its {@code toString} method + * if a header delegate is not available. If {@code value} is {@code null} + * then all current headers of the same name will be removed. + * @return the updated response. + */ + public GXOutboundSuccessResponse withHeader(String name, Object value) { + this.delegate.header(name, value); + return this; + } + /** + * Builds the response to return. + * + * @return the response + */ + public Response build() { + return this.delegate.build(); + } +} diff --git a/gxJRS/src/main/java/org/gcube/common/gxrest/response/outbound/LocalCodeException.java b/gxJRS/src/main/java/org/gcube/common/gxrest/response/outbound/LocalCodeException.java new file mode 100644 index 0000000..ba49c8e --- /dev/null +++ b/gxJRS/src/main/java/org/gcube/common/gxrest/response/outbound/LocalCodeException.java @@ -0,0 +1,29 @@ + +package org.gcube.common.gxrest.response.outbound; + +/** + * A local exception wrapping an {@link ErrorCode}. + * + * @author Manuele Simi (ISTI CNR) + * + */ +public class LocalCodeException extends Exception implements ErrorCode { + + private static final long serialVersionUID = 1872093579811881630L; + private final int id; + private final String message; + + public LocalCodeException(ErrorCode code) { + super(); + this.id = code.getId(); + this.message = code.getMessage(); + } + + public int getId() { + return id; + } + + public String getMessage() { + return message; + } +} diff --git a/gxJRS/src/main/java/org/gcube/common/gxrest/response/outbound/WebCodeException.java b/gxJRS/src/main/java/org/gcube/common/gxrest/response/outbound/WebCodeException.java new file mode 100644 index 0000000..6530456 --- /dev/null +++ b/gxJRS/src/main/java/org/gcube/common/gxrest/response/outbound/WebCodeException.java @@ -0,0 +1,34 @@ +package org.gcube.common.gxrest.response.outbound; + +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.Response; + +import org.gcube.common.gxrest.response.entity.CodeEntity; +import org.gcube.common.gxrest.response.entity.EntityTag; +import org.gcube.common.gxrest.response.entity.SerializableErrorEntity; + +/** + * Exception with error code returned by a resource method. + * + * @author Manuele Simi (ISTI CNR) + * + */ +final class WebCodeException extends WebApplicationException { + + private static final long serialVersionUID = 333945715086602250L; + + protected WebCodeException() { + super(Response.status(Response.Status.NOT_ACCEPTABLE).build()); + } + + protected WebCodeException(ErrorCode code, Response.Status status) { + super(Response.status(status).entity(new CodeEntity(new SerializableErrorEntity(code))).tag(EntityTag.gxError) + .build()); + } + + protected WebCodeException(ErrorCode code) { + super(Response.status(Response.Status.NOT_ACCEPTABLE) + .entity(new CodeEntity(new SerializableErrorEntity(code))).tag(EntityTag.gxError).build()); + } + +} diff --git a/gxJRS/src/main/java/org/gcube/common/gxrest/response/outbound/WebStreamException.java b/gxJRS/src/main/java/org/gcube/common/gxrest/response/outbound/WebStreamException.java new file mode 100644 index 0000000..f39afa8 --- /dev/null +++ b/gxJRS/src/main/java/org/gcube/common/gxrest/response/outbound/WebStreamException.java @@ -0,0 +1,64 @@ +package org.gcube.common.gxrest.response.outbound; + +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.Response; + +import org.gcube.common.gxrest.response.entity.CodeEntity; +import org.gcube.common.gxrest.response.entity.EntityTag; +import org.gcube.common.gxrest.response.entity.SerializableErrorEntity; + +/** + * An exception returned to the Rest client. + * + * @author Manuele Simi (ISTI CNR) + * + */ +final class WebStreamException extends WebApplicationException { + + private static final long serialVersionUID = 822443082773903217L; + + /** + * Returns the exception. + * + * @param exception + * @param keepLines + * @param status + * the HTTP status to associate to the response. + */ + protected WebStreamException(E exception, int keepLines, Response.Status status) { + super(exception.getCause(), Response.status(status) + .entity(new CodeEntity(new SerializableErrorEntity(exception, keepLines))).tag(EntityTag.gxError).build()); + } + + /** + * Returns the exception. + * + * @param exception + * @param status + * the HTTP status to associate to the response. + */ + protected WebStreamException(E exception, int keepLines) { + this(exception, keepLines, Response.Status.NOT_ACCEPTABLE); + + } + + /** + * Returns the exception. + * + * @param exception + */ + protected WebStreamException(E exception) { + this(exception, 0, Response.Status.NOT_ACCEPTABLE); + } + + /** + * Returns the exception. + * + * @param exception + * @param status + * the HTTP status to associate to the response. + */ + protected WebStreamException(E exception, Response.Status status) { + this(exception, 0, status); + } +} diff --git a/gxJRS/src/main/java/org/gcube/common/gxrest/response/outbound/package-info.java b/gxJRS/src/main/java/org/gcube/common/gxrest/response/outbound/package-info.java new file mode 100644 index 0000000..c1f28b2 --- /dev/null +++ b/gxJRS/src/main/java/org/gcube/common/gxrest/response/outbound/package-info.java @@ -0,0 +1,8 @@ +/** + * Outbound responses for web applications. + * These responses are returned by the resource methods of the web application. + * + * @author Manuele Simi (ISTI-CNR) + * + */ +package org.gcube.common.gxrest.response.outbound; \ No newline at end of file diff --git a/gxJRS/src/test/java/org/gcube/common/gxrest/request/GXHTTPStringRequestTest.java b/gxJRS/src/test/java/org/gcube/common/gxrest/request/GXHTTPStringRequestTest.java new file mode 100644 index 0000000..961f61e --- /dev/null +++ b/gxJRS/src/test/java/org/gcube/common/gxrest/request/GXHTTPStringRequestTest.java @@ -0,0 +1,240 @@ +package org.gcube.common.gxrest.request; + +import static org.junit.Assert.*; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Map; +import java.util.Properties; +import java.util.WeakHashMap; + +import org.gcube.common.authorization.client.Constants; +import org.gcube.common.authorization.client.exceptions.ObjectNotFound; +import org.gcube.common.authorization.library.AuthorizationEntry; +import org.gcube.common.authorization.library.provider.SecurityTokenProvider; +import org.gcube.common.gxrest.response.inbound.GXInboundResponse; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.BlockJUnit4ClassRunner; + +import com.fasterxml.jackson.databind.JsonMappingException; + +/** + * Test cases for {@link GXHTTPStringRequest} + * + * @author Manuele Simi (ISTI-CNR) + * + */ +@RunWith(BlockJUnit4ClassRunner.class) +public class GXHTTPStringRequestTest { + + private GXHTTPStringRequest request; + + public static String DEFAULT_TEST_SCOPE = ""; + + static String DEFAULT_RM_URL = ""; + + static String DEFAULT_RR_URL = ""; + + + private static boolean skipTest = false; + + static { + Properties properties = new Properties(); + try (InputStream input = GXHTTPStringRequestTest.class.getClassLoader().getResourceAsStream("token.props")) { + // load the properties file + properties.load(input); + DEFAULT_TEST_SCOPE = properties.getProperty("DEFAULT_SCOPE_TOKEN"); + if (DEFAULT_TEST_SCOPE.isEmpty()) + skipTest = true; + DEFAULT_RM_URL = properties.getProperty("RM_URL"); + DEFAULT_RR_URL = properties.getProperty("RR_URL"); + } catch (IOException | NullPointerException e) { + skipTest = true; + } + } + + @BeforeClass + public static void beforeClass() throws Exception { + setContext(DEFAULT_TEST_SCOPE); + } + + public static void setContext(String token) throws ObjectNotFound, Exception { + if (DEFAULT_TEST_SCOPE.isEmpty()) { + skipTest = true; + return; + } + SecurityTokenProvider.instance.set(token); + } + + public static String getCurrentScope(String token) throws ObjectNotFound, Exception { + AuthorizationEntry authorizationEntry = Constants.authorizationService().get(token); + String context = authorizationEntry.getContext(); + return context; + } + + @AfterClass + public static void afterClass() throws Exception { + SecurityTokenProvider.instance.reset(); + } + + /** + * Test method for {@link org.gcube.common.gxrest.request.GXHTTPStringRequest#newRequest(java.lang.String)}. + */ + @Before + public void testNewRequest() { + request = GXHTTPStringRequest.newRequest(DEFAULT_RM_URL).from("GXRequestTest"); + } + + /** + * Test method for {@link org.gcube.common.gxrest.request.GXHTTPStringRequest#post()}. + */ + @Test + public void testPost() { + if (skipTest) + return; + request.clear(); + String context ="{\"@class\":\"Context\",\"header\":{\"@class\":\"Header\",\"uuid\":\"6f86dc81-2f59-486b-8aa9-3ab5486313c4\",\"creator\":null,\"modifiedBy\":\"gxRestTest\",\"creationTime\":null,\"lastUpdateTime\":null},\"name\":\"gxTest\",\"parent\":null,\"children\":[]}"; + Map queryParams = new WeakHashMap<>(); + queryParams.put("rrURL", DEFAULT_RR_URL); + try { + GXInboundResponse response = request.path("gxrest") + .header("Another header", "GXHTTPRequestTest") + .queryParams(queryParams).post(context); + assertTrue("Unexpected returned code.", response.hasCREATEDCode()); + if (response.hasException()) { + try { + throw response.getException(); + } catch (ClassNotFoundException e) { + //that's OK, we can tolerate this + } catch (Exception e) { + e.printStackTrace(); + throw e; + } + } else { + System.out.println("Returned string " + response.getStreamedContentAsString()); + } + } catch (Exception e) { + e.printStackTrace(); + fail("Failed to send a POST request"); + } + } + + @Test(expected=com.fasterxml.jackson.databind.JsonMappingException.class) + public void testPostNoBody() throws Exception { + if (skipTest) + throw new JsonMappingException(""); + request.clear(); + Map queryParams = new WeakHashMap<>(); + queryParams.put("rrURL", DEFAULT_RR_URL); + try { + GXInboundResponse response = request.path("gxrest") + .header("Another header", "GXHTTPRequestTest") + .queryParams(queryParams).post(); + assertTrue("Unexpected returned code.", response.hasNOT_ACCEPTABLECode()); + assertTrue("Unexpected GX status", response.hasGXError()); + if (response.hasGXError()) { + try { + throw response.getException(); + } catch (ClassNotFoundException e) { + //that's OK, we can tolerate this + } catch (Exception e) { + e.printStackTrace(); + throw e; + } + } else { + System.out.println("Returned string " + response.getStreamedContentAsString()); + } + } catch (Exception e) { + e.printStackTrace(); + throw e; + //fail("Failed to send a POST request"); + } + } + + /** + * Test method for {@link org.gcube.common.gxrest.request.GXHTTPStringRequest#get()}. + */ + @Test + public void testGet() { + request.clear(); + } + + /** + * Test method for {@link org.gcube.common.gxrest.request.GXHTTPStringRequest#put()}. + */ + @Test + public void testPut() { + if (skipTest) + return; + request.clear(); + Map queryParams = new WeakHashMap<>(); + queryParams.put("rrURL", DEFAULT_RR_URL); + String context ="{\"@class\":\"Context\",\"header\":{\"@class\":\"Header\",\"uuid\":\"6f86dc81-2f59-486b-8aa9-3ab5486313c4\",\"creator\":null,\"modifiedBy\":\"gxRestTest\",\"creationTime\":null,\"lastUpdateTime\":null},\"name\":\"gxTest\",\"parent\":null,\"children\":[]}"; + try { + GXInboundResponse response = request.path("gxrest") + .header("Another header", "GXHTTPRequestTest") + .path("5f86dc81-2f59-486b-8aa9-3ab5486313c4").queryParams(queryParams).put(context); + assertTrue("Unexpected returned code.", response.hasCREATEDCode()); + if (response.hasException()) { + try { + throw response.getException(); + } catch (ClassNotFoundException e) { + //that's OK, we can tolerate this + } catch (Exception e) { + e.printStackTrace(); + throw e; + } + } else { + System.out.println("Returned string " + response.getStreamedContentAsString()); + } + } catch (Exception e) { + e.printStackTrace(); + fail("Failed to send a DELETE request"); + } + } + + /** + * Test method for {@link org.gcube.common.gxrest.request.GXHTTPStringRequest#delete()}. + */ + @Test + public void testDelete() { + if (skipTest) + return; + request.clear(); + Map queryParams = new WeakHashMap<>(); + queryParams.put("rrURL", DEFAULT_RR_URL); + try { + GXInboundResponse response = request.path("gxrest") + .header("Another header", "GXHTTPRequestTest") + .path("5f86dc81-2f59-486b-8aa9-3ab5486313c4").queryParams(queryParams).delete(); + assertTrue("Unexpected returned code.", response.hasBAD_REQUESTCode()); + if (response.hasException()) { + try { + throw response.getException(); + } catch (ClassNotFoundException e) { + //that's OK, we can tolerate this + } catch (Exception e) { + e.printStackTrace(); + throw e; + } + } + } catch (Exception e) { + e.printStackTrace(); + fail("Failed to send a DELETE request"); + } + } + + + /** + * Test method for {@link org.gcube.common.gxrest.request.GXHTTPStringRequest#head()}. + */ + @Test + public void testHead() { + request.clear(); + } + +} diff --git a/gxJRS/src/test/java/org/gcube/common/gxrest/request/GXWebTargetAdapterRequestTest.java b/gxJRS/src/test/java/org/gcube/common/gxrest/request/GXWebTargetAdapterRequestTest.java new file mode 100644 index 0000000..2b97336 --- /dev/null +++ b/gxJRS/src/test/java/org/gcube/common/gxrest/request/GXWebTargetAdapterRequestTest.java @@ -0,0 +1,178 @@ +package org.gcube.common.gxrest.request; + +import static org.junit.Assert.*; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Map; +import java.util.Properties; +import java.util.WeakHashMap; + +import javax.ws.rs.client.Entity; +import javax.ws.rs.core.MediaType; + +import org.gcube.common.authorization.client.Constants; +import org.gcube.common.authorization.client.exceptions.ObjectNotFound; +import org.gcube.common.authorization.library.AuthorizationEntry; +import org.gcube.common.authorization.library.provider.SecurityTokenProvider; +import org.gcube.common.gxrest.response.inbound.GXInboundResponse; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.BlockJUnit4ClassRunner; + +/** + * Test cases for {@link GXWebTargetAdapterRequest} + * + * @author Manuele Simi (ISTI-CNR) + * + */ +@RunWith(BlockJUnit4ClassRunner.class) +public class GXWebTargetAdapterRequestTest { + + private GXWebTargetAdapterRequest request; + + public static String DEFAULT_TEST_SCOPE = ""; + + static String DEFAULT_RM_URL = ""; + + static String DEFAULT_RR_URL = ""; + + private static boolean skipTest = false; + + static { + Properties properties = new Properties(); + try (InputStream input = GXWebTargetAdapterRequest.class.getClassLoader().getResourceAsStream("token.props")) { + // load the properties file + properties.load(input); + DEFAULT_TEST_SCOPE = properties.getProperty("DEFAULT_SCOPE_TOKEN"); + if (DEFAULT_TEST_SCOPE.isEmpty()) + skipTest = true; + DEFAULT_RM_URL = properties.getProperty("RM_URL"); + DEFAULT_RR_URL = properties.getProperty("RR_URL"); + } catch (IOException | NullPointerException e) { + skipTest = true; + } + } + @BeforeClass + public static void beforeClass() throws Exception { + if (!skipTest) + setContext(DEFAULT_TEST_SCOPE); + } + + public static void setContext(String token) throws ObjectNotFound, Exception { + if (skipTest || DEFAULT_TEST_SCOPE.isEmpty()) { + skipTest = true; + return; + } else { + SecurityTokenProvider.instance.set(token); + } + } + + public static String getCurrentScope(String token) throws ObjectNotFound, Exception { + AuthorizationEntry authorizationEntry = Constants.authorizationService().get(token); + String context = authorizationEntry.getContext(); + return context; + } + + @AfterClass + public static void afterClass() throws Exception { + SecurityTokenProvider.instance.reset(); + } + + /** + * Test method for {@link org.gcube.common.gxrest.request.GXHTTPStringRequest#newRequest(java.lang.String)}. + */ + @Before + public void testNewRequest() { + request = GXWebTargetAdapterRequest.newRequest(DEFAULT_RM_URL).from("GXRequestTest"); + } + + /** + * Test method for {@link org.gcube.common.gxrest.request.GXWebTargetAdapterRequest#put()}. + */ + @Test + public void testPut() { + if (skipTest) + return; + //fail("Not yet implemented"); + } + + /** + * Test method for {@link org.gcube.common.gxrest.request.GXWebTargetAdapterRequest#delete()}. + */ + @Test + public void testDelete() { + if (skipTest) + return; + //fail("Not yet implemented"); + } + + /** + * Test method for {@link org.gcube.common.gxrest.request.GXWebTargetAdapterRequest#post()}. + */ + @Test + public void testPost() { + if (skipTest) + return; + String context ="{\"@class\":\"Context\",\"header\":{\"@class\":\"Header\",\"uuid\":\"6f86dc81-2f59-486b-8aa9-3ab5486313c4\",\"creator\":null,\"modifiedBy\":\"gxRestTest\",\"creationTime\":null,\"lastUpdateTime\":null},\"name\":\"gxTest\",\"parent\":null,\"children\":[]}"; + Map queryParams = new WeakHashMap<>(); + queryParams.put("rrURL", new String[]{DEFAULT_RR_URL}); + try { + GXInboundResponse response = request.path("gxrest") + .header("User-Agent", this.getClass().getSimpleName()) + .header("Another header", "another value") + .queryParams(queryParams).post(Entity.entity(context, MediaType.APPLICATION_JSON + ";charset=UTF-8")); + assertTrue("Unexpected returned code.", response.hasCREATEDCode()); + if (response.hasGXError()) { + if (response.hasException()) { + try { + throw response.getException(); + } catch (ClassNotFoundException e) { + //that's OK, we can tolerate this + } catch (Exception e) { + e.printStackTrace(); + throw e; + } + } + } else { + if (response.hasCREATEDCode()) { + System.out.println("Resource successfully created!"); + System.out.println("Returned message: " + response.getStreamedContentAsString()); + } else { + System.out.println("Resource creation failed. Returned status:" + response.getHTTPCode()); + //if you want to use the original responser + response.getSource(); + //then consume the response + } + } + + } catch (Exception e) { + e.printStackTrace(); + fail("Failed to send a POST request"); + } + } + + /** + * Test method for {@link org.gcube.common.gxrest.request.GXWebTargetAdapterRequest#head()}. + */ + @Test + public void testHead() { + if (skipTest) + return; + //fail("Not yet implemented"); + } + + /** + * Test method for {@link org.gcube.common.gxrest.request.GXWebTargetAdapterRequest#get()}. + */ + @Test + public void testGet() { + if (skipTest) + return; + //fail("Not yet implemented"); + } + +} diff --git a/gxJRS/src/test/resources/logback-test.xml b/gxJRS/src/test/resources/logback-test.xml new file mode 100644 index 0000000..ce9b6c4 --- /dev/null +++ b/gxJRS/src/test/resources/logback-test.xml @@ -0,0 +1,20 @@ + + + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{0}: %msg%n + + + + + + + + + + + + + \ No newline at end of file diff --git a/idea/.idea/.name b/idea/.idea/.name new file mode 100644 index 0000000..cdbe401 --- /dev/null +++ b/idea/.idea/.name @@ -0,0 +1 @@ +gxREST \ No newline at end of file diff --git a/idea/.idea/compiler.xml b/idea/.idea/compiler.xml new file mode 100644 index 0000000..32cc322 --- /dev/null +++ b/idea/.idea/compiler.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/idea/.idea/encodings.xml b/idea/.idea/encodings.xml new file mode 100644 index 0000000..ccb66c5 --- /dev/null +++ b/idea/.idea/encodings.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/idea/.idea/misc.xml b/idea/.idea/misc.xml new file mode 100644 index 0000000..c530e55 --- /dev/null +++ b/idea/.idea/misc.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/idea/.idea/vcs.xml b/idea/.idea/vcs.xml new file mode 100644 index 0000000..ecb3690 --- /dev/null +++ b/idea/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..d4ea4dd --- /dev/null +++ b/pom.xml @@ -0,0 +1,69 @@ + + 4.0.0 + + org.gcube.tools + maven-parent + 1.0.0 + + + org.gcube.common + gxREST + 1.1.2-SNAPSHOT + pom + gCube eXtensions to REST + gCube eXtensions to REST + + + gxHTTP + gxJRS + + + + UTF-8 + Common + 2.25.1 + 2.0.1 + + + + scm:https://svn.d4science.research-infrastructures.eu/gcube/trunk/common/${project.artifactId} + scm:https://svn.d4science.research-infrastructures.eu/gcube/trunk/common/${project.artifactId} + https://svn.d4science.research-infrastructures.eu/gcube/trunk/common/${project.artifactId} + + + + + + + + org.glassfish.jersey + jersey-bom + ${jersey.version} + pom + import + + + org.gcube.distribution + gcube-bom + LATEST + pom + import + + + org.gcube.information-system + information-system-bom + LATEST + pom + import + + + + + + + + + +