Package com.enioka.jqm.api

Source Code of com.enioka.jqm.api.HibernateClient

/**
* Copyright © 2013 enioka. All rights reserved
* Authors: Marc-Antoine GOUILLART (marc-antoine.gouillart@enioka.com)
*          Pierre COPPEE (pierre.coppee@enioka.com)
*
* 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.api;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.UUID;

import javax.naming.NameNotFoundException;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.LockModeType;
import javax.persistence.NoResultException;
import javax.persistence.Persistence;
import javax.persistence.TypedQuery;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.enioka.jqm.api.Query.SortSpec;
import com.enioka.jqm.jpamodel.Deliverable;
import com.enioka.jqm.jpamodel.History;
import com.enioka.jqm.jpamodel.JobDef;
import com.enioka.jqm.jpamodel.JobDefParameter;
import com.enioka.jqm.jpamodel.JobHistoryParameter;
import com.enioka.jqm.jpamodel.JobInstance;
import com.enioka.jqm.jpamodel.JobParameter;
import com.enioka.jqm.jpamodel.Message;
import com.enioka.jqm.jpamodel.MessageJi;
import com.enioka.jqm.jpamodel.Queue;
import com.enioka.jqm.jpamodel.State;

/**
* Main JQM client API entry point.
*/
final class HibernateClient implements JqmClient
{
    private static Logger jqmlogger = LoggerFactory.getLogger(HibernateClient.class);
    private static final String PERSISTENCE_UNIT = "jobqueue-api-pu";
    private EntityManagerFactory emf = null;
    Properties p;

    // /////////////////////////////////////////////////////////////////////
    // Construction/Connection
    // /////////////////////////////////////////////////////////////////////

    // No public constructor. MUST use factory.
    HibernateClient()
    {
        p = new Properties();
    }

    HibernateClient(Properties p)
    {
        this.p = p;
        if (p.containsKey("emf"))
        {
            jqmlogger.trace("emf present in properties");
            emf = (EntityManagerFactory) p.get("emf");
        }
    }

    private EntityManagerFactory createFactory()
    {
        jqmlogger.debug("Creating connection pool to database");

        InputStream fis = null;
        try
        {
            fis = this.getClass().getClassLoader().getResourceAsStream("META-INF/jqm.properties");
            if (fis == null)
            {
                jqmlogger.trace("No jqm.properties file found.");
            }
            else
            {
                p.load(fis);
                jqmlogger.trace("A jqm.properties file was found");
            }

            fis = this.getClass().getClassLoader().getResourceAsStream("db.properties");
            if (fis == null)
            {
                jqmlogger.trace("No db.properties file found.");
            }
            else
            {
                p.load(fis);
                jqmlogger.trace("A db.properties file was found");
            }
        }
        catch (IOException e)
        {
            // We allow no configuration files, but not an unreadable configuration file.
            throw new JqmClientException("META-INF/jqm.properties file is invalid", e);
        }
        finally
        {
            IOUtils.closeQuietly(fis);
        }

        EntityManagerFactory newEmf = null;
        if (p.containsKey("javax.persistence.nonJtaDataSource"))
        {
            // This is a hack. Some containers will use root context as default for JNDI (WebSphere, Glassfish...), other will use
            // java:/comp/env/ (Tomcat...). So if we actually know the required alias, we try both, and the user only has to provide a
            // root JNDI alias that will work in both cases.
            try
            {
                newEmf = Persistence.createEntityManagerFactory(PERSISTENCE_UNIT, p);
                // Do a stupid query to force EMF initialization
                EntityManager em = newEmf.createEntityManager();
                em.createQuery("SELECT n from Node n WHERE 1=0").getResultList().size();
                em.close();
            }
            catch (RuntimeException e)
            {
                if (e.getCause() != null && e.getCause().getCause() != null && e.getCause().getCause() instanceof NameNotFoundException)
                {
                    jqmlogger.debug("JNDI alias " + p.getProperty("javax.persistence.nonJtaDataSource")
                            + " was not found. Trying with java:/comp/env/ prefix");
                    p.setProperty("javax.persistence.nonJtaDataSource",
                            "java:/comp/env/" + p.getProperty("javax.persistence.nonJtaDataSource"));
                    newEmf = Persistence.createEntityManagerFactory(PERSISTENCE_UNIT, p);
                    // Do a stupid query to force EMF initialization
                    EntityManager em = newEmf.createEntityManager();
                    em.createQuery("SELECT n from Node n WHERE 1=3").getResultList().size();
                    em.close();
                }
                else
                {
                    throw e;
                }
            }
        }
        else
        {
            newEmf = Persistence.createEntityManagerFactory(PERSISTENCE_UNIT, p);
        }
        return newEmf;
    }

    EntityManager getEm()
    {
        if (emf == null)
        {
            emf = createFactory();
        }

        try
        {
            return emf.createEntityManager();
        }
        catch (Exception e)
        {
            jqmlogger.error("Could not create EM.", e);
            throw new JqmClientException("Could not create EntityManager", e);
        }
    }

    private void closeQuietly(EntityManager em)
    {
        try
        {
            if (em != null)
            {
                if (em.getTransaction().isActive())
                {
                    em.getTransaction().rollback();
                }
                em.close();
            }
        }
        catch (Exception e)
        {
            // fail silently
        }
    }

    @Override
    public void dispose()
    {
        try
        {
            this.emf.close();
        }
        catch (Exception e)
        {
            // Nothing - dispose function must fail silently.
        }
        this.emf = null;
        p = null;
    }

    // /////////////////////////////////////////////////////////////////////
    // Enqueue functions
    // /////////////////////////////////////////////////////////////////////

