package hudson.plugins.javanet_trigger_installer; import hudson.model.AbstractProject; import hudson.model.Project; import hudson.scm.CVSSCM; import hudson.scm.SCM; import hudson.scm.SubversionSCM; import hudson.scm.SubversionSCM.ModuleLocation; import hudson.triggers.Trigger; import org.kohsuke.jnt.JNMailingList; import org.kohsuke.jnt.JNProject; import org.kohsuke.jnt.JavaNet; import org.kohsuke.jnt.ProcessingException; import org.kohsuke.jnt.SubscriptionMode; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.util.Collections; import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Represents the subscription/unsubscription related work to be executed. * * @author Kohsuke Kawaguchi */ abstract class Task { final AbstractProject<?,?> project; public Task(AbstractProject project) { this.project = project; } /** * Updates the project's configuration from the actual java.net setting. */ static final class Check extends Task { public Check(Project project) { super(project); } protected void execute(JNMailingList list) throws ProcessingException, IOException { if(getSubscriptionAddress(list)!=null) { if(project.getTrigger(JavaNetScmTrigger.class)!=null) return; LOGGER.info(project.getName()+" is apparently hooked to the java.net SCM change trigger"); project.addTrigger(new JavaNetScmTrigger()); } else { if(project.getTrigger(JavaNetScmTrigger.class)==null) return; LOGGER.info(project.getName()+" is apparently removed from the java.net SCM change trigger"); project.removeTrigger(Trigger.all().get(JavaNetScmTrigger.DescriptorImpl.class)); } } } /** * Updates java.net setting from the project configuration */ static final class Update extends Task { public Update(AbstractProject project) { super(project); } protected void execute(JNMailingList list) throws ProcessingException, IOException { // shall we subscribe or unsubscribe? boolean subscribe = project.getTrigger(JavaNetScmTrigger.class)!=null; String adrs = getSubscriptionAddress(list); if(adrs !=null) { if(subscribe) return; // already subscribed LOGGER.info("Unsubscribing "+project.getName()+" from java.net SCM trigger"); list.massUnsubscribe(Collections.singletonList(adrs), SubscriptionMode.NORMAL, null); } else { if(!subscribe) return; // already unsubscribed LOGGER.info("Subscribing "+project.getName()+" to java.net SCM trigger"); list.massSubscribe("hudson-"+escape(project.getName())+calcSuffix()+"@hudson.sfbay.sun.com", SubscriptionMode.NORMAL); } } private String calcSuffix() { SCM scm = project.getScm(); if(scm instanceof CVSSCM) { String branch = ((CVSSCM)scm).getBranch(); if(branch!=null) return "+branch="+branch; else return "+branch=trunk"; } // no particular suffix return ""; } } /** * Schedules the execution of this task. * It will be executed at some later point. */ void schedule() { synchronized(Worker.queue) { Worker.queue.add(this); Worker.queue.notify(); } } /** * Schedules the execution of this task * for at some later point, but make sure * it won't be blocked by other lower priority background tasks. */ void scheduleHighPriority() { synchronized(Worker.queue) { Worker.queue.add(0,this); Worker.queue.notify(); } } /** * Figures out the java.net project for this project. * * @return * null if we couldn't find it. */ private JNProject getProject(JavaNet con) throws ProcessingException { SCM scm = project.getScm(); if (scm instanceof CVSSCM) { CVSSCM cvs = (CVSSCM) scm; String cvsroot = cvs.getCvsRoot(); // basic sanity checking // we can't test 'cvs.dev.java.net' because of the bridges if(!cvsroot.endsWith(":/cvs")) return null; // then check the module name String m = cvs.getAllModules(); if(m.indexOf(" ")>=0) return null; // no support for multi-module for now. int idx = m.indexOf('/'); if(idx>=0) m = m.substring(0,idx); return con.getProject(m); } if (scm instanceof SubversionSCM) { SubversionSCM svn = (SubversionSCM)scm; ModuleLocation[] locs = svn.getLocations(); if(locs.length==0) return null; // no support for multi-module for now. try { URL url = new URL(locs[0].remote); Matcher matcher = SVN_PATH_PATTERN.matcher(url.getPath()); if(!matcher.matches()) return null; // doesn't look like java.net URL return con.getProject(matcher.group(1)); } catch (MalformedURLException e) { // this shouldn't really happen LOGGER.info("Failed to parse SVN URL "+locs[0].remote); return null; } } // java.net only support CVS and SVN return null; } public void execute(JavaNet connection) throws IOException , ProcessingException { JNProject p = getProject(connection); if(p==null) return; if(project.getScm() instanceof CVSSCM) { execute(p.getMailingLists().get("cvs")); return; } if(project.getScm() instanceof SubversionSCM) { execute(p.getMailingLists().get("commits")); return; } // huh? } /** * Checks if the trigger e-mail address is already subscribed. */ protected final String getSubscriptionAddress(JNMailingList list) throws ProcessingException { Pattern triggerAddress = Pattern.compile("hudson-"+escape(project.getName())+"(\\+.+)?@(kohsuke|hudson)\\.sfbay\\.sun\\.com"); for (String adrs : list.getSubscribers(SubscriptionMode.NORMAL)) { if(triggerAddress.matcher(adrs).matches()) return adrs; } return null; } /** * Escapes the character unsafe for e-mail address. */ private static String escape(String projectName) { // TODO: escape non-ASCII characters StringBuilder buf = new StringBuilder(projectName.length()); for( int i=0; i<projectName.length(); i++ ) { char ch = projectName.charAt(i); if(('a'<=ch && ch<='z') || ('z'<=ch && ch<='Z') || ('0'<=ch && ch<='9') || "-_.".indexOf(ch)>=0) buf.append(ch); else buf.append('_'); // escape } return projectName; } /** * Acts on the mailing list to perform the task. */ protected abstract void execute(JNMailingList list) throws ProcessingException, IOException; private static final Logger LOGGER = Logger.getLogger(Task.class.getName()); private static final Pattern SVN_PATH_PATTERN = Pattern.compile("/svn/([^/]+)/.+"); }