All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.daisy.pipeline.webservice.restlet.impl.JobsResource Maven / Gradle / Ivy

package org.daisy.pipeline.webservice.restlet.impl;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.StringReader;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.zip.ZipFile;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.sax.SAXSource;

import org.daisy.common.priority.Priority;
import org.daisy.pipeline.job.Job;
import org.daisy.pipeline.job.JobIdFactory;
import org.daisy.pipeline.job.JobManager;
import org.daisy.pipeline.job.JobResources;
import org.daisy.pipeline.job.ZippedJobResources;
import org.daisy.pipeline.script.BoundScript;
import org.daisy.pipeline.script.Script;
import org.daisy.pipeline.script.ScriptOption;
import org.daisy.pipeline.script.ScriptPort;
import org.daisy.pipeline.script.ScriptService;
import org.daisy.pipeline.webservice.Callback.CallbackType;
import org.daisy.pipeline.webservice.impl.PosterCallback;
import org.daisy.pipeline.webservice.CallbackHandler;
import org.daisy.pipeline.webservice.restlet.AuthenticatedResource;
import org.daisy.pipeline.webservice.restlet.MultipartRequestData;
import org.daisy.pipeline.webservice.xml.JobXmlWriter;
import org.daisy.pipeline.webservice.xml.JobsXmlWriter;
import org.daisy.pipeline.webservice.xml.ValidationStatus;
import org.daisy.pipeline.webservice.xml.Validator;
import org.daisy.pipeline.webservice.xml.XmlUtils;

import org.restlet.Request;
import org.restlet.data.MediaType;
import org.restlet.data.Status;
import org.restlet.ext.xml.DomRepresentation;
import org.restlet.representation.Representation;
import org.restlet.resource.Get;
import org.restlet.resource.Post;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import com.google.common.base.Optional;

// TODO: Auto-generated Javadoc
/**
 * The Class JobsResource.
 */
public class JobsResource extends AuthenticatedResource {

        private static final String JOB_DATA_FIELD = "job-data";
        private static final String JOB_REQUEST_FIELD = "job-request";

        /** The logger. */
        private static Logger logger = LoggerFactory.getLogger(JobsResource.class.getName());

        /**
         * Gets the resource.
         *
         * @return the resource
         */
        @Get("xml")
        public Representation getResource() {
                logRequest();
                maybeEnableCORS();
                if (!isAuthenticated()) {
                        setStatus(Status.CLIENT_ERROR_UNAUTHORIZED);
                        return null;
                }
                JobManager jobMan = getJobManager(this.getClient());
                JobsXmlWriter writer = new JobsXmlWriter(
                        jobMan.getJobs(),
                        getJobManager(getStorage().getClientStorage().defaultClient()).getExecutionQueue(),
                        getRequest().getRootRef().toString());
                if (getConfiguration().isLocalFS()){
                	writer.withLocalPaths();
                }
                Document doc = writer.getXmlDocument();
                DomRepresentation dom = new DomRepresentation(MediaType.APPLICATION_XML, doc);
                setStatus(Status.SUCCESS_OK);
                logResponse(dom);
                return dom;
        }