    @Override
    public int enqueue(JobRequest jd)
    {
        jqmlogger.trace("BEGINING ENQUEUE");
        EntityManager em = getEm();
        JobDef job = null;
        try
        {
            job = em.createQuery(
                    "SELECT j FROM JobDef j LEFT JOIN FETCH j.queue LEFT JOIN FETCH j.parameters WHERE j.applicationName = :name",
                    JobDef.class).setParameter("name", jd.getApplicationName()).getSingleResult();
        }
        catch (NoResultException ex)
        {
            jqmlogger.error("Job definition named " + jd.getApplicationName() + " does not exist");
            closeQuietly(em);
            throw new JqmInvalidRequestException("no job definition named " + jd.getApplicationName());
        }

        jqmlogger.trace("Job to enqueue is from JobDef " + job.getId());
        Integer hl = null;
        List<JobParameter> jps = overrideParameter(job, jd, em);

        // Begin transaction (that will hold a lock in case of Highlander)
        try
        {
            em.getTransaction().begin();

            if (job.isHighlander())
            {
                hl = highlanderMode(job, em);
            }

            if (hl != null)
            {
                jqmlogger.trace("JI won't actually be enqueued because a job in highlander mode is currently submitted: " + hl);
                closeQuietly(em);
                return hl;
            }
            jqmlogger.trace("Not in highlander mode or no currently enqueued instance");
        }
        catch (Exception e)
        {
            closeQuietly(em);
            throw new JqmClientException("Could not do highlander analysis", e);
        }

        try
        {
            JobInstance ji = new JobInstance();
            ji.setJd(job);
            ji.setSessionID(jd.getSessionID());
            ji.setUserName(jd.getUser());
            ji.setState(State.SUBMITTED);
            ji.setQueue(job.getQueue());
            ji.setNode(null);
            // Can be null (if no email is asked for)
            ji.setEmail(jd.getEmail());
            ji.setCreationDate(Calendar.getInstance());
            if (jd.getParentID() != null)
            {
                ji.setParentId(jd.getParentID());
            }
            ji.setProgress(0);
            ji.setParameters(new ArrayList<JobParameter>());
            em.persist(ji);
            ji.setInternalPosition(ji.getId());

            for (JobParameter jp : jps)
            {
                jqmlogger.trace("Parameter: " + jp.getKey() + " - " + jp.getValue());
                em.persist(ji.addParameter(jp.getKey(), jp.getValue()));
            }
            jqmlogger.trace("JI just created: " + ji.getId());

            em.getTransaction().commit();
            return ji.getId();
        }
        catch (Exception e)
        {
            throw new JqmClientException("Could not create new JobInstance", e);
        }
        finally
        {
            closeQuietly(em);
        }
    }

    @Override
    public int enqueue(String applicationName, String userName)
    {
        return enqueue(new JobRequest(applicationName, userName));
    }

    @Override
    public int enqueueFromHistory(int jobIdToCopy)
    {
        EntityManager em = null;
        History h = null;
        try
        {
            em = getEm();
            h = em.find(History.class, jobIdToCopy);
            return enqueue(getJobRequest(h));
        }
        catch (NoResultException e)
        {
            throw new JqmInvalidRequestException("No job for this ID in the history");
        }
        finally
        {
            closeQuietly(em);
        }
    }

    // Helper
    private List<JobParameter> overrideParameter(JobDef jdef, JobRequest jdefinition, EntityManager em)
    {
        List<JobParameter> res = new ArrayList<JobParameter>();
        Map<String, String> resm = new HashMap<String, String>();

        // 1st: default parameters
        for (JobDefParameter jp : jdef.getParameters())
        {
            resm.put(jp.getKey(), jp.getValue());
        }

        // 2nd: overloads inside the user enqueue form.
        resm.putAll(jdefinition.getParameters());

        // 3rd: create the JobParameter objects
        for (Entry<String, String> e : resm.entrySet())
        {
            res.add(createJobParameter(e.getKey(), e.getValue(), em));
        }

        // Done
        return res;
    }

    // Helper. Must be called within an active JPA transaction
    private Integer highlanderMode(JobDef jd, EntityManager em)
    {
        // Synchronization is done through locking the JobDef
        em.lock(jd, LockModeType.PESSIMISTIC_WRITE);

        // Do the analysis
        Integer res = null;
        jqmlogger.trace("Highlander mode analysis is begining");
        ArrayList<JobInstance> jobs = (ArrayList<JobInstance>) em
                .createQuery("SELECT j FROM JobInstance j WHERE j.jd = :j AND j.state = :s", JobInstance.class).setParameter("j", jd)
                .setParameter("s", State.SUBMITTED).getResultList();

        for (JobInstance j : jobs)
        {
            jqmlogger.trace("JI seen by highlander: " + j.getId() + j.getState());
            if (j.getState().equals(State.SUBMITTED))
            {
                // HIGHLANDER: only one enqueued job can survive!
                // current request must be cancelled and enqueue must return the id of the existing submitted JI
                res = j.getId();
                break;
            }
        }
        jqmlogger.trace("Highlander mode will return: " + res);
        return res;
    }

    // Helper
    private JobRequest getJobRequest(History h)
    {
        JobRequest jd = new JobRequest();
        jd.setApplication(h.getApplication());
        jd.setApplicationName(h.getJd().getApplicationName());
        jd.setEmail(h.getEmail());
        jd.setKeyword1(h.getKeyword1());
        jd.setKeyword2(h.getKeyword2());
        jd.setKeyword3(h.getKeyword3());
        jd.setModule(h.getModule());
        jd.setParentID(h.getParentJobId());
        jd.setSessionID(h.getSessionId());
        jd.setUser(h.getUserName());

        for (JobHistoryParameter p : h.getParameters())
        {
            jd.addParameter(p.getKey(), p.getValue());
        }

        return jd;
    }

    // /////////////////////////////////////////////////////////////////////
    // Job destruction
    // /////////////////////////////////////////////////////////////////////

