org.headsupdev.agile.app.ci.CIApplication.java Source code

Java tutorial

Introduction

Here is the source code for org.headsupdev.agile.app.ci.CIApplication.java

Source

/*
 * HeadsUp Agile
 * Copyright 2009-2013 Heads Up Development Ltd.
 *
 * 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 org.headsupdev.agile.app.ci;

import org.headsupdev.agile.api.rest.Api;
import org.headsupdev.agile.app.ci.builders.BuildHandlerFactory;
import org.headsupdev.agile.app.ci.rest.BuildStatusApi;
import org.headsupdev.support.java.FileUtil;
import org.headsupdev.agile.app.ci.irc.BuildCommand;
import org.headsupdev.agile.storage.StoredProject;
import org.headsupdev.agile.storage.hibernate.IdProjectId;
import org.headsupdev.agile.storage.ci.Build;
import org.headsupdev.agile.storage.HibernateStorage;
import org.headsupdev.agile.storage.HibernateUtil;
import org.headsupdev.agile.app.ci.event.BuildFailedEvent;
import org.headsupdev.agile.app.ci.event.BuildSucceededEvent;
import org.headsupdev.agile.app.ci.event.UploadApplicationEvent;
import org.headsupdev.agile.app.ci.permission.BuildViewPermission;
import org.headsupdev.agile.app.ci.permission.BuildListPermission;
import org.headsupdev.agile.app.ci.permission.BuildForcePermission;
import org.headsupdev.agile.web.WebApplication;
import org.headsupdev.agile.api.*;
import org.headsupdev.irc.IRCCommand;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.Query;
import org.osgi.framework.BundleContext;

import java.util.List;
import java.util.LinkedList;
import java.io.File;

/**
 * The application descriptor for continuous integration
 *
 * @author Andrew Williams
 * @version $Id$
 * @since 1.0
 */
public class CIApplication extends WebApplication {
    public static final String ID = "builds";
    protected static BuildHandlerFactory handlerFactory = new BuildHandlerFactory();

    public static final ConfigurationItem CONFIGURATION_NOTIFY_REPEAT_PASS = new ConfigurationItem(
            "notify.pass.repeat", false, "Send notifications for continual success",
            "Set this to true if you wish every build to cause a notification, otherwise only failed "
                    + "or newly passed builds will send notifications");

    public static final ConfigurationItem CONFIGURATION_MAVEN_HOME = new ConfigurationItem("maven.home", "",
            "Maven Home (not including /bin/mvn)",
            "Change this parameter if you wish to use a particular version of maven "
                    + "or if your maven installation is not in the system path");
    //    {
    //        public boolean test( String value )
    //        {
    //            File mvn = new File( new File( value, "bin" ), "mvn" );
    //            return mvn.exists();
    //        }
    //    } );
    public static final ConfigurationItem CONFIGURATION_ANT_HOME = new ConfigurationItem("ant.home", "",
            "Ant Home Directory", "Change this parameter if you wish to use a particular version of ant "
                    + "or if your ant installation is not in the system path");
    public static final ConfigurationItem CONFIGURATION_GRADLE_HOME = new ConfigurationItem("gradle.home", "",
            "Gradle Home Directory", "Change this parameter if you wish to use a particular version of gradle "
                    + "or if your gradle installation is not in the system path");
    public static final ConfigurationItem CONFIGURATION_ECLIPSE_HOME = new ConfigurationItem("eclipse.home", "",
            "Eclipse home directory", "The directory where Eclipse is installed");

    public static final ConfigurationItem CONFIGURATION_ANT_TASKS = new ConfigurationItem("ant.tasks", "",
            "Ant build tasks", "A space separated list of ant tasks to run when building");

    public static final ConfigurationItem CONFIGURATION_GRADLE_TASKS = new ConfigurationItem("gradle.tasks", "",
            "Gradle build tasks", "A space separated list of gradle tasks to run when building");

    public static final ConfigurationItem CONFIGURATION_MAVEN_GOALS = new ConfigurationItem("maven.goals",
            "clean install", "Maven build goals",
            "A space separated list of maven goals and phases to run when building");
    public static final ConfigurationItem CONFIGURATION_MAVEN_PROFILES = new ConfigurationItem("maven.profiles", "",
            "Maven build profiles", "A space separated list of maven profiles to activate when building");
    public final ConfigurationItem CONFIGURATION_MAVEN_HOME_OVERRIDE = new ConfigurationItem("maven.home", "",
            "Maven Home (not including /bin/mvn)",
            "Use this option to specify a different maven installation for " + "this build schedule") {
        @Override
        public Object getDefault() {
            PropertyTree appConfig = CIApplication.this.getConfiguration();

            return appConfig.getProperty(CONFIGURATION_MAVEN_HOME.getKey(),
                    (String) CONFIGURATION_MAVEN_HOME.getDefault());
        }
    };

