Skip to content
Snippets Groups Projects
ExperimentApi.java 14.11 KiB
package eu.hbp.mip.controllers;

import com.google.common.collect.Lists;
import com.google.gson.*;
import eu.hbp.mip.configuration.SecurityConfiguration;
import eu.hbp.mip.model.ExperimentQuery;
import eu.hbp.mip.model.User;
import eu.hbp.mip.utils.HTTPUtil;
import io.swagger.annotations.*;
import org.apache.log4j.Logger;
import eu.hbp.mip.model.Experiment;
import eu.hbp.mip.repositories.ExperimentRepository;
import eu.hbp.mip.repositories.ModelRepository;
import eu.hbp.mip.utils.JSONUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;

import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;

/**
 * Created by habfast on 21/04/16.
 */
@RestController
@RequestMapping(value = "/experiments", produces = {APPLICATION_JSON_VALUE})
@Api(value = "/experiments", description = "the experiments API")
public class ExperimentApi {

    private static final Logger LOGGER = Logger.getLogger(ExperimentApi.class);

    private static final String EXAREME_ALGO_JSON_FILE="data/exareme_algorithms.json";

    private static final Gson gson = new GsonBuilder()
            .serializeNulls()
            .setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
            .excludeFieldsWithoutExposeAnnotation()
            .create();

    private static final String EXAREME_LR_ALGO = "WP_LINEAR_REGRESSION";

    @Value("#{'${services.woken.experimentUrl:http://dockerhost:8087/experiment}'}")
    private String experimentUrl;

    @Value("#{'${services.woken.listMethodsUrl:http://dockerhost:8087/list-methods}'}")
    private String listMethodsUrl;

    @Value("#{'${services.exareme.miningExaremeUrl:http://hbps2.chuv.ch:9090/mining/query}'}")
    private String miningExaremeQueryUrl;

    @Autowired
    private SecurityConfiguration securityConfiguration;

    @Autowired
    private ModelRepository modelRepository;

    @Autowired
    private ExperimentRepository experimentRepository;

    @ApiOperation(value = "Send a request to the workflow to run an experiment", response = Experiment.class)
    @RequestMapping(method = RequestMethod.POST)
    public ResponseEntity<String> runExperiment(@RequestBody ExperimentQuery incomingQueryObj) {
        LOGGER.info("Run an experiment");

        String incomingQueryString = new Gson().toJson(incomingQueryObj);

        JsonObject incomingQuery = gson.fromJson(incomingQueryString, JsonObject.class);

        Experiment experiment = new Experiment();
        experiment.setUuid(UUID.randomUUID());
        User user = securityConfiguration.getUser();

        experiment.setAlgorithms(incomingQuery.get("algorithms").toString());
        experiment.setValidations(incomingQuery.get("validations").toString());
        experiment.setName(incomingQuery.get("name").getAsString());
        experiment.setCreatedBy(user);
        experiment.setModel(modelRepository.findOne(incomingQuery.get("model").getAsString()));
        experimentRepository.save(experiment);

        LOGGER.info("Experiment saved");

        try {
            if(isExaremeAlgo(experiment))
            {
                sendExaremeExperiment(experiment);
            }
            else
            {
                sendExperiment(experiment);
            }
        } catch (MalformedURLException mue) { LOGGER.trace(mue.getMessage()); } // ignore

        return new ResponseEntity<>(gson.toJson(experiment.jsonify()), HttpStatus.OK);
    }

    @ApiOperation(value = "get an experiment", response = Experiment.class)
    @RequestMapping(value = "/{uuid}", method = RequestMethod.GET)
    public ResponseEntity<String> getExperiment(@ApiParam(value = "uuid", required = true) @PathVariable("uuid") String uuid) {
        LOGGER.info("Get an experiment");

        Experiment experiment;
        UUID experimentUuid;
        try {
            experimentUuid = UUID.fromString(uuid);
        } catch (IllegalArgumentException iae) {
            LOGGER.trace(iae);
            LOGGER.warn("An invalid Experiment UUID was received !");
            return ResponseEntity.badRequest().body("Invalid Experiment UUID");
        }

        experiment = experimentRepository.findOne(experimentUuid);

        if (experiment == null) {
            return new ResponseEntity<>("Not found", HttpStatus.NOT_FOUND);
        }

        return new ResponseEntity<>(gson.toJson(experiment.jsonify()), HttpStatus.OK);
    }