    @Override
    public void cancelJob(int idJob)
    {
        EntityManager em = null;
        JobInstance ji = null;
        try
        {
            em = getEm();
            em.getTransaction().begin();
            ji = em.find(JobInstance.class, idJob, LockModeType.PESSIMISTIC_WRITE);
            if (ji.getState().equals(State.SUBMITTED))
            {
                ji.setState(State.CANCELLED);
            }
            else
            {
                throw new NoResultException();
            }
            em.getTransaction().commit();
        }
        catch (NoResultException e)
        {
            closeQuietly(em);
            throw new JqmClientException("the job is already running, has already finished or never existed to begin with");
        }

        try
        {
            em.getTransaction().begin();
            History h = new History();
            h.setId(ji.getId());
            h.setJd(ji.getJd());
            h.setSessionId(ji.getSessionID());
            h.setQueue(ji.getQueue());
            h.setMessages(new ArrayList<Message>());
            h.setEnqueueDate(ji.getCreationDate());
            h.setUserName(ji.getUserName());
            h.setEmail(ji.getEmail());
            h.setParentJobId(ji.getParentId());
            h.setApplication(ji.getApplication());
            h.setModule(ji.getModule());
            h.setKeyword1(ji.getKeyword1());
            h.setKeyword2(ji.getKeyword2());
            h.setKeyword3(ji.getKeyword3());
            h.setProgress(ji.getProgress());
            h.setParameters(new ArrayList<JobHistoryParameter>());
            h.setStatus(State.CANCELLED);
            h.setNode(ji.getNode());
            em.persist(h);

            em.createQuery("DELETE FROM MessageJi WHERE jobInstance = :i").setParameter("i", ji).executeUpdate();
            em.createQuery("DELETE FROM JobParameter WHERE jobInstance = :i").setParameter("i", ji).executeUpdate();
            em.createQuery("DELETE FROM JobInstance WHERE id = :i").setParameter("i", ji.getId()).executeUpdate();
            em.getTransaction().commit();
        }
        catch (Exception e)
        {
            throw new JqmClientException("could not cancel job instance", e);
        }
        finally
        {
            closeQuietly(em);
        }
    }

    @Override
    public void deleteJob(int idJob)
    {
        jqmlogger.trace("Job status number " + idJob + " will be deleted");
        EntityManager em = null;

        try
        {
            em = getEm();

            // Two transactions against deadlock.
            JobInstance job = em.find(JobInstance.class, idJob);
            em.getTransaction().begin();
            em.refresh(job, LockModeType.PESSIMISTIC_WRITE);
            if (job.getState().equals(State.SUBMITTED))
            {
                job.setState(State.CANCELLED);
            }
            em.getTransaction().commit();

            if (!job.getState().equals(State.CANCELLED))
            {
                // Job is not in queue anymore - just return.
                return;
            }

            em.getTransaction().begin();
            em.createQuery("DELETE FROM MessageJi WHERE jobInstance = :i").setParameter("i", job).executeUpdate();
            em.createQuery("DELETE FROM JobParameter WHERE jobInstance = :i").setParameter("i", job).executeUpdate();
            em.createQuery("DELETE FROM JobInstance WHERE id = :i").setParameter("i", job.getId()).executeUpdate();
            em.getTransaction().commit();
        }
        catch (NoResultException e)
        {
            throw new JqmInvalidRequestException("An attempt was made to delete a job instance that did not exist.");
        }
        catch (Exception e)
        {
            throw new JqmClientException("could not delete a job (internal error)", e);
        }
        finally
        {
            closeQuietly(em);
        }
    }

    @Override
    public void killJob(int idJob)
    {
        EntityManager em = null;
        try
        {
            em = getEm();
            em.getTransaction().begin();
            JobInstance j = em.find(JobInstance.class, idJob, LockModeType.PESSIMISTIC_READ);
            jqmlogger.trace("The " + j.getState() + " job (ID: " + idJob + ")" + " will be marked for kill");

            j.setState(State.KILLED);

            MessageJi m = new MessageJi();
            m.setJobInstance(j);
            m.setTextMessage("Kill attempt on the job");
            em.persist(m);
            em.getTransaction().commit();
        }
        catch (NoResultException e)
        {
            throw new JqmInvalidRequestException("An attempt was made to kill a job instance that did not exist.");
        }
        catch (Exception e)
        {
            throw new JqmClientException("could not kill a job (internal error)", e);
        }
        finally
        {
            closeQuietly(em);
        }
    }

    // /////////////////////////////////////////////////////////////////////
    // Job Pause/restart
    // /////////////////////////////////////////////////////////////////////

    @Override
    public void pauseQueuedJob(int idJob)
    {
        jqmlogger.trace("Job status number " + idJob + " will be set to HOLDED");
        EntityManager em = null;

        try
        {
            em = getEm();
            em.getTransaction().begin();
            em.createQuery("UPDATE JobInstance j SET j.state = 'HOLDED' WHERE j.id = :idJob").setParameter("idJob", idJob).executeUpdate();
            em.getTransaction().commit();
        }
        catch (NoResultException e)
        {
            throw new JqmInvalidRequestException("An attempt was made to pause a job instance that did not exist.");
        }
        catch (Exception e)
        {
            throw new JqmClientException("could not pause a job (internal error)", e);
        }
        finally
        {
            closeQuietly(em);
        }
    }

    @Override
    public void resumeJob(int idJob)
    {
        jqmlogger.trace("Job status number " + idJob + " will be resumed");
        EntityManager em = null;

        try
        {
            em = getEm();
            em.getTransaction().begin();
            em.createQuery("UPDATE JobInstance j SET j.state = 'SUBMITTED' WHERE j.id = :idJob").setParameter("idJob", idJob)
                    .executeUpdate();
            em.getTransaction().commit();
        }
        catch (NoResultException e)
        {
            throw new JqmInvalidRequestException("An attempt was made to resume a job instance that did not exist.");
        }
        catch (Exception e)
        {
            throw new JqmClientException("could not resume a job (internal error)", e);
        }
        finally
        {
            closeQuietly(em);
        }
    }