    public final ConfigurationItem CONFIGURATION_ANT_HOME_OVERRIDE = new ConfigurationItem("ant.home", "",
            "Ant Home Directory",
            "Use this option to specify a different ant installation for " + "this build schedule") {
        @Override
        public Object getDefault() {
            PropertyTree appConfig = CIApplication.this.getConfiguration();

            return appConfig.getProperty(CONFIGURATION_ANT_HOME.getKey(),
                    (String) CONFIGURATION_ANT_HOME.getDefault());
        }
    };

    public final ConfigurationItem CONFIGURATION_GRADLE_HOME_OVERRIDE = new ConfigurationItem("gradle.home", "",
            "Gradle Home Directory",
            "Use this option to specify a different gradle installation for " + "this build schedule") {
        @Override
        public Object getDefault() {
            PropertyTree appConfig = CIApplication.this.getConfiguration();

            return appConfig.getProperty(CONFIGURATION_GRADLE_HOME.getKey(),
                    (String) CONFIGURATION_GRADLE_HOME.getDefault());
        }
    };

    public final ConfigurationItem CONFIGURATION_ECLIPSE_HOME_OVERRIDE = new ConfigurationItem("eclipse.home", "",
            "Eclipse home directory",
            "Use this option to specify a different eclipse installation for " + "this build schedule") {
        @Override
        public Object getDefault() {
            PropertyTree appConfig = CIApplication.this.getConfiguration();

            return appConfig.getProperty(CONFIGURATION_ECLIPSE_HOME.getKey(),
                    (String) CONFIGURATION_ECLIPSE_HOME.getDefault());
        }
    };

    public static final ConfigurationItem CONFIGURATION_COMMAND_LINE = new ConfigurationItem("commandline", "make",
            "Command line", "The command used to start the build process for this project");

    public static final ConfigurationItem CONFIGURATION_XCODE_BUILD_WORKSPACE = new ConfigurationItem(
            "xcode.workspacebuild", false, "Build using an Xcode Workspace",
            "Should this configuration build using an Xcode workspace rather than a project setup");

    public static final ConfigurationItem CONFIGURATION_XCODE_WORKSPACE = new ConfigurationItem("xcode.workspace",
            "", "Workspace Name", "The xcode workspace to load for this build (optional)");
    public static final ConfigurationItem CONFIGURATION_XCODE_SCHEME = new ConfigurationItem("xcode.scheme", "",
            "Build Scheme (This will need to be marked as a 'shared' scheme from Xcode)",
            "The xcode scheme to build this project (optional - default is same as workspace)");

    public static final ConfigurationItem CONFIGURATION_XCODE_TARGET = new ConfigurationItem("xcode.target", "",
            "Project Target", "The xcode target to build this project (optional)");

    public static final ConfigurationItem CONFIGURATION_XCODE_CONFIG = new ConfigurationItem("xcode.config",
            "Release", "Project Configuration", "The xcode configuation to build this project");
    public static final ConfigurationItem CONFIGURATION_XCODE_SDK = new ConfigurationItem("xcode.sdk", "",
            "Project SDK", "The SDK to build this project against - mainly used for testing (optional)");
    public static final ConfigurationItem CONFIGURATION_XCODE_RUN_TESTS = new ConfigurationItem("xcode.runtests",
            false, "Run Tests",
            "Compile and run any tests defined in the project configuration (requires a scheme name)");
    public static final ConfigurationItem CONFIGURATION_ANALYZE = new ConfigurationItem("analyze", false, "Analyze",
            "Check for common coding errors");

    public static final ConfigurationItem CONFIGURATION_BUILD_NAME = new ConfigurationItem("name", "",
            "Configuration Name", "A name used to identify this build configuration");
    public static final ConfigurationItem CONFIGURATION_BUILD_DISABLED = new ConfigurationItem("disabled", false,
            "Disable Configuration", "Disable this build from being included in any automated running");
    public static final ConfigurationItem CONFIGURATION_CRON_EXPRESSION = new ConfigurationItem(
            ConfigurationItem.TYPE_CRON, "cron", "0 15 23 * * ?", "Cron Expression",
            "A cron expression to specify when the schedule should run (use format s m h DoM M DoW) or 'never' to disable the schedule");