        /**
         * Creates the resource.
         *
         * @param representation the representation
         * @return the representation
         * @throws Exception the exception
         */
        @Post
        public Representation createResource(Representation representation) {
                logRequest();
                maybeEnableCORS();
                if (!isAuthenticated()) {
                        setStatus(Status.CLIENT_ERROR_UNAUTHORIZED);
                        return null;
                }

                if (representation == null) {
                        // POST request with no entity.
                        setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
                        return this.getErrorRepresentation("POST request with no entity");
                }


                Document doc = null;
                ZipFile zipfile = null;

                if (MediaType.MULTIPART_FORM_DATA.equals(representation.getMediaType(), true)) {
                        Request request = getRequest();
                        // sort through the multipart request
                        MultipartRequestData data = null;
                        try {
                                data = MultipartRequestData.processMultipart(request,
                                                                             JOB_DATA_FIELD,
                                                                             JOB_REQUEST_FIELD,
                                                                             new File(getConfiguration().getTmpDir()));
                        } catch (Exception e) {
                                return badRequest(e);
                        }
                        if (data == null) {
                                setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
                                return this.getErrorRepresentation("Multipart data is empty");
                        }
                        doc = data.getXml();
                        zipfile = data.getZipFile();
                }
                // else it's not multipart; all data should be inline.
                else {
                        String s;
                        try {
                                s = representation.getText();
                                DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
                                factory.setNamespaceAware(true);
                                DocumentBuilder builder = factory.newDocumentBuilder();
                                InputSource is = new InputSource(new StringReader(s));
                                doc = builder.parse(is);
                        } catch (IOException e) {
                                return badRequest(e);
                        } catch (ParserConfigurationException e) {
                                return badRequest(e);
                        } catch (SAXException e) {
                                return badRequest(e);
                        }
                }
                if (logger.isDebugEnabled())
                        logger.debug(XmlUtils.nodeToString(doc));

                ValidationStatus status = Validator.validateJobRequest(doc, getScriptRegistry());

                if (!status.isValid()) {
                        setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
                        return this.getErrorRepresentation(status.getMessage());
                }

                Optional job;
                try {
                        job = createJob(doc, zipfile );
                } catch (LocalInputException e) {
                        return badRequest(e);
                } catch (FileNotFoundException e) {
                        return badRequest(e);
                } catch (IllegalArgumentException e) {
                        return badRequest(e);
                }

                if (!job.isPresent()) {
                        setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
                        return this.getErrorRepresentation("Could not create job ");
                }
                
                //store the config
                getStorage().getJobConfigurationStorage()
                    .add(job.get().getId(), XmlUtils.nodeToString(doc));

                // Note that we're not using JobXmlWriter's messagesThreshold argument. It is no use because
                // filtering of messages on log level already happens in MessageBus and JobProgressAppender.
                JobXmlWriter writer = new JobXmlWriter(job.get(), getRequest().getRootRef().toString());
                if (job.get().getStatus() == Job.Status.IDLE) {
                        writer.withPriority(getJobPriority(job.get()));
                }
                Document jobXml = writer.withScriptDetails().getXmlDocument();

                // initiate callbacks
                registerCallbacks(job.get(), doc);

                DomRepresentation dom = new DomRepresentation(MediaType.APPLICATION_XML, jobXml);
                setStatus(Status.SUCCESS_CREATED);
                logResponse(dom);
                return dom;

        }

        private Priority getJobPriority(Job job) {
                return getJobManager(getStorage().getClientStorage().defaultClient()).getExecutionQueue().getJobPriority(job.getId());
        }

        private Representation badRequest(Exception e) {
                logger.error("bad request:", e);
                setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
                return this.getErrorRepresentation(e.getMessage());
        }

        /**
         * Creates the job.
         *
         * @param doc the doc
         * @param zip the zip
         * @return the job
         * @throws LocalInputException
         */
        private Optional createJob(Document doc, ZipFile zip)
                        throws LocalInputException, FileNotFoundException {

                Element scriptElm = (Element) doc.getElementsByTagNameNS(Validator.NS_DAISY, "script").item(0);
                Priority priority=Priority.MEDIUM;
                String niceName="";
                //get nice name
                NodeList elems=doc.getElementsByTagNameNS(Validator.NS_DAISY,"nicename"); 
                if(elems.getLength()!=0)
                        niceName=elems.item(0).getTextContent();
                logger.debug(String.format("Job's nice name: %s",niceName));
                String batchId="";
                //get the batch name 
                elems=doc.getElementsByTagName("batchId"); 
                if(elems.getLength()!=0)
                        batchId=elems.item(0).getTextContent();
                logger.debug(String.format("Job's batch id: %s",batchId));

                //get priority
                elems=doc.getElementsByTagNameNS(Validator.NS_DAISY,"priority"); 
                if(elems.getLength()!=0){
                        String prioString=elems.item(0).getTextContent();
                        priority=Priority.valueOf(prioString.toUpperCase());
                }
                
                logger.debug(String.format("Jobs priority: %s",priority));


                // TODO eventually we might want to have an href-script ID lookup table
                // but for now, we'll get the script ID from the last part of the URL
                String scriptId = scriptElm.getAttribute("href");
                if (scriptId.endsWith("/")) {
                        scriptId = scriptId.substring(0, scriptId.length() - 1);
                }
                int idx = scriptId.lastIndexOf('/');
                scriptId = scriptId.substring(idx+1);

                // get the script from the ID
                ScriptService scriptService = getScriptRegistry().getScript(scriptId);
                if (scriptService == null) {
                        logger.error("Script not found");
                        return Optional.absent();
                }
                Script script = scriptService.load();
                JobResources resourceCollection = zip != null ? new ZippedJobResources(zip) : null;
                BoundScript.Builder bound = new BoundScript.Builder(script, resourceCollection);

                addInputsToJob(doc.getElementsByTagNameNS(Validator.NS_DAISY,"input"), script, bound, zip != null);
                addOptionsToJob(doc.getElementsByTagNameNS(Validator.NS_DAISY,"option"), script, bound, zip != null);
                if (doc.getElementsByTagNameNS(Validator.NS_DAISY,"output").getLength() > 0) {
                        // show deprecation warning in server logs
                        logger.warn("Deprecated  element used. Job results should be retrieved through the /jobs/ID/result API.");
                        // show deprecation warning in response header
                        addWarningHeader(
                                199,
                                "\"Deprecated API\": "
                                + " is deprecated, job results should be retrieved through the /jobs/ID/result API");
                }

                JobManager jobMan = getJobManager(this.getClient());
                return jobMan.newJob(bound.build())
                        .withNiceName(niceName).withBatchId(JobIdFactory.newBatchIdFromString(batchId))
                        .withPriority(priority).build();
        }

