388 lines
15 KiB
Java
388 lines
15 KiB
Java
/**
|
|
*
|
|
*/
|
|
package org.gcube.datatransfer.resolver.services;
|
|
|
|
import static org.gcube.common.authorization.client.Constants.authorizationService;
|
|
import static org.gcube.resources.discovery.icclient.ICFactory.clientFor;
|
|
import static org.gcube.resources.discovery.icclient.ICFactory.queryFor;
|
|
|
|
import java.io.File;
|
|
import java.io.FileInputStream;
|
|
import java.io.IOException;
|
|
import java.net.URL;
|
|
import java.nio.file.Files;
|
|
import java.util.Collection;
|
|
import java.util.HashMap;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
|
|
import javax.servlet.http.HttpServletRequest;
|
|
import javax.ws.rs.Consumes;
|
|
import javax.ws.rs.POST;
|
|
import javax.ws.rs.Path;
|
|
import javax.ws.rs.Produces;
|
|
import javax.ws.rs.WebApplicationException;
|
|
import javax.ws.rs.core.Context;
|
|
import javax.ws.rs.core.MediaType;
|
|
import javax.ws.rs.core.Response;
|
|
import javax.xml.bind.JAXBException;
|
|
|
|
import org.apache.commons.io.IOUtils;
|
|
import org.gcube.common.authorization.library.AuthorizationEntry;
|
|
import org.gcube.common.authorization.library.provider.ClientInfo;
|
|
import org.gcube.common.authorization.library.provider.SecurityTokenProvider;
|
|
import org.gcube.common.encryption.StringEncrypter;
|
|
import org.gcube.common.resources.gcore.ServiceEndpoint;
|
|
import org.gcube.common.resources.gcore.ServiceEndpoint.AccessPoint;
|
|
import org.gcube.common.resources.gcore.ServiceEndpoint.Property;
|
|
import org.gcube.common.scope.api.ScopeProvider;
|
|
import org.gcube.common.scope.impl.ScopeBean;
|
|
import org.gcube.common.scope.impl.ScopeBean.Type;
|
|
import org.gcube.common.storagehub.model.Metadata;
|
|
import org.gcube.data.analysis.dminvocation.ActionType;
|
|
import org.gcube.data.analysis.dminvocation.DataMinerInvocationManager;
|
|
import org.gcube.data.analysis.dminvocation.model.DataMinerInvocation;
|
|
import org.gcube.datatransfer.resolver.requesthandler.RequestHandler;
|
|
import org.gcube.datatransfer.resolver.services.error.ExceptionManager;
|
|
import org.gcube.datatransfer.resolver.services.exceptions.InternalServerException;
|
|
import org.gcube.datatransfer.resolver.util.ScopeUtil;
|
|
import org.gcube.datatransfer.resolver.util.Util;
|
|
import org.gcube.resources.discovery.client.api.DiscoveryClient;
|
|
import org.gcube.resources.discovery.client.queries.api.SimpleQuery;
|
|
import org.gcube.storagehub.ApplicationMode;
|
|
import org.gcube.storagehub.StorageHubManagement;
|
|
import org.slf4j.Logger;
|
|
import org.slf4j.LoggerFactory;
|
|
import org.xml.sax.SAXException;
|
|
|
|
/**
|
|
* The AnalyticsCreateResolver provides a method to get the Analytics URLs (that
|
|
* are simply URLs) by which is possible to invoke through the DataMiner
|
|
* Executor the run of an algorithm stored in a certain VRE. By the DataMiner
|
|
* Executor then the user is able to monitoring the algorithm execution.
|
|
*
|
|
* See more at
|
|
* https://gcube.wiki.gcube-system.org/gcube/URI_Resolver#Analytics_Resolver
|
|
*
|
|
* @author Francesco Mangiacrapa at ISTI-CNR (francesco.mangiacrapa@isti.cnr.it)
|
|
* Dec 12, 2018
|
|
*/
|
|
@Path("analytics")
|
|
public class AnalyticsCreateResolver {
|
|
|
|
/**
|
|
*
|
|
*/
|
|
protected static final String GCUBE_TOKEN = "gcube-token";
|
|
/**
|
|
*
|
|
*/
|
|
private static final String ANALYTICS_RESOLVER_SERVICE_ENDPOINT_NAME = "Analytics-Resolver";
|
|
private static final String DATAMINER_INVOCATION_MODEL = "dim";
|
|
private static Logger logger = LoggerFactory.getLogger(AnalyticsCreateResolver.class);
|
|
private static String helpURI = "https://gcube.wiki.gcube-system.org/gcube/URI_Resolver#Analytics_Resolver";
|
|
|
|
/**
|
|
* Creates the analytics url.
|
|
*
|
|
* Request format: [URI_RESOLVER_SERVICE_ENDPOINT]/analytics/create
|
|
*
|
|
* See more at
|
|
* https://gcube.wiki.gcube-system.org/gcube/URI_Resolver#HTTP_POST:_Create_an_Analytics_URL
|
|
*
|
|
* @param req the req
|
|
* @param body the body
|
|
* @return the response
|
|
* @throws WebApplicationException the web application exception
|
|
*/
|
|
@POST
|
|
@Path("/create")
|
|
@Consumes(MediaType.TEXT_PLAIN)
|
|
@Produces(MediaType.TEXT_PLAIN)
|
|
public Response createAnalyticsURL(@Context HttpServletRequest req, String body) throws WebApplicationException {
|
|
logger.info(this.getClass().getSimpleName() + " POST starts...");
|
|
|
|
try {
|
|
logger.info("body is: " + body);
|
|
|
|
DataMinerInvocation jsonRequest = null;
|
|
try {
|
|
|
|
jsonRequest = DataMinerInvocationManager.getInstance().unmarshalingJSON(IOUtils.toInputStream(body),
|
|
true);
|
|
} catch (IOException | JAXBException | SAXException e1) {
|
|
logger.error("The body is not a valid DataMinerInvocation JSON request", e1);
|
|
throw ExceptionManager.badRequestException(req,
|
|
"Bad 'dataminer-invocation' JSON request: \n" + e1.getCause().getMessage(), this.getClass(),
|
|
helpURI);
|
|
}
|
|
|
|
logger.debug("The body contains the request: " + jsonRequest.toString());
|
|
|
|
String contextToken = SecurityTokenProvider.instance.get();
|
|
String scope = ScopeProvider.instance.get();
|
|
// logger.info("SecurityTokenProvider contextToken: "+contextToken);
|
|
logger.info("ScopeProvider has scope: " + scope);
|
|
|
|
String appToken = req.getServletContext().getInitParameter(RequestHandler.ROOT_APP_TOKEN);
|
|
|
|
if (contextToken.compareTo(appToken) == 0) {
|
|
logger.error("Token not passed, SecurityTokenProvider contains the root app token: "
|
|
+ appToken.substring(0, 10) + "...");
|
|
throw ExceptionManager.unauthorizedException(req,
|
|
"You are not authorized. You must pass a token of VRE", this.getClass(), helpURI);
|
|
}
|
|
|
|
String operatorID = jsonRequest.getOperatorId();
|
|
|
|
if (scope == null || scope.isEmpty()) {
|
|
logger.error("The parameter 'scope' not found or empty in the JSON object");
|
|
throw ExceptionManager.badRequestException(req,
|
|
"Mandatory body parameter 'scope' not found or empty in the JSON object", this.getClass(),
|
|
helpURI);
|
|
}
|
|
|
|
if (operatorID == null || operatorID.isEmpty()) {
|
|
logger.error("The parameter 'operatorId' not found or empty in the JSON object");
|
|
throw ExceptionManager.badRequestException(req,
|
|
"Mandatory body parameter 'operatorId' not found or empty in the JSON object", this.getClass(),
|
|
helpURI);
|
|
}
|
|
|
|
ScopeBean scopeBean = new ScopeBean(scope);
|
|
String publicLinkToDMInvFile = "";
|
|
|
|
if (scopeBean.is(Type.VRE)) {
|
|
String vreName = scopeBean.name();
|
|
|
|
String analyticsGetResolverURL = String.format("%s/%s", Util.getServerURL(req), "analytics/get");
|
|
// Creating DM invocation file
|
|
if (jsonRequest.getActionType() == null)
|
|
jsonRequest.setActionType(ActionType.RUN);
|
|
|
|
File tempInvocationFile = null;
|
|
try {
|
|
|
|
String xmlRequest = DataMinerInvocationManager.getInstance().marshalingXML(jsonRequest, true, true);
|
|
String uniqueName = createDMInvocationFileName(jsonRequest.getOperatorId());
|
|
tempInvocationFile = createTempFile(uniqueName, ".xml", xmlRequest.getBytes());
|
|
logger.info("Created StorageHubClient Instance, uploading file: " + tempInvocationFile.getName());
|
|
|
|
AuthorizationEntry entry = authorizationService().get(contextToken);
|
|
// retrieve the info of the token owner
|
|
ClientInfo clientInfo = entry.getClientInfo();
|
|
String owner = clientInfo.getId(); // IS THIS THE USERNAME?
|
|
|
|
String infra = ScopeUtil.getInfrastructureNameFromScope(ScopeProvider.instance.get());
|
|
String theAppToken = readApplicationTokenFromSE(req, infra);
|
|
|
|
logger.info("By using infra scope: " + infra + " and the Application Token: "
|
|
+ theAppToken.substring(0, theAppToken.length() / 2) + "... of "
|
|
+ ANALYTICS_RESOLVER_SERVICE_ENDPOINT_NAME + " to instance the "
|
|
+ StorageHubManagement.class.getSimpleName());
|
|
ScopeProvider.instance.set(infra);
|
|
ApplicationMode applicationMode = new ApplicationMode(theAppToken);
|
|
applicationMode.start();
|
|
|
|
StorageHubManagement storageHubManagement = new StorageHubManagement();
|
|
Metadata metadata = new Metadata();
|
|
Map<String, String> theMap = new HashMap<String, String>();
|
|
theMap.put("owner", owner);
|
|
URL thePublicLink = null;
|
|
try {
|
|
|
|
logger.info("Saving dataminer-invocation file for the user: " + owner);
|
|
thePublicLink = storageHubManagement.persistFile(new FileInputStream(tempInvocationFile),
|
|
tempInvocationFile.getName(), "application/xml", metadata);
|
|
logger.info("Saved dataminer-invocation file at: " + thePublicLink);
|
|
} catch (Exception e) {
|
|
logger.error("Error when storing your 'dataminer-invocation':", e);
|
|
throw ExceptionManager
|
|
.internalErrorException(
|
|
req, "Error when storing your 'dataminer-invocation' request with "
|
|
+ jsonRequest + ". \nPlease contact the support",
|
|
this.getClass(), helpURI);
|
|
}
|
|
|
|
// FileContainer fileContainer = shc.getWSRoot().uploadFile(new FileInputStream(tempInvocationFile), tempInvocationFile.getName(), "DataMinerInvocation Request created by "+this.getClass().getSimpleName());
|
|
// logger.info("UPLOADED FILE at: "+fileContainer.getPublicLink());
|
|
// URL thePublicLink = fileContainer.getPublicLink();
|
|
publicLinkToDMInvFile = thePublicLink != null ? thePublicLink.toString() : null;
|
|
} catch (Exception e) {
|
|
|
|
if (e instanceof InternalServerException) {
|
|
// error during storing the file via StorageHubManagent
|
|
throw e;
|
|
}
|
|
logger.error("Error on creating 'dataminer-invocation:", e);
|
|
throw ExceptionManager.badRequestException(req,
|
|
"Error on creating your 'dataminer-invocation' request with " + jsonRequest
|
|
+ ". \nPlease contact the support",
|
|
this.getClass(), helpURI);
|
|
} finally {
|
|
// No needed to reset the scope, it is provided by TokenSetter
|
|
try {
|
|
// DELETING THE TEMP FILE
|
|
if (tempInvocationFile != null && tempInvocationFile.exists())
|
|
tempInvocationFile.delete();
|
|
} catch (Exception e) {
|
|
// silent
|
|
}
|
|
}
|
|
|
|
if (publicLinkToDMInvFile == null) {
|
|
logger.error("Error on creating the public link to file");
|
|
throw ExceptionManager.badRequestException(req,
|
|
"Error on getting link to your 'dataminer-invocation' request. Plese contact the support "
|
|
+ jsonRequest,
|
|
this.getClass(), helpURI);
|
|
}
|
|
|
|
String dataMinerURL = String.format("%s/%s?%s=%s", analyticsGetResolverURL, vreName,
|
|
DATAMINER_INVOCATION_MODEL, publicLinkToDMInvFile);
|
|
logger.info("Returning Analytics URL: " + dataMinerURL);
|
|
return Response.ok(dataMinerURL).header("Location", dataMinerURL).build();
|
|
|
|
} else {
|
|
logger.error("The input scope " + scope + " is not a VRE");
|
|
throw ExceptionManager.badRequestException(req,
|
|
"Working in the " + scope + " scope that is not a VRE. Use a token of VRE", this.getClass(),
|
|
helpURI);
|
|
}
|
|
|
|
} catch (Exception e) {
|
|
|
|
if (!(e instanceof WebApplicationException)) {
|
|
// UNEXPECTED EXCEPTION managing it as WebApplicationException
|
|
String error = "Error occurred on creating the Analytics for the request " + body
|
|
+ ". Please, contact the support!";
|
|
if (e.getCause() != null)
|
|
error += "\n\nCaused: " + e.getCause().getMessage();
|
|
throw ExceptionManager.internalErrorException(req, error, this.getClass(), helpURI);
|
|
}
|
|
// ALREADY MANAGED AS WebApplicationException
|
|
logger.error("Exception:", e);
|
|
throw (WebApplicationException) e;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Reads the Application Token from Service Endpoint
|
|
* {@link AnalyticsCreateResolver#ANALYTICS_RESOLVER_SERVICE_ENDPOINT_NAME}
|
|
*
|
|
* @param req the req
|
|
* @param scope the scope
|
|
* @return the string
|
|
*/
|
|
private static String readApplicationTokenFromSE(HttpServletRequest req, String scope) {
|
|
|
|
String callerScope = null;
|
|
String gCubeAppToken = null;
|
|
try {
|
|
callerScope = ScopeProvider.instance.get();
|
|
ScopeProvider.instance.set(scope);
|
|
logger.info("Searching SE " + ANALYTICS_RESOLVER_SERVICE_ENDPOINT_NAME + " configurations in the scope: "
|
|
+ ScopeProvider.instance.get());
|
|
|
|
SimpleQuery query = queryFor(ServiceEndpoint.class);
|
|
query.addCondition("$resource/Profile/Name/text() eq '" + ANALYTICS_RESOLVER_SERVICE_ENDPOINT_NAME + "'");
|
|
query.addCondition("$resource/Profile/Category/text() eq 'Service'");
|
|
|
|
DiscoveryClient<ServiceEndpoint> client = clientFor(ServiceEndpoint.class);
|
|
List<ServiceEndpoint> toReturn = client.submit(query);
|
|
|
|
logger.info("The query returned " + toReturn.size() + " ServiceEndpoint/s");
|
|
|
|
if (toReturn.size() == 0) {
|
|
String errorMessage = "No " + ANALYTICS_RESOLVER_SERVICE_ENDPOINT_NAME + " registered in the scope: "
|
|
+ ScopeProvider.instance.get();
|
|
logger.error(errorMessage);
|
|
throw ExceptionManager.internalErrorException(req, errorMessage, AnalyticsCreateResolver.class,
|
|
helpURI);
|
|
}
|
|
|
|
ServiceEndpoint se = toReturn.get(0);
|
|
Collection<AccessPoint> theAccessPoints = se.profile().accessPoints().asCollection();
|
|
for (AccessPoint accessPoint : theAccessPoints) {
|
|
Collection<Property> properties = accessPoint.properties().asCollection();
|
|
for (Property property : properties) {
|
|
if (property.name().equalsIgnoreCase(GCUBE_TOKEN)) {
|
|
logger.info("gcube-token as property was found, returning it");
|
|
gCubeAppToken = property.value();
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (gCubeAppToken != null)
|
|
break;
|
|
}
|
|
|
|
if (gCubeAppToken != null) {
|
|
String decryptedPassword = StringEncrypter.getEncrypter().decrypt(gCubeAppToken);
|
|
logger.info("Returning decrypted Application Token registered into "
|
|
+ ANALYTICS_RESOLVER_SERVICE_ENDPOINT_NAME + " SE: "
|
|
+ decryptedPassword.substring(0, decryptedPassword.length() / 2) + "....");
|
|
return decryptedPassword;
|
|
}
|
|
|
|
String errorMessage = "No " + GCUBE_TOKEN + " as Property saved in the "
|
|
+ ANALYTICS_RESOLVER_SERVICE_ENDPOINT_NAME + " SE registered in the scope: "
|
|
+ ScopeProvider.instance.get();
|
|
logger.error(errorMessage);
|
|
throw ExceptionManager.internalErrorException(req, errorMessage, AnalyticsCreateResolver.class, helpURI);
|
|
|
|
} catch (Exception e) {
|
|
String errorMessage = "Error occurred on reading the " + ANALYTICS_RESOLVER_SERVICE_ENDPOINT_NAME
|
|
+ " SE registered in the scope: " + ScopeProvider.instance.get();
|
|
logger.error(errorMessage, e);
|
|
throw ExceptionManager.internalErrorException(req, errorMessage, AnalyticsCreateResolver.class, helpURI);
|
|
|
|
} finally {
|
|
if (callerScope != null) {
|
|
logger.info("Setting to the callerScope scope: " + callerScope);
|
|
ScopeProvider.instance.set(callerScope);
|
|
} else {
|
|
logger.info("Reset scope");
|
|
ScopeProvider.instance.reset();
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Creates the temp file.
|
|
*
|
|
* @param fileName the file name
|
|
* @param extension the extension
|
|
* @param data the data
|
|
* @return the file
|
|
* @throws IOException Signals that an I/O exception has occurred.
|
|
*/
|
|
private static File createTempFile(String fileName, String extension, byte[] data) throws IOException {
|
|
// Since Java 1.7 Files and Path API simplify operations on files
|
|
java.nio.file.Path path = Files.createTempFile(fileName, extension);
|
|
File file = path.toFile();
|
|
// writing sample data
|
|
Files.write(path, data);
|
|
logger.info("Created the Temp File: " + file.getAbsolutePath());
|
|
return file;
|
|
}
|
|
|
|
/**
|
|
* Creates the dm invocation file name.
|
|
*
|
|
* @param operatorId the operator id
|
|
* @return the string
|
|
*/
|
|
private static String createDMInvocationFileName(String operatorId) {
|
|
String fileName = "dim";
|
|
int index = operatorId.lastIndexOf(".");
|
|
if (index > 0 && index < operatorId.length()) {
|
|
fileName += "-" + operatorId.substring(index + 1, operatorId.length());
|
|
}
|
|
fileName += "-" + System.currentTimeMillis();
|
|
return fileName;
|
|
}
|
|
}
|