    private List<MenuLink> links;
    private List<String> eventTypes;

    protected List<ConfigurationItem> globalItems = new LinkedList<ConfigurationItem>();

    protected List<ConfigurationItem> antProjectItems = new LinkedList<ConfigurationItem>();
    protected List<ConfigurationItem> gradleProjectItems = new LinkedList<ConfigurationItem>();
    protected List<ConfigurationItem> eclipseProjectItems = new LinkedList<ConfigurationItem>();
    protected List<ConfigurationItem> cmdProjectItems = new LinkedList<ConfigurationItem>();
    protected List<ConfigurationItem> xcodeProjectItems = new LinkedList<ConfigurationItem>();
    protected List<ConfigurationItem> otherProjectItems = new LinkedList<ConfigurationItem>();

    private static CIBuilder builder = new CIBuilder();

    private static CIScheduler scheduler = new CIScheduler();

    public static CIBuilder getBuilder() {
        return builder;
    }

    public static BuildHandlerFactory getHandlerFactory() {
        return handlerFactory;
    }

    public static void setHandlerFactory(BuildHandlerFactory handlerFactory) {
        CIApplication.handlerFactory = handlerFactory;
    }

    public static CIScheduler getScheduler() {
        return scheduler;
    }

    public void start(BundleContext bc) {
        super.start(bc);

        links = new LinkedList<MenuLink>();

        eventTypes = new LinkedList<String>();
        eventTypes.add("buildfailed");
        eventTypes.add("buildsucceeded");

        builder.setApplication(this);
        Manager.getInstance().addProjectListener(builder);

        globalItems.add(CONFIGURATION_MAVEN_HOME);
        globalItems.add(CONFIGURATION_ANT_HOME);
        globalItems.add(CONFIGURATION_ECLIPSE_HOME);
        globalItems.add(CONFIGURATION_NOTIFY_REPEAT_PASS);

        List<ConfigurationItem> items = new LinkedList<ConfigurationItem>();
        items.add(CONFIGURATION_BUILD_NAME);
        items.add(CONFIGURATION_ANT_TASKS);
        items.add(CONFIGURATION_ANT_HOME_OVERRIDE);
        items.add(CONFIGURATION_BUILD_DISABLED);
        antProjectItems.add(new ConfigurationItem("schedule.default", "Default Build Schedule", items));
        items = new LinkedList<ConfigurationItem>();
        items.add(CONFIGURATION_BUILD_NAME);
        items.add(CONFIGURATION_ANT_TASKS);
        items.add(CONFIGURATION_ANT_HOME_OVERRIDE);
        items.add(CONFIGURATION_CRON_EXPRESSION);
        items.add(CONFIGURATION_BUILD_DISABLED);
        antProjectItems.add(new ConfigurationItem("schedule", "Build Schedule",
                new ConfigurationItem("schedule", "Build Schedule", items)));

        items = new LinkedList<ConfigurationItem>();
        items.add(CONFIGURATION_BUILD_NAME);
        items.add(CONFIGURATION_GRADLE_TASKS);
        items.add(CONFIGURATION_GRADLE_HOME_OVERRIDE);
        items.add(CONFIGURATION_BUILD_DISABLED);
        gradleProjectItems.add(new ConfigurationItem("schedule.default", "Default Build Schedule", items));
        items = new LinkedList<ConfigurationItem>();
        items.add(CONFIGURATION_BUILD_NAME);
        items.add(CONFIGURATION_GRADLE_TASKS);
        items.add(CONFIGURATION_GRADLE_HOME_OVERRIDE);
        items.add(CONFIGURATION_CRON_EXPRESSION);
        items.add(CONFIGURATION_BUILD_DISABLED);
        gradleProjectItems.add(new ConfigurationItem("schedule", "Build Schedule",
                new ConfigurationItem("schedule", "Build Schedule", items)));

        items = new LinkedList<ConfigurationItem>();
        items.add(CONFIGURATION_BUILD_NAME);
        items.add(CONFIGURATION_ECLIPSE_HOME_OVERRIDE);
        items.add(CONFIGURATION_BUILD_DISABLED);
        eclipseProjectItems.add(new ConfigurationItem("schedule.default", "Default Build Schedule", items));
        items = new LinkedList<ConfigurationItem>();
        items.add(CONFIGURATION_BUILD_NAME);
        items.add(CONFIGURATION_ECLIPSE_HOME_OVERRIDE);
        items.add(CONFIGURATION_CRON_EXPRESSION);
        items.add(CONFIGURATION_BUILD_DISABLED);
        eclipseProjectItems.add(new ConfigurationItem("schedule", "Build Schedule",
                new ConfigurationItem("schedule", "Build Schedule", items)));

        items = new LinkedList<ConfigurationItem>();
        items.add(CONFIGURATION_BUILD_NAME);
        items.add(CONFIGURATION_COMMAND_LINE);
        items.add(CONFIGURATION_BUILD_DISABLED);
        cmdProjectItems.add(new ConfigurationItem("schedule.default", "Default Build Schedule", items));
        items = new LinkedList<ConfigurationItem>();
        items.add(CONFIGURATION_BUILD_NAME);
        items.add(CONFIGURATION_COMMAND_LINE);
        items.add(CONFIGURATION_CRON_EXPRESSION);
        items.add(CONFIGURATION_BUILD_DISABLED);
        cmdProjectItems.add(new ConfigurationItem("schedule", "Build Schedule",
                new ConfigurationItem("schedule", "Build Schedule", items)));

        items = new LinkedList<ConfigurationItem>();
        items.add(CONFIGURATION_BUILD_NAME);
        // type of build
        items.add(CONFIGURATION_XCODE_BUILD_WORKSPACE);
        // workspace build...
        items.add(CONFIGURATION_XCODE_WORKSPACE);
        items.add(CONFIGURATION_XCODE_SCHEME);
        // ...or project build
        items.add(CONFIGURATION_XCODE_TARGET);

        items.add(CONFIGURATION_XCODE_CONFIG);
        items.add(CONFIGURATION_XCODE_SDK);
        items.add(CONFIGURATION_XCODE_RUN_TESTS);
        items.add(CONFIGURATION_ANALYZE);
        items.add(CONFIGURATION_BUILD_DISABLED);
        xcodeProjectItems.add(new ConfigurationItem("schedule.default", "Default Build Schedule", items));

        items = new LinkedList<ConfigurationItem>();
        items.add(CONFIGURATION_BUILD_NAME);
        // type of build
        items.add(CONFIGURATION_XCODE_BUILD_WORKSPACE);
        // workspace build...
        items.add(CONFIGURATION_XCODE_WORKSPACE);
        items.add(CONFIGURATION_XCODE_SCHEME);
        // ...or project build
        items.add(CONFIGURATION_XCODE_TARGET);

        items.add(CONFIGURATION_XCODE_CONFIG);
        items.add(CONFIGURATION_XCODE_SDK);
        items.add(CONFIGURATION_XCODE_RUN_TESTS);
        items.add(CONFIGURATION_ANALYZE);
        items.add(CONFIGURATION_CRON_EXPRESSION);
        items.add(CONFIGURATION_BUILD_DISABLED);
        xcodeProjectItems.add(new ConfigurationItem("schedule", "Build Schedule",
                new ConfigurationItem("schedule", "Build Schedule", items)));

        // a fallback for other project types. Allow naming and then a named schedule.
        // This could do anything based on the builder code loaded at the time...
        items = new LinkedList<ConfigurationItem>();
        items.add(CONFIGURATION_BUILD_NAME);
        items.add(CONFIGURATION_BUILD_DISABLED);
        otherProjectItems.add(new ConfigurationItem("schedule.default", "Default Build Schedule", items));
        items = new LinkedList<ConfigurationItem>();
        items.add(CONFIGURATION_BUILD_NAME);
        items.add(CONFIGURATION_CRON_EXPRESSION);
        items.add(CONFIGURATION_BUILD_DISABLED);
        otherProjectItems.add(new ConfigurationItem("schedule", "Build Schedule",
                new ConfigurationItem("schedule", "Build Schedule", items)));

        for (Project project : Manager.getStorageInstance().getProjects()) {
            scheduler.resetProject(project);
        }

        // find a sensible default for maven.home
        File maven = FileUtil.lookupGrandparentInPath("mvn");
        if (maven == null) {
            maven = FileUtil.lookupGrandparentInPath("mvn.bat");
        }
        if (maven != null) {
            CONFIGURATION_MAVEN_HOME.setDefault(maven.getAbsolutePath());
        }

        // find a sensible default for ant.home
        File ant = FileUtil.lookupParentInPath("ant");
        if (ant == null) {
            ant = FileUtil.lookupParentInPath("ant.bat");
        }
        if (ant != null) {
            CONFIGURATION_ANT_HOME.setDefault(ant.getAbsolutePath());
        }

        // find a sensible default for gradle.home
        File gradle = FileUtil.lookupParentInPath("gradle");
        if (gradle == null) {
            gradle = FileUtil.lookupParentInPath("gradle.bat");
        }
        if (gradle != null) {
            CONFIGURATION_GRADLE_HOME.setDefault(gradle.getAbsolutePath());
        }

        // lookup the different binaries for eclipse...
        File eclipse = FileUtil.lookupParentInPath("eclipse");
        if (eclipse == null) {
            eclipse = FileUtil.lookupParentInPath("eclipse.exe");
        }

        if (eclipse != null) {
            CONFIGURATION_ECLIPSE_HOME.setDefault(eclipse.getAbsolutePath());
        }
    }

