Java tutorial
/* * 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.IOException; import java.io.InputStream; import java.io.Serializable; import java.net.URI; import java.net.URISyntaxException; import java.util.GregorianCalendar; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.UUID; import javax.ws.rs.Consumes; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.Context; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; import javax.ws.rs.core.UriInfo; import org.apache.commons.io.FilenameUtils; import org.apache.commons.lang3.StringUtils; import org.apache.cxf.jaxrs.ext.multipart.Attachment; import org.springframework.stereotype.Component; import eu.openanalytics.rsb.Constants; import eu.openanalytics.rsb.Util; import eu.openanalytics.rsb.message.AbstractJob; import eu.openanalytics.rsb.message.AbstractWorkItem.Source; import eu.openanalytics.rsb.message.JsonFunctionCallJob; import eu.openanalytics.rsb.message.MultiFilesJob; import eu.openanalytics.rsb.message.XmlFunctionCallJob; import eu.openanalytics.rsb.rest.types.JobToken; /** * Handles asynchronous R job processing requests. * * @author "OpenAnalytics <rsb.development@openanalytics.eu>" */ @Component("jobsResource") @Path("/" + Constants.JOBS_PATH) @Produces({ Constants.RSB_XML_CONTENT_TYPE, Constants.RSB_JSON_CONTENT_TYPE }) public class JobsResource extends AbstractResource { private interface JobBuilder { AbstractJob build(final String applicationName, final UUID jobId, final GregorianCalendar submissionTime) throws IOException; } /** * Handles a function call job with a JSON payload. * * @param jsonArgument Argument passed to the function called on RServi. * @param httpHeaders * @return * @throws URISyntaxException * @throws IOException */ @POST @Consumes(Constants.JSON_CONTENT_TYPE) public Response handleJsonFunctionCallJob(final String jsonArgument, @Context final HttpHeaders httpHeaders, @Context final UriInfo uriInfo) throws URISyntaxException, IOException { return handleNewRestJob(httpHeaders, uriInfo, new JobBuilder() { @Override public AbstractJob build(final String applicationName, final UUID jobId, final GregorianCalendar submissionTime) { return new JsonFunctionCallJob(Source.REST, applicationName, getUserName(), jobId, submissionTime, jsonArgument); } }); } /** * Handles a function call job with a XML payload. * * @param xmlArgument Argument passed to the function called on RServi. * @param httpHeaders * @return * @throws URISyntaxException * @throws IOException */ @POST @Consumes(Constants.XML_CONTENT_TYPE) public Response handleXmlFunctionCallJob(final String xmlArgument, @Context final HttpHeaders httpHeaders, @Context final UriInfo uriInfo) throws URISyntaxException, IOException { return handleNewRestJob(httpHeaders, uriInfo, new JobBuilder() { @Override public AbstractJob build(final String applicationName, final UUID jobId, final GregorianCalendar submissionTime) { return new XmlFunctionCallJob(Source.REST, applicationName, getUserName(), jobId, submissionTime, xmlArgument); } }); } /** * Handles a function call job with a ZIP payload. * * @param in Input stream of the ZIP data. * @param httpHeaders * @return * @throws URISyntaxException * @throws IOException */ @POST @Consumes({ Constants.ZIP_CONTENT_TYPE, Constants.ZIP_CONTENT_TYPE2, Constants.ZIP_CONTENT_TYPE3 }) public Response handleZipJob(final InputStream in, @Context final HttpHeaders httpHeaders, @Context final UriInfo uriInfo) throws URISyntaxException, IOException { return handleNewRestJob(httpHeaders, uriInfo, new JobBuilder() { @Override public AbstractJob build(final String applicationName, final UUID jobId, final GregorianCalendar submissionTime) throws IOException { final MultiFilesJob job = new MultiFilesJob(Source.REST, applicationName, getUserName(), jobId, submissionTime, getJobMeta(httpHeaders)); MultiFilesJob.addZipFilesToJob(in, job); return job; } }); } /** * Handles a multi-part form job upload. * * @param parts * @param httpHeaders * @param uriInfo * @return * @throws URISyntaxException * @throws IOException */ @POST @Consumes(Constants.MULTIPART_CONTENT_TYPE) // force content type to plain XML and JSON (browsers choke on subtypes) @Produces({ Constants.XML_CONTENT_TYPE, Constants.JSON_CONTENT_TYPE }) public Response handleMultipartFormJob(final List<Attachment> parts, @Context final HttpHeaders httpHeaders, @Context final UriInfo uriInfo) throws URISyntaxException, IOException { String applicationName = null; final Map<String, Serializable> jobMeta = new HashMap<String, Serializable>(); for (final Attachment part : parts) { final String partName = getPartName(part); if (StringUtils.equals(partName, Constants.APPLICATION_NAME_HTTP_HEADER)) { applicationName = part.getObject(String.class); } else if (StringUtils.startsWith(partName, Constants.RSB_META_HEADER_HTTP_PREFIX)) { final String metaName = StringUtils.substringAfter(partName, Constants.RSB_META_HEADER_HTTP_PREFIX); final String metaValue = part.getObject(String.class); if (StringUtils.isNotEmpty(metaValue)) { jobMeta.put(metaName, metaValue); } } } final String finalApplicationName = applicationName; return handleNewJob(finalApplicationName, httpHeaders, uriInfo, new JobBuilder() { @Override public AbstractJob build(final String applicationName, final UUID jobId, final GregorianCalendar submissionTime) throws IOException { final MultiFilesJob job = new MultiFilesJob(Source.REST, applicationName, getUserName(), jobId, submissionTime, jobMeta); for (final Attachment part : parts) { if (StringUtils.equals(getPartName(part), Constants.JOB_FILES_MULTIPART_NAME)) { final InputStream data = part.getDataHandler().getInputStream(); if (data.available() == 0) { // if the form is submitted with no file attached, we get // an empty part continue; } MultiFilesJob.addDataToJob(part.getContentType().toString(), getPartFileName(part), data, job); } } return job; } }); } private Response handleNewRestJob(final HttpHeaders httpHeaders, final UriInfo uriInfo, final JobBuilder jobBuilder) throws URISyntaxException, IOException { final String applicationName = Util.getSingleHeader(httpHeaders, Constants.APPLICATION_NAME_HTTP_HEADER); return handleNewJob(applicationName, httpHeaders, uriInfo, jobBuilder); } private Response handleNewJob(final String applicationName, final HttpHeaders httpHeaders, final UriInfo uriInfo, final JobBuilder jobBuilder) throws IOException, URISyntaxException { final UUID jobId = UUID.randomUUID(); final AbstractJob job = jobBuilder.build(applicationName, jobId, (GregorianCalendar) GregorianCalendar.getInstance()); getMessageDispatcher().dispatch(job); final JobToken jobToken = buildJobToken(uriInfo, httpHeaders, job); return Response.status(Status.ACCEPTED).entity(jobToken).build(); } private Map<String, Serializable> getJobMeta(final HttpHeaders httpHeaders) { final Map<String, Serializable> meta = new HashMap<String, Serializable>(); for (final Entry<String, List<String>> multiValues : httpHeaders.getRequestHeaders().entrySet()) { if (!StringUtils.startsWithIgnoreCase(multiValues.getKey(), Constants.RSB_META_HEADER_HTTP_PREFIX)) { continue; } if (multiValues.getValue().size() > 1) { throw new IllegalArgumentException("Multiple values found for header: " + multiValues.getKey()); } meta.put(StringUtils.substring(multiValues.getKey(), Constants.RSB_META_HEADER_HTTP_PREFIX.length()), multiValues.getValue().get(0)); } return Util.normalizeJobMeta(meta); } private JobToken buildJobToken(final UriInfo uriInfo, final HttpHeaders httpHeaders, final AbstractJob job) throws URISyntaxException { final JobToken jobToken = Util.REST_OBJECT_FACTORY.createJobToken(); jobToken.setApplicationName(job.getApplicationName()); jobToken.setSubmissionTime(Util.convertToXmlDate(job.getSubmissionTime())); final String jobIdAsString = job.getJobId().toString(); jobToken.setJobId(jobIdAsString); final URI uriBuilder = Util.getUriBuilder(uriInfo, httpHeaders).path(Constants.RESULTS_PATH) .path(job.getApplicationName()).build(); jobToken.setApplicationResultsUri(uriBuilder.toString()); jobToken.setResultUri( Util.buildResultUri(job.getApplicationName(), jobIdAsString, httpHeaders, uriInfo).toString()); return jobToken; } private static String getPartName(final Attachment part) { return part.getContentDisposition().getParameter("name"); } private static String getPartFileName(final Attachment part) { return FilenameUtils.getName(part.getContentDisposition().getParameter("filename")); } }