/** * */ 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.ByteArrayOutputStream; 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.TokenSetter; import org.gcube.datatransfer.resolver.services.error.ExceptionManager; 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 Class AnalyticsCreateResolver. * * @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. * * @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().unmarshaling(IOUtils.toInputStream(body), org.gcube.data.analysis.dminvocation.MediaType.ApplicationJSON, 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(TokenSetter.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 { ByteArrayOutputStream xmlByteArray = DataMinerInvocationManager.getInstance().marshaling(jsonRequest, org.gcube.data.analysis.dminvocation.MediaType.ApplicationXML, true); String uniqueName = createDMInvocationFileName(jsonRequest.getOperatorId()); tempInvocationFile = createTempFile(uniqueName, ".xml", xmlByteArray.toByteArray()); logger.info("Created StorageHubClient Instance, uploading file: "+tempInvocationFile.getName()); String infra = ScopeUtil.getInfrastructureNameFromScope(ScopeProvider.instance.get()); String theAppToken = readApplicationTokenFromSE(req, infra); ApplicationMode applicationMode = new ApplicationMode(theAppToken); applicationMode.start(); StorageHubManagement storageHubManagement = new StorageHubManagement(); Metadata metadata = new Metadata(); Map theMap = new HashMap(); AuthorizationEntry entry = authorizationService().get(contextToken); //retrieve the info of the token owner ClientInfo clientInfo = entry.getClientInfo(); String owner = clientInfo.getId(); //IS THIS THE USERNAME? theMap.put("owner", owner); logger.info("Saving dataminer-invocation file for the user: "+owner); URL thePublicLink = storageHubManagement.persistFile(new FileInputStream(tempInvocationFile), tempInvocationFile.getName(), "application/xml", metadata); logger.info("Saved dataminer-invocation file at: "+thePublicLink); // 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) { 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{ 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 origalScope = null; String gCubeAppToken = null; try{ origalScope = 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 client = clientFor(ServiceEndpoint.class); List 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 theAccessPoints = se.profile().accessPoints().asCollection(); for (AccessPoint accessPoint : theAccessPoints) { Collection 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(origalScope!=null){ logger.info("Resetting 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