org.hudsonci.plugins.team.cli.CopyTeamCommand.java Source code

Java tutorial

Introduction

Here is the source code for org.hudsonci.plugins.team.cli.CopyTeamCommand.java

Source

/*
 * Copyright (c) 2013 Hudson.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    Hudson - initial API and implementation and/or initial documentation
 */

package org.hudsonci.plugins.team.cli;

import hudson.Extension;
import hudson.XmlFile;
import hudson.cli.CLICommand;
import hudson.model.Failure;
import hudson.model.Hudson;
import hudson.model.Job;
import hudson.model.TopLevelItem;
import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.StringTokenizer;
import org.apache.commons.lang.StringUtils;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.eclipse.hudson.security.team.Team;
import org.eclipse.hudson.security.team.TeamManager;
import org.eclipse.hudson.security.team.TeamManager.TeamAlreadyExistsException;
import org.eclipse.hudson.security.team.TeamManager.TeamNotFoundException;
import org.eclipse.hudson.security.team.TeamNode;
import org.eclipse.hudson.security.team.TeamView;
import org.kohsuke.args4j.Argument;
import org.kohsuke.args4j.Option;

/**
 * Copy a team and its jobs from the command line. User must be sys admin.
 * <h3>EMAIL
 * Email <recipients> are replaced with contents of email argument.
 * <pre>
 * <project>
 *   <project-properties class="java.util.concurrent.ConcurrentHashMap">
 *     <entry>
 *       <string>hudson-tasks-Mailer</string>
 *       <external-property>
 *         <originalValue class="hudson.tasks.Mailer">
 *           <recipients>${email}</recipients>
 *           <dontNotifyEveryUnstableBuild>false</dontNotifyEveryUnstableBuild>
 *           <sendToIndividuals>false</sendToIndividuals>
 *         </originalValue>
 *         <propertyOverridden>false</propertyOverridden>
 *         <modified>true</modified>
 *       </external-property>
 *     </entry>
 * </pre>
 * If email not specified, replace entire entry with:
 * <pre>
 * <entry>
 *   <string>hudson-tasks-Mailer</string>
 *   <external-property>
 *     <propertyOverridden>false</propertyOverridden>
 *     <modified>false</modified>
 *   </external-property>
 * </entry>
 * </pre>
 * Cascading project names qualified with the old team name are requalified
 * with the new team name. E.g., for copy-team Team1 TeamX
 * <pre>
 * <project>
 *   <cascadingProjectName>Team1.JobBill2</cascadingProjectName>
 *   <cascadingChildrenNames class="java.util.concurrent.CopyOnWriteArraySet">
 *     <string>Team1.JobBill5</string>
 *   </cascadingChildrenNames>
 * </pre>
 * Is converted to:
 * <pre>
 * <project>
 *   <cascadingProjectName>TeamX.JobBill2</cascadingProjectName>
 *   <cascadingChildrenNames class="java.util.concurrent.CopyOnWriteArraySet">
 *     <string>TeamX.JobBill5</string>
 *   </cascadingChildrenNames>
 * </pre>
 * If build trigger is specified, replace old team name with new team name, e.g.,
 * <pre>
 * <project>
 *   <project-properties class="java.util.concurrent.ConcurrentHashMap">
 *     <entry>
 *       <string>hudson-tasks-BuildTrigger</string>
 *       <external-property>
 *         <originalValue class="hudson.tasks.BuildTrigger">
 *           <childProjects>Team1.JobBill4, Team1.JobBill5</childProjects>
 * </pre>
 * Is converted to:
 * <pre>
 * <project>
 *   <project-properties class="java.util.concurrent.ConcurrentHashMap">
 *     <entry>
 *       <string>hudson-tasks-BuildTrigger</string>
 *       <external-property>
 *         <originalValue class="hudson.tasks.BuildTrigger">
 *           <childProjects>TeamX.JobBill4, TeamX.JobBill5</childProjects>
 * </pre>
 * Note that project names that are not qualified with the old team name
 * are untouched.
 * <h3>-nodes option
 * <table>
 * <tr>
 * <th>Value</th>
 * <th>Meaning</th>
 * </tr>
 * <tr>
 * <td>move</td>
 * <td>Move nodes owned by the from team to the to team.</td>
 * <td>visible</td>
 * <td>Make nodes owned by the from team visible to the to team</td>
 * <td>ignore</td>
 * <td>Do nothing about nodes owned by the from team (default)</td>
 * </tr>
 * </table>
 * <table>
 * <h3>-views option
 * <tr>
 * <th>Value</th>
 * <th>Meaning</th>
 * </tr>
 * <tr>
 * <td>move</td>
 * <td>Move views owned by the from team to the to team.</td>
 * <td>visible</td>
 * <td>Make views owned by the from team visible to the to team</td>
 * <td>ignore</td>
 * <td>Do nothing about views owned by the from team (default)</td>
 * </tr>
 * </table>
 * @author Bob Foster
 */
@Extension
public class CopyTeamCommand extends CLICommand {

