org.opentox.www.rest.resources.AlgorithmResource.java Source code

Java tutorial

Introduction

Here is the source code for org.opentox.www.rest.resources.AlgorithmResource.java

Source

/*
 *
 * YAQP - Yet Another QSAR Project:
 * Machine Learning algorithms designed for the prediction of toxicological
 * features of chemical compounds become available on the Web. Yaqp is developed
 * under OpenTox (http://opentox.org) which is an FP7-funded EU research project.
 * This project was developed at the Automatic Control Lab in the Chemical Engineering
 * School of the National Technical University of Athens. Please read README for more
 * information.
 *
 * Copyright (C) 2009-2010 Pantelis Sopasakis & Charalampos Chomenides
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Contact:
 * Pantelis Sopasakis
 * chvng@mail.ntua.gr
 * Address: Iroon Politechniou St. 9, Zografou, Athens Greece
 * tel. +30 210 7723236
 */
package org.opentox.www.rest.resources;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.Enumeration;
import org.opentox.config.Configuration;
import org.opentox.core.exceptions.Cause;
import org.opentox.core.exceptions.YaqpException;
import org.opentox.core.processors.Pipeline;
import org.opentox.io.processors.InputProcessor;
import org.opentox.io.processors.OutputProcessor;
import org.opentox.io.processors.Publisher;
import org.opentox.ontology.components.Algorithm;
import org.opentox.ontology.components.QSARModel;
import org.opentox.ontology.components.User;
import org.opentox.ontology.data.DatasetBuilder;
import org.opentox.ontology.processors.InstancesProcessor;
import org.opentox.ontology.util.YaqpAlgorithms;
import org.opentox.ontology.util.vocabulary.ConstantParameters;
import org.opentox.qsar.processors.filters.AttributeCleanup;
import org.opentox.qsar.processors.filters.AttributeCleanup.ATTRIBUTE_TYPE;
import org.opentox.util.logging.YaqpLogger;
import org.opentox.util.logging.levels.Fatal;
import org.opentox.www.rest.components.URITemplate;
import org.opentox.www.rest.components.YaqpForm;
import org.opentox.www.rest.components.YaqpResource;
import org.opentox.www.rest.services.Trainers;
import org.opentox.www.rest.services.TrainingService;
import org.restlet.data.MediaType;
import org.restlet.data.Reference;
import org.restlet.representation.Representation;
import org.restlet.representation.StringRepresentation;
import org.restlet.representation.Variant;
import org.restlet.resource.ResourceException;
import weka.core.Attribute;
import weka.core.Instances;

/**
 *
 * The algorithm resource is the interface to all algorithm - related functionallities
 * such as model training, data cleanup services and other. The methods GET and POST are
 * implemented. Use GET to acquire a representation of the underlying algorithm in a supported
 * mediatype and POST to run the algorithm. The algorithm resource provides access to all
 * metadata of the algorithm (title, description, parameters etc).
 * @author Pantelis Sopasakis
 * @author Charalampos Chomenides
 */
public class AlgorithmResource extends YaqpResource {

    /** URI Template */
    public static final URITemplate template = new URITemplate("algorithm", "algorithm_id", null);

    /**
     * Trainer class used to perform the training
     */
    Class trainer;

    /**
     * Name of the algorithm.
     */
    String algorithmName;

    /**
     * Initialized the resource
     * @throws ResourceException
     *      In case the resource cannot be initialized.
     */
    @Override
    protected void doInit() throws ResourceException {
        super.doInit();
        setAutoCommitting(false);
        initialize(MediaType.APPLICATION_RDF_XML, MediaType.APPLICATION_RDF_TURTLE, MediaType.APPLICATION_PDF,
                MediaType.TEXT_URI_LIST);

        algorithmName = Reference.decode(getRequest().getAttributes().get(template.getPrimaryKey()).toString());
        try {
            trainer = Trainers.valueOf(algorithmName).getTrainer();
        } catch (IllegalArgumentException ex) {
            trainer = null;
        }

    }

    /**
     * The representation of a certain algorithm in some of the available mimes.
     * @param variant
     *      The requested variant for the representation of the algorithm.
     * @return
     *      Represenation of an algorithm in a supported mime type. If the requested
     *      MIME is not available, the default (application/rdf+xml) is returned instead.
     * @throws ResourceException
     *      In case the representation cannot be generated and returned to the client.
     */
    @Override
    @SuppressWarnings({ "unchecked" })
    protected Representation get(final Variant variant) throws ResourceException {
        final Algorithm a = YaqpAlgorithms.getByName(algorithmName);
        // IF THE REQUESTED ALGORITHM WAS NOT FOUND, RETURN AN EXPLANATORY MESSAGE
        // AND SET THE STATUS CODE TO 404.
        if (a == null) {
            toggleNotFound();
            String message = "You have requested an algorithm which does not exist (" + algorithmName
                    + "). You can " + "get a complete list of all available algorithms at " + Configuration.BASE_URI
                    + "/algorithm" + NEWLINE;
            return sendMessage(message);
        }
        final Publisher publisher = new Publisher(variant.getMediaType());
        final OutputProcessor representer = new OutputProcessor();
        final Pipeline pipe = new Pipeline(publisher, representer);
        try {
            toggleSuccess();
            return (Representation) pipe.process(a);
        } catch (final YaqpException ex) {
            toggleServerError();
            YaqpLogger.LOG.log(new Fatal(getClass(),
                    "error 500 occured when a client asked for the representation of the algorithm :"
                            + algorithmName));
            return sendMessage(_500_);
        }
    }