    public int restartCrashedJob(int idJob)
    {
        EntityManager em = null;

        // History and Job ID have the same ID.
        History h = null;
        try
        {
            em = getEm();
            h = em.find(History.class, idJob);
        }
        catch (NoResultException e)
        {
            closeQuietly(em);
            throw new JqmClientException("You cannot restart a job that is not done or which was purged from history");
        }
        catch (Exception e)
        {
            closeQuietly(em);
            throw new JqmClientException("could not restart a job (internal error)", e);
        }

        if (!h.getState().equals(State.CRASHED))
        {
            closeQuietly(em);
            throw new JqmClientException("You cannot restart a job that has not crashed");
        }

        if (!h.getJd().isCanBeRestarted())
        {
            closeQuietly(em);
            throw new JqmClientException("This type of job was configured to prevent being restarded");
        }

        try
        {
            em.getTransaction().begin();
            em.remove(h);
            em.getTransaction().commit();
            return enqueue(getJobRequest(h));
        }
        catch (Exception e)
        {
            throw new JqmClientException("could not purge & restart a job (internal error)", e);
        }
        finally
        {
            closeQuietly(em);
        }
    }

    // /////////////////////////////////////////////////////////////////////
    // Misc.
    // /////////////////////////////////////////////////////////////////////

    @Override
    public void setJobQueue(int idJob, int idQueue)
    {
        EntityManager em = null;
        JobInstance ji = null;
        Queue q = null;

        try
        {
            em = getEm();
            q = em.find(Queue.class, idQueue);
        }
        catch (NoResultException e)
        {
            closeQuietly(em);
            throw new JqmClientException("Queue does not exist");
        }
        catch (Exception e)
        {
            closeQuietly(em);
            throw new JqmClientException("Cannot retrieve queue", e);
        }

        try
        {
            em.getTransaction().begin();
            ji = em.find(JobInstance.class, idJob, LockModeType.PESSIMISTIC_WRITE);
            if (!ji.getState().equals(State.SUBMITTED))
            {
                throw new NoResultException();
            }
            ji.setQueue(q);
            em.getTransaction().commit();
        }
        catch (NoResultException e)
        {
            throw new JqmClientException("Job instance does not exist or has already started");
        }
        catch (Exception e)
        {
            throw new JqmClientException("could not change the queue of a job (internal error)", e);
        }
        finally
        {
            closeQuietly(em);
        }
    }

    @Override
    public void setJobQueue(int idJob, com.enioka.jqm.api.Queue queue)
    {
        setJobQueue(idJob, queue.getId());
    }

    @Override
    public void setJobQueuePosition(int idJob, int position)
    {
        EntityManager em = null;
        JobInstance ji = null;
        try
        {
            em = getEm();
            em.getTransaction().begin();
            ji = em.find(JobInstance.class, idJob, LockModeType.PESSIMISTIC_WRITE);
        }
        catch (Exception e)
        {
            closeQuietly(em);
            throw new JqmClientException(
                    "Could not lock a job by the given ID. It may already have been executed or a timeout may have occurred.", e);
        }

        if (!ji.getState().equals(State.SUBMITTED))
        {
            closeQuietly(em);
            throw new JqmInvalidRequestException("Job is already set for execution. Too late to change its position in the queue");
        }

        try
        {
            int current = ji.getCurrentPosition(em);
            int betweenUp = 0;
            int betweenDown = 0;

            if (current == position)
            {
                // Nothing to do
                em.getTransaction().rollback();
                return;
            }
            else if (current < position)
            {
                betweenDown = position;
                betweenUp = position + 1;
            }
            else
            {
                betweenDown = position - 1;
                betweenUp = position;
            }

            // No locking - we'll deal with exceptions
            List<JobInstance> currentJobs = em.createQuery("SELECT ji from JobInstance ji ORDER BY ji.internalPosition", JobInstance.class)
                    .setMaxResults(betweenUp).getResultList();

            if (currentJobs.isEmpty())
            {
                ji.setInternalPosition(0);
            }
            else if (currentJobs.size() < betweenUp)
            {
                ji.setInternalPosition(currentJobs.get(currentJobs.size() - 1).getInternalPosition() + 0.00001);
            }
            else
            {
                // Normal case: put the JI between the two others.
                ji.setInternalPosition((currentJobs.get(betweenUp - 1).getInternalPosition() + currentJobs.get(betweenDown - 1)
                        .getInternalPosition()) / 2);
            }
            em.getTransaction().commit();
        }
        catch (Exception e)
        {
            throw new JqmClientException("could not change the queue position of a job (internal error)", e);
        }
        finally
        {
            closeQuietly(em);
        }
    }

    // /////////////////////////////////////////////////////////////////////
    // Job queries
    // /////////////////////////////////////////////////////////////////////

    // Helper
    private com.enioka.jqm.api.JobInstance getJob(JobInstance h)
    {
        com.enioka.jqm.api.JobInstance ji = new com.enioka.jqm.api.JobInstance();
        ji.setId(h.getId());
        ji.setApplicationName(h.getJd().getApplicationName());
        ji.setParameters(new HashMap<String, String>());
        ji.setParent(h.getParentId());
        ji.setQueue(getQueue(h.getQueue()));
        ji.setSessionID(h.getSessionID());
        ji.setState(com.enioka.jqm.api.State.valueOf(h.getState().toString()));
        ji.setUser(h.getUserName());
        ji.setProgress(h.getProgress());
        for (JobParameter p : h.getParameters())
        {
            ji.getParameters().put(p.getKey(), p.getValue());
        }
        for (MessageJi m : h.getMessages())
        {
            ji.getMessages().add(m.getTextMessage());
        }
        ji.setKeyword1(h.getKeyword1());
        ji.setKeyword2(h.getKeyword2());
        ji.setKeyword3(h.getKeyword3());
        ji.setApplication(h.getApplication());
        ji.setModule(h.getModule());
        ji.setEmail(h.getEmail());
        ji.setEnqueueDate(h.getCreationDate());
        ji.setBeganRunningDate(h.getExecutionDate());
        if (h.getNode() != null)
        {
            ji.setNodeName(h.getNode().getName());
        }

        return ji;
    }