    @Override
    public String getShortDescription() {
        return "Copy a team and its jobs to a newly created team";
    }

    @Argument(metaVar = "FROM", usage = "Team name to copy (required)", required = true, index = 0)
    public String from;
    @Argument(metaVar = "TO", usage = "Team name to create (required)", required = true, index = 1)
    public String to;
    @Argument(metaVar = "EMAIL", usage = "Email recipients separated by commas (optional); if not specified, recipients will be removed", required = false, index = 2)
    public String email;
    @Option(name = "-n", aliases = "--nodes", usage = "MOVE (move nodes to new team), VISIBLE (make nodes visible to new team), IGNORE (ignore nodes - default), ")
    public String nodes;
    @Option(name = "-v", aliases = "--views", usage = "MOVE (move views to new team), VISIBLE (make views visible to new team), IGNORE (ignore views - default), ")
    public String views;

    protected int run() throws Exception {
        Hudson h = Hudson.getInstance();

        if (!h.isTeamManagementEnabled()) {
            stderr.println("Team management is not enabled");
            return -1;
        }

        TeamManager teamManager = h.getTeamManager();

        if (!teamManager.isCurrentUserSysAdmin()) {
            stderr.println("User not authorized to create team");
            return -1;
        }

        Team fromTeam;
        try {
            fromTeam = teamManager.findTeam(from);
        } catch (TeamNotFoundException e) {
            stderr.println("From team " + from + " not found");
            return -1;
        }

        if (nodes != null && !("move".equalsIgnoreCase(nodes) || "visible".equalsIgnoreCase(nodes)
                || "ignore".equalsIgnoreCase(nodes))) {
            stderr.println("nodes must be one of move, visible or ignore");
        }

        if (views != null && !("move".equalsIgnoreCase(views) || "visible".equalsIgnoreCase(views)
                || "ignore".equalsIgnoreCase(views))) {
            stderr.println("views must be one of move, visible or ignore");
        }

        Team toTeam = null;
        try {
            toTeam = teamManager.createTeam(to);
        } catch (IOException ex) {
            stderr.println(ex.getMessage());
            return -1;
        } catch (TeamAlreadyExistsException ex) {
            stderr.println("To team " + to + " already exists");
            return -1;
        }

        Set<String> jobNames = fromTeam.getJobNames();
        for (String jobName : jobNames) {
            String unqualifiedName = teamManager.getUnqualifiedJobName(jobName);
            TopLevelItem item = h.getItem(jobName);
            if (item instanceof Job) {
                Job job = (Job) item;
                XmlFile file = job.getConfigFile();
                InputStream in;
                try {
                    in = fixConfigFile(file, from, to, email);
                } catch (Failure ex) {
                    stderr.println("Error reading config.xml for job " + jobName);
                    stderr.println(ex.getMessage());
                    return -1;
                }
                h.createProjectFromXML(unqualifiedName, to, in);
            }
        }

        if (nodes != null) {
            if ("move".equalsIgnoreCase(nodes)) {
                for (String nodeName : fromTeam.getNodeNames()) {
                    teamManager.moveNode(fromTeam, toTeam, nodeName);
                }
            } else if ("visible".equalsIgnoreCase(nodes)) {
                for (TeamNode teamNode : fromTeam.getNodes()) {
                    teamManager.addNodeVisibility(teamNode, toTeam.getName());
                    // Assume target team wants to be able to use the node.
                    teamManager.setNodeEnabled(teamNode.getId(), toTeam, true);
                }
            }
        }

        if (views != null) {
            if ("move".equalsIgnoreCase(views)) {
                for (String viewName : fromTeam.getViewNames()) {
                    teamManager.moveView(fromTeam, toTeam, viewName);
                }
            } else if ("visible".equalsIgnoreCase(nodes)) {
                for (TeamView teamView : fromTeam.getViews()) {
                    teamManager.addViewVisibility(teamView, toTeam.getName());
                }
            }
        }

        return 0;
    }

