File D:\maven-qcr-plugin-testsunitaires\qcr4java\jenkins-master\target\site\qc\core\src\main\java\hudson\model\AbstractItem.java.html

1 /*
2 * The MIT License
3 *
4 * Copyright (c) 2004-2011, Sun Microsystems, Inc., Kohsuke Kawaguchi,
5 * Daniel Dyer, Tom Huybrechts, Yahoo!, Inc.
6 *
7 * Permission is hereby granted, free of charge, to any person obtaining a copy
8 * of this software and associated documentation files (the "Software"), to deal
9 * in the Software without restriction, including without limitation the rights
10 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 * copies of the Software, and to permit persons to whom the Software is
12 * furnished to do so, subject to the following conditions:
13 *
14 * The above copyright notice and this permission notice shall be included in
15 * all copies or substantial portions of the Software.
16 *
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 * THE SOFTWARE.
24 */
25 package hudson.model;
26
27 import com.infradna.tool.bridge_method_injector.WithBridgeMethods;
28 import hudson.AbortException;
29 import hudson.XmlFile;
30 import hudson.Util;
31 import hudson.Functions;
32 import hudson.BulkChange;
33 import hudson.cli.declarative.CLIMethod;
34 import hudson.cli.declarative.CLIResolver;
35 import hudson.model.listeners.ItemListener;
36 import hudson.model.listeners.SaveableListener;
37 import hudson.security.AccessControlled;
38 import hudson.security.Permission;
39 import hudson.security.ACL;
40 import hudson.util.AlternativeUiTextProvider;
41 import hudson.util.AlternativeUiTextProvider.Message;
42 import hudson.util.AtomicFileWriter;
43 import hudson.util.IOException2;
44 import hudson.util.IOUtils;
45 import jenkins.model.Jenkins;
46 import org.apache.tools.ant.taskdefs.Copy;
47 import org.apache.tools.ant.types.FileSet;
48 import org.kohsuke.stapler.WebMethod;
49 import org.kohsuke.stapler.export.Exported;
50 import org.kohsuke.stapler.export.ExportedBean;
51
52 import java.io.File;
53 import java.io.IOException;
54 import java.util.Collection;
55 import java.util.HashMap;
56 import java.util.Map;
57
58 import org.kohsuke.stapler.StaplerRequest;
59 import org.kohsuke.stapler.StaplerResponse;
60 import org.kohsuke.stapler.Stapler;
61 import org.kohsuke.stapler.HttpDeletable;
62 import org.kohsuke.args4j.Argument;
63 import org.kohsuke.args4j.CmdLineException;
64 import org.kohsuke.stapler.interceptor.RequirePOST;
65
66 import javax.servlet.ServletException;
67 import javax.xml.transform.Source;
68 import javax.xml.transform.Transformer;
69 import javax.xml.transform.TransformerException;
70 import javax.xml.transform.TransformerFactory;
71 import javax.xml.transform.stream.StreamResult;
72 import javax.xml.transform.stream.StreamSource;
73
74 import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
75
76 /**
77 * Partial default implementation of {@link Item}.
78 *
79 * @author Kohsuke Kawaguchi
80 */
81 // Item doesn't necessarily have to be Actionable, but
82 // Java doesn't let multiple inheritance.
83 @ExportedBean
84 public abstract class AbstractItem extends Actionable implements Item, HttpDeletable, AccessControlled, DescriptorByNameOwner {
85 /**
86 * Project name.
87 */
88 protected /*final*/ transient String name;
89
90 /**
91 * Project description. Can be HTML.
92 */
93 protected volatile String description;
94
95 private transient ItemGroup parent;
96
97 protected String displayName;
98
99 protected AbstractItem(ItemGroup parent, String name) {
100 this.parent = parent;
101 doSetName(name);
102 }
103
104 public void onCreatedFromScratch() {
105 // noop
106 }
107
108 @Exported(visibility=999)
109 public String getName() {
110 return name;
111 }
112
113 /**
114 * Get the term used in the UI to represent this kind of
115 * {@link Item}. Must start with a capital letter.
116 */
117 public String getPronoun() {
118 return AlternativeUiTextProvider.get(PRONOUN, this, Messages.AbstractItem_Pronoun());
119 }
120
121 @Exported
122 /**
123 * @return The display name of this object, or if it is not set, the name
124 * of the object.
125 */
126 public String getDisplayName() {
127 if(null!=displayName) {
128 return displayName;
129 }
130 // if the displayName is not set, then return the name as we use to do
131 return getName();
132 }
133
134 @Exported
135 /**
136 * This is intended to be used by the Job configuration pages where
137 * we want to return null if the display name is not set.
138 * @return The display name of this object or null if the display name is not
139 * set
140 */
141 public String getDisplayNameOrNull() {
142 return displayName;
143 }
144
145 /**
146 * This method exists so that the Job configuration pages can use
147 * getDisplayNameOrNull so that nothing is shown in the display name text
148 * box if the display name is not set.
149 * @param displayName
150 * @throws IOException
151 */
152 public void setDisplayNameOrNull(String displayName) throws IOException {
153 setDisplayName(displayName);
154 }
155
156 public void setDisplayName(String displayName) throws IOException {
157 this.displayName = Util.fixEmpty(displayName);
158 save();
159 }
160
161 public File getRootDir() {
162 return parent.getRootDirFor(this);
163 }
164
165 /**
166 * This bridge method is to maintain binary compatibility with {@link TopLevelItem#getParent()}.
167 */
168 @WithBridgeMethods(value=Jenkins.class,castRequired=true)
169 public ItemGroup getParent() {
170 assert parent!=null;
171 return parent;
172 }
173
174 /**
175 * Gets the project description HTML.
176 */
177 @Exported
178 public String getDescription() {
179 return description;
180 }
181
182 /**
183 * Sets the project description HTML.
184 */
185 public void setDescription(String description) throws IOException {
186 this.description = description;
187 save();
188 }
189
190 /**
191 * Just update {@link #name} without performing the rename operation,
192 * which would involve copying files and etc.
193 */
194 protected void doSetName(String name) {
195 this.name = name;
196 }
197
198 /**
199 * Renames this item.
200 * Not all the Items need to support this operation, but if you decide to do so,
201 * you can use this method.
202 */
203 protected void renameTo(String newName) throws IOException {
204 // always synchronize from bigger objects first
205 final ItemGroup parent = getParent();
206 synchronized (parent) {
207 synchronized (this) {
208 // sanity check
209 if (newName == null)
210 throw new IllegalArgumentException("New name is not given");
211
212 // noop?
213 if (this.name.equals(newName))
214 return;
215
216 Item existing = parent.getItem(newName);
217 if (existing != null && existing!=this)
218 // the look up is case insensitive, so we need "existing!=this"
219 // to allow people to rename "Foo" to "foo", for example.
220 // see http://www.nabble.com/error-on-renaming-project-tt18061629.html
221 throw new IllegalArgumentException("Job " + newName
222 + " already exists");
223
224 String oldName = this.name;
225 File oldRoot = this.getRootDir();
226
227 doSetName(newName);
228 File newRoot = this.getRootDir();
229
230 boolean success = false;
231
232 try {// rename data files
233 boolean interrupted = false;
234 boolean renamed = false;
235
236 // try to rename the job directory.
237 // this may fail on Windows due to some other processes
238 // accessing a file.
239 // so retry few times before we fall back to copy.
240 for (int retry = 0; retry < 5; retry++) {
241 if (oldRoot.renameTo(newRoot)) {
242 renamed = true;
243 break; // succeeded
244 }
245 try {
246 Thread.sleep(500);
247 } catch (InterruptedException e) {
248 // process the interruption later
249 interrupted = true;
250 }
251 }
252
253 if (interrupted)
254 Thread.currentThread().interrupt();
255
256 if (!renamed) {
257 // failed to rename. it must be that some lengthy
258 // process is going on
259 // to prevent a rename operation. So do a copy. Ideally
260 // we'd like to
261 // later delete the old copy, but we can't reliably do
262 // so, as before the VM
263 // shuts down there might be a new job created under the
264 // old name.
265 Copy cp = new Copy();
266 cp.setProject(new org.apache.tools.ant.Project());
267 cp.setTodir(newRoot);
268 FileSet src = new FileSet();
269 src.setDir(getRootDir());
270 cp.addFileset(src);
271 cp.setOverwrite(true);
272 cp.setPreserveLastModified(true);
273 cp.setFailOnError(false); // keep going even if
274 // there's an error
275 cp.execute();
276
277 // try to delete as much as possible
278 try {
279 Util.deleteRecursive(oldRoot);
280 } catch (IOException e) {
281 // but ignore the error, since we expect that
282 e.printStackTrace();
283 }
284 }
285
286 success = true;
287 } finally {
288 // if failed, back out the rename.
289 if (!success)
290 doSetName(oldName);
291 }
292
293 callOnRenamed(newName, parent, oldName);
294
295 for (ItemListener l : ItemListener.all())
296 l.onRenamed(this, oldName, newName);
297 }
298 }
299 }
300
301 /**
302 * A pointless function to work around what appears to be a HotSpot problem. See JENKINS-5756 and bug 6933067
303 * on BugParade for more details.
304 */
305 private void callOnRenamed(String newName, ItemGroup parent, String oldName) throws IOException {
306 try {
307 parent.onRenamed(this, oldName, newName);
308 } catch (AbstractMethodError _) {
309 // ignore
310 }
311 }
312
313 /**
314 * Gets all the jobs that this {@link Item} contains as descendants.
315 */
316 public abstract Collection<? extends Job> getAllJobs();
317
318 public final String getFullName() {
319 String n = getParent().getFullName();
320 if(n.length()==0) return getName();
321 else return n+'/'+getName();
322 }
323
324 public final String getFullDisplayName() {
325 String n = getParent().getFullDisplayName();
326 if(n.length()==0) return getDisplayName();
327 else return n+" \u00BB "+getDisplayName();
328 }
329
330 public String getRelativeNameFrom(ItemGroup p) {
331 // first list up all the parents
332 Map<ItemGroup,Integer> parents = new HashMap<ItemGroup,Integer>();
333 int depth=0;
334 while (p!=null) {
335 parents.put(p, depth++);
336 if (p instanceof Item)
337 p = ((Item)p).getParent();
338 else
339 p = null;
340 }
341
342 StringBuilder buf = new StringBuilder();
343 Item i=this;
344 while (true) {
345 if (buf.length()>0) buf.insert(0,'/');
346 buf.insert(0,i.getName());
347 ItemGroup g = i.getParent();
348
349 Integer d = parents.get(g);
350 if (d!=null) {
351 String s="";
352 for (int j=d; j>0; j--)
353 s+="../";
354 return s+buf;
355 }
356
357 if (g instanceof Item)
358 i = (Item)g;
359 else
360 return null;
361 }
362 }
363
364 public String getRelativeNameFrom(Item item) {
365 return getRelativeNameFrom(item.getParent());
366 }
367
368 /**
369 * Called right after when a {@link Item} is loaded from disk.
370 * This is an opporunity to do a post load processing.
371 */
372 public void onLoad(ItemGroup<? extends Item> parent, String name) throws IOException {
373 this.parent = parent;
374 doSetName(name);
375 }
376
377 /**
378 * When a {@link Item} is copied from existing one,
379 * the files are first copied on the file system,
380 * then it will be loaded, then this method will be invoked
381 * to perform any implementation-specific work.
382 *
383 * <p>
384 *
385 *
386 * @param src
387 * Item from which it's copied from. The same type as {@code this}. Never null.
388 */
389 public void onCopiedFrom(Item src) {
390 }
391
392 public final String getUrl() {
393 // try to stick to the current view if possible
394 StaplerRequest req = Stapler.getCurrentRequest();
395 if (req != null) {
396 String seed = Functions.getNearestAncestorUrl(req,this);
397 if(seed!=null) {
398 // trim off the context path portion and leading '/', but add trailing '/'
399 return seed.substring(req.getContextPath().length()+1)+'/';
400 }
401 }
402
403 // otherwise compute the path normally
404 return getParent().getUrl()+getShortUrl();
405 }
406
407 public String getShortUrl() {
408 return getParent().getUrlChildPrefix()+'/'+Util.rawEncode(getName())+'/';
409 }
410
411 public String getSearchUrl() {
412 return getShortUrl();
413 }
414
415 @Exported(visibility=999,name="url")
416 public final String getAbsoluteUrl() {
417 String r = Jenkins.getInstance().getRootUrl();
418 if(r==null)
419 throw new IllegalStateException("Root URL isn't configured yet. Cannot compute absolute URL.");
420 return Util.encode(r+getUrl());
421 }
422
423 /**
424 * Remote API access.
425 */
426 public final Api getApi() {
427 return new Api(this);
428 }
429
430 /**
431 * Returns the {@link ACL} for this object.
432 */
433 public ACL getACL() {
434 return Jenkins.getInstance().getAuthorizationStrategy().getACL(this);
435 }
436
437 /**
438 * Short for {@code getACL().checkPermission(p)}
439 */
440 public void checkPermission(Permission p) {
441 getACL().checkPermission(p);
442 }
443
444 /**
445 * Short for {@code getACL().hasPermission(p)}
446 */
447 public boolean hasPermission(Permission p) {
448 return getACL().hasPermission(p);
449 }
450
451 /**
452 * Save the settings to a file.
453 */
454 public synchronized void save() throws IOException {
455 if(BulkChange.contains(this)) return;
456 getConfigFile().write(this);
457 SaveableListener.fireOnChange(this, getConfigFile());
458 }
459
460 public final XmlFile getConfigFile() {
461 return Items.getConfigFile(this);
462 }
463
464 public Descriptor getDescriptorByName(String className) {
465 return Jenkins.getInstance().getDescriptorByName(className);
466 }
467
468 /**
469 * Accepts the new description.
470 */
471 public synchronized void doSubmitDescription( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
472 checkPermission(CONFIGURE);
473
474 setDescription(req.getParameter("description"));
475 rsp.sendRedirect("."); // go to the top page
476 }
477
478 /**
479 * Deletes this item.
480 */
481 @CLIMethod(name="delete-job")
482 @RequirePOST
483 public void doDoDelete( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException, InterruptedException {
484 delete();
485 if (rsp != null) // null for CLI
486 rsp.sendRedirect2(req.getContextPath()+"/"+getParent().getUrl());
487 }
488
489 public void delete( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
490 try {
491 doDoDelete(req,rsp);
492 } catch (InterruptedException e) {
493 // TODO: allow this in Stapler
494 throw new ServletException(e);
495 }
496 }
497
498 /**
499 * Deletes this item.
500 *
501 * <p>
502 * Any exception indicates the deletion has failed, but {@link AbortException} would prevent the caller
503 * from showing the stack trace. This
504 */
505 public synchronized void delete() throws IOException, InterruptedException {
506 checkPermission(DELETE);
507 performDelete();
508
509 try {
510 invokeOnDeleted();
511 } catch (AbstractMethodError e) {
512 // ignore
513 }
514
515 Jenkins.getInstance().rebuildDependencyGraph();
516 }
517
518 /**
519 * A pointless function to work around what appears to be a HotSpot problem. See JENKINS-5756 and bug 6933067
520 * on BugParade for more details.
521 */
522 private void invokeOnDeleted() throws IOException {
523 getParent().onDeleted(this);
524 }
525
526 /**
527 * Does the real job of deleting the item.
528 */
529 protected void performDelete() throws IOException, InterruptedException {
530 getConfigFile().delete();
531 Util.deleteRecursive(getRootDir());
532 }
533
534 /**
535 * Accepts <tt>config.xml</tt> submission, as well as serve it.
536 */
537 @WebMethod(name = "config.xml")
538 public void doConfigDotXml(StaplerRequest req, StaplerResponse rsp)
539 throws IOException {
540 if (req.getMethod().equals("GET")) {
541 // read
542 checkPermission(EXTENDED_READ);
543 rsp.setContentType("application/xml");
544 IOUtils.copy(getConfigFile().getFile(),rsp.getOutputStream());
545 return;
546 }
547 if (req.getMethod().equals("POST")) {
548 // submission
549 updateByXml((Source)new StreamSource(req.getReader()));
550 return;
551 }
552
553 // huh?
554 rsp.sendError(SC_BAD_REQUEST);
555 }
556
557 /**
558 * @deprecated as of 1.473
559 * Use {@link #updateByXml(Source)}
560 */
561 public void updateByXml(StreamSource source) throws IOException {
562 updateByXml((Source)source);
563 }
564
565 /**
566 * Updates Job by its XML definition.
567 */
568 public void updateByXml(Source source) throws IOException {
569 checkPermission(CONFIGURE);
570 XmlFile configXmlFile = getConfigFile();
571 AtomicFileWriter out = new AtomicFileWriter(configXmlFile.getFile());
572 try {
573 try {
574 // this allows us to use UTF-8 for storing data,
575 // plus it checks any well-formedness issue in the submitted
576 // data
577 Transformer t = TransformerFactory.newInstance()
578 .newTransformer();
579 t.transform(source,
580 new StreamResult(out));
581 out.close();
582 } catch (TransformerException e) {
583 throw new IOException2("Failed to persist configuration.xml", e);
584 }
585
586 // try to reflect the changes by reloading
587 new XmlFile(Items.XSTREAM, out.getTemporaryFile()).unmarshal(this);
588 Items.updatingByXml.set(true);
589 try {
590 onLoad(getParent(), getRootDir().getName());
591 } finally {
592 Items.updatingByXml.set(false);
593 }
594 Jenkins.getInstance().rebuildDependencyGraph();
595
596 // if everything went well, commit this new version
597 out.commit();
598 SaveableListener.fireOnChange(this, getConfigFile());
599 } finally {
600 out.abort(); // don't leave anything behind
601 }
602 }
603
604
605 /* (non-Javadoc)
606 * @see hudson.model.AbstractModelObject#getSearchName()
607 */
608 @Override
609 public String getSearchName() {
610 // the search name of abstract items should be the name and not display name.
611 // this will make suggestions use the names and not the display name
612 // so that the links will 302 directly to the thing the user was finding
613 return getName();
614 }
615
616 public String toString() {
617 return super.toString()+'['+getFullName()+']';
618 }
619
620 /**
621 * Used for CLI binding.
622 */
623 @CLIResolver
624 public static AbstractItem resolveForCLI(
625 @Argument(required=true,metaVar="NAME",usage="Job name") String name) throws CmdLineException {
626 AbstractItem item = Jenkins.getInstance().getItemByFullName(name, AbstractItem.class);
627 if (item==null)
628 throw new CmdLineException(null,Messages.AbstractItem_NoSuchJobExists(name,AbstractProject.findNearest(name).getFullName()));
629 return item;
630 }
631
632 /**
633 * Replaceable pronoun of that points to a job. Defaults to "Job"/"Project" depending on the context.
634 */
635 public static final Message<AbstractItem> PRONOUN = new Message<AbstractItem>();
636 }
637