    public String getName() {
        return "Builds";
    }

    public String getApplicationId() {
        return ID;
    }

    public String getDescription() {
        return "The " + Manager.getStorageInstance().getGlobalConfiguration().getProductName()
                + " continuous integration application";
    }

    public List<MenuLink> getLinks(Project project) {
        return links;
    }

    public List<String> getEventTypes() {
        return eventTypes;
    }

    public Class[] getPersistantClasses() {
        return new Class[] { BuildFailedEvent.class, BuildSucceededEvent.class, UploadApplicationEvent.class };
    }

    @Override
    public Class<? extends Page>[] getPages() {
        return new Class[] { CI.class, Tests.class, View.class };
    }

    @Override
    public Class<? extends Api>[] getApis() {
        return (Class<? extends Api>[]) new Class[] { BuildStatusApi.class };
    }

    @Override
    public Class<? extends Page> getHomePage() {
        return CI.class;
    }

    @Override
    public Permission[] getPermissions() {
        return new Permission[] { new BuildForcePermission(), new BuildListPermission(),
                new BuildViewPermission() };
    }

    @Override
    public LinkProvider[] getLinkProviders() {
        return new LinkProvider[] { new BuildLinkProvider() };
    }

    @Override
    public IRCCommand[] getIRCCommands() {
        return new IRCCommand[] { new BuildCommand() };
    }

