/*
*
* 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);
}
}