ItemGroupMixIn.java 11.0 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
import hudson.Util;
27
import hudson.XmlFile;
28 29
import hudson.model.listeners.ItemListener;
import hudson.security.AccessControlled;
30 31
import hudson.util.CopyOnWriteMap;
import hudson.util.Function1;
32
import hudson.util.IOUtils;
33
import jenkins.model.Jenkins;
34 35
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
36

37 38
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletResponse;
39 40 41
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
42
import java.io.InputStream;
43
import java.util.Map;
44 45
import java.util.logging.Level;
import java.util.logging.Logger;
46
import jenkins.security.NotReallyRoleSensitiveCallable;
47 48 49

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

86 87 88 89 90 91 92 93 94 95 96
    /**
     * 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) {
O
Olivier Lamy 已提交
97
                return child.isDirectory();
98 99 100 101 102
            }
        });
        CopyOnWriteMap.Tree<K,V> configurations = new CopyOnWriteMap.Tree<K,V>();
        for (File subdir : subdirs) {
            try {
103 104 105
                // Try to retain the identity of an existing child object if we can.
                V item = (V) parent.getItem(subdir.getName());
                if (item == null) {
106 107
                    XmlFile xmlFile = Items.getConfigFile( subdir );
                    if (xmlFile.exists()) {
O
Olivier Lamy 已提交
108
                        item = (V) Items.load( parent, subdir );
109 110
                    }else{
                        Logger.getLogger( ItemGroupMixIn.class.getName() ).log( Level.WARNING, "could not find file " + xmlFile.getFile());
111
                        continue;
O
Olivier Lamy 已提交
112
                    }
113 114 115
                } else {
                    item.onLoad(parent, subdir.getName());
                }
116 117
                configurations.put(key.call(item), item);
            } catch (IOException e) {
118
                Logger.getLogger(ItemGroupMixIn.class.getName()).log(Level.WARNING, "could not load " + subdir, e);
119 120
            } catch (Exception e) {
                Logger.getLogger(ItemGroupMixIn.class.getName()).log(Level.WARNING, "could not load " + subdir, e);
121 122 123 124 125 126 127 128 129 130 131 132 133 134
            }
        }

        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();
        }
    };
135 136 137

    /**
     * Creates a {@link TopLevelItem} from the submission of the '/lib/hudson/newFromList/formList'
138
     * or throws an exception if it fails.
139 140
     */
    public synchronized TopLevelItem createTopLevelItem( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
141
        acl.checkPermission(Item.CREATE);
142 143 144 145 146 147 148 149 150 151 152 153 154 155

        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
156
            Jenkins.checkGoodName(name);
157 158 159 160 161 162 163 164
            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 已提交
165 166

            // resolve a name to Item
167 168 169
            Item src = null;
            if (!from.startsWith("/"))
                src = parent.getItem(from);
K
Kohsuke Kawaguchi 已提交
170
            if (src==null)
171
                src = Jenkins.getInstance().getItemByFullName(from);
K
Kohsuke Kawaguchi 已提交
172

173 174 175 176 177 178
            if(src==null) {
                if(Util.fixEmpty(from)==null)
                    throw new Failure("Specify which job to copy");
                else
                    throw new Failure("No such job: "+from);
            }
179
            if (!(src instanceof TopLevelItem))
180 181 182 183 184 185 186 187 188 189 190
                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");
191 192 193 194
                TopLevelItemDescriptor descriptor = Items.all().findByName(mode);
                if (descriptor == null) {
                    throw new Failure("No item type ‘" + mode + "’ is known");
                }
195 196

                // create empty job and redirect to the project config screen
197
                result = createProject(descriptor, name, true);
198 199 200
            }
        }

K
Kohsuke Kawaguchi 已提交
201
        rsp.sendRedirect2(redirectAfterCreateItem(req, result));
202 203 204
        return result;
    }

205 206 207 208 209 210 211
    /**
     * 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";
    }

212 213 214 215 216
    /**
     * Copies an existing {@link TopLevelItem} to a new name.
     */
    @SuppressWarnings({"unchecked"})
    public synchronized <T extends TopLevelItem> T copy(T src, String name) throws IOException {
217
        acl.checkPermission(Item.CREATE);
218
        src.checkPermission(Item.EXTENDED_READ);
219 220 221 222 223 224 225

        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
226
        final File rootDir = result.getRootDir();
227
        result = Items.whileUpdatingByXml(new NotReallyRoleSensitiveCallable<T,IOException>() {
228 229 230 231
            @Override public T call() throws IOException {
                return (T) Items.load(parent, rootDir);
            }
        });
232 233 234 235
        result.onCopiedFrom(src);

        add(result);
        ItemListener.fireOnCopied(src,result);
236
        Jenkins.getInstance().rebuildDependencyGraphAsync();
237 238 239 240 241

        return result;
    }

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

244
        Jenkins.getInstance().getProjectNamingStrategy().checkName(name);
245 246 247
        if (parent.getItem(name) != null) {
            throw new IllegalArgumentException(parent.getDisplayName() + " already contains an item '" + name + "'");
        }
248
        // TODO what if we have no DISCOVER permission on the existing job?
249

250 251
        // place it as config.xml
        File configXml = Items.getConfigFile(getRootDirFor(name)).getFile();
252 253
        final File dir = configXml.getParentFile();
        dir.mkdirs();
254 255 256 257
        try {
            IOUtils.copy(xml,configXml);

            // load it
258
            TopLevelItem result = Items.whileUpdatingByXml(new NotReallyRoleSensitiveCallable<TopLevelItem,IOException>() {
259 260 261 262
                @Override public TopLevelItem call() throws IOException {
                    return (TopLevelItem) Items.load(parent, dir);
                }
            });
263 264 265
            add(result);

            ItemListener.fireOnCreated(result);
266
            Jenkins.getInstance().rebuildDependencyGraphAsync();
267 268 269 270

            return result;
        } catch (IOException e) {
            // if anything fails, delete the config file to avoid further confusion
271
            Util.deleteRecursive(dir);
272 273 274 275 276 277
            throw e;
        }
    }

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

280
        Jenkins.getInstance().getProjectNamingStrategy().checkName(name);
281 282
        if(parent.getItem(name)!=null)
            throw new IllegalArgumentException("Project of the name "+name+" already exists");
283
        // TODO problem with DISCOVER as noted above
284

285
        TopLevelItem item = type.newInstance(parent, name);
286 287 288 289 290
        try {
            callOnCreatedFromScratch(item);
        } catch (AbstractMethodError e) {
            // ignore this error. Must be older plugin that doesn't have this method
        }
291 292
        item.save();
        add(item);
293
        Jenkins.getInstance().rebuildDependencyGraphAsync();
294 295 296 297 298 299

        if (notify)
            ItemListener.fireOnCreated(item);

        return item;
    }
300 301 302 303 304 305 306

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