    @ApiOperation(value = "Mark an experiment as viewed", response = Experiment.class)
    @RequestMapping(value = "/{uuid}/markAsViewed", method = RequestMethod.GET)
    public ResponseEntity<String> markExperimentAsViewed(@ApiParam(value = "uuid", required = true) @PathVariable("uuid") String uuid) {
        LOGGER.info("Mark an experiment as viewed");

        Experiment experiment;
        UUID experimentUuid;
        User user = securityConfiguration.getUser();
        try {
            experimentUuid = UUID.fromString(uuid);
        } catch (IllegalArgumentException iae) {
            LOGGER.trace(iae);
            LOGGER.warn("An invalid Experiment UUID was received !");
            return ResponseEntity.badRequest().body("Invalid Experiment UUID");
        }

        experiment = experimentRepository.findOne(experimentUuid);
        if (!experiment.getCreatedBy().getUsername().equals(user.getUsername()))
            return new ResponseEntity<>("You're not the owner of this experiment", HttpStatus.BAD_REQUEST);
        experiment.setResultsViewed(true);
        experimentRepository.save(experiment);

        LOGGER.info("Experiment updated (marked as viewed)");

        return new ResponseEntity<>(gson.toJson(experiment.jsonify()), HttpStatus.OK);
    }

    @ApiOperation(value = "Mark an experiment as shared", response = Experiment.class)
    @RequestMapping(value = "/{uuid}/markAsShared", method = RequestMethod.GET)
    public ResponseEntity<String> markExperimentAsShared(@ApiParam(value = "uuid", required = true) @PathVariable("uuid") String uuid) {
        LOGGER.info("Mark an experiment as shared");

        return doMarkExperimentAsShared(uuid, true);
    }

    @ApiOperation(value = "Mark an experiment as unshared", response = Experiment.class)
    @RequestMapping(value = "/{uuid}/markAsUnshared", method = RequestMethod.GET)
    public ResponseEntity<String> markExperimentAsUnshared(@ApiParam(value = "uuid", required = true) @PathVariable("uuid") String uuid) {
        LOGGER.info("Mark an experiment as unshared");

        return doMarkExperimentAsShared(uuid, false);
    }

    @ApiOperation(value = "list experiments", response = Experiment.class, responseContainer = "List")
    @RequestMapping(value = "/mine", method = RequestMethod.GET, params = {"maxResultCount"})
    public ResponseEntity<String> listExperiments(
            @ApiParam(value = "maxResultCount", required = false) @RequestParam int maxResultCount
    ) {
        LOGGER.info("List experiments");

        return doListExperiments(true, maxResultCount, null);
    }

    @ApiOperation(value = "list experiments", response = Experiment.class, responseContainer = "List")
    @RequestMapping(method = RequestMethod.GET, params = {"slug", "maxResultCount"})
    public ResponseEntity<String> listExperiments(
            @ApiParam(value = "slug", required = false) @RequestParam("slug") String modelSlug,
            @ApiParam(value = "maxResultCount", required = false) @RequestParam("maxResultCount") int maxResultCount
    ) {
        LOGGER.info("List experiments");

        if (maxResultCount <= 0 && (modelSlug == null || "".equals(modelSlug))) {
            return new ResponseEntity<>("You must provide at least a slug or a limit of result", HttpStatus.BAD_REQUEST);
        }

        return doListExperiments(false, maxResultCount, modelSlug);
    }

    @ApiOperation(value = "List available methods and validations", response = String.class)
    @RequestMapping(path = "/methods", method = RequestMethod.GET)
    public ResponseEntity<String> listAvailableMethodsAndValidations() throws IOException {
        LOGGER.info("List available methods and validations");

        StringBuilder response = new StringBuilder();

        int code = HTTPUtil.sendGet(listMethodsUrl, response);
        if (code < 200 || code > 299) {
            return new ResponseEntity<>(response.toString(), HttpStatus.valueOf(code));
        }

        JsonObject catalog = new JsonParser().parse(response.toString()).getAsJsonObject();

        InputStream is = ExperimentApi.class.getClassLoader().getResourceAsStream(EXAREME_ALGO_JSON_FILE);
        InputStreamReader isr = new InputStreamReader(is);
        BufferedReader br = new BufferedReader(isr);
        JsonObject exaremeAlgo = new JsonParser().parse(br).getAsJsonObject();

        catalog.get("algorithms").getAsJsonArray().add(exaremeAlgo);

        return new ResponseEntity<>(new Gson().toJson(catalog), HttpStatus.valueOf(code));
    }