    /*
     * This method intentionally does not use XStream or any of the objects
     * associated with various elements in the config.xml file, dealing
     * instead with the "raw" XML, because the objects and their methods
     * have too many unforseeable side effects.
     */
    private InputStream fixConfigFile(XmlFile file, String oldTeam, String newTeam, String email) {
        InputStream in = null;
        String oldPrefix = oldTeam + TeamManager.TEAM_SEPARATOR;
        String newPrefix = newTeam + TeamManager.TEAM_SEPARATOR;
        try {
            in = new FileInputStream(file.getFile());
            SAXReader reader = new SAXReader();
            Document doc = reader.read(in);

            Element root = doc.getRootElement();
            // The root element name varies by project type, e.g.,
            // project, matrix-project, etc. Code assumes that
            // following elements are common to all project types.

            // Cascading
            Element cascadingParent = root.element("cascadingProjectName");
            if (cascadingParent != null) {
                fixTeamName(cascadingParent, oldPrefix, newPrefix);
            }
            Element cascadingChildren = root.element("cascadingChildrenNames");
            if (cascadingChildren != null) {
                for (Object elem : cascadingChildren.elements("string")) {
                    fixTeamName((Element) elem, oldPrefix, newPrefix);
                }
            }
            // Properties (open-ended problem)
            Element properties = root.element("project-properties");
            if (properties == null) {
                throw new Failure("Project has no <project-properties>");
            }
            List<Element> removeEntries = new ArrayList<Element>();
            for (Object ent : properties.elements("entry")) {
                Element entry = (Element) ent;
                Element extProp = entry.element("external-property");
                Element origValue = extProp != null ? extProp.element("originalValue") : null;
                Element str = entry.element("string");
                if (str != null) {
                    String propName = str.getTextTrim();
                    if ("hudson-tasks-Mailer".equals(propName)) {
                        // email
                        if (extProp != null) {
                            if (email == null) {
                                // A recent fix removes entries that are not specified
                                removeEntries.add(entry);
                                /*
                                // Replace entire entry
                                entry.remove(extProp);
                                extProp = entry.addElement("external-property");
                                Element propOver = extProp.addElement("propertyOverridden");
                                propOver.setText("false");
                                Element modified = extProp.addElement("modified");
                                modified.setText("false");
                                */
                            } else if (origValue != null) {
                                fixEmailProperty(origValue, email);
                            }
                        }
                    } else if ("hudson-tasks-BuildTrigger".equals(propName)) {
                        // trigger
                        if (origValue != null) {
                            fixTriggerProperty(origValue, oldPrefix, newPrefix);
                        }
                    } else if ("builders".equals(propName)) {
                        // can be many of these
                        Element describableList = entry.element("describable-list-property");
                        origValue = describableList != null ? describableList.element("originalValue") : null;
                        // copyartifact
                        List artifacts = origValue != null
                                ? origValue.elements("hudson.plugins.copyartifact.CopyArtifact")
                                : null;
                        if (artifacts != null) {
                            for (Object obj : artifacts) {
                                Element copyArtifact = (Element) obj;
                                Element projectName = copyArtifact.element("projectName");
                                fixTeamName(projectName, oldPrefix, newPrefix);
                            }
                        }
                        // multijob
                        List multiJob = origValue != null
                                ? origValue.elements("com.tikal.jenkins.plugins.multijob.MultiJobBuilder")
                                : null;
                        for (Object obj : multiJob) {
                            Element builder = (Element) obj;
                            List jobs = builder.elements("phaseJobs");
                            if (jobs != null) {
                                for (Object j : jobs) {
                                    Element job = (Element) j;
                                    List configs = job
                                            .elements("com.tikal.jenkins.plugins.multijob.PhaseJobsConfig");
                                    if (configs != null) {
                                        for (Object c : configs) {
                                            Element config = (Element) c;
                                            Element jobName = config.element("jobName");
                                            fixTeamName(jobName, oldPrefix, newPrefix);
                                        }
                                    }
                                }
                            }
                        }

                    } else if ("hudson-plugins-redmine-RedmineProjectProperty".equals(propName)) {
                        Element baseProp = entry.element("base-property");
                        Element projectName = baseProp != null ? baseProp.element("projectName") : null;
                        if (projectName != null) {
                            fixTeamName(projectName, oldPrefix, newPrefix);
                        }
                    }
                }
            }
            for (Element entry : removeEntries) {
                properties.remove(entry);
            }

            StringWriter writer = new StringWriter();
            doc.write(writer);
            return new ByteArrayInputStream(writer.toString().getBytes("UTF-8"));
        } catch (FileNotFoundException ex) {
            throw new Failure("File not found");
        } catch (DocumentException ex) {
            throw new Failure("Unable to parse document");
        } catch (IOException ex) {
            throw new Failure("Document write failed");
        } finally {
            try {
                in.close();
            } catch (IOException ex) {
                ;
            }
        }
    }

    private void fixTeamName(Element element, String oldPrefix, String newPrefix) {
        String jobName = element.getTextTrim();
        if (jobName.startsWith(oldPrefix)) {
            element.setText(newPrefix + jobName.substring(oldPrefix.length()));
        }
    }

    private void fixTriggerProperty(Element origValue, String oldPrefix, String newPrefix) {
        Element cp = origValue.element("childProjects");
        String childProjects = cp.getTextTrim();
        List<String> children = new ArrayList<String>();
        StringTokenizer st = new StringTokenizer(childProjects, ", ");
        boolean changed = false;
        while (st.hasMoreTokens()) {
            String child = st.nextToken();
            if (child.startsWith(oldPrefix)) {
                changed = true;
                child = newPrefix + child.substring(oldPrefix.length());
            }
            children.add(child);
        }
        if (changed) {
            childProjects = StringUtils.join(children, ", ");
            cp.setText(childProjects);
        }
    }

    private void fixEmailProperty(Element origValue, String email) {
        Element recipients = origValue.element("recipients");
        if (recipients != null) {
            recipients.setText(email);
        }
    }
}