ItemGroupMixIn.java 9.9 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
import java.util.Map;
43 44
import java.util.logging.Level;
import java.util.logging.Logger;
45 46 47

/**
 * Defines a bunch of static methods to be used as a "mix-in" for {@link ItemGroup}
48
 * implementations. Not meant for a consumption from outside {@link ItemGroup}s.
49 50
 *
 * @author Kohsuke Kawaguchi
51
 * @see ViewGroupMixIn
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 82 83
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.
 */

84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103
    /**
     * 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) {
104
                Logger.getLogger(ItemGroupMixIn.class.getName()).log(Level.WARNING, "could not load " + subdir, e);
105 106 107 108 109 110 111 112 113 114 115 116 117 118
            }
        }

        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();
        }
    };
119 120 121

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

        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
140
            Jenkins.checkGoodName(name);
141 142 143 144 145 146 147 148
            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 已提交
149 150

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

157 158 159 160 161 162
            if(src==null) {
                if(Util.fixEmpty(from)==null)
                    throw new Failure("Specify which job to copy");
                else
                    throw new Failure("No such job: "+from);
            }
163
            if (!(src instanceof TopLevelItem))
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");
175 176 177 178
                TopLevelItemDescriptor descriptor = Items.all().findByName(mode);
                if (descriptor == null) {
                    throw new Failure("No item type ‘" + mode + "’ is known");
                }
179 180

                // create empty job and redirect to the project config screen
181
                result = createProject(descriptor, name, true);
182 183 184
            }
        }

K
Kohsuke Kawaguchi 已提交
185
        rsp.sendRedirect2(redirectAfterCreateItem(req, result));
186 187 188
        return result;
    }

189 190 191 192 193 194 195
    /**
     * 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";
    }

196 197 198 199 200
    /**
     * Copies an existing {@link TopLevelItem} to a new name.
     */
    @SuppressWarnings({"unchecked"})
    public synchronized <T extends TopLevelItem> T copy(T src, String name) throws IOException {
201
        acl.checkPermission(Item.CREATE);
202 203 204 205 206 207 208

        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
209 210 211 212 213 214
        Items.updatingByXml.set(true);
        try {
            result = (T)Items.load(parent,result.getRootDir());
        } finally {
            Items.updatingByXml.set(false);
        }
215 216 217 218
        result.onCopiedFrom(src);

        add(result);
        ItemListener.fireOnCopied(src,result);
219
        Jenkins.getInstance().rebuildDependencyGraphAsync();
220 221 222 223 224

        return result;
    }

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

227
        Jenkins.getInstance().getProjectNamingStrategy().checkName(name);
228 229 230 231
        if (parent.getItem(name) != null) {
            throw new IllegalArgumentException(parent.getDisplayName() + " already contains an item '" + name + "'");
        }

232 233 234 235 236 237 238
        // place it as config.xml
        File configXml = Items.getConfigFile(getRootDirFor(name)).getFile();
        configXml.getParentFile().mkdirs();
        try {
            IOUtils.copy(xml,configXml);

            // load it
239 240 241 242 243 244 245
            TopLevelItem result;
            Items.updatingByXml.set(true);
            try {
                result = (TopLevelItem)Items.load(parent,configXml.getParentFile());
            } finally {
                Items.updatingByXml.set(false);
            }
246 247 248
            add(result);

            ItemListener.fireOnCreated(result);
249
            Jenkins.getInstance().rebuildDependencyGraphAsync();
250 251 252 253 254 255 256 257 258 259 260

            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 {
261
        acl.checkPermission(Item.CREATE);
262

263
        Jenkins.getInstance().getProjectNamingStrategy().checkName(name);
264 265 266
        if(parent.getItem(name)!=null)
            throw new IllegalArgumentException("Project of the name "+name+" already exists");

267
        TopLevelItem item = type.newInstance(parent, name);
268 269 270 271 272
        try {
            callOnCreatedFromScratch(item);
        } catch (AbstractMethodError e) {
            // ignore this error. Must be older plugin that doesn't have this method
        }
273 274
        item.save();
        add(item);
275
        Jenkins.getInstance().rebuildDependencyGraphAsync();
276 277 278 279 280 281

        if (notify)
            ItemListener.fireOnCreated(item);

        return item;
    }
282 283 284 285 286 287 288

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