        /**
         * Initiate callbacks declared in the job request XML.
         *
         * @param doc The job request XML.
         */
        private void registerCallbacks(Job job, Document doc) {
                NodeList callbacks = doc.getElementsByTagNameNS(Validator.NS_DAISY,"callback");
                for (int i = 0; i 0) {
                                freq = Integer.parseInt(frequency);
                        }
                        try {
                                URI href = new URI(elm.getAttribute("href"));
                                CallbackHandler handler = getCallbackHandler();
                                if (handler == null) {
                                        throw new RuntimeException("No push notifier");
                                }
                                handler.addCallback(new PosterCallback(job, type, freq, href, getClient(),
                                                                       getRequest().getRootRef().toString()));
                        } catch (URISyntaxException e) {
                                logger.warn("Cannot create callback: " + e.getMessage());
                        }
                }
        }

        /**
         * Adds the inputs to job.
         *
         * @param nodes the nodes
         * @param inputPorts the input ports
         * @param builder the builder
         * @throws LocalInputException
         */
        private void addInputsToJob(NodeList nodes, Script script, BoundScript.Builder builder, boolean zippedContext)
                        throws LocalInputException, FileNotFoundException {

                for (ScriptPort input : script.getInputPorts()) {
                        String inputName = input.getName();
                        for (int i = 0; i < nodes.getLength(); i++) {
                                Element inputElm = (Element) nodes.item(i);
                                String name = inputElm.getAttribute("name");
                                if (name.equals(inputName)) {
                                        NodeList fileNodes = inputElm.getElementsByTagNameNS(Validator.NS_DAISY,"item");
                                        NodeList docwrapperNodes = inputElm.getElementsByTagNameNS(Validator.NS_DAISY,"docwrapper");

                                        if (fileNodes.getLength() > 0) {
                                                for (int j = 0; j < fileNodes.getLength(); j++) {
                                                        URI src = URI.create(((Element)fileNodes.item(j)).getAttribute("value"));
                                                        checkInput(src, zippedContext);
                                                        builder.withInput(name, src);
                                                }
                                        }
                                        else {
                                                for (int j = 0; j< docwrapperNodes.getLength(); j++){
                                                        Element docwrapper = (Element)docwrapperNodes.item(j);
                                                        Node content = null;
                                                        // find the first element child
                                                        for (int q = 0; q < docwrapper.getChildNodes().getLength(); q++) {
                                                                if (docwrapper.getChildNodes().item(q).getNodeType() == Node.ELEMENT_NODE) {
                                                                        content = docwrapper.getChildNodes().item(q);
                                                                        break;
                                                                }
                                                        }
                                                        SAXSource source = new SAXSource();
                                                        String xml = XmlUtils.nodeToString(content);
                                                        InputSource is = new InputSource(new StringReader(xml));
                                                        source.setInputSource(is);
                                                        builder.withInput(name, source);
                                                }
                                        }
                                }
                        }
                }

        }
        
        /**
         * Adds the options to job.
         * @throws LocalInputException
         */
        private void addOptionsToJob(NodeList nodes, Script script, BoundScript.Builder builder, boolean zippedContext)
                        throws LocalInputException, FileNotFoundException {

                Iterable options = script.getOptions();
                for (ScriptOption option : options) {
                        for (int i = 0; i< nodes.getLength(); i++) {
                                Element optionElm = (Element) nodes.item(i);
                                String name = optionElm.getAttribute("name");
                                if (name.equals(option.getName())) {
                                        boolean isInput = "anyDirURI".equals(option.getType().getId())
                                                       || "anyFileURI".equals(option.getType().getId());
                                        //eventhough the option is a sequence it may happen that 
                                        //there are no item elements, just one value
                                        NodeList items = optionElm.getElementsByTagNameNS(Validator.NS_DAISY,"item");
                                        if (items.getLength() > 0) {
                                                // accept  children even if it is not a sequence option
                                                // but at most one (this is verified in BoundScript.Builder)
                                                for (int j = 0; j




© 2015 - 2025 Weber Informatics LLC | Privacy Policy