ItemGroupMixIn.java 9.5 KB
Newer Older
K
kohsuke 已提交
1 2 3
/*
 * The MIT License
 * 
4
 * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, CloudBees, Inc.
K
kohsuke 已提交
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
24 25
package hudson.model;

26 27 28
import hudson.Util;
import hudson.model.listeners.ItemListener;
import hudson.security.AccessControlled;
29 30
import hudson.util.CopyOnWriteMap;
import hudson.util.Function1;
31
import hudson.util.IOUtils;
32
import jenkins.model.Jenkins;
33 34
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
35

36 37
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletResponse;
38 39 40
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
41
import java.io.InputStream;
42 43 44 45
import java.util.Map;

/**
 * Defines a bunch of static methods to be used as a "mix-in" for {@link ItemGroup}
46
 * implementations. Not meant for a consumption from outside {@link ItemGroup}s.
47 48
 *
 * @author Kohsuke Kawaguchi
49
 * @see ViewGroupMixIn
50
 */
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
public abstract class ItemGroupMixIn {
    /**
     * {@link ItemGroup} for which we are working.
     */
    private final ItemGroup parent;
    private final AccessControlled acl;

    protected ItemGroupMixIn(ItemGroup parent, AccessControlled acl) {
        this.parent = parent;
        this.acl = acl;
    }

    /*
    * Callback methods to be implemented by the ItemGroup implementation.
    */

    /**
     * Adds a newly created item to the parent.
     */
    protected abstract void add(TopLevelItem item);

    /**
     * Assigns the root directory for a prospective item.
     */
    protected abstract File getRootDirFor(String name);


/*
 * The rest is the methods that provide meat.
 */

82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116
    /**
     * Loads all the child {@link Item}s.
     *
     * @param modulesDir
     *      Directory that contains sub-directories for each child item.
     */
    public static <K,V extends Item> Map<K,V> loadChildren(ItemGroup parent, File modulesDir, Function1<? extends K,? super V> key) {
        modulesDir.mkdirs(); // make sure it exists

        File[] subdirs = modulesDir.listFiles(new FileFilter() {
            public boolean accept(File child) {
                return child.isDirectory();
            }
        });
        CopyOnWriteMap.Tree<K,V> configurations = new CopyOnWriteMap.Tree<K,V>();
        for (File subdir : subdirs) {
            try {
                V item = (V) Items.load(parent,subdir);
                configurations.put(key.call(item), item);
            } catch (IOException e) {
                e.printStackTrace(); // TODO: logging
            }
        }

        return configurations;
    }

    /**
     * {@link Item} -> name function.
     */
    public static final Function1<String,Item> KEYED_BY_NAME = new Function1<String, Item>() {
        public String call(Item item) {
            return item.getName();
        }
    };
117 118 119