    private ResponseEntity<String> doListExperiments(
            boolean mine,
            int maxResultCount,
            String modelSlug
    ) {
        User user = securityConfiguration.getUser();

        Iterable<Experiment> myExperiments = experimentRepository.findByCreatedBy(user);
        List<Experiment> expList = Lists.newLinkedList(myExperiments);
        if(!mine)
        {
            Iterable<Experiment> sharedExperiments = experimentRepository.findByShared(true);
            List<Experiment> sharedExpList = Lists.newLinkedList(sharedExperiments);
            expList.addAll(sharedExpList);
        }

        if (modelSlug != null && !"".equals(modelSlug)) {
            for(Iterator<Experiment> it = expList.iterator(); it.hasNext();)
            {
                Experiment e = it.next();
                e.setResult(null);
                e.setAlgorithms(null);
                e.setValidations(null);
                if(!e.getModel().getSlug().equals(modelSlug))
                {
                    it.remove();
                }
            }
        }

        return new ResponseEntity<>(gson.toJson(expList), HttpStatus.OK);
    }

    private ResponseEntity<String> doMarkExperimentAsShared(String uuid, boolean shared) {
        Experiment experiment;
        UUID experimentUuid;
        User user = securityConfiguration.getUser();
        try {
            experimentUuid = UUID.fromString(uuid);
        } catch (IllegalArgumentException iae) {
            LOGGER.trace(iae);
            LOGGER.warn("An invalid Experiment UUID was received !");
            return ResponseEntity.badRequest().body("Invalid Experiment UUID");
        }

        experiment = experimentRepository.findOne(experimentUuid);

        if (!experiment.getCreatedBy().getUsername().equals(user.getUsername()))
            return new ResponseEntity<>("You're not the owner of this experiment", HttpStatus.BAD_REQUEST);

        experiment.setShared(shared);
        experimentRepository.save(experiment);

        LOGGER.info("Experiment updated (marked as shared)");

        return new ResponseEntity<>(gson.toJson(experiment.jsonify()), HttpStatus.OK);
    }

    private void sendExperiment(Experiment experiment) throws MalformedURLException {
        // this runs in the background. For future optimization: use a thread pool
        final String url = experimentUrl;
        final String query = experiment.computeQuery();
        new Thread() {
            @Override
            public void run() {
                // Results are stored in the experiment object
                try {
                    executeExperiment(url, query, experiment);
                } catch (IOException e) {
                    LOGGER.trace(e);
                    LOGGER.warn("Experiment failed to run properly !");
                    setExperimentError(e, experiment);
                }
                finishExpermient(experiment);
            }
        }.start();
    }

    private void sendExaremeExperiment(Experiment experiment) {
        // this runs in the background. For future optimization: use a thread pool
        new Thread() {
            @Override
            public void run() {
                String query = experiment.computeExaremeQuery();
                String url = miningExaremeQueryUrl + "/" + EXAREME_LR_ALGO;

                // Results are stored in the experiment object
                try {
                    executeExperiment(url, query, experiment);
                } catch (IOException e) {
                    LOGGER.trace(e);
                    LOGGER.warn("Exareme experiment failed to run properly !");
                    setExperimentError(e, experiment);
                }

                if(!JSONUtil.isJSONValid(experiment.getResult()))
                    {
                        experiment.setResult("Unsupported variables !");
                    }
                finishExpermient(experiment);
            }
        }.start();
    }

    private void finishExpermient(Experiment experiment)
    {
        experiment.setFinished(new Date());
        experimentRepository.save(experiment);

        LOGGER.info("Experiment updated (finished)");
    }

    private static void executeExperiment(String url, String query, Experiment experiment) throws IOException {
        StringBuilder results = new StringBuilder();
        int code = HTTPUtil.sendPost(url, query, results);
        experiment.setResult(results.toString());
        experiment.setHasError(code >= 400);
        experiment.setHasServerError(code >= 500);
    }

    private static void setExperimentError(IOException e, Experiment experiment) {
        experiment.setHasError(true);
        experiment.setHasServerError(true);
        experiment.setResult(e.getMessage());
    }

    private static boolean isExaremeAlgo(Experiment experiment)  {
        JsonArray algorithms = new JsonParser().parse(experiment.getAlgorithms()).getAsJsonArray();
        String algoCode = algorithms.get(0).getAsJsonObject().get("code").getAsString();
        return "glm_exareme".equals(algoCode);
    }

}