com.enioka.jqm.tools.JobManagerHandler.java Source code

Java tutorial

Introduction

Here is the source code for com.enioka.jqm.tools.JobManagerHandler.java

Source

/**
 * Copyright  2013 enioka. All rights reserved
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.enioka.jqm.tools;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Calendar;
import java.util.Map;
import java.util.Properties;
import java.util.UUID;

import javax.naming.NamingException;
import javax.naming.spi.NamingManager;
import javax.persistence.EntityManager;
import javax.persistence.LockModeType;
import javax.sql.DataSource;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.log4j.Logger;

import com.enioka.jqm.api.JobRequest;
import com.enioka.jqm.api.JqmClient;
import com.enioka.jqm.api.JqmClientFactory;
import com.enioka.jqm.api.Query;
import com.enioka.jqm.jpamodel.Deliverable;
import com.enioka.jqm.jpamodel.History;
import com.enioka.jqm.jpamodel.JobDef;
import com.enioka.jqm.jpamodel.JobInstance;
import com.enioka.jqm.jpamodel.Node;
import com.enioka.jqm.jpamodel.State;

/**
 * This is the implementation behind the proxy described in the <code>JobManager</code> interface inside the jqm-api artifact.
 */
class JobManagerHandler implements InvocationHandler {
    private static Logger jqmlogger = Logger.getLogger(JobManagerHandler.class);

    private JobInstance ji;
    private JobDef jd = null;
    private Properties p = null;
    private Map<String, String> params = null;
    private String defaultCon = null, application = null, sessionId = null;
    private Node node = null;
    private Calendar lastPeek = null;

    JobManagerHandler(JobInstance ji, Map<String, String> prms) {
        p = new Properties();
        p.put("emf", Helpers.getEmf());

        EntityManager em = Helpers.getNewEm();
        this.ji = em.find(JobInstance.class, ji.getId());
        params = prms;

        defaultCon = em.createQuery("SELECT gp.value FROM GlobalParameter gp WHERE gp.key = 'defaultConnection'",
                String.class).getSingleResult();

        this.jd = this.ji.getJd();
        this.application = this.jd.getApplication();
        this.sessionId = this.ji.getSessionID();
        this.node = this.ji.getNode();
        this.node.getDlRepo();
        em.close();
    }

    private JqmClient getJqmClient() {
        return JqmClientFactory.getClient("uncached", p, false);
    }