    // Helper
    private com.enioka.jqm.api.JobInstance getJob(History h)
    {
        com.enioka.jqm.api.JobInstance ji = new com.enioka.jqm.api.JobInstance();
        ji.setId(h.getId());
        ji.setApplicationName(h.getJd().getApplicationName());
        ji.setParameters(new HashMap<String, String>());
        ji.setParent(h.getParentJobId());
        ji.setQueue(getQueue(h.getQueue()));
        ji.setSessionID(h.getSessionId());
        ji.setState(com.enioka.jqm.api.State.valueOf(h.getStatus().toString()));
        ji.setUser(h.getUserName());
        ji.setProgress(h.getProgress());
        for (JobHistoryParameter p : h.getParameters())
        {
            ji.getParameters().put(p.getKey(), p.getValue());
        }
        for (Message m : h.getMessages())
        {
            ji.getMessages().add(m.getTextMessage());
        }
        ji.setKeyword1(h.getKeyword1());
        ji.setKeyword2(h.getKeyword2());
        ji.setKeyword3(h.getKeyword3());
        ji.setApplication(h.getApplication());
        ji.setModule(h.getModule());
        ji.setEmail(h.getEmail());
        ji.setEnqueueDate(h.getEnqueueDate());
        ji.setBeganRunningDate(h.getExecutionDate());
        ji.setEndDate(h.getEndDate());
        if (h.getNode() != null)
        {
            ji.setNodeName(h.getNode().getName());
        }

        return ji;
    }

    // GetJob helper - String predicates are all created the same way, so this factors some code.
    private String getStringPredicate(String fieldName, String filterValue, Map<String, Object> prms)
    {
        if (filterValue != null)
        {
            if (!filterValue.isEmpty())
            {
                String prmName = fieldName.split("\\.")[fieldName.split("\\.").length - 1] + Math.abs(fieldName.hashCode());
                prms.put(prmName, filterValue);
                if (filterValue.contains("%"))
                {
                    return String.format("AND (%s LIKE :%s) ", fieldName, prmName);
                }
                else
                {
                    return String.format("AND (%s = :%s) ", fieldName, prmName);
                }
            }
            else
            {
                return String.format("AND (h.%s IS NULL OR h.%s = '') ", fieldName, fieldName);
            }
        }
        return "";
    }

    private String getIntPredicate(String fieldName, Integer filterValue, Map<String, Object> prms)
    {
        if (filterValue != null)
        {
            if (filterValue != -1)
            {
                String prmName = fieldName.split("\\.")[fieldName.split("\\.").length - 1];
                prms.put(prmName, filterValue);
                return String.format("AND (%s = :%s) ", fieldName, prmName);
            }
            else
            {
                return String.format("AND (h.%s IS NULL) ", fieldName);
            }
        }
        return "";
    }

    private String getCalendarPredicate(String fieldName, Calendar filterValue, String comparison, Map<String, Object> prms)
    {
        if (filterValue != null)
        {
            String prmName = fieldName.split("\\.")[fieldName.split("\\.").length - 1] + Math.abs(comparison.hashCode());
            prms.put(prmName, filterValue);
            return String.format("AND (%s %s :%s) ", fieldName, comparison, prmName);
        }
        else
        {
            return "";
        }
    }

    private String getStatusPredicate(String fieldName, List<com.enioka.jqm.api.State> status, Map<String, Object> prms)
    {
        if (status == null || status.isEmpty())
        {
            return "";
        }

        String res = String.format("AND ( %s IN ( ", fieldName);

        for (com.enioka.jqm.api.State s : status)
        {
            String prmName = "status" + s.hashCode();
            res += " :" + prmName + ",";
            prms.put(prmName, State.valueOf(s.toString()));
        }
        res = res.substring(0, res.length() - 1) + ")) ";
        return res;
    }

