/*
* R Service Bus
*
* Copyright (c) Copyright of Open Analytics NV, 2010-2015
*
* ===========================================================================
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package eu.openanalytics.rsb.component;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.Serializable;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import javax.activation.DataHandler;
import javax.activation.FileDataSource;
import javax.jws.WebService;
import javax.mail.util.ByteArrayDataSource;
import javax.xml.ws.soap.MTOM;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.Validate;
import org.springframework.stereotype.Component;
import eu.openanalytics.rsb.Constants;
import eu.openanalytics.rsb.Util;
import eu.openanalytics.rsb.message.AbstractFunctionCallResult;
import eu.openanalytics.rsb.message.AbstractResult;
import eu.openanalytics.rsb.message.AbstractWorkItem.Source;
import eu.openanalytics.rsb.message.JsonFunctionCallJob;
import eu.openanalytics.rsb.message.JsonFunctionCallResult;
import eu.openanalytics.rsb.message.MultiFilesJob;
import eu.openanalytics.rsb.message.MultiFilesResult;
import eu.openanalytics.rsb.message.XmlFunctionCallJob;
import eu.openanalytics.rsb.message.XmlFunctionCallResult;
import eu.openanalytics.rsb.security.ApplicationPermissionEvaluator;
import eu.openanalytics.rsb.soap.jobs.MtomJobProcessor;
import eu.openanalytics.rsb.soap.types.JobType;
import eu.openanalytics.rsb.soap.types.JobType.Parameter;
import eu.openanalytics.rsb.soap.types.ObjectFactory;
import eu.openanalytics.rsb.soap.types.PayloadType;
import eu.openanalytics.rsb.soap.types.ResultType;
/**
* Handles synchronous SOAP/MTOM R job processing requests.
*
* @author "OpenAnalytics <rsb.development@openanalytics.eu>"
*/
@MTOM
@WebService(endpointInterface = "eu.openanalytics.rsb.soap.jobs.MtomJobProcessor", targetNamespace = "http://soap.rsb.openanalytics.eu/jobs", serviceName = "MtomJobService", portName = "MtomJobProcessorPort", wsdlLocation = "wsdl/mtom-jobs.wsdl")
@Component("soapMtomJobHandler")
public class SoapMtomJobHandler extends AbstractComponent implements MtomJobProcessor
{
private static final String NULL_RESULT_RECEIVED = "Null result received: has the job timed out?";
private final static ObjectFactory soapOF = new ObjectFactory();
/**
* Processes a single R job.
*
* @throws IOException
*/
public ResultType process(final JobType job)
{
try
{
final String applicationName = job.getApplicationName();
final Map<String, Serializable> meta = getMeta(job);
final ResultType potentialFunctionCallResult = processPotentialFunctionCallJob(applicationName,
job, meta);
if (potentialFunctionCallResult != null)
{
return potentialFunctionCallResult;
}
return processMultiFilesJob(applicationName, job, meta);
}
catch (final IOException ioe)
{
throw new RuntimeException(ioe);
}
}
private Map<String, Serializable> getMeta(final JobType job)
{
final Map<String, Serializable> meta = new HashMap<String, Serializable>();
for (final Parameter parameter : job.getParameter())
{
meta.put(parameter.getName(), parameter.getValue());
}
return meta;
}
private ResultType processPotentialFunctionCallJob(final String applicationName,
final JobType job,
final Map<String, Serializable> meta)
throws IOException
{
if (!isPotentiallyAFunctionCallJob(job, meta))
{
return null;
}
// we consider only the first payload
final PayloadType payload = job.getPayload().get(0);
final String contentType = payload.getContentType();
if (Constants.XML_CONTENT_TYPE.equals(contentType))
{
final String argument = IOUtils.toString(payload.getData().getInputStream());
final XmlFunctionCallJob xmlFunctionCallJob = new XmlFunctionCallJob(Source.SOAP,
applicationName, ApplicationPermissionEvaluator.NO_AUTHENTICATED_USERNAME, UUID.randomUUID(),
(GregorianCalendar) GregorianCalendar.getInstance(), argument);
final XmlFunctionCallResult xmlFunctionCallResult = getMessageDispatcher().process(
xmlFunctionCallJob);
return buildResult(xmlFunctionCallResult);
}
if (Constants.JSON_CONTENT_TYPE.equals(payload.getContentType()))
{
final String argument = IOUtils.toString(payload.getData().getInputStream());
final JsonFunctionCallJob jsonFunctionCallJob = new JsonFunctionCallJob(Source.SOAP,
applicationName, ApplicationPermissionEvaluator.NO_AUTHENTICATED_USERNAME, UUID.randomUUID(),
(GregorianCalendar) GregorianCalendar.getInstance(), argument);
final JsonFunctionCallResult jsonFunctionCallResult = getMessageDispatcher().process(
jsonFunctionCallJob);
return buildResult(jsonFunctionCallResult);
}
// wasn't a function call after all...
return null;
}
private boolean isPotentiallyAFunctionCallJob(final JobType job, final Map<String, Serializable> meta)
{
// function call jobs have a single attachment and no meta
return (job.getPayload().size() == 1) && (meta.isEmpty());
}
private ResultType processMultiFilesJob(final String applicationName,
final JobType job,
final Map<String, Serializable> meta)
throws IOException, FileNotFoundException
{
final MultiFilesJob multiFilesJob = new MultiFilesJob(Source.SOAP, applicationName,
ApplicationPermissionEvaluator.NO_AUTHENTICATED_USERNAME, UUID.randomUUID(),
(GregorianCalendar) GregorianCalendar.getInstance(), meta);
for (final PayloadType payload : job.getPayload())
{
MultiFilesJob.addDataToJob(payload.getContentType(), payload.getName(), payload.getData()
.getInputStream(), multiFilesJob);
}
final MultiFilesResult multiFilesResult = getMessageDispatcher().process(multiFilesJob);
return buildResult(job, multiFilesResult);
}
private ResultType buildResult(final AbstractFunctionCallResult functionCallResult) throws IOException
{
Validate.notNull(functionCallResult, NULL_RESULT_RECEIVED);
final String resultContentType = functionCallResult.getMimeType().toString();
final PayloadType payload = soapOF.createPayloadType();
payload.setContentType(resultContentType);
payload.setName(functionCallResult.getResultFileName());
payload.setData(new DataHandler(new ByteArrayDataSource(functionCallResult.getPayload(),
resultContentType)));
final ResultType result = createResult(functionCallResult);
result.getPayload().add(payload);
return result;
}
private ResultType buildResult(final JobType job, final MultiFilesResult multiFilesResult)
throws FileNotFoundException, IOException
{
Validate.notNull(multiFilesResult, NULL_RESULT_RECEIVED);
final boolean isOneZipJob = job.getPayload().size() == 1
&& Constants.ZIP_CONTENT_TYPES.contains(job.getPayload()
.get(0)
.getContentType());
final File[] resultFiles = isOneZipJob
? new File[]{MultiFilesResult.zipResultFilesIfNotError(multiFilesResult)}
: multiFilesResult.getPayload();
final ResultType result = createResult(multiFilesResult);
for (final File resultFile : resultFiles)
{
final PayloadType payload = soapOF.createPayloadType();
payload.setContentType(Util.getContentType(resultFile));
payload.setName(resultFile.getName());
payload.setData(new DataHandler(new FileDataSource(resultFile)));
result.getPayload().add(payload);
}
return result;
}
private ResultType createResult(final AbstractResult<?> result)
{
final ResultType response = soapOF.createResultType();
response.setApplicationName(result.getApplicationName());
response.setJobId(result.getJobId().toString());
response.setSuccess(result.isSuccess());
return response;
}
}