Java tutorial
/* * 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); } }