    @Override
    public List<ConfigurationItem> getConfigurationItems() {
        return globalItems;
    }

    protected List<ConfigurationItem> getMavenProjectConfiguration(String type) {
        List<ConfigurationItem> mavenItems = new LinkedList<ConfigurationItem>();

        List<ConfigurationItem> items = new LinkedList<ConfigurationItem>();
        items.add(CONFIGURATION_BUILD_NAME);
        items.add(CONFIGURATION_MAVEN_GOALS);
        items.add(CONFIGURATION_MAVEN_PROFILES);
        items.add(CONFIGURATION_MAVEN_HOME_OVERRIDE);
        if (type.startsWith("apk")) {
            items.add(CONFIGURATION_ANALYZE);
        }
        items.add(CONFIGURATION_BUILD_DISABLED);

        mavenItems.add(new ConfigurationItem("schedule.default", "Default Build Schedule", items));
        items = new LinkedList<ConfigurationItem>();
        items.add(CONFIGURATION_BUILD_NAME);
        items.add(CONFIGURATION_MAVEN_GOALS);
        items.add(CONFIGURATION_MAVEN_PROFILES);
        items.add(CONFIGURATION_MAVEN_HOME_OVERRIDE);
        if (type.startsWith("apk")) {
            items.add(CONFIGURATION_ANALYZE);
        }
        items.add(CONFIGURATION_CRON_EXPRESSION);
        items.add(CONFIGURATION_BUILD_DISABLED);
        mavenItems.add(new ConfigurationItem("schedule", "Build Schedule",
                new ConfigurationItem("schedule", "Build Schedule", items)));

        return mavenItems;
    }

    @Override
    public List<ConfigurationItem> getProjectConfigurationItems(Project project) {
        if (project instanceof MavenTwoProject) {
            return getMavenProjectConfiguration(((MavenTwoProject) project).getPackaging());
        } else if (project instanceof AntProject) {
            return antProjectItems;
        } else if (project instanceof GradleProject) {
            return gradleProjectItems;
        } else if (project instanceof EclipseProject) {
            return eclipseProjectItems;
        } else if (project instanceof CommandLineProject) {
            return cmdProjectItems;
        } else if (project instanceof XCodeProject) {
            return xcodeProjectItems;
        } else if (project.equals(StoredProject.getDefault())) {
            return new LinkedList<ConfigurationItem>();
        } else {
            return otherProjectItems;
        }
    }