    /**
     * Creates a {@link TopLevelItem} from the submission of the '/lib/hudson/newFromList/formList'
120
     * or throws an exception if it fails.
121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137
     */
    public synchronized TopLevelItem createTopLevelItem( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
        acl.checkPermission(Job.CREATE);

        TopLevelItem result;

        String requestContentType = req.getContentType();
        if(requestContentType==null)
            throw new Failure("No Content-Type header set");

        boolean isXmlSubmission = requestContentType.startsWith("application/xml") || requestContentType.startsWith("text/xml");

        String name = req.getParameter("name");
        if(name==null)
            throw new Failure("Query parameter 'name' is required");

        {// check if the name looks good
138
            Jenkins.checkGoodName(name);
139 140 141 142 143 144 145 146
            name = name.trim();
            if(parent.getItem(name)!=null)
                throw new Failure(Messages.Hudson_JobAlreadyExists(name));
        }

        String mode = req.getParameter("mode");
        if(mode!=null && mode.equals("copy")) {
            String from = req.getParameter("from");
K
Kohsuke Kawaguchi 已提交
147 148

            // resolve a name to Item
149 150 151
            Item src = null;
            if (!from.startsWith("/"))
                src = parent.getItem(from);
K
Kohsuke Kawaguchi 已提交
152
            if (src==null)
153
                src = Jenkins.getInstance().getItemByFullName(from);
K
Kohsuke Kawaguchi 已提交
154

155 156 157 158 159 160
            if(src==null) {
                if(Util.fixEmpty(from)==null)
                    throw new Failure("Specify which job to copy");
                else
                    throw new Failure("No such job: "+from);
            }
161
            if (!(src instanceof TopLevelItem))
162 163 164 165 166 167 168 169 170 171 172 173 174
                throw new Failure(from+" cannot be copied");

            result = copy((TopLevelItem) src,name);
        } else {
            if(isXmlSubmission) {
                result = createProjectFromXML(name, req.getInputStream());
                rsp.setStatus(HttpServletResponse.SC_OK);
                return result;
            } else {
                if(mode==null)
                    throw new Failure("No mode given");

                // create empty job and redirect to the project config screen
175
                result = createProject(Items.all().findByName(mode), name, true);
176 177 178
            }
        }

K
Kohsuke Kawaguchi 已提交
179
        rsp.sendRedirect2(redirectAfterCreateItem(req, result));
180 181 182
        return result;
    }

183 184 185 186 187 188 189
    /**
     * Computes the redirection target URL for the newly created {@link TopLevelItem}.
     */
    protected String redirectAfterCreateItem(StaplerRequest req, TopLevelItem result) throws IOException {
        return req.getContextPath()+'/'+result.getUrl()+"configure";
    }

190 191 192 193 194 195 196 197 198 199 200 201 202
    /**
     * Copies an existing {@link TopLevelItem} to a new name.
     */
    @SuppressWarnings({"unchecked"})
    public synchronized <T extends TopLevelItem> T copy(T src, String name) throws IOException {
        acl.checkPermission(Job.CREATE);

        T result = (T)createProject(src.getDescriptor(),name,false);

        // copy config
        Util.copyFile(Items.getConfigFile(src).getFile(),Items.getConfigFile(result).getFile());

        // reload from the new config
203 204 205 206 207 208
        Items.updatingByXml.set(true);
        try {
            result = (T)Items.load(parent,result.getRootDir());
        } finally {
            Items.updatingByXml.set(false);
        }
209 210 211 212
        result.onCopiedFrom(src);

        add(result);
        ItemListener.fireOnCopied(src,result);
213
        Jenkins.getInstance().rebuildDependencyGraphAsync();
214 215 216 217 218 219 220

        return result;
    }

    public synchronized TopLevelItem createProjectFromXML(String name, InputStream xml) throws IOException {
        acl.checkPermission(Job.CREATE);

221
        Jenkins.getInstance().getProjectNamingStrategy().checkName(name);
222 223 224 225 226 227 228
        // place it as config.xml
        File configXml = Items.getConfigFile(getRootDirFor(name)).getFile();
        configXml.getParentFile().mkdirs();
        try {
            IOUtils.copy(xml,configXml);

            // load it
229 230 231 232 233 234 235
            TopLevelItem result;
            Items.updatingByXml.set(true);
            try {
                result = (TopLevelItem)Items.load(parent,configXml.getParentFile());
            } finally {
                Items.updatingByXml.set(false);
            }
236 237 238
            add(result);

            ItemListener.fireOnCreated(result);
239
            Jenkins.getInstance().rebuildDependencyGraphAsync();
240 241 242 243 244 245 246 247 248 249 250 251 252

            return result;
        } catch (IOException e) {
            // if anything fails, delete the config file to avoid further confusion
            Util.deleteRecursive(configXml.getParentFile());
            throw e;
        }
    }

    public synchronized TopLevelItem createProject( TopLevelItemDescriptor type, String name, boolean notify )
            throws IOException {
        acl.checkPermission(Job.CREATE);

253
        Jenkins.getInstance().getProjectNamingStrategy().checkName(name);
254 255 256 257 258 259 260 261 262
        if(parent.getItem(name)!=null)
            throw new IllegalArgumentException("Project of the name "+name+" already exists");

        TopLevelItem item;
        try {
            item = type.newInstance(parent,name);
        } catch (Exception e) {
            throw new IllegalArgumentException(e);
        }
263 264 265 266 267
        try {
            callOnCreatedFromScratch(item);
        } catch (AbstractMethodError e) {
            // ignore this error. Must be older plugin that doesn't have this method
        }
268 269
        item.save();
        add(item);
270
        Jenkins.getInstance().rebuildDependencyGraphAsync();
271 272 273 274 275 276

        if (notify)
            ItemListener.fireOnCreated(item);

        return item;
    }
277 278 279 280 281 282 283

    /**
     * Pointless wrapper to avoid HotSpot problem. See JENKINS-5756
     */
    private void callOnCreatedFromScratch(TopLevelItem item) {
        item.onCreatedFromScratch();
    }
284
}