    @Override
    public List<com.enioka.jqm.api.JobInstance> getJobs(Query query)
    {
        if ((query.getFirstRow() != null || query.getPageSize() != null) && query.isQueryLiveInstances())
        {
            throw new JqmInvalidRequestException("cannot use paging on live instances");
        }

        EntityManager em = null;
        try
        {
            em = getEm();

            // Not using CriteriaBuilder - too much hassle for too little benefit
            String wh = "";
            Map<String, Object> prms = new HashMap<String, Object>();

            // String predicates
            wh += getStringPredicate("jd.applicationName", query.getApplicationName(), prms);
            wh += getStringPredicate("userName", query.getUser(), prms);
            wh += getStringPredicate("sessionId", query.getSessionId(), prms);
            wh += getStringPredicate("instanceKeyword1", query.getInstanceKeyword1(), prms);
            wh += getStringPredicate("instanceKeyword2", query.getInstanceKeyword2(), prms);
            wh += getStringPredicate("instanceKeyword3", query.getInstanceKeyword3(), prms);
            wh += getStringPredicate("instanceModule", query.getInstanceModule(), prms);
            wh += getStringPredicate("instanceApplication", query.getInstanceApplication(), prms);
            wh += getStringPredicate("jd.keyword1", query.getJobDefKeyword1(), prms);
            wh += getStringPredicate("jd.keyword2", query.getJobDefKeyword2(), prms);
            wh += getStringPredicate("jd.keyword3", query.getJobDefKeyword3(), prms);
            wh += getStringPredicate("jd.module", query.getJobDefModule(), prms);
            wh += getStringPredicate("jd.application", query.getJobDefApplication(), prms);
            wh += getStringPredicate("queue.name", query.getQueueName(), prms);

            // Integer
            wh += getIntPredicate("parentId", query.getParentId(), prms);
            wh += getIntPredicate("id", query.getJobInstanceId(), prms);
            wh += getIntPredicate("queue.id", query.getQueueName() == null ? null : query.getQueueId(), prms);

            // Now, run queries...
            List<com.enioka.jqm.api.JobInstance> res2 = new ArrayList<com.enioka.jqm.api.JobInstance>();

            // ////////////////////////////////////////
            // Job Instance query
            if (query.isQueryLiveInstances())
            {
                // Sort
                String sort = "";
                for (SortSpec s : query.getSorts())
                {
                    sort += s.col.getJiField() == null ? "" : ",h." + s.col.getJiField() + " "
                            + (s.order == Query.SortOrder.ASCENDING ? "ASC" : "DESC");
                }
                if (sort.isEmpty())
                {
                    sort = " ORDER BY h.id";
                }
                else
                {
                    sort = " ORDER BY " + sort.substring(1);
                }

                // Calendar fields are specific (no common fields between History and JobInstance)
                String wh2 = "" + wh;
                Map<String, Object> prms2 = new HashMap<String, Object>();
                prms2.putAll(prms);
                wh2 += getCalendarPredicate("creationDate", query.getEnqueuedAfter(), ">=", prms2);
                wh2 += getCalendarPredicate("creationDate", query.getEnqueuedBefore(), "<=", prms2);
                wh2 += getCalendarPredicate("executionDate", query.getBeganRunningAfter(), ">=", prms2);
                wh2 += getCalendarPredicate("executionDate", query.getBeganRunningBefore(), "<=", prms2);
                wh2 += getStatusPredicate("state", query.getStatus(), prms2);
                if (wh2.length() >= 3)
                {
                    wh2 = " WHERE " + wh2.substring(3);
                }

                TypedQuery<JobInstance> q2 = em.createQuery("SELECT h FROM JobInstance h " + wh2, JobInstance.class);
                for (Map.Entry<String, Object> entry : prms2.entrySet())
                {
                    q2.setParameter(entry.getKey(), entry.getValue());
                }

                for (JobInstance ji : q2.getResultList())
                {
                    res2.add(getJob(ji));
                }
            }

            // ////////////////////////////////////////
            // History query
            if (query.isQueryHistoryInstances())
            {
                // Calendar fields are specific (no common fields between History and JobInstance)
                wh += getCalendarPredicate("enqueueDate", query.getEnqueuedAfter(), ">=", prms);
                wh += getCalendarPredicate("enqueueDate", query.getEnqueuedBefore(), "<=", prms);
                wh += getCalendarPredicate("executionDate", query.getBeganRunningAfter(), ">=", prms);
                wh += getCalendarPredicate("executionDate", query.getBeganRunningBefore(), "<=", prms);
                wh += getCalendarPredicate("endDate", query.getEndedAfter(), ">=", prms);
                wh += getCalendarPredicate("endDate", query.getEndedBefore(), "<=", prms);
                wh += getStatusPredicate("status", query.getStatus(), prms);
                if (wh.length() >= 3)
                {
                    wh = " WHERE " + wh.substring(3);
                }

                // Order by
                String sort = "";
                for (SortSpec s : query.getSorts())
                {
                    sort += ",h." + s.col.getHistoryField() + " " + (s.order == Query.SortOrder.ASCENDING ? "ASC" : "DESC");
                }
                if (sort.isEmpty())
                {
                    sort = " ORDER BY h.id";
                }
                else
                {
                    sort = " ORDER BY " + sort.substring(1);
                }

                TypedQuery<History> q1 = em.createQuery("SELECT h FROM History h " + wh + sort, History.class);
                for (Map.Entry<String, Object> entry : prms.entrySet())
                {
                    q1.setParameter(entry.getKey(), entry.getValue());
                }
                if (query.getFirstRow() != null)
                {
                    q1.setFirstResult(query.getFirstRow());
                }
                if (query.getPageSize() != null)
                {
                    q1.setMaxResults(query.getPageSize());
                }
                if (query.getFirstRow() != null || query.getPageSize() != null)
                {
                    TypedQuery<Long> qCount = em.createQuery("SELECT COUNT(h) FROM History h " + wh, Long.class);
                    for (Map.Entry<String, Object> entry : prms.entrySet())
                    {
                        qCount.setParameter(entry.getKey(), entry.getValue());
                    }
                    query.setResultSize(new BigDecimal(qCount.getSingleResult()).intValueExact());
                }

                for (History ji : q1.getResultList())
                {
                    res2.add(getJob(ji));
                }
            }

            query.setResults(res2);
            return res2;
        }
        catch (Exception e)
        {
            throw new JqmClientException("an error occured during query execution", e);
        }
        finally
        {
            closeQuietly(em);
        }
    }

    @Override
    public com.enioka.jqm.api.JobInstance getJob(int idJob)
    {
        EntityManager em = null;
        try
        {
            em = getEm();
            History h = em.find(History.class, idJob);
            com.enioka.jqm.api.JobInstance res = null;
            if (h != null)
            {
                res = getJob(h);
            }
            else
            {
                JobInstance ji = em.find(JobInstance.class, idJob);
                if (ji != null)
                {
                    res = getJob(ji);
                }
                else
                {
                    throw new JqmInvalidRequestException("No job instance of ID " + idJob);
                }
            }
            return res;
        }
        catch (JqmInvalidRequestException e)
        {
            throw e;
        }
        catch (Exception e)
        {
            throw new JqmClientException("an error occured during query execution", e);
        }
        finally
        {
            closeQuietly(em);
        }
    }