    @Override
    public void onProjectConfigurationChanged(Project project) {
        scheduler.resetProject(project);
    }

    public static List<Build> getBuildsForProject(Project project) {
        Session session = ((HibernateStorage) Manager.getStorageInstance()).getHibernateSession();

        Query q = session.createQuery("from Build b where id.project.id = :pid order by id.id desc");
        q.setString("pid", project.getId());
        q.setMaxResults(25);
        return q.list();
    }

    public List<Build> getRunningBuilds() {
        Session session = ((HibernateStorage) Manager.getStorageInstance()).getHibernateSession();
        Transaction tx = session.beginTransaction();

        Query q = session.createQuery("from Build b where status = " + Build.BUILD_RUNNING);
        List<Build> ret = q.list();

        tx.commit();
        return ret;
    }

    static public Build getBuild(long id, Project project) {
        Session session = ((HibernateStorage) Manager.getStorageInstance()).getHibernateSession();

        Query q = session.createQuery("from Build b where id.id = :id and id.project.id = :pid");
        q.setLong("id", id);
        q.setString("pid", project.getId());
        return (Build) q.uniqueResult();
    }

    public static Build getLatestBuildForProject(Project project) {
        Build build = null;
        Session session = ((HibernateStorage) Manager.getStorageInstance()).getHibernateSession();

        Query q = session.createQuery("from Build b where id.project.id = :pid order by id.id desc");
        q.setString("pid", project.getId());
        q.setMaxResults(1);
        List<Build> builds = q.list();
        if (builds.size() > 0) {
            build = builds.get(0);
        }

        return build;
    }

    public static Build getLatestPassedBuildForProject(Project project) {
        Build build = null;
        Session session = ((HibernateStorage) Manager.getStorageInstance()).getHibernateSession();

        Query q = session.createQuery("from Build b where id.project.id = :pid and status = "
                + Build.BUILD_SUCCEEDED + " order by endTime desc");
        q.setString("pid", project.getId());
        q.setMaxResults(1);
        List<Build> builds = q.list();
        if (builds.size() > 0) {
            build = builds.get(0);
        }

        return build;
    }

    public static String getLatestChangePassed(Project project) {
        Build build = getLatestPassedBuildForProject(project);

        if (build == null) {
            return "";
        }
        return build.getRevision();
    }

    public static Build getPreviousChangePassed(Build current, Project project) {
        Build build = null;
        Session session = ((HibernateStorage) Manager.getStorageInstance()).getHibernateSession();

        Query q = session.createQuery("from Build b where id.project.id = :pid and status = "
                + Build.BUILD_SUCCEEDED + " and startTime < :beforeDate order by endTime desc");
        q.setString("pid", project.getId());
        q.setTimestamp("beforeDate", current.getStartTime());
        q.setMaxResults(1);
        List<Build> builds = q.list();
        if (builds.size() > 0) {
            build = builds.get(0);
        }

        return build;
    }

    public long addBuild(Build build) {
        Session session = HibernateUtil.getCurrentSession();

        Transaction tx = session.beginTransaction();
        IdProjectId id = (IdProjectId) session.save(build);
        long ret = id.getId();
        tx.commit();

        return ret;
    }

    public void saveBuild(Build build) {
        Session session = HibernateUtil.getCurrentSession();

        Transaction tx = session.beginTransaction();
        session.saveOrUpdate(build);
        tx.commit();
    }

    public static File getProjectDir(Project project) {
        File appDir = new File(Manager.getStorageInstance().getDataDirectory(), "builds");
        return new File(appDir, project.getId().replace(':', '_'));
    }

    public static UploadApplicationEvent getLatestUploadEvent(Project project) {
        Session session = ((HibernateStorage) Manager.getStorageInstance()).getHibernateSession();

        Query q = session.createQuery("from UploadApplicationEvent e where project.id = :pid order by time desc");
        q.setString("pid", project.getId());
        q.setMaxResults(1);
        List<UploadApplicationEvent> events = q.list();
        if (events.size() == 0) {
            return null;
        }

        return events.get(0);
    }
}