    /**
     * Exploits the {@link TrainingService } to train a new model, and returns the
     * trained model in a supported mime type accerding to the request of the client.
     * @param entity
     *      The parameters posted by the client as application/x-www-form-urlencoded
     * @param variant
     *      The requested variant for the respponse from the server including the
     *      Accept field in the header of the request.
     * @return
     *      Representation of the trained model.
     * @throws ResourceException
     *      In case the training cannot be performed due to client or server error
     */
    @Override
    @SuppressWarnings({ "unchecked" })
    protected Representation post(Representation entity, final Variant variant) throws ResourceException {
        // CHECK IF THE ALGORITHM EXISTS...
        final Algorithm a = YaqpAlgorithms.getByName(algorithmName);
        if (a == null) {
            toggleNotFound();
            String message = "You have requested an algorithm which does not exist (" + algorithmName + "). You can"
                    + "get a complete list of all available algorithms at " + Configuration.BASE_URI + "/algorithm"
                    + NEWLINE;
            return sendMessage(message);
        }

        // @hampos: This is not quite generic though solves the problem for now.
        //          We should discuss about how to
        if (this.algorithmName.equals(YaqpAlgorithms.CLEAN_UP.getMeta().getName())) {
            return filterData(entity, variant);
        } else {
            return trainModel(entity, variant);
        }

    }

    @SuppressWarnings({ "unchecked" })
    private Representation trainModel(final Representation entity, final Variant variant) {
        // THE DEFAULT MEDIATYPE OF THE RESPONSE IS text/uri-list UNLESS THE CLIENT ASKS FOR
        // SOMETHING DIFFERENT SETTING THE 'Accept' HEADER OF THE REQUEST ACCORDINGLY, TO ONE OF THE
        // SUPPORTED MEDIATYPES.
        MediaType responseMedia = MediaType.TEXT_URI_LIST;

        // IF THE CLIENT ASKS FOR A CERTAIN MEDIATYPE, SET THE responseMedia ACCORDINGLY
        if (!getRequest().getClientInfo().getAcceptedMediaTypes().get(0).getMetadata().equals(MediaType.ALL)
                && getVariants().contains(new Variant(responseMedia))) {
            responseMedia = variant.getMediaType();
        }
        /*
         *
         * Perform the training calling a Training Service.
         */
        try {
            QSARModel trainedModel = null;
            try {
                // trainer is any implementation of WekaTrainer.
                trainedModel = new TrainingService(new YaqpForm(entity), new User(), trainer, responseMedia).call();
            } catch (final YaqpException ex) {
                toggleBadRequest();
                return sendMessage(ex.toString() + NEWLINE);
            } catch (final Exception ex) {
                toggleServerError();
                return sendMessage(_500_);
            }
            /**
             * In the future, training services (i.e. the class TrainingService) will be submitted in
             * an executor and a task will be created right afterwards. The URI of the created task will
             * be returned to the client.
             */
            final Publisher publisher = new Publisher(responseMedia);
            final OutputProcessor representer = new OutputProcessor();
            Pipeline representationPipe = new Pipeline(publisher, representer);
            return (Representation) representationPipe.process(trainedModel);
        } catch (final YaqpException ex) {
            toggleBadRequest();
            if (ex.getCode() == Cause.XQF412) {
                final String tooSparseDataset = "The dataset you provided is too sparse, thus cannot be used to build a QSAR model.";
                return sendMessage(tooSparseDataset + NEWLINE);
            }
            return sendMessage(ex.toString() + NEWLINE);
        } catch (final Exception ex) {
            toggleServerError();
            YaqpLogger.LOG.log(new Fatal(getClass(), ex.toString()));
            return sendMessage(_500_);
        }
    }

    @SuppressWarnings({ "unchecked" })
    private Representation filterData(final Representation entity, final Variant variant) {
        InputProcessor p1 = new InputProcessor();
        DatasetBuilder p2 = new DatasetBuilder();
        InstancesProcessor p3 = new InstancesProcessor();
        AttributeCleanup p4 = new AttributeCleanup(ATTRIBUTE_TYPE.string);
        Pipeline pipe = new Pipeline(p1, p2, p3, p4);

        YaqpForm form = new YaqpForm(entity);
        URI uri;
        try {
            uri = new URI(form.getFirstValue(ConstantParameters.dataset_uri));
        } catch (URISyntaxException ex) {
            toggleBadRequest();
            return sendMessage(
                    "Inacceptable URI (" + form.getFirstValue(ConstantParameters.dataset_uri) + ")" + NEWLINE);
        }
        Instances filteredData = null;
        try {
            filteredData = (Instances) pipe.process(uri);
        } catch (YaqpException ex) {
            toggleBadRequest();
            return sendMessage(ex.toString());
        }

        Enumeration attributes = filteredData.enumerateAttributes();
        String list = "";
        Attribute att;
        while (attributes.hasMoreElements()) {
            att = (Attribute) attributes.nextElement();
            list += "feature_uris[]=" + att.name();
            if (attributes.hasMoreElements())
                list += "&";
        }
        return new StringRepresentation(uri + "?" + list, MediaType.TEXT_URI_LIST);
    }

}