    @Override
    public List<com.enioka.jqm.api.JobInstance> getJobs()
    {
        ArrayList<com.enioka.jqm.api.JobInstance> jobs = new ArrayList<com.enioka.jqm.api.JobInstance>();
        EntityManager em = null;

        try
        {
            em = getEm();
            for (JobInstance h : em.createQuery("SELECT j FROM JobInstance j ORDER BY j.id", JobInstance.class).getResultList())
            {
                jobs.add(getJob(h));
            }
            for (History h : em.createQuery("SELECT j FROM History j ORDER BY j.id", History.class).getResultList())
            {
                jobs.add(getJob(h));
            }
        }
        catch (Exception e)
        {
            throw new JqmClientException("Could not query history and queues", e);
        }
        finally
        {
            closeQuietly(em);
        }
        return jobs;
    }

    @Override
    public List<com.enioka.jqm.api.JobInstance> getActiveJobs()
    {
        ArrayList<com.enioka.jqm.api.JobInstance> jobs = new ArrayList<com.enioka.jqm.api.JobInstance>();
        EntityManager em = null;

        try
        {
            em = getEm();
            for (JobInstance h : em.createQuery("SELECT j FROM JobInstance j ORDER BY j.id", JobInstance.class).getResultList())
            {
                jobs.add(getJob(h));
            }
        }
        catch (Exception e)
        {
            throw new JqmClientException("Could not query queues", e);
        }
        finally
        {
            closeQuietly(em);
        }
        return jobs;
    }

    @Override
    public List<com.enioka.jqm.api.JobInstance> getUserActiveJobs(String user)
    {
        if (user == null || user.isEmpty())
        {
            throw new JqmInvalidRequestException("user cannot be null or empty");
        }
        ArrayList<com.enioka.jqm.api.JobInstance> jobs = new ArrayList<com.enioka.jqm.api.JobInstance>();
        EntityManager em = null;

        try
        {
            em = getEm();
            for (JobInstance h : em.createQuery("SELECT j FROM JobInstance j WHERE j.userName = :u ORDER BY j.id", JobInstance.class)
                    .setParameter("u", user).getResultList())
            {
                jobs.add(getJob(h));
            }
            for (History h : em.createQuery("SELECT j FROM History j WHERE j.userName = :u ORDER BY j.id", History.class)
                    .setParameter("u", user).getResultList())
            {
                jobs.add(getJob(h));
            }
        }
        catch (Exception e)
        {
            throw new JqmClientException("Could not query both queues and history for job instances of user " + user, e);
        }
        finally
        {
            closeQuietly(em);
        }
        return jobs;
    }

    // /////////////////////////////////////////////////////////////////////
    // Helpers to quickly access some job instance properties
    // /////////////////////////////////////////////////////////////////////

    @Override
    public List<String> getJobMessages(int idJob)
    {
        return getJob(idJob).getMessages();
    }

    @Override
    public int getJobProgress(int idJob)
    {
        return getJob(idJob).getProgress();
    }

    // /////////////////////////////////////////////////////////////////////
    // Deliverables retrieval
    // /////////////////////////////////////////////////////////////////////

    @Override
    public List<com.enioka.jqm.api.Deliverable> getJobDeliverables(int idJob)
    {
        List<Deliverable> deliverables = null;
        EntityManager em = null;

        try
        {
            em = getEm();
            deliverables = em.createQuery("SELECT d FROM Deliverable d WHERE d.jobId = :idJob", Deliverable.class)
                    .setParameter("idJob", idJob).getResultList();
        }
        catch (Exception e)
        {
            throw new JqmClientException("Deliverables cannot be found", e);
        }
        finally
        {
            closeQuietly(em);
        }

        List<com.enioka.jqm.api.Deliverable> res = new ArrayList<com.enioka.jqm.api.Deliverable>();
        for (Deliverable d : deliverables)
        {
            res.add(new com.enioka.jqm.api.Deliverable(d.getFilePath(), d.getFileFamily(), d.getId(), d.getOriginalFileName()));
        }

        return res;
    }

    @Override
    public List<InputStream> getJobDeliverablesContent(int idJob)
    {
        EntityManager em = null;
        ArrayList<InputStream> streams = new ArrayList<InputStream>();
        List<Deliverable> tmp = null;

        try
        {
            em = getEm();
            tmp = em.createQuery("SELECT d FROM Deliverable d WHERE d.jobId = :idJob", Deliverable.class).setParameter("idJob", idJob)
                    .getResultList();

            for (Deliverable del : tmp)
            {
                streams.add(getDeliverableContent(del));
            }
        }
        catch (Exception e)
        {
            throw new JqmClientException("could not retrieve file streams", e);
        }
        finally
        {
            closeQuietly(em);
        }
        return streams;
    }

    @Override
    public InputStream getDeliverableContent(com.enioka.jqm.api.Deliverable d)
    {
        EntityManager em = null;
        Deliverable deliverable = null;

        try
        {
            em = getEm();
            deliverable = em.find(Deliverable.class, d.getId());
        }
        catch (Exception e)
        {
            throw new JqmInvalidRequestException("Could not get find deliverable description inside DB - your ID may be wrong", e);
        }
        finally
        {
            closeQuietly(em);
        }

        return getDeliverableContent(deliverable);
    }

    // Helper
    private InputStream getDeliverableContent(Deliverable deliverable)
    {
        EntityManager em = getEm();
        URL url = null;
        File file = null;
        History h = null;

        try
        {
            h = em.createQuery("SELECT h FROM History h WHERE h.id = :job", History.class).setParameter("job", deliverable.getJobId())
                    .getSingleResult();
        }
        catch (Exception e)
        {
            h = null;
            closeQuietly(em);
            throw new JqmInvalidRequestException("No ended job found with the deliverable ID", e);
        }

        String destDir = System.getProperty("java.io.tmpdir");
        jqmlogger.trace("File will be copied into " + destDir);

        try
        {
            url = new URL("http://" + h.getNode().getDns() + ":" + h.getNode().getPort() + "/getfile?file=" + deliverable.getRandomId());
            jqmlogger.trace("URL: " + url.toString());
        }
        catch (MalformedURLException e)
        {
            throw new JqmClientException("URL is not valid " + url, e);
        }

        try
        {
            file = new File(destDir + "/" + UUID.randomUUID().toString() + deliverable.getOriginalFileName());
            FileUtils.copyURLToFile(url, file);
            jqmlogger.trace("File was downloaded to " + file.getAbsolutePath());
        }
        catch (IOException e)
        {
            throw new JqmClientException("Could not create a webserver-local copy of the file", e);
        }
        finally
        {
            closeQuietly(em);
        }

        FileInputStream res = null;
        try
        {
            res = new SelfDestructFileStream(file);
        }
        catch (IOException e)
        {
            throw new JqmClientException("File seems not to be present where it should have been downloaded", e);
        }
        return res;
    }