    @SuppressWarnings("unchecked")
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        ClassLoader initial = null;
        String methodName = method.getName();
        Class<?>[] classes = method.getParameterTypes();
        jqmlogger.trace("An engine API method was called: " + methodName + " with nb arguments: " + classes.length);
        try {
            initial = Thread.currentThread().getContextClassLoader();
            Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
            shouldKill();

            if (classes.length == 0) {
                if ("jobApplicationId".equals(methodName)) {
                    return jd.getId();
                } else if ("parentID".equals(methodName)) {
                    return ji.getParentId();
                } else if ("jobInstanceID".equals(methodName)) {
                    return ji.getId();
                } else if ("canBeRestarted".equals(methodName)) {
                    return jd.isCanBeRestarted();
                } else if ("applicationName".equals(methodName)) {
                    return jd.getApplicationName();
                } else if ("sessionID".equals(methodName)) {
                    return ji.getSessionID();
                } else if ("application".equals(methodName)) {
                    return application;
                } else if ("module".equals(methodName)) {
                    return jd.getModule();
                } else if ("keyword1".equals(methodName)) {
                    return ji.getKeyword1();
                } else if ("keyword2".equals(methodName)) {
                    return ji.getKeyword2();
                } else if ("keyword3".equals(methodName)) {
                    return ji.getKeyword3();
                } else if ("definitionKeyword1".equals(methodName)) {
                    return jd.getKeyword1();
                } else if ("definitionKeyword2".equals(methodName)) {
                    return jd.getKeyword2();
                } else if ("definitionKeyword3".equals(methodName)) {
                    return jd.getKeyword3();
                } else if ("userName".equals(methodName)) {
                    return ji.getUserName();
                } else if ("parameters".equals(methodName)) {
                    return params;
                } else if ("defaultConnect".equals(methodName)) {
                    return this.defaultCon;
                } else if ("getDefaultConnection".equals(methodName)) {
                    return this.getDefaultConnection();
                } else if ("getWorkDir".equals(methodName)) {
                    return getWorkDir();
                } else if ("yield".equals(methodName)) {
                    return null;
                } else if ("waitChildren".equals(methodName)) {
                    waitChildren();
                    return null;
                }
            } else if ("sendMsg".equals(methodName) && classes.length == 1 && classes[0] == String.class) {
                sendMsg((String) args[0]);
                return null;
            } else if ("sendProgress".equals(methodName) && classes.length == 1 && classes[0] == Integer.class) {
                sendProgress((Integer) args[0]);
                return null;
            } else if ("enqueue".equals(methodName) && classes.length == 10 && classes[0] == String.class) {
                return enqueue((String) args[0], (String) args[1], (String) args[2], (String) args[3],
                        (String) args[4], (String) args[5], (String) args[6], (String) args[7], (String) args[8],
                        (Map<String, String>) args[9]);
            } else if ("enqueueSync".equals(methodName) && classes.length == 10 && classes[0] == String.class) {
                return enqueueSync((String) args[0], (String) args[1], (String) args[2], (String) args[3],
                        (String) args[4], (String) args[5], (String) args[6], (String) args[7], (String) args[8],
                        (Map<String, String>) args[9]);
            } else if ("addDeliverable".equals(methodName) && classes.length == 2 && classes[0] == String.class
                    && classes[1] == String.class) {
                return addDeliverable((String) args[0], (String) args[1]);
            } else if ("waitChild".equals(methodName) && classes.length == 1 && (args[0] instanceof Integer)) {
                waitChild((Integer) args[0]);
                return null;
            } else if ("hasEnded".equals(methodName) && classes.length == 1 && (args[0] instanceof Integer)) {
                return hasEnded((Integer) args[0]);
            } else if ("hasSucceeded".equals(methodName) && classes.length == 1 && (args[0] instanceof Integer)) {
                return hasSucceeded((Integer) args[0]);
            } else if ("hasFailed".equals(methodName) && classes.length == 1 && (args[0] instanceof Integer)) {
                return hasFailed((Integer) args[0]);
            }

            throw new NoSuchMethodException(methodName);
        } finally {
            Thread.currentThread().setContextClassLoader(initial);
        }
    }

    private void shouldKill() {
        // Throttle: only peek once every 1 second.
        if (lastPeek != null && Calendar.getInstance().getTimeInMillis() - lastPeek.getTimeInMillis() < 1000L) {
            return;
        }

        EntityManager em = Helpers.getNewEm();
        try {
            this.ji = em.find(JobInstance.class, this.ji.getId());
            jqmlogger.trace("Analysis: should JI " + ji.getId() + " get killed? Status is " + ji.getState());
            if (ji.getState().equals(State.KILLED)) {
                jqmlogger.info("Job will be killed at the request of a user");
                Thread.currentThread().interrupt();
                throw new JqmKillException("This job" + "(ID: " + ji.getId() + ")" + " has been killed by a user");
            }
        } finally {
            em.close();
            lastPeek = Calendar.getInstance();
        }
    }

    /**
     * Create a {@link com.enioka.jqm.jpamodel.Message} with the given message. The {@link com.enioka.jqm.jpamodel.History} to link to is
     * deduced from the context.
     * 
     * @param msg
     * @throws JqmKillException
     */
    private void sendMsg(String msg) {
        EntityManager em = Helpers.getNewEm();
        try {
            em.getTransaction().begin();
            Helpers.createMessage(msg, ji, em);
            em.getTransaction().commit();
        } finally {
            em.close();
        }
    }

    /**
     * Update the {@link com.enioka.jqm.jpamodel.History} with the given progress data.
     * 
     * @param msg
     * @throws JqmKillException
     */
    private void sendProgress(Integer msg) {
        EntityManager em = Helpers.getNewEm();
        try {
            em.getTransaction().begin();
            this.ji = em.find(JobInstance.class, this.ji.getId(), LockModeType.PESSIMISTIC_WRITE);
            ji.setProgress(msg);
            em.getTransaction().commit();
        } finally {
            em.close();
        }
    }

    private Integer enqueue(String applicationName, String user, String mail, String sessionId, String application,
            String module, String keyword1, String keyword2, String keyword3, Map<String, String> parameters) {
        JobRequest jr = new JobRequest(applicationName, user, mail);
        jr.setApplicationName(applicationName);
        jr.setUser(user == null ? ji.getUserName() : user);
        jr.setEmail(mail);
        jr.setSessionID(sessionId == null ? this.sessionId : sessionId);
        jr.setApplication(application == null ? jd.getApplication() : application);
        jr.setModule(module == null ? jd.getModule() : module);
        jr.setKeyword1(keyword1);
        jr.setKeyword2(keyword2);
        jr.setKeyword3(keyword3);
        jr.setParentID(this.ji.getId());
        if (parameters != null) {
            jr.setParameters(parameters);
        }

        return getJqmClient().enqueue(jr);
    }

    private int enqueueSync(String applicationName, String user, String mail, String sessionId, String application,
            String module, String keyword1, String keyword2, String keyword3, Map<String, String> parameters) {
        int i = enqueue(applicationName, user, mail, sessionId, application, module, keyword1, keyword2, keyword3,
                parameters);
        waitChild(i);
        return i;
    }

    private void waitChild(int id) {
        JqmClient c = getJqmClient();
        Query q = Query.create().setQueryHistoryInstances(false).setQueryLiveInstances(true).setJobInstanceId(id);

        while (!c.getJobs(q).isEmpty()) {
            try {
                Thread.sleep(1000);
                shouldKill();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                break;
            }
        }
    }

    private void waitChildren() {
        JqmClient c = getJqmClient();
        Query q = Query.create().setQueryHistoryInstances(false).setQueryLiveInstances(true)
                .setParentId(ji.getId());

        while (!c.getJobs(q).isEmpty()) {
            try {
                Thread.sleep(1000);
                shouldKill();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                break;
            }
        }
    }

    private Integer addDeliverable(String path, String fileLabel) throws IOException {
        Deliverable d = null;
        EntityManager em = Helpers.getNewEm();
        try {
            this.ji = em.find(JobInstance.class, ji.getId());

            String outputRoot = this.ji.getNode().getDlRepo();
            String ext = FilenameUtils.getExtension(path);
            String relDestPath = "" + ji.getJd().getApplicationName() + "/" + ji.getId() + "/" + UUID.randomUUID()
                    + "." + ext;
            String absDestPath = FilenameUtils.concat(outputRoot, relDestPath);
            String fileName = FilenameUtils.getName(path);
            FileUtils.moveFile(new File(path), new File(absDestPath));
            jqmlogger.debug("A deliverable is added. Stored as " + absDestPath + ". Initial name: " + fileName);

            em.getTransaction().begin();
            d = Helpers.createDeliverable(relDestPath, fileName, fileLabel, this.ji.getId(), em);
            em.getTransaction().commit();
        } finally {
            em.close();
        }
        return d.getId();
    }

    private File getWorkDir() {
        File f = new File(FilenameUtils.concat(node.getTmpDirectory(), "" + this.ji.getId()));
        if (!f.isDirectory()) {
            try {
                FileUtils.forceMkdir(f);
            } catch (Exception e) {
                throw new JqmRuntimeException("Could not create work directory", e);
            }
        }
        return f;
    }

    private DataSource getDefaultConnection() throws NamingException {
        Object dso = NamingManager.getInitialContext(null).lookup(this.defaultCon);
        DataSource q = (DataSource) dso;

        return q;
    }

    private JobInstance getRunningJI(int jobId) {
        EntityManager em = Helpers.getNewEm();
        JobInstance jj = null;
        try {
            jj = em.find(JobInstance.class, jobId);
        } catch (Exception e) {
            throw new JqmRuntimeException("Could not query job instance", e);
        } finally {
            em.close();
        }
        return jj;
    }

    private History getEndedJI(int jobId) {
        EntityManager em = Helpers.getNewEm();
        History h = null;
        try {
            h = em.find(History.class, jobId);
        } catch (Exception e) {
            throw new JqmRuntimeException("Could not query history", e);
        } finally {
            em.close();
        }
        return h;
    }

    private boolean hasEnded(int jobId) {
        return getRunningJI(jobId) == null;
    }

    private Boolean hasSucceeded(int requestId) {
        History h = getEndedJI(requestId);
        if (h == null) {
            return null;
        }
        return h.getState().equals(State.ENDED);
    }

    private Boolean hasFailed(int requestId) {
        Boolean b = hasSucceeded(requestId);
        if (b == null) {
            return b;
        } else {
            return !b;
        }
    }
}