    @Override
    public InputStream getJobLogStdOut(int jobId)
    {
        return getJobLog(jobId, ".stdout", "out");
    }

    @Override
    public InputStream getJobLogStdErr(int jobId)
    {
        return getJobLog(jobId, ".stderr", "err");
    }

    private InputStream getJobLog(int jobId, String extension, String param)
    {
        // 1: retrieve node to address
        EntityManager em = null;
        History h = null;
        try
        {
            em = getEm();
            h = em.find(History.class, jobId);
            if (h == null)
            {
                throw new NoResultException("No history for this jobId. perhaps the job isn't ended yet");
            }
        }
        catch (Exception e)
        {
            h = null;
            throw new JqmInvalidRequestException("No ended job found with the deliverable ID " + jobId, e);
        }
        finally
        {
            closeQuietly(em);
        }

        // 2: build URL
        URL url = null;
        try
        {
            url = new URL("http://" + h.getNode().getDns() + ":" + h.getNode().getPort() + "/log?" + param + "=" + jobId);
            jqmlogger.trace("URL: " + url.toString());
        }
        catch (MalformedURLException e)
        {
            throw new JqmClientException("URL is not valid " + url, e);
        }

        // 3: copy file locally
        File file = null;
        String destDir = System.getProperty("java.io.tmpdir");
        try
        {
            file = new File(destDir + "/" + UUID.randomUUID().toString() + jobId + extension);
            FileUtils.copyURLToFile(url, file);
            jqmlogger.trace("File was downloaded to " + file.getAbsolutePath());
        }
        catch (IOException e)
        {
            throw new JqmClientException("Could not create a webserver-local copy of the file", e);
        }

        // 4: wrap the local copy inside a self destructable stream so that we won't fill the file system
        FileInputStream res = null;
        try
        {
            res = new SelfDestructFileStream(file);
        }
        catch (IOException e)
        {
            throw new JqmClientException("File seems not to be present where it should have been downloaded", e);
        }
        return res;
    }

    // /////////////////////////////////////////////////////////////////////
    // Parameters retrieval
    // /////////////////////////////////////////////////////////////////////

    @Override
    public List<com.enioka.jqm.api.Queue> getQueues()
    {
        List<com.enioka.jqm.api.Queue> res = new ArrayList<com.enioka.jqm.api.Queue>();
        EntityManager em = null;
        com.enioka.jqm.api.Queue tmp = null;

        try
        {
            em = getEm();
            for (Queue q : em.createQuery("SELECT q FROM Queue q ORDER BY q.name", Queue.class).getResultList())
            {
                tmp = getQueue(q);
                res.add(tmp);
            }
            return res;
        }
        catch (Exception e)
        {
            throw new JqmClientException("could not query queues", e);
        }
        finally
        {
            closeQuietly(em);
        }
    }

    private static com.enioka.jqm.api.Queue getQueue(Queue queue)
    {
        com.enioka.jqm.api.Queue q = new com.enioka.jqm.api.Queue();

        q.setDescription(queue.getDescription());
        q.setId(queue.getId());
        q.setName(queue.getName());

        return q;
    }

    private static JobParameter createJobParameter(String key, String value, EntityManager em)
    {
        JobParameter j = new JobParameter();

        j.setKey(key);
        j.setValue(value);

        em.persist(j);
        return j;
    }

    @Override
    public List<com.enioka.jqm.api.JobDef> getJobDefinitions()
    {
        return getJobDefinitions(null);
    }

    @Override
    public List<com.enioka.jqm.api.JobDef> getJobDefinitions(String application)
    {
        List<com.enioka.jqm.api.JobDef> res = new ArrayList<com.enioka.jqm.api.JobDef>();
        EntityManager em = null;
        List<JobDef> dbr = null;

        try
        {
            em = getEm();
            if (application == null)
            {
                dbr = em.createQuery("SELECT jd from JobDef jd ORDER BY jd.application, jd.module, jd.applicationName", JobDef.class)
                        .getResultList();
            }
            else
            {
                dbr = em.createQuery(
                        "SELECT jd from JobDef jd WHERE jd.application = :name ORDER BY jd.application, jd.module, jd.applicationName",
                        JobDef.class).setParameter("name", application).getResultList();
            }

            for (JobDef jd : dbr)
            {
                res.add(getJobDef(jd));
            }
            return res;
        }
        catch (Exception e)
        {
            throw new JqmClientException("could not query JobDef", e);
        }
        finally
        {
            closeQuietly(em);
        }
    }

    private static com.enioka.jqm.api.JobDef getJobDef(JobDef jd)
    {
        com.enioka.jqm.api.JobDef res = new com.enioka.jqm.api.JobDef();
        res.setApplication(jd.getApplication());
        res.setApplicationName(jd.getApplicationName());
        res.setCanBeRestarted(jd.isCanBeRestarted());
        res.setDescription(jd.getDescription());
        res.setHighlander(jd.isHighlander());
        res.setKeyword1(jd.getKeyword1());
        res.setKeyword2(jd.getKeyword2());
        res.setKeyword3(jd.getKeyword3());
        res.setModule(jd.getModule());
        res.setQueue(getQueue(jd.getQueue()));
        res.setId(jd.getId());
        return res;
    }
}
TOP

Related Classes of com.enioka.jqm.api.